@rancher/shell 3.0.6 → 3.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/images/pl/dark/rancher-logo.svg +131 -44
- package/assets/images/pl/rancher-logo.svg +120 -44
- package/assets/styles/base/_basic.scss +2 -2
- package/assets/styles/base/_color-classic.scss +51 -0
- package/assets/styles/base/_color.scss +3 -3
- package/assets/styles/base/_mixins.scss +1 -1
- package/assets/styles/base/_variables-classic.scss +47 -0
- package/assets/styles/global/_button.scss +49 -17
- package/assets/styles/global/_form.scss +1 -1
- package/assets/styles/themes/_dark.scss +4 -0
- package/assets/styles/themes/_light.scss +3 -69
- package/assets/styles/themes/_modern.scss +194 -50
- package/assets/styles/vendor/vue-select.scss +1 -2
- package/assets/translations/en-us.yaml +33 -21
- package/components/ClusterIconMenu.vue +1 -1
- package/components/ClusterProviderIcon.vue +1 -1
- package/components/CodeMirror.vue +1 -1
- package/components/IconOrSvg.vue +40 -29
- package/components/ResourceDetail/index.vue +1 -0
- package/components/SortableTable/sorting.js +3 -1
- package/components/Tabbed/index.vue +5 -5
- package/components/form/ResourceTabs/index.vue +37 -18
- package/components/form/SecretSelector.vue +6 -2
- package/components/nav/Group.vue +29 -9
- package/components/nav/Header.vue +6 -8
- package/components/nav/NamespaceFilter.vue +1 -1
- package/components/nav/TopLevelMenu.helper.ts +47 -20
- package/components/nav/TopLevelMenu.vue +44 -14
- package/components/nav/Type.vue +0 -5
- package/components/nav/__tests__/TopLevelMenu.test.ts +2 -0
- package/config/pagination-table-headers.js +10 -2
- package/config/product/explorer.js +4 -3
- package/config/table-headers.js +9 -0
- package/core/plugin.ts +18 -6
- package/core/types.ts +8 -0
- package/detail/provisioning.cattle.io.cluster.vue +1 -0
- package/dialog/InstallExtensionDialog.vue +71 -45
- package/dialog/UninstallExtensionDialog.vue +2 -1
- package/dialog/__tests__/InstallExtensionDialog.test.ts +111 -0
- package/edit/auth/oidc.vue +86 -16
- package/mixins/__tests__/chart.test.ts +1 -1
- package/mixins/chart.js +1 -1
- package/models/event.js +7 -0
- package/models/provisioning.cattle.io.cluster.js +9 -0
- package/package.json +1 -1
- package/pages/c/_cluster/explorer/EventsTable.vue +3 -6
- package/pages/c/_cluster/settings/performance.vue +1 -1
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +159 -62
- package/pages/c/_cluster/uiplugins/__tests__/PluginInfoPanel.test.ts +102 -0
- package/pages/c/_cluster/uiplugins/__tests__/{index.spec.ts → index.test.ts} +121 -55
- package/pages/c/_cluster/uiplugins/index.vue +110 -94
- package/plugins/__tests__/subscribe.events.test.ts +194 -0
- package/plugins/dashboard-store/actions.js +3 -0
- package/plugins/dashboard-store/getters.js +1 -1
- package/plugins/dashboard-store/resource-class.js +3 -3
- package/plugins/steve/__tests__/subscribe.spec.ts +27 -24
- package/plugins/steve/index.js +18 -10
- package/plugins/steve/mutations.js +2 -2
- package/plugins/steve/resourceWatcher.js +2 -2
- package/plugins/steve/steve-pagination-utils.ts +12 -9
- package/plugins/steve/subscribe.js +113 -85
- package/plugins/subscribe-events.ts +211 -0
- package/rancher-components/BadgeState/BadgeState.vue +8 -6
- package/rancher-components/Banner/Banner.vue +2 -1
- package/rancher-components/Form/Checkbox/Checkbox.vue +3 -3
- package/rancher-components/Form/Radio/RadioButton.vue +3 -3
- package/store/index.js +12 -22
- package/types/extension-manager.ts +8 -1
- package/types/resources/settings.d.ts +24 -17
- package/types/shell/index.d.ts +352 -335
- package/types/store/subscribe-events.types.ts +70 -0
- package/types/store/subscribe.types.ts +6 -22
- package/utils/pagination-utils.ts +87 -28
- package/utils/pagination-wrapper.ts +6 -8
- package/utils/sort.js +5 -0
- package/utils/unit-tests/pagination-utils.spec.ts +283 -0
- package/utils/validators/formRules/__tests__/index.test.ts +7 -0
- package/utils/validators/formRules/index.ts +2 -2
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { mapGetters } from 'vuex';
|
|
3
|
+
import day from 'dayjs';
|
|
3
4
|
import { mapPref, PLUGIN_DEVELOPER } from '@shell/store/prefs';
|
|
4
5
|
import { sortBy } from '@shell/utils/sort';
|
|
5
6
|
import { allHash } from '@shell/utils/promise';
|
|
6
|
-
import { CATALOG, UI_PLUGIN, MANAGEMENT } from '@shell/config/types';
|
|
7
|
+
import { CATALOG, UI_PLUGIN, MANAGEMENT, ZERO_TIME } from '@shell/config/types';
|
|
7
8
|
import { SETTING } from '@shell/config/settings';
|
|
8
9
|
import { fetchOrCreateSetting } from '@shell/utils/settings';
|
|
9
10
|
import { getVersionData, isRancherPrime } from '@shell/config/version';
|
|
@@ -40,10 +41,8 @@ const MAX_DESCRIPTION_LENGTH = 200;
|
|
|
40
41
|
|
|
41
42
|
const TABS_VALUES = {
|
|
42
43
|
INSTALLED: 'installed',
|
|
43
|
-
UPDATES: 'updates',
|
|
44
44
|
AVAILABLE: 'available',
|
|
45
45
|
BUILTIN: 'builtin',
|
|
46
|
-
ALL: 'all',
|
|
47
46
|
};
|
|
48
47
|
|
|
49
48
|
export default {
|
|
@@ -67,7 +66,7 @@ export default {
|
|
|
67
66
|
return {
|
|
68
67
|
TABS_VALUES,
|
|
69
68
|
kubeVersion: null,
|
|
70
|
-
|
|
69
|
+
activeTab: '',
|
|
71
70
|
installing: {},
|
|
72
71
|
errors: {},
|
|
73
72
|
plugins: [], // The installed plugins
|
|
@@ -122,11 +121,6 @@ export default {
|
|
|
122
121
|
|
|
123
122
|
this.addExtensionReposBannerSetting = await fetchOrCreateSetting(this.$store, SETTING.ADD_EXTENSION_REPOS_BANNER_DISPLAY, 'true', true) || {};
|
|
124
123
|
|
|
125
|
-
// If there are no plugins installed, default to the catalog view
|
|
126
|
-
if (this.plugins.length === 0) {
|
|
127
|
-
this.$refs.tabs?.select(TABS_VALUES.AVAILABLE);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
124
|
this.loading = false;
|
|
131
125
|
},
|
|
132
126
|
|
|
@@ -207,12 +201,10 @@ export default {
|
|
|
207
201
|
// If not an extension developer, then don't include built-in extensions
|
|
208
202
|
const all = this.pluginDeveloper ? this.available : this.available.filter((p) => !p.builtin);
|
|
209
203
|
|
|
210
|
-
switch (this.
|
|
204
|
+
switch (this.activeTab) {
|
|
211
205
|
case TABS_VALUES.INSTALLED:
|
|
212
206
|
// We never show built-in extensions as installed - installed are just the ones the user has installed
|
|
213
|
-
return all.filter((p) => !p.builtin && (!!p.installed || !!p.installing));
|
|
214
|
-
case TABS_VALUES.UPDATES:
|
|
215
|
-
return this.updates;
|
|
207
|
+
return all.filter((p) => !p.builtin && (!!p.installed || !!p.installing) && p.installableVersions?.length > 0);
|
|
216
208
|
case TABS_VALUES.AVAILABLE:
|
|
217
209
|
return all.filter((p) => !p.installed);
|
|
218
210
|
case TABS_VALUES.BUILTIN:
|
|
@@ -226,14 +218,14 @@ export default {
|
|
|
226
218
|
return this.menuActions?.length > 0;
|
|
227
219
|
},
|
|
228
220
|
|
|
229
|
-
// Message to display when the tab
|
|
221
|
+
// Message to display when the active tab is empty (depends on the tab)
|
|
230
222
|
emptyMessage() {
|
|
231
223
|
// i18n-uses plugins.empty.*
|
|
232
|
-
return this.t(`plugins.empty.${ this.
|
|
224
|
+
return this.t(`plugins.empty.${ this.activeTab }`);
|
|
233
225
|
},
|
|
234
226
|
|
|
235
|
-
|
|
236
|
-
return this.available.filter((
|
|
227
|
+
installed() {
|
|
228
|
+
return this.available.filter((p) => !p.builtin && (!!p.installed || !!p.installing));
|
|
237
229
|
},
|
|
238
230
|
|
|
239
231
|
pluginCards() {
|
|
@@ -298,6 +290,7 @@ export default {
|
|
|
298
290
|
item.displayVersion = latestCompatible.version;
|
|
299
291
|
item.displayVersionLabel = getPluginChartVersionLabel(latestCompatible);
|
|
300
292
|
item.icon = latestCompatible.icon;
|
|
293
|
+
item.created = latestCompatible.created;
|
|
301
294
|
} else {
|
|
302
295
|
item.experimental = uiPluginHasAnnotation(chart, CATALOG_ANNOTATIONS.EXPERIMENTAL, 'true');
|
|
303
296
|
item.certified = uiPluginHasAnnotation(chart, CATALOG_ANNOTATIONS.CERTIFIED, CATALOG_ANNOTATIONS._RANCHER);
|
|
@@ -305,6 +298,7 @@ export default {
|
|
|
305
298
|
item.displayVersion = item.versions?.[0]?.version;
|
|
306
299
|
item.displayVersionLabel = getPluginChartVersionLabel(item.versions?.[0]);
|
|
307
300
|
item.icon = chart.icon || latestCompatible?.annotations?.['catalog.cattle.io/ui-icon'];
|
|
301
|
+
item.created = item.versions?.[0]?.created;
|
|
308
302
|
}
|
|
309
303
|
|
|
310
304
|
// add message of extension card if there's a newer version of the extension, but it's not available to be installed
|
|
@@ -353,6 +347,7 @@ export default {
|
|
|
353
347
|
displayVersion: p.metadata?.version,
|
|
354
348
|
displayVersionLabel: p.metadata?.version || '-',
|
|
355
349
|
installed: true,
|
|
350
|
+
installedVersion: p.metadata?.version,
|
|
356
351
|
builtin: !!p.builtin,
|
|
357
352
|
experimental: rancher?.annotations?.[CATALOG_ANNOTATIONS.EXPERIMENTAL] === 'true',
|
|
358
353
|
certified: rancher?.annotations?.[CATALOG_ANNOTATIONS.CERTIFIED] === CATALOG_ANNOTATIONS._RANCHER
|
|
@@ -373,8 +368,7 @@ export default {
|
|
|
373
368
|
if (chart) {
|
|
374
369
|
chart.installed = true;
|
|
375
370
|
chart.uiplugin = p;
|
|
376
|
-
chart.
|
|
377
|
-
let displayVersionLabel = p.version;
|
|
371
|
+
chart.installedVersion = p.version;
|
|
378
372
|
|
|
379
373
|
// Can't do this here
|
|
380
374
|
chart.installing = this.installing[chart.name];
|
|
@@ -389,13 +383,10 @@ export default {
|
|
|
389
383
|
if (installedVersion) {
|
|
390
384
|
chart.experimental = installedVersion?.annotations?.[CATALOG_ANNOTATIONS.EXPERIMENTAL] === 'true';
|
|
391
385
|
chart.certified = installedVersion?.annotations?.[CATALOG_ANNOTATIONS.CERTIFIED] === CATALOG_ANNOTATIONS._RANCHER;
|
|
392
|
-
displayVersionLabel = getPluginChartVersionLabel(installedVersion);
|
|
393
386
|
}
|
|
394
387
|
|
|
395
388
|
chart.upgrade = getPluginChartVersionLabel(latestInstallableVersion);
|
|
396
389
|
}
|
|
397
|
-
|
|
398
|
-
chart.displayVersionLabel = displayVersionLabel;
|
|
399
390
|
} else {
|
|
400
391
|
// No chart, so add a card for the plugin based on its Custom resource being present
|
|
401
392
|
const item = {
|
|
@@ -474,10 +465,10 @@ export default {
|
|
|
474
465
|
|
|
475
466
|
if (active) {
|
|
476
467
|
// Can use the status directly, apart from upgrade, which maps to update
|
|
477
|
-
const status = op.status.action
|
|
468
|
+
const status = op.status.action;
|
|
478
469
|
|
|
479
|
-
if (status === '
|
|
480
|
-
// Helm op is an upgrade, but we initiated a
|
|
470
|
+
if (status === 'upgrade' && this.installing[plugin.name] === 'downgrade') {
|
|
471
|
+
// Helm op is an upgrade, but we initiated a downgrade, so keep the 'downgrade' status
|
|
481
472
|
} else {
|
|
482
473
|
this.updatePluginInstallStatus(plugin.name, status);
|
|
483
474
|
}
|
|
@@ -535,6 +526,16 @@ export default {
|
|
|
535
526
|
},
|
|
536
527
|
|
|
537
528
|
methods: {
|
|
529
|
+
handlePanelAction(button) {
|
|
530
|
+
const { action, plugin, version } = button;
|
|
531
|
+
|
|
532
|
+
if (action === 'uninstall') {
|
|
533
|
+
this.showUninstallDialog(plugin, {});
|
|
534
|
+
} else {
|
|
535
|
+
this.showInstallDialog(plugin, action, {}, version);
|
|
536
|
+
}
|
|
537
|
+
},
|
|
538
|
+
|
|
538
539
|
async refreshCharts(forceChartsUpdate = false) {
|
|
539
540
|
// we might need to force the request, so that we know at all times if what's the status of the offical and partners repos (installed or not)
|
|
540
541
|
// tied to the SetupUIPlugins, AddExtensionRepos checkboxes
|
|
@@ -560,8 +561,8 @@ export default {
|
|
|
560
561
|
return hasFeatureFlag;
|
|
561
562
|
},
|
|
562
563
|
|
|
563
|
-
|
|
564
|
-
this.
|
|
564
|
+
tabChanged(f) {
|
|
565
|
+
this.activeTab = f.selectedName;
|
|
565
566
|
},
|
|
566
567
|
|
|
567
568
|
// Developer Load is in the action menu
|
|
@@ -623,10 +624,10 @@ export default {
|
|
|
623
624
|
});
|
|
624
625
|
},
|
|
625
626
|
|
|
626
|
-
showInstallDialog(plugin, action, ev) {
|
|
627
|
-
ev
|
|
628
|
-
ev
|
|
629
|
-
ev
|
|
627
|
+
showInstallDialog(plugin, action, ev, initialVersion = null) {
|
|
628
|
+
ev?.target?.blur();
|
|
629
|
+
ev?.preventDefault?.();
|
|
630
|
+
ev?.stopPropagation?.();
|
|
630
631
|
|
|
631
632
|
this.$store.dispatch('management/promptModal', {
|
|
632
633
|
component: 'InstallExtensionDialog',
|
|
@@ -636,6 +637,7 @@ export default {
|
|
|
636
637
|
componentProps: {
|
|
637
638
|
plugin,
|
|
638
639
|
action,
|
|
640
|
+
initialVersion,
|
|
639
641
|
updateStatus: (pluginName, type) => {
|
|
640
642
|
this.updatePluginInstallStatus(pluginName, type);
|
|
641
643
|
},
|
|
@@ -647,9 +649,9 @@ export default {
|
|
|
647
649
|
},
|
|
648
650
|
|
|
649
651
|
showUninstallDialog(plugin, ev) {
|
|
650
|
-
ev
|
|
651
|
-
ev
|
|
652
|
-
ev
|
|
652
|
+
ev?.target?.blur();
|
|
653
|
+
ev?.preventDefault?.();
|
|
654
|
+
ev?.stopPropagation?.();
|
|
653
655
|
|
|
654
656
|
this.$store.dispatch('management/promptModal', {
|
|
655
657
|
component: 'UninstallExtensionDialog',
|
|
@@ -683,7 +685,7 @@ export default {
|
|
|
683
685
|
|
|
684
686
|
didInstall(plugin) {
|
|
685
687
|
if (plugin) {
|
|
686
|
-
// Change the
|
|
688
|
+
// Change the activeTab to installed if we started installing a plugin
|
|
687
689
|
this.$refs.tabs?.select(TABS_VALUES.INSTALLED);
|
|
688
690
|
|
|
689
691
|
// Clear the load error, if there was one previously
|
|
@@ -692,7 +694,9 @@ export default {
|
|
|
692
694
|
},
|
|
693
695
|
|
|
694
696
|
showPluginDetail(plugin) {
|
|
695
|
-
this
|
|
697
|
+
const tags = this.getFooterItems(plugin);
|
|
698
|
+
|
|
699
|
+
this.$refs.infoPanel.show({ ...plugin, tags });
|
|
696
700
|
},
|
|
697
701
|
|
|
698
702
|
updatePluginInstallStatus(name, status) {
|
|
@@ -741,42 +745,46 @@ export default {
|
|
|
741
745
|
getPluginActions(plugin) {
|
|
742
746
|
const actions = [];
|
|
743
747
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
if (canUpdate) {
|
|
758
|
-
actions.push({
|
|
759
|
-
label: this.t('plugins.update.label'),
|
|
760
|
-
action: 'update',
|
|
761
|
-
icon: 'icon-edit',
|
|
762
|
-
});
|
|
763
|
-
}
|
|
748
|
+
if (plugin.installed) {
|
|
749
|
+
const canUpdate = !!plugin.upgrade;
|
|
750
|
+
const canDowngrade = plugin.installableVersions?.some((v) => isChartVersionHigher(plugin.installedVersion, v.version));
|
|
751
|
+
const canUninstall = !plugin.builtin;
|
|
752
|
+
|
|
753
|
+
if (canUpdate) {
|
|
754
|
+
actions.push({
|
|
755
|
+
label: this.t('plugins.upgrade.label'),
|
|
756
|
+
action: 'upgrade',
|
|
757
|
+
icon: 'icon-upgrade-alt',
|
|
758
|
+
});
|
|
759
|
+
}
|
|
764
760
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
761
|
+
if (canDowngrade) {
|
|
762
|
+
actions.push({
|
|
763
|
+
label: this.t('plugins.downgrade.label'),
|
|
764
|
+
action: 'downgrade',
|
|
765
|
+
icon: 'icon-downgrade-alt',
|
|
766
|
+
});
|
|
767
|
+
}
|
|
772
768
|
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
769
|
+
if (canUninstall) {
|
|
770
|
+
if (actions.length > 0) {
|
|
771
|
+
actions.push({ divider: true });
|
|
772
|
+
}
|
|
773
|
+
actions.push({
|
|
774
|
+
label: this.t('plugins.uninstall.label'),
|
|
775
|
+
action: 'uninstall',
|
|
776
|
+
icon: 'icon-delete',
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
} else {
|
|
780
|
+
if (plugin.installableVersions?.length) {
|
|
781
|
+
actions.push({
|
|
782
|
+
label: this.t('plugins.install.label'),
|
|
783
|
+
action: 'install',
|
|
784
|
+
icon: 'icon-plus',
|
|
785
|
+
enabled: true,
|
|
786
|
+
});
|
|
787
|
+
}
|
|
780
788
|
}
|
|
781
789
|
|
|
782
790
|
return actions;
|
|
@@ -784,12 +792,25 @@ export default {
|
|
|
784
792
|
|
|
785
793
|
getSubHeaderItems(plugin) {
|
|
786
794
|
const items = [{
|
|
787
|
-
icon:
|
|
788
|
-
iconTooltip:
|
|
789
|
-
label:
|
|
790
|
-
labelTooltip: plugin.upgrade ? this.t('plugins.upgradeAvailableTooltip', { version: plugin.upgrade }) : ''
|
|
795
|
+
icon: 'icon-version-alt',
|
|
796
|
+
iconTooltip: { key: 'tableHeaders.version' },
|
|
797
|
+
label: plugin.displayVersionLabel,
|
|
791
798
|
}];
|
|
792
799
|
|
|
800
|
+
if (plugin.created) {
|
|
801
|
+
const hasZeroTime = plugin.created === ZERO_TIME;
|
|
802
|
+
const lastUpdatedItem = {
|
|
803
|
+
icon: 'icon-refresh-alt',
|
|
804
|
+
iconTooltip: { key: 'tableHeaders.lastUpdated' },
|
|
805
|
+
label: hasZeroTime ? this.t('generic.na') : day(plugin.created).format('MMM D, YYYY')
|
|
806
|
+
};
|
|
807
|
+
|
|
808
|
+
if (hasZeroTime) {
|
|
809
|
+
lastUpdatedItem.labelTooltip = this.t('catalog.charts.appChartCard.subHeaderItem.missingVersionDate');
|
|
810
|
+
}
|
|
811
|
+
items.push(lastUpdatedItem);
|
|
812
|
+
}
|
|
813
|
+
|
|
793
814
|
if (plugin.installing) {
|
|
794
815
|
let label;
|
|
795
816
|
|
|
@@ -800,11 +821,11 @@ export default {
|
|
|
800
821
|
case 'uninstall':
|
|
801
822
|
label = this.t('plugins.labels.uninstalling');
|
|
802
823
|
break;
|
|
803
|
-
case '
|
|
804
|
-
label = this.t('plugins.labels.
|
|
824
|
+
case 'upgrade':
|
|
825
|
+
label = this.t('plugins.labels.upgrading');
|
|
805
826
|
break;
|
|
806
|
-
case '
|
|
807
|
-
label = this.t('plugins.labels.
|
|
827
|
+
case 'downgrade':
|
|
828
|
+
label = this.t('plugins.labels.downgrading');
|
|
808
829
|
break;
|
|
809
830
|
default:
|
|
810
831
|
label = this.t('plugins.labels.installing');
|
|
@@ -874,7 +895,7 @@ export default {
|
|
|
874
895
|
|
|
875
896
|
if (plugin.installed && !plugin.builtin && !plugin.installing) {
|
|
876
897
|
statuses.push({
|
|
877
|
-
icon: 'icon-confirmation-alt', color: 'success', tooltip: {
|
|
898
|
+
icon: 'icon-confirmation-alt', color: 'success', tooltip: { text: `${ this.t('generic.installed') } (${ plugin.installedVersion })` }
|
|
878
899
|
});
|
|
879
900
|
}
|
|
880
901
|
|
|
@@ -958,7 +979,10 @@ export default {
|
|
|
958
979
|
</div>
|
|
959
980
|
|
|
960
981
|
<!-- extensions slide-in panel -->
|
|
961
|
-
<PluginInfoPanel
|
|
982
|
+
<PluginInfoPanel
|
|
983
|
+
ref="infoPanel"
|
|
984
|
+
@action="handlePanelAction"
|
|
985
|
+
/>
|
|
962
986
|
|
|
963
987
|
<!-- extensions not enabled by feature flag -->
|
|
964
988
|
<div v-if="!hasFeatureFlag">
|
|
@@ -1008,15 +1032,18 @@ export default {
|
|
|
1008
1032
|
</Banner>
|
|
1009
1033
|
|
|
1010
1034
|
<Tabbed
|
|
1035
|
+
v-if="!loading"
|
|
1011
1036
|
ref="tabs"
|
|
1012
1037
|
:tabs-only="true"
|
|
1013
1038
|
data-testid="extension-tabs"
|
|
1014
|
-
@changed="
|
|
1039
|
+
@changed="tabChanged"
|
|
1015
1040
|
>
|
|
1016
1041
|
<Tab
|
|
1042
|
+
v-if="installed.length"
|
|
1017
1043
|
:name="TABS_VALUES.INSTALLED"
|
|
1018
1044
|
data-testid="extension-tab-installed"
|
|
1019
1045
|
label-key="plugins.tabs.installed"
|
|
1046
|
+
:badge="installed.length"
|
|
1020
1047
|
:weight="20"
|
|
1021
1048
|
/>
|
|
1022
1049
|
<Tab
|
|
@@ -1025,23 +1052,12 @@ export default {
|
|
|
1025
1052
|
label-key="plugins.tabs.available"
|
|
1026
1053
|
:weight="19"
|
|
1027
1054
|
/>
|
|
1028
|
-
<Tab
|
|
1029
|
-
:name="TABS_VALUES.UPDATES"
|
|
1030
|
-
label-key="plugins.tabs.updates"
|
|
1031
|
-
:weight="18"
|
|
1032
|
-
:badge="updates.length"
|
|
1033
|
-
/>
|
|
1034
1055
|
<Tab
|
|
1035
1056
|
v-if="pluginDeveloper"
|
|
1036
1057
|
:name="TABS_VALUES.BUILTIN"
|
|
1037
1058
|
label-key="plugins.tabs.builtin"
|
|
1038
1059
|
:weight="17"
|
|
1039
1060
|
/>
|
|
1040
|
-
<Tab
|
|
1041
|
-
:name="TABS_VALUES.ALL"
|
|
1042
|
-
label-key="plugins.tabs.all"
|
|
1043
|
-
:weight="16"
|
|
1044
|
-
/>
|
|
1045
1061
|
</Tabbed>
|
|
1046
1062
|
<div
|
|
1047
1063
|
v-if="loading"
|
|
@@ -1079,8 +1095,8 @@ export default {
|
|
|
1079
1095
|
:clickable="true"
|
|
1080
1096
|
@card-click="showPluginDetail(card.plugin)"
|
|
1081
1097
|
@uninstall="({event}) => showUninstallDialog(card.plugin, event)"
|
|
1082
|
-
@
|
|
1083
|
-
@
|
|
1098
|
+
@upgrade="({event}) => showInstallDialog(card.plugin, 'upgrade', event)"
|
|
1099
|
+
@downgrade="({event}) => showInstallDialog(card.plugin, 'downgrade', event)"
|
|
1084
1100
|
@install="({event}) => showInstallDialog(card.plugin, 'install', event)"
|
|
1085
1101
|
>
|
|
1086
1102
|
<template #item-card-sub-header>
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { SteveWatchEventListenerManager } from '@shell/plugins/subscribe-events';
|
|
2
|
+
import { STEVE_WATCH_EVENT_TYPES } from '@shell/types/store/subscribe.types';
|
|
3
|
+
|
|
4
|
+
// Mock function to create a consistent key for testing
|
|
5
|
+
const mockKeyForSubscribe = jest.fn(({
|
|
6
|
+
params: {
|
|
7
|
+
type, name, id, selector, mode
|
|
8
|
+
}
|
|
9
|
+
}) => {
|
|
10
|
+
return `${ type }-${ name }-${ id }-${ selector }-${ mode }`;
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// Mock parameters and callbacks
|
|
14
|
+
const mockParams1 = {
|
|
15
|
+
type: 'pods', name: 'my-pod', id: 'abc-123', selector: 'app=test'
|
|
16
|
+
};
|
|
17
|
+
const mockCallback1 = jest.fn();
|
|
18
|
+
const mockCallback2 = jest.fn();
|
|
19
|
+
|
|
20
|
+
// The class under test
|
|
21
|
+
let manager: SteveWatchEventListenerManager;
|
|
22
|
+
|
|
23
|
+
describe('steveWatchEventListenerManager', () => {
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
// Reset the manager and mocks before each test
|
|
26
|
+
manager = new SteveWatchEventListenerManager();
|
|
27
|
+
jest.clearAllMocks();
|
|
28
|
+
// Replace the internal keyForSubscribe with our mock
|
|
29
|
+
(manager as any).keyForSubscribe = mockKeyForSubscribe;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('initialization and Properties', () => {
|
|
33
|
+
it('should be created successfully', () => {
|
|
34
|
+
expect(manager).toBeInstanceOf(SteveWatchEventListenerManager);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should have a supportedEventTypes array with STEVE_WATCH_EVENT_TYPES.CHANGES', () => {
|
|
38
|
+
expect(manager.supportedEventTypes).toStrictEqual([STEVE_WATCH_EVENT_TYPES.CHANGES]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should correctly identify a supported event type', () => {
|
|
42
|
+
const isSupported = manager.isSupportedEventType(STEVE_WATCH_EVENT_TYPES.CHANGES);
|
|
43
|
+
|
|
44
|
+
expect(isSupported).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should correctly identify an unsupported event type', () => {
|
|
48
|
+
const isSupported = manager.isSupportedEventType('some.other.event' as STEVE_WATCH_EVENT_TYPES);
|
|
49
|
+
|
|
50
|
+
expect(isSupported).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('watch Management', () => {
|
|
55
|
+
it('should return undefined when getting a non-existent watch', () => {
|
|
56
|
+
const watch = manager.getWatch({ params: mockParams1 });
|
|
57
|
+
|
|
58
|
+
expect(watch).toBeUndefined();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should create a watch when setStandardWatch is called with standardWatch true and no watch exists', () => {
|
|
62
|
+
manager.setStandardWatch({ standardWatch: true, args: { params: mockParams1 } });
|
|
63
|
+
const watch = (manager as any).watches[mockKeyForSubscribe({ params: mockParams1 })];
|
|
64
|
+
|
|
65
|
+
expect(watch).toBeDefined();
|
|
66
|
+
expect(watch.hasStandardWatch).toBe(true);
|
|
67
|
+
expect(watch.listeners).toStrictEqual([]);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should not create a watch when setStandardWatch is called with standardWatch false and no watch exists', () => {
|
|
71
|
+
manager.setStandardWatch({ standardWatch: false, args: { params: mockParams1 } });
|
|
72
|
+
const watch = (manager as any).watches[mockKeyForSubscribe({ params: mockParams1 })];
|
|
73
|
+
|
|
74
|
+
expect(watch).toBeUndefined();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should delete a watch when hasStandardWatch becomes false and there are no listeners', () => {
|
|
78
|
+
manager.setStandardWatch({ standardWatch: true, args: { params: mockParams1 } });
|
|
79
|
+
manager.setStandardWatch({ standardWatch: false, args: { params: mockParams1 } });
|
|
80
|
+
const watch = (manager as any).watches[mockKeyForSubscribe({ params: mockParams1 })];
|
|
81
|
+
|
|
82
|
+
expect(watch).toBeUndefined();
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('listener and Callback Management', () => {
|
|
87
|
+
it('should add a new listener and a callback', () => {
|
|
88
|
+
const listener = manager.addEventListenerCallback({
|
|
89
|
+
callback: mockCallback1,
|
|
90
|
+
args: {
|
|
91
|
+
event: STEVE_WATCH_EVENT_TYPES.CHANGES,
|
|
92
|
+
params: mockParams1,
|
|
93
|
+
id: 'cb-1'
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
expect(listener).toBeDefined();
|
|
98
|
+
expect(listener.event).toBe(STEVE_WATCH_EVENT_TYPES.CHANGES);
|
|
99
|
+
expect(listener.callbacks['cb-1']).toBe(mockCallback1);
|
|
100
|
+
const watch = manager.getWatch({ params: mockParams1 });
|
|
101
|
+
|
|
102
|
+
expect(watch?.listeners.length).toBe(1);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should add a second callback to an existing listener', () => {
|
|
106
|
+
manager.addEventListenerCallback({
|
|
107
|
+
callback: mockCallback1,
|
|
108
|
+
args: {
|
|
109
|
+
event: STEVE_WATCH_EVENT_TYPES.CHANGES,
|
|
110
|
+
params: mockParams1,
|
|
111
|
+
id: 'cb-1'
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
manager.addEventListenerCallback({
|
|
115
|
+
callback: mockCallback2,
|
|
116
|
+
args: {
|
|
117
|
+
event: STEVE_WATCH_EVENT_TYPES.CHANGES,
|
|
118
|
+
params: mockParams1,
|
|
119
|
+
id: 'cb-2'
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const listener = manager.getEventListener({ args: { event: STEVE_WATCH_EVENT_TYPES.CHANGES, params: mockParams1 } });
|
|
124
|
+
|
|
125
|
+
expect(Object.keys(listener?.callbacks || {})).toHaveLength(2);
|
|
126
|
+
expect(listener?.callbacks['cb-2']).toBe(mockCallback2);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should trigger a specific event listener and its callbacks', () => {
|
|
130
|
+
manager.addEventListenerCallback({
|
|
131
|
+
callback: mockCallback1,
|
|
132
|
+
args: {
|
|
133
|
+
event: STEVE_WATCH_EVENT_TYPES.CHANGES,
|
|
134
|
+
params: mockParams1,
|
|
135
|
+
id: 'cb-1'
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
manager.addEventListenerCallback({
|
|
139
|
+
callback: mockCallback2,
|
|
140
|
+
args: {
|
|
141
|
+
event: STEVE_WATCH_EVENT_TYPES.CHANGES,
|
|
142
|
+
params: mockParams1,
|
|
143
|
+
id: 'cb-2'
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
manager.triggerEventListener({ event: STEVE_WATCH_EVENT_TYPES.CHANGES, params: mockParams1 });
|
|
148
|
+
expect(mockCallback1).toHaveBeenCalledTimes(1);
|
|
149
|
+
expect(mockCallback2).toHaveBeenCalledTimes(1);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should remove a specific callback from a listener', () => {
|
|
153
|
+
manager.addEventListenerCallback({
|
|
154
|
+
callback: mockCallback1,
|
|
155
|
+
args: {
|
|
156
|
+
event: STEVE_WATCH_EVENT_TYPES.CHANGES,
|
|
157
|
+
params: mockParams1,
|
|
158
|
+
id: 'cb-1'
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
manager.removeEventListenerCallback({
|
|
162
|
+
event: STEVE_WATCH_EVENT_TYPES.CHANGES,
|
|
163
|
+
params: mockParams1,
|
|
164
|
+
id: 'cb-1'
|
|
165
|
+
});
|
|
166
|
+
const listener = manager.getEventListener({ args: { event: STEVE_WATCH_EVENT_TYPES.CHANGES, params: mockParams1 } });
|
|
167
|
+
|
|
168
|
+
expect(listener?.callbacks['cb-1']).toBeUndefined();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should trigger all callbacks for a given watch', () => {
|
|
172
|
+
manager.addEventListenerCallback({
|
|
173
|
+
callback: mockCallback1,
|
|
174
|
+
args: {
|
|
175
|
+
event: STEVE_WATCH_EVENT_TYPES.CHANGES,
|
|
176
|
+
params: mockParams1,
|
|
177
|
+
id: 'cb-1'
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
manager.addEventListenerCallback({
|
|
181
|
+
callback: mockCallback2,
|
|
182
|
+
args: {
|
|
183
|
+
event: 'another.event' as STEVE_WATCH_EVENT_TYPES,
|
|
184
|
+
params: mockParams1,
|
|
185
|
+
id: 'cb-2'
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
manager.triggerAllEventListeners({ params: mockParams1 });
|
|
190
|
+
expect(mockCallback1).toHaveBeenCalledTimes(1);
|
|
191
|
+
expect(mockCallback2).toHaveBeenCalledTimes(1);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
});
|
|
@@ -220,6 +220,9 @@ export default {
|
|
|
220
220
|
)
|
|
221
221
|
) {
|
|
222
222
|
if (opt.watch !== false ) {
|
|
223
|
+
// Note - Empty revision here seems broken
|
|
224
|
+
// - list page (watch all) --> detail page (stop watch all, watch one) --> list page (watch all - no revision)
|
|
225
|
+
// - the missing revision means watch start from now... instead of the point the clusters were last monitored (cache contains stale data)
|
|
223
226
|
const args = {
|
|
224
227
|
type,
|
|
225
228
|
revision: '',
|
|
@@ -526,7 +526,7 @@ export default {
|
|
|
526
526
|
const store = state.config.namespace;
|
|
527
527
|
const resource = id || context ? { id, context } : null;
|
|
528
528
|
|
|
529
|
-
return paginationUtils.isEnabled({ rootGetters }, { store, resource });
|
|
529
|
+
return paginationUtils.isEnabled({ rootGetters, $plugin: rootState.$plugin }, { store, resource });
|
|
530
530
|
},
|
|
531
531
|
|
|
532
532
|
/**
|
|
@@ -62,7 +62,7 @@ const DEFAULT_COLOR = 'warning';
|
|
|
62
62
|
const DEFAULT_ICON = 'x';
|
|
63
63
|
|
|
64
64
|
const DEFAULT_WAIT_INTERVAL = 1000;
|
|
65
|
-
const
|
|
65
|
+
const DEFAULT_WAIT_TIMEOUT = 30000;
|
|
66
66
|
|
|
67
67
|
export const STATES_ENUM = {
|
|
68
68
|
IN_USE: 'in-use',
|
|
@@ -806,7 +806,7 @@ export default class Resource {
|
|
|
806
806
|
// ------------------------------------------------------------------
|
|
807
807
|
|
|
808
808
|
waitForTestFn(fn, msg, timeoutMs, intervalMs) {
|
|
809
|
-
return waitFor(() => fn.apply(this), msg, timeoutMs ||
|
|
809
|
+
return waitFor(() => fn.apply(this), msg, timeoutMs || DEFAULT_WAIT_TIMEOUT, intervalMs || DEFAULT_WAIT_INTERVAL, true);
|
|
810
810
|
}
|
|
811
811
|
|
|
812
812
|
waitForState(state, timeout, interval) {
|
|
@@ -855,7 +855,7 @@ export default class Resource {
|
|
|
855
855
|
return (entry.status || '').toLowerCase() === `${ withStatus }`.toLowerCase();
|
|
856
856
|
}
|
|
857
857
|
|
|
858
|
-
waitForCondition(name, withStatus = 'True', timeoutMs =
|
|
858
|
+
waitForCondition(name, withStatus = 'True', timeoutMs = DEFAULT_WAIT_TIMEOUT, intervalMs = DEFAULT_WAIT_INTERVAL) {
|
|
859
859
|
return this.waitForTestFn(() => {
|
|
860
860
|
return this.isCondition(name, withStatus);
|
|
861
861
|
}, `condition ${ name }=${ withStatus }`, timeoutMs, intervalMs);
|