3333 >
3434 {{ error }}
3535 </Banner >
36+ <!-- install button -->
3637 <button
37- v-if =" !error"
38+ v-if =" !error && !currentAction && !installedVersion "
3839 data-test =" button-install"
39- :class =" isInstalled ? 'role-danger': 'role-primary'"
40- class =" btn btn-xs"
41- :disabled =" loading"
42- @click =" appInstallation(installationAction)"
40+ class =" role-primary btn btn-xs"
41+ @click =" appInstallation('install')"
4342 >
44- <span
45- v-if =" loading"
46- name =" loading"
47- :is-loading =" loading"
48- >
49- <loading-indicator >{{ buttonLabel }}</loading-indicator >
43+ {{ t('marketplace.labels.install') }}
44+ </button >
45+ <!-- upgrade button -->
46+ <button
47+ v-if =" !error && !currentAction && canUpgrade"
48+ class =" role-primary btn btn-xs"
49+ @click =" appInstallation('upgrade')"
50+ >
51+ {{ t('marketplace.labels.upgrade') }}
52+ </button >
53+ <!-- uninstall button -->
54+ <button
55+ v-if =" !error && !currentAction && installedVersion"
56+ data-test =" button-uninstall"
57+ class =" role-danger btn btn-xs"
58+ @click =" appInstallation('uninstall')"
59+ >
60+ {{ t('marketplace.labels.uninstall') }}
61+ </button >
62+ <!-- "loading" fake button -->
63+ <button
64+ v-if =" !error && currentAction"
65+ data-test =" button-loading"
66+ class =" role-primary btn btn-xs"
67+ disabled =" true"
68+ >
69+ <span name =" loading" is-loading =" true" >
70+ <loading-indicator >{{ loadingLabel }}</loading-indicator >
5071 </span >
51- <span v-if =" !loading" >{{ buttonLabel }}</span >
5272 </button >
5373 </div >
5474 </div >
5575</template >
5676
5777<script lang="ts">
5878import { Banner } from ' @rancher/components' ;
79+ import semver from ' semver' ;
5980
6081import LoadingIndicator from ' @pkg/components/LoadingIndicator.vue' ;
6182import { MarketplaceData } from ' @pkg/store/extensions.js' ;
6283
6384import type { PropType } from ' vue' ;
6485
86+ type action = ' install' | ' uninstall' | ' upgrade' ;
87+
6588export default {
6689 components: { LoadingIndicator , Banner },
6790 props: {
@@ -77,19 +100,21 @@ export default {
77100 type: Boolean ,
78101 required: true ,
79102 },
103+ installedVersion: {
104+ type: String ,
105+ required: false ,
106+ default: undefined ,
107+ },
80108 },
81109 data() {
82110 return {
83- loading: false ,
84- error: null as string | null ,
85- response: null ,
86- bannerActive: false ,
111+ currentAction: null as null | action ,
112+ error: null as string | null ,
113+ response: null ,
114+ bannerActive: false ,
87115 };
88116 },
89117 computed: {
90- installationAction() {
91- return this .isInstalled ? ' uninstall' : ' install' ;
92- },
93118 versionedExtension() {
94119 return ` ${ this .extensionWithoutVersion }:${ this .extension .version } ` ;
95120 },
@@ -101,52 +126,45 @@ export default {
101126 extensionLink() {
102127 return this .extension .slug .startsWith (' ghcr.io/' ) ? ` https://${ this .extension .slug } ` : ` https://hub.docker.com/extensions/${ this .extension .slug } ` ;
103128 },
104- buttonLabel() {
105- if (this .loading ) {
106- return this .isInstalled ? this .t (' marketplace.sidebar.uninstallButton.loading' ) : this .t (' marketplace.sidebar.installButton.loading' );
107- } else {
108- return this .isInstalled ? this .t (' marketplace.sidebar.uninstallButton.label' ) : this .t (' marketplace.sidebar.installButton.label' );
129+ canUpgrade() {
130+ try {
131+ return this .installedVersion && semver .gt (this .extension .version , this .installedVersion );
132+ } catch {
133+ // If installed or available version is not semver.
134+ return false ;
109135 }
110136 },
137+ loadingLabel() {
138+ return this .t (` marketplace.loading.${ this .currentAction } ` );
139+ },
111140 },
112141
113142 methods: {
114143 resetBanners() {
115144 this .error = null ;
116145 },
117- appInstallation(action : ' uninstall ' | ' install ' ) {
118- this .loading = true ;
146+ async appInstallation(action : action ) {
147+ this .currentAction = action ;
119148 this .resetBanners ();
120- const extensionId = action === ' uninstall' ? this .extensionWithoutVersion : this .versionedExtension ;
121-
122- fetch (
123- ` http://localhost:${ this .credentials ?.port }/v1/extensions/${ action }?id=${ extensionId } ` ,
124- {
125- method: ' POST' ,
126- headers: new Headers ({
127- Authorization: ` Basic ${ window .btoa (
128- ` ${ this .credentials ?.user }:${ this .credentials ?.password } ` ,
129- ) } ` ,
130- ' Content-Type' : ' application/x-www-form-urlencoded' ,
131- }),
132- },
133- ).then ((r ) => {
134- if (! r .ok ) {
135- this .error = r .statusText ;
136- this .loading = false ;
137- }
149+ const id = action === ' uninstall' ? this .extensionWithoutVersion : this .versionedExtension ;
150+ const verb = action === ' uninstall' ? ' uninstall' : ' install' ; // upgrades are installs
151+
152+ try {
153+ const result = await this .$store .dispatch (` extensions/${ verb } ` , { id });
138154
139- if (r .status === 201 ) {
140- this .loading = false ;
155+ if (typeof result === ' string' ) {
156+ this .error = result ;
157+ this .currentAction = null ;
158+ } else if (result ) {
159+ this .currentAction = null ;
141160 }
142- })
143- .finally (() => {
144- this .$emit (' update:extension' );
145-
146- setTimeout (() => {
147- this .resetBanners ();
148- }, 3000 );
149- });
161+ } finally {
162+ this .$emit (' update:extension' );
163+
164+ setTimeout (() => {
165+ this .resetBanners ();
166+ }, 3_000 );
167+ }
150168 },
151169 },
152170};
@@ -206,6 +224,10 @@ export default {
206224 .banner {
207225 margin : 0 ;
208226 }
227+
228+ button :not (:first-of-type ) {
229+ margin-left : 10px ;
230+ }
209231 }
210232
211233 & -more-info {
0 commit comments