Skip to content

Commit 22458ed

Browse files
authored
Merge pull request #8334 from mook-as/rdx/ui/upgrade-notification
UI: Add badge for upgradable extensions
2 parents 86a2fee + 71c9098 commit 22458ed

File tree

7 files changed

+101
-122
lines changed

7 files changed

+101
-122
lines changed

pkg/rancher-desktop/components/MarketplaceCard.vue

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
</Banner>
3636
<!-- install button -->
3737
<button
38-
v-if="!error && !currentAction && !installedVersion"
38+
v-if="!error && !currentAction && !installed"
3939
data-test="button-install"
4040
class="role-primary btn btn-xs"
4141
@click="appInstallation('install')"
@@ -44,15 +44,15 @@
4444
</button>
4545
<!-- upgrade button -->
4646
<button
47-
v-if="!error && !currentAction && canUpgrade"
47+
v-if="!error && !currentAction && installed?.canUpgrade"
4848
class="role-primary btn btn-xs"
4949
@click="appInstallation('upgrade')"
5050
>
5151
{{ t('marketplace.labels.upgrade') }}
5252
</button>
5353
<!-- uninstall button -->
5454
<button
55-
v-if="!error && !currentAction && installedVersion"
55+
v-if="!error && !currentAction && installed"
5656
data-test="button-uninstall"
5757
class="role-danger btn btn-xs"
5858
@click="appInstallation('uninstall')"
@@ -76,10 +76,9 @@
7676

7777
<script lang="ts">
7878
import { Banner } from '@rancher/components';
79-
import semver from 'semver';
8079
8180
import LoadingIndicator from '@pkg/components/LoadingIndicator.vue';
82-
import { MarketplaceData } from '@pkg/store/extensions.js';
81+
import type { ExtensionState, MarketplaceData } from '@pkg/store/extensions';
8382
8483
import type { PropType } from 'vue';
8584
@@ -92,16 +91,8 @@ export default {
9291
type: Object as PropType<MarketplaceData>,
9392
required: true,
9493
},
95-
credentials: {
96-
type: Object,
97-
required: true,
98-
},
99-
isInstalled: {
100-
type: Boolean,
101-
required: true,
102-
},
103-
installedVersion: {
104-
type: String,
94+
installed: {
95+
type: Object as undefined | PropType<ExtensionState>,
10596
required: false,
10697
default: undefined,
10798
},
@@ -119,21 +110,11 @@ export default {
119110
return `${ this.extensionWithoutVersion }:${ this.extension.version }`;
120111
},
121112
extensionWithoutVersion() {
122-
const index = this.extension.slug.lastIndexOf(':');
123-
124-
return this.extension.slug.substring(0, index) || this.extension.slug;
113+
return this.extension.slug;
125114
},
126115
extensionLink() {
127116
return this.extension.slug.startsWith('ghcr.io/') ? `https://${ this.extension.slug }` : `https://hub.docker.com/extensions/${ this.extension.slug }`;
128117
},
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;
135-
}
136-
},
137118
loadingLabel() {
138119
return this.t(`marketplace.loading.${ this.currentAction }`);
139120
},
@@ -159,8 +140,6 @@ export default {
159140
this.currentAction = null;
160141
}
161142
} finally {
162-
this.$emit('update:extension');
163-
164143
setTimeout(() => {
165144
this.resetBanners();
166145
}, 3_000);

pkg/rancher-desktop/components/MarketplaceCatalog.vue

Lines changed: 12 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
<script lang="ts">
2-
import semver from 'semver';
32
import Vue, { VueConstructor } from 'vue';
43
import { mapGetters } from 'vuex';
54
65
import MarketplaceCard from '@pkg/components/MarketplaceCard.vue';
76
import { Settings, ContainerEngine } from '@pkg/config/settings';
8-
import { ExtensionState, MarketplaceData } from '@pkg/store/extensions.js';
7+
import type { ExtensionState, MarketplaceData } from '@pkg/store/extensions';
98
10-
type ExtensionData = MarketplaceData & { installed: boolean };
9+
type ExtensionData = MarketplaceData;
1110
1211
interface VuexBindings {
1312
getPreferences: Settings;
@@ -17,33 +16,14 @@ export default (Vue as VueConstructor<Vue & VuexBindings>).extend({
1716
name: 'marketplace-catalog',
1817
components: { MarketplaceCard },
1918
data() {
20-
return {
21-
searchValue: '',
22-
loading: true,
23-
credentials: {
24-
user: '',
25-
password: '',
26-
port: 0,
27-
},
28-
};
29-
},
30-
async fetch() {
31-
this.credentials = await this.$store.dispatch(
32-
'credentials/fetchCredentials',
33-
);
34-
35-
if (!this.credentials) {
36-
return;
37-
}
38-
39-
this.loading = false;
19+
return { searchValue: '' };
4020
},
4121
computed: {
4222
...mapGetters('preferences', ['getPreferences']),
43-
...mapGetters('extensions', { installedExtensions: 'list', extensions: 'marketData' }) as {
44-
installedExtensions: () => ({ id: string } & ExtensionState )[],
45-
extensions: () => MarketplaceData[],
46-
},
23+
...mapGetters('extensions', ['installedExtensions', 'marketData']) as {
24+
installedExtensions: () => ExtensionState[],
25+
marketData: () => MarketplaceData[],
26+
},
4727
containerEngine(): string {
4828
return this.getPreferences.containerEngine.name;
4929
},
@@ -57,15 +37,9 @@ export default (Vue as VueConstructor<Vue & VuexBindings>).extend({
5737
return this.getPreferences.application.extensions.allowed.list;
5838
},
5939
filteredExtensions(): ExtensionData[] {
60-
let tempExtensions = this.extensions
40+
let tempExtensions = this.marketData
6141
.filter((item) => {
6242
return this.isAllowed(item.slug);
63-
})
64-
.map((item) => {
65-
return {
66-
...item,
67-
installed: this.isInstalled(item.slug),
68-
};
6943
});
7044
7145
if (this.searchValue) {
@@ -84,21 +58,15 @@ export default (Vue as VueConstructor<Vue & VuexBindings>).extend({
8458
},
8559
},
8660
methods: {
87-
isInstalled(slug: string) {
88-
return !!this.installedExtensions.find(item => item?.id === slug);
89-
},
90-
isOutdated(slug: string) {
91-
const available = this.extensions.find(item => item.slug === slug);
92-
const installed = this.installedExtensions.find(item => item?.id === slug);
93-
94-
return available && installed && semver.gt(available.version, installed.version);
95-
},
9661
installedVersion(slug: string) {
9762
return this.installedExtensions.find(item => item.id === slug)?.version;
9863
},
9964
isAllowed(slug: string) {
10065
return !this.allowedListEnabled || this.allowedExtensions.includes(slug);
10166
},
67+
installedExtension(slug: string) {
68+
return this.installedExtensions.find(item => item.id === slug);
69+
},
10270
},
10371
});
10472
</script>
@@ -117,7 +85,6 @@ export default (Vue as VueConstructor<Vue & VuexBindings>).extend({
11785
{{ t('marketplace.noResults') }}
11886
</div>
11987
<div
120-
v-if="!loading"
12188
class="extensions-content"
12289
>
12390
<div
@@ -127,11 +94,8 @@ export default (Vue as VueConstructor<Vue & VuexBindings>).extend({
12794
>
12895
<MarketplaceCard
12996
:extension="item"
97+
:installed="installedExtension(item.slug)"
13098
:data-test="`extension-card-${item.title.toLowerCase()}`"
131-
:is-installed="item.installed"
132-
:installed-version="installedVersion(item.slug)"
133-
:credentials="credentials"
134-
@update:extension="isInstalled"
13599
/>
136100
</div>
137101
</div>

pkg/rancher-desktop/components/Nav.vue

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,11 @@ import NavItem from './NavItem.vue';
7373
7474
import DashboardButton from '@pkg/components/DashboardOpen.vue';
7575
import PreferencesButton from '@pkg/components/Preferences/ButtonOpen.vue';
76-
import type { ExtensionMetadata } from '@pkg/main/extensions/types';
76+
import type { ExtensionState } from '@pkg/store/extensions';
7777
import { hexEncode } from '@pkg/utils/string-encode';
7878
79-
type ExtensionWithUI = ExtensionMetadata & {
80-
ui: { 'dashboard-tab': { title: string } };
79+
type ExtensionWithUI = ExtensionState & {
80+
metadata: { ui: { 'dashboard-tab': { title: string } } };
8181
};
8282
8383
export default Vue.extend({
@@ -90,7 +90,7 @@ export default Vue.extend({
9090
},
9191
props: {
9292
items: {
93-
type: Array,
93+
type: Array as PropType<{route: string; error?: number; experimental?: boolean}[]>,
9494
required: true,
9595
validator: (value: {route: string, error?: number}[]) => {
9696
const nuxt: NuxtApp = (global as any).$nuxt;
@@ -112,7 +112,7 @@ export default Vue.extend({
112112
},
113113
},
114114
extensions: {
115-
type: Array as PropType<{ id: string, metadata: ExtensionMetadata }[]>,
115+
type: Array as PropType<ExtensionState[]>,
116116
required: true,
117117
},
118118
},
@@ -133,10 +133,12 @@ export default Vue.extend({
133133
};
134134
},
135135
computed: {
136-
extensionsWithUI(): { id: string, metadata: ExtensionWithUI }[] {
137-
const allExtensions: { id: string, metadata: ExtensionMetadata }[] = (this as any).extensions;
136+
extensionsWithUI(): ExtensionWithUI[] {
137+
function hasUI(ext: ExtensionState): ext is ExtensionWithUI {
138+
return !!ext.metadata.ui?.['dashboard-tab']?.title;
139+
}
138140
139-
return allExtensions.filter(ext => ext.metadata?.ui?.['dashboard-tab']) as any;
141+
return this.extensions.filter<ExtensionWithUI>(hasUI);
140142
},
141143
},
142144
methods: {

pkg/rancher-desktop/layouts/default.vue

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<rd-nav
99
class="nav"
1010
:items="routes"
11-
:extensions="extensions"
11+
:extensions="installedExtensions"
1212
@open-dashboard="openDashboard"
1313
@open-preferences="openPreferences"
1414
/>
@@ -71,17 +71,33 @@ export default {
7171
7272
computed: {
7373
routes() {
74-
return mainRoutes.map(route => route.route === '/Diagnostics' ? { ...route, error: this.errorCount } : route);
74+
const badges = {
75+
'/Diagnostics': this.diagnosticsCount,
76+
'/Extensions': this.extensionUpgradeCount,
77+
};
78+
79+
return mainRoutes.map((route) => {
80+
if (route.route in badges) {
81+
return { ...route, error: badges[route.route] };
82+
}
83+
84+
return route;
85+
});
7586
},
7687
paths() {
7788
return mainRoutes.map(r => r.route);
7889
},
79-
errorCount() {
90+
/** @returns {number} The number of diagnostics errors. */
91+
diagnosticsCount() {
8092
return this.diagnostics.filter(diagnostic => !diagnostic.mute).length;
8193
},
94+
/** @returns {number} The number of extensions with upgrade available. */
95+
extensionUpgradeCount() {
96+
return this.installedExtensions.filter(ext => ext.canUpgrade).length;
97+
},
8298
...mapState('credentials', ['credentials']),
8399
...mapGetters('diagnostics', ['diagnostics']),
84-
...mapGetters('extensions', { extensions: 'list' }),
100+
...mapGetters('extensions', ['installedExtensions']),
85101
},
86102
87103
beforeMount() {

pkg/rancher-desktop/pages/Images.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export default {
5757
.map(image => image.imageName);
5858
},
5959
installedExtensionImages() {
60-
return this.extensions.map(image => image.id);
60+
return this.installedExtensions.map(image => image.id);
6161
},
6262
protectedImages() {
6363
return [
@@ -69,7 +69,7 @@ export default {
6969
},
7070
...mapGetters('k8sManager', { k8sState: 'getK8sState' }),
7171
...mapGetters('imageManager', { imageManagerState: 'getImageManagerState' }),
72-
...mapGetters('extensions', { extensions: 'list' }),
72+
...mapGetters('extensions', ['installedExtensions']),
7373
},
7474
7575
watch: {

0 commit comments

Comments
 (0)