@rancher/shell 3.0.8-rc.8 → 3.0.8-rc.9
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/brand/suse/dark/rancher-logo.svg +1 -64
- package/assets/translations/en-us.yaml +9 -1
- package/components/BackLink.vue +8 -0
- package/components/BannerGraphic.vue +1 -5
- package/components/BrandImage.vue +17 -6
- package/components/Cron/CronExpressionEditor.vue +1 -1
- package/components/Cron/CronExpressionEditorModal.vue +1 -1
- package/components/Drawer/ResourceDetailDrawer/ConfigTab.vue +1 -0
- package/components/Drawer/ResourceDetailDrawer/index.vue +1 -0
- package/components/Drawer/ResourceDetailDrawer/types.ts +2 -1
- package/components/Questions/__tests__/index.test.ts +159 -0
- package/components/Resource/Detail/Metadata/Annotations/index.vue +2 -2
- package/components/Resource/Detail/Metadata/Labels/index.vue +2 -2
- package/components/Resource/Detail/Metadata/index.vue +3 -3
- package/components/Resource/Detail/composables.ts +2 -2
- package/components/Tabbed/__tests__/index.test.ts +86 -0
- package/components/auth/SelectPrincipal.vue +24 -6
- package/components/auth/__tests__/SelectPrincipal.test.ts +119 -0
- package/components/formatter/InternalExternalIP.vue +4 -1
- package/components/formatter/__tests__/InternalExternalIP.test.ts +1 -1
- package/components/templates/standalone.vue +1 -1
- package/composables/useI18n.ts +10 -1
- package/config/__test__/uiplugins.test.ts +309 -0
- package/config/labels-annotations.js +1 -0
- package/config/product/explorer.js +3 -1
- package/config/router/routes.js +6 -2
- package/config/types.js +7 -0
- package/config/uiplugins.js +46 -2
- package/core/__test__/extension-manager-impl.test.js +236 -0
- package/core/extension-manager-impl.js +23 -6
- package/core/types-provisioning.ts +1 -1
- package/detail/provisioning.cattle.io.cluster.vue +1 -0
- package/dialog/DeveloperLoadExtensionDialog.vue +12 -3
- package/dialog/RollbackWorkloadDialog.vue +2 -5
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +2 -2
- package/edit/autoscaling.horizontalpodautoscaler/index.vue +1 -0
- package/edit/configmap.vue +1 -0
- package/edit/constraints.gatekeeper.sh.constraint/index.vue +1 -0
- package/edit/fleet.cattle.io.helmop.vue +6 -6
- package/edit/helm.cattle.io.projecthelmchart.vue +1 -0
- package/edit/k8s.cni.cncf.io.networkattachmentdefinition.vue +1 -0
- package/edit/logging-flow/index.vue +1 -0
- package/edit/logging.banzaicloud.io.output/index.vue +1 -0
- package/edit/management.cattle.io.fleetworkspace.vue +1 -1
- package/edit/management.cattle.io.project.vue +1 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/index.vue +4 -1
- package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +2 -1
- package/edit/monitoring.coreos.com.prometheusrule/index.vue +1 -0
- package/edit/monitoring.coreos.com.receiver/index.vue +2 -1
- package/edit/monitoring.coreos.com.route.vue +1 -1
- package/edit/namespace.vue +1 -0
- package/edit/networking.istio.io.destinationrule/index.vue +1 -0
- package/edit/networking.k8s.io.ingress/index.vue +1 -0
- package/edit/networking.k8s.io.networkpolicy/PolicyRules.vue +1 -0
- package/edit/networking.k8s.io.networkpolicy/index.vue +1 -0
- package/edit/node.vue +1 -0
- package/edit/persistentvolume/index.vue +27 -22
- package/edit/persistentvolume/plugins/awsElasticBlockStore.vue +13 -14
- package/edit/persistentvolume/plugins/azureDisk.vue +49 -48
- package/edit/persistentvolume/plugins/azureFile.vue +15 -14
- package/edit/persistentvolume/plugins/cephfs.vue +15 -14
- package/edit/persistentvolume/plugins/cinder.vue +15 -14
- package/edit/persistentvolume/plugins/csi.vue +18 -16
- package/edit/persistentvolume/plugins/fc.vue +13 -14
- package/edit/persistentvolume/plugins/flexVolume.vue +15 -14
- package/edit/persistentvolume/plugins/flocker.vue +1 -3
- package/edit/persistentvolume/plugins/gcePersistentDisk.vue +13 -14
- package/edit/persistentvolume/plugins/glusterfs.vue +15 -14
- package/edit/persistentvolume/plugins/hostPath.vue +40 -39
- package/edit/persistentvolume/plugins/iscsi.vue +13 -14
- package/edit/persistentvolume/plugins/local.vue +1 -3
- package/edit/persistentvolume/plugins/longhorn.vue +23 -22
- package/edit/persistentvolume/plugins/nfs.vue +15 -14
- package/edit/persistentvolume/plugins/photonPersistentDisk.vue +1 -14
- package/edit/persistentvolume/plugins/portworxVolume.vue +15 -14
- package/edit/persistentvolume/plugins/quobyte.vue +15 -14
- package/edit/persistentvolume/plugins/rbd.vue +15 -14
- package/edit/persistentvolume/plugins/scaleIO.vue +15 -14
- package/edit/persistentvolume/plugins/storageos.vue +15 -14
- package/edit/persistentvolume/plugins/vsphereVolume.vue +1 -3
- package/edit/provisioning.cattle.io.cluster/rke2.vue +1 -0
- package/edit/secret/index.vue +1 -1
- package/edit/service.vue +1 -0
- package/edit/serviceaccount.vue +1 -0
- package/edit/storage.k8s.io.storageclass/index.vue +1 -0
- package/edit/workload/index.vue +2 -1
- package/edit/workload/mixins/workload.js +1 -1
- package/initialize/App.vue +4 -4
- package/initialize/install-plugins.js +17 -2
- package/mixins/__tests__/brand.spec.ts +2 -2
- package/mixins/brand.js +1 -7
- package/mixins/create-edit-view/index.js +5 -0
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +112 -5
- package/models/management.cattle.io.cluster.js +21 -3
- package/models/provisioning.cattle.io.cluster.js +21 -9
- package/package.json +5 -4
- package/pages/auth/login.vue +1 -3
- package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +135 -0
- package/pages/c/_cluster/apps/charts/chart.vue +33 -15
- package/pages/c/_cluster/explorer/index.vue +8 -6
- package/pages/c/_cluster/manager/hostedprovider/index.vue +12 -6
- package/pages/c/_cluster/settings/brand.vue +1 -1
- package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +7 -0
- package/pages/c/_cluster/uiplugins/catalogs.vue +147 -0
- package/pages/c/_cluster/uiplugins/index.vue +126 -184
- package/plugins/dashboard-client-init.js +3 -0
- package/plugins/dashboard-store/getters.js +18 -1
- package/plugins/dashboard-store/resource-class.js +3 -2
- package/plugins/i18n.js +8 -0
- package/plugins/steve/__tests__/steve-pagination-utils.test.ts +333 -0
- package/plugins/steve/steve-pagination-utils.ts +39 -20
- package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +6 -42
- package/rancher-components/Pill/RcStatusBadge/index.ts +0 -1
- package/rancher-components/Pill/RcStatusBadge/types.ts +1 -1
- package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +5 -28
- package/rancher-components/Pill/RcStatusIndicator/types.ts +2 -1
- package/rancher-components/Pill/types.ts +0 -1
- package/rancher-components/RcIcon/RcIcon.test.ts +51 -0
- package/rancher-components/RcIcon/RcIcon.vue +46 -0
- package/rancher-components/RcIcon/index.ts +1 -0
- package/rancher-components/RcIcon/types.ts +160 -0
- package/rancher-components/utils/status.test.ts +67 -0
- package/rancher-components/utils/status.ts +77 -0
- package/scripts/typegen.sh +1 -0
- package/store/action-menu.js +8 -0
- package/store/auth.js +3 -3
- package/store/catalog.js +6 -0
- package/store/index.js +4 -4
- package/store/prefs.js +4 -5
- package/store/wm.ts +4 -4
- package/types/shell/index.d.ts +38 -2
- package/types/store/__tests__/pagination.types.spec.ts +137 -0
- package/types/store/pagination.types.ts +157 -9
- package/utils/__tests__/provider.test.ts +98 -0
- package/utils/__tests__/selector-typed.test.ts +263 -0
- package/utils/color.js +1 -1
- package/utils/dynamic-content/__tests__/info.test.ts +6 -0
- package/utils/dynamic-content/info.ts +43 -0
- package/utils/favicon.js +4 -4
- package/utils/provider.ts +14 -0
- package/utils/selector-typed.ts +6 -2
- package/plugins/nuxt-client-init.js +0 -3
|
@@ -56,11 +56,13 @@ export default {
|
|
|
56
56
|
|
|
57
57
|
data() {
|
|
58
58
|
return {
|
|
59
|
-
principals:
|
|
60
|
-
searchStr:
|
|
61
|
-
options:
|
|
62
|
-
newValue:
|
|
63
|
-
tooltipContent:
|
|
59
|
+
principals: null,
|
|
60
|
+
searchStr: '',
|
|
61
|
+
options: [],
|
|
62
|
+
newValue: '',
|
|
63
|
+
tooltipContent: null,
|
|
64
|
+
hasSearchTooShort: false,
|
|
65
|
+
minSearchLength: 2,
|
|
64
66
|
};
|
|
65
67
|
},
|
|
66
68
|
|
|
@@ -133,9 +135,20 @@ export default {
|
|
|
133
135
|
this.searchStr = str;
|
|
134
136
|
|
|
135
137
|
if ( str ) {
|
|
138
|
+
// Backend requires minimum 2 characters for search
|
|
139
|
+
if (str.length < this.minSearchLength) {
|
|
140
|
+
this.hasSearchTooShort = true;
|
|
141
|
+
this.options = [];
|
|
142
|
+
loading(false);
|
|
143
|
+
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
this.hasSearchTooShort = false;
|
|
136
148
|
loading(true);
|
|
137
149
|
this.debouncedSearch(str, loading);
|
|
138
150
|
} else {
|
|
151
|
+
this.hasSearchTooShort = false;
|
|
139
152
|
this.search(null, loading);
|
|
140
153
|
}
|
|
141
154
|
},
|
|
@@ -196,7 +209,12 @@ export default {
|
|
|
196
209
|
@on-close="setTooltipContent()"
|
|
197
210
|
>
|
|
198
211
|
<template v-slot:no-options="{ searching }">
|
|
199
|
-
<template v-if="
|
|
212
|
+
<template v-if="hasSearchTooShort">
|
|
213
|
+
<span class="search-slot">
|
|
214
|
+
{{ t('cluster.memberRoles.addClusterMember.minCharacters', { count: minSearchLength }) }}
|
|
215
|
+
</span>
|
|
216
|
+
</template>
|
|
217
|
+
<template v-else-if="searching">
|
|
200
218
|
<span class="search-slot">
|
|
201
219
|
{{ t('cluster.memberRoles.addClusterMember.noResults') }}
|
|
202
220
|
</span>
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { shallowMount, type VueWrapper } from '@vue/test-utils';
|
|
2
|
+
import SelectPrincipal from '@shell/components/auth/SelectPrincipal.vue';
|
|
3
|
+
|
|
4
|
+
describe('component: SelectPrincipal', () => {
|
|
5
|
+
const mockStore = { dispatch: jest.fn().mockResolvedValue([]) };
|
|
6
|
+
|
|
7
|
+
const defaultMountOptions = {
|
|
8
|
+
global: {
|
|
9
|
+
mocks: {
|
|
10
|
+
$fetchState: { pending: false },
|
|
11
|
+
$store: mockStore,
|
|
12
|
+
t: (key: string, opts?: any) => opts?.count ? `${ key } ${ opts.count }` : key,
|
|
13
|
+
},
|
|
14
|
+
stubs: {
|
|
15
|
+
LabeledSelect: {
|
|
16
|
+
template: '<div class="labeled-select-stub"><slot name="no-options" :searching="searching" /></div>',
|
|
17
|
+
props: ['options', 'searchable', 'filterable'],
|
|
18
|
+
data() {
|
|
19
|
+
return { searching: false };
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
Principal: true,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
jest.clearAllMocks();
|
|
29
|
+
mockStore.dispatch.mockResolvedValue([]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('onSearch', () => {
|
|
33
|
+
it('should set hasSearchTooShort to true when search string is less than minSearchLength', async() => {
|
|
34
|
+
const wrapper: VueWrapper<any> = shallowMount(SelectPrincipal, defaultMountOptions);
|
|
35
|
+
|
|
36
|
+
// Set principals to an empty array to avoid null errors
|
|
37
|
+
wrapper.vm.principals = [];
|
|
38
|
+
await wrapper.vm.$nextTick();
|
|
39
|
+
|
|
40
|
+
const loadingFn = jest.fn();
|
|
41
|
+
|
|
42
|
+
wrapper.vm.onSearch('a', loadingFn);
|
|
43
|
+
|
|
44
|
+
expect(wrapper.vm.hasSearchTooShort).toBe(true);
|
|
45
|
+
expect(wrapper.vm.options).toStrictEqual([]);
|
|
46
|
+
expect(loadingFn).toHaveBeenCalledWith(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should set hasSearchTooShort to false when search string meets minSearchLength', async() => {
|
|
50
|
+
const wrapper: VueWrapper<any> = shallowMount(SelectPrincipal, defaultMountOptions);
|
|
51
|
+
|
|
52
|
+
wrapper.vm.principals = [];
|
|
53
|
+
await wrapper.vm.$nextTick();
|
|
54
|
+
|
|
55
|
+
const loadingFn = jest.fn();
|
|
56
|
+
|
|
57
|
+
wrapper.vm.onSearch('ab', loadingFn);
|
|
58
|
+
|
|
59
|
+
expect(wrapper.vm.hasSearchTooShort).toBe(false);
|
|
60
|
+
expect(loadingFn).toHaveBeenCalledWith(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should set hasSearchTooShort to false when search string is empty', async() => {
|
|
64
|
+
const wrapper: VueWrapper<any> = shallowMount(SelectPrincipal, defaultMountOptions);
|
|
65
|
+
|
|
66
|
+
wrapper.vm.principals = [];
|
|
67
|
+
await wrapper.vm.$nextTick();
|
|
68
|
+
|
|
69
|
+
// First set hasSearchTooShort to true
|
|
70
|
+
wrapper.vm.hasSearchTooShort = true;
|
|
71
|
+
|
|
72
|
+
const loadingFn = jest.fn();
|
|
73
|
+
|
|
74
|
+
wrapper.vm.onSearch('', loadingFn);
|
|
75
|
+
|
|
76
|
+
expect(wrapper.vm.hasSearchTooShort).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should not call debouncedSearch when search string is too short', async() => {
|
|
80
|
+
const wrapper: VueWrapper<any> = shallowMount(SelectPrincipal, defaultMountOptions);
|
|
81
|
+
|
|
82
|
+
wrapper.vm.principals = [];
|
|
83
|
+
await wrapper.vm.$nextTick();
|
|
84
|
+
|
|
85
|
+
// Spy on the debounced search
|
|
86
|
+
const debouncedSearchSpy = jest.spyOn(wrapper.vm, 'debouncedSearch');
|
|
87
|
+
const loadingFn = jest.fn();
|
|
88
|
+
|
|
89
|
+
wrapper.vm.onSearch('x', loadingFn);
|
|
90
|
+
|
|
91
|
+
expect(debouncedSearchSpy).not.toHaveBeenCalled();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should call debouncedSearch when search string meets minimum length', async() => {
|
|
95
|
+
const wrapper: VueWrapper<any> = shallowMount(SelectPrincipal, defaultMountOptions);
|
|
96
|
+
|
|
97
|
+
wrapper.vm.principals = [];
|
|
98
|
+
await wrapper.vm.$nextTick();
|
|
99
|
+
|
|
100
|
+
const debouncedSearchSpy = jest.spyOn(wrapper.vm, 'debouncedSearch');
|
|
101
|
+
const loadingFn = jest.fn();
|
|
102
|
+
|
|
103
|
+
wrapper.vm.onSearch('xy', loadingFn);
|
|
104
|
+
|
|
105
|
+
expect(debouncedSearchSpy).toHaveBeenCalledWith('xy', loadingFn);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('minSearchLength', () => {
|
|
110
|
+
it('should have a default minSearchLength of 2', async() => {
|
|
111
|
+
const wrapper: VueWrapper<any> = shallowMount(SelectPrincipal, defaultMountOptions);
|
|
112
|
+
|
|
113
|
+
wrapper.vm.principals = [];
|
|
114
|
+
await wrapper.vm.$nextTick();
|
|
115
|
+
|
|
116
|
+
expect(wrapper.vm.minSearchLength).toBe(2);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -108,10 +108,13 @@ export default {
|
|
|
108
108
|
>
|
|
109
109
|
<template #default>
|
|
110
110
|
<RcStatusBadge
|
|
111
|
-
v-clean-tooltip="tooltipContent"
|
|
111
|
+
v-clean-tooltip="{content: tooltipContent, triggers: ['hover', 'focus']}"
|
|
112
|
+
:aria-label="t('generic.plusMore', {n: remainingIpCount})"
|
|
113
|
+
tabindex="0"
|
|
112
114
|
status="info"
|
|
113
115
|
data-testid="plus-more"
|
|
114
116
|
@click.stop
|
|
117
|
+
@keyup.enter.space="$refs.dropdown.show()"
|
|
115
118
|
>
|
|
116
119
|
{{ t('generic.plusMore', {n: remainingIpCount}) }}
|
|
117
120
|
</RcStatusBadge>
|
|
@@ -24,7 +24,7 @@ describe('component: InternalExternalIP', () => {
|
|
|
24
24
|
components: { 'v-dropdown': { name: 'v-dropdown', template: '<div><slot /><slot name="popper" /></div>' } },
|
|
25
25
|
directives: {
|
|
26
26
|
'clean-tooltip': (el, binding) => {
|
|
27
|
-
el.setAttribute('v-clean-tooltip', binding.value);
|
|
27
|
+
el.setAttribute('v-clean-tooltip', binding.value.content);
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
30
|
mocks: { $store: { getters: mockGetters } }
|
package/composables/useI18n.ts
CHANGED
|
@@ -11,7 +11,16 @@ let store: Store<any> | null = null;
|
|
|
11
11
|
* @returns A translated string or the raw value if the raw parameter is set to true.
|
|
12
12
|
*/
|
|
13
13
|
const t = (key: string, args?: unknown, raw?: boolean): string => {
|
|
14
|
-
|
|
14
|
+
if (!store) {
|
|
15
|
+
if (!!process.env.dev) {
|
|
16
|
+
// eslint-disable-next-line no-console
|
|
17
|
+
console.warn('useI18n: store not available');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return key;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return stringFor(store, key, args as any, raw);
|
|
15
24
|
};
|
|
16
25
|
|
|
17
26
|
export type I18n = { t: typeof t };
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import {
|
|
2
|
+
UI_PLUGIN_ANNOTATION,
|
|
3
|
+
UI_PLUGIN_CHART_ANNOTATIONS,
|
|
4
|
+
EXTENSIONS_INCOMPATIBILITY_TYPES,
|
|
5
|
+
isUIPlugin,
|
|
6
|
+
uiPluginHasAnnotation,
|
|
7
|
+
parseRancherVersion,
|
|
8
|
+
// shouldNotLoadPlugin, this is required per test so that we can mock semver.coerce
|
|
9
|
+
isSupportedChartVersion,
|
|
10
|
+
isChartVersionHigher
|
|
11
|
+
} from '@shell/config/uiplugins';
|
|
12
|
+
|
|
13
|
+
import * as VersionModule from '@shell/config/version';
|
|
14
|
+
|
|
15
|
+
let semver;
|
|
16
|
+
let originalCoerce: any;
|
|
17
|
+
const MOCK_API_VERSION = '33.33.33';
|
|
18
|
+
|
|
19
|
+
describe('uiPlugins Config methods', () => {
|
|
20
|
+
describe('fx: isUIPlugin', () => {
|
|
21
|
+
const pluginChart = { versions: [{ annotations: { [UI_PLUGIN_ANNOTATION.NAME]: UI_PLUGIN_ANNOTATION.VALUE } }, { annotations: {} }] };
|
|
22
|
+
|
|
23
|
+
it('should return true if any version has the UI plugin annotation', () => {
|
|
24
|
+
expect(isUIPlugin(pluginChart)).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
it('should return false if chart is null/undefined', () => {
|
|
27
|
+
expect(isUIPlugin(null)).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('fx: uiPluginHasAnnotation', () => {
|
|
32
|
+
const version1 = { annotations: { [UI_PLUGIN_ANNOTATION.NAME]: UI_PLUGIN_ANNOTATION.VALUE } };
|
|
33
|
+
const chart = { versions: [version1] };
|
|
34
|
+
|
|
35
|
+
it('should return true if it finds version with annotation and its corresponding value', () => {
|
|
36
|
+
expect(uiPluginHasAnnotation(chart, UI_PLUGIN_ANNOTATION.NAME, UI_PLUGIN_ANNOTATION.VALUE)).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
it('should return false if chart has no version with a given pair of annotation/value', () => {
|
|
39
|
+
expect(uiPluginHasAnnotation(chart, 'bananas', UI_PLUGIN_ANNOTATION.VALUE)).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('fx: parseRancherVersion', () => {
|
|
44
|
+
it('should handle standard version strings', () => {
|
|
45
|
+
expect(parseRancherVersion('v2.8.1')).toBe('2.8.1');
|
|
46
|
+
});
|
|
47
|
+
it('should apply .999 patch for RC versions', () => {
|
|
48
|
+
expect(parseRancherVersion('v2.8.1-rc1')).toBe('2.8.999');
|
|
49
|
+
});
|
|
50
|
+
it('should apply .999 patch for "head" versions', () => {
|
|
51
|
+
expect(parseRancherVersion('2.8.0-head')).toBe('2.8.999');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const PLUGIN_NAME = 'test-plugin';
|
|
56
|
+
|
|
57
|
+
const basePluginResource = {
|
|
58
|
+
name: PLUGIN_NAME,
|
|
59
|
+
version: '1.0.0',
|
|
60
|
+
endpoint: '/some/path',
|
|
61
|
+
metadata: { [UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION]: '>=1.0.0' }
|
|
62
|
+
};
|
|
63
|
+
const compatibleEnv = {
|
|
64
|
+
rancherVersion: '2.7.5',
|
|
65
|
+
kubeVersion: 'v1.25.10',
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
describe('fx: shouldNotLoadPlugin', () => {
|
|
69
|
+
beforeEach(() => {
|
|
70
|
+
jest.resetModules();
|
|
71
|
+
process.env.UI_EXTENSIONS_API_VERSION = MOCK_API_VERSION;
|
|
72
|
+
|
|
73
|
+
semver = require('semver');
|
|
74
|
+
originalCoerce = semver.coerce;
|
|
75
|
+
|
|
76
|
+
jest.spyOn(semver, 'coerce').mockImplementation((v) => {
|
|
77
|
+
if (v === MOCK_API_VERSION) {
|
|
78
|
+
return { version: '3.0.0' };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Use the original for all other inputs (Rancher, Kube)
|
|
82
|
+
return originalCoerce(v);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
afterEach(() => {
|
|
87
|
+
jest.restoreAllMocks();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should return false when compatible', () => {
|
|
91
|
+
const { shouldNotLoadPlugin } = require('./../uiplugins');
|
|
92
|
+
|
|
93
|
+
expect(shouldNotLoadPlugin(basePluginResource, compatibleEnv, [])).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should return "plugins.error.api" if Extensions API version is incompatible', () => {
|
|
97
|
+
const { shouldNotLoadPlugin } = require('./../uiplugins');
|
|
98
|
+
|
|
99
|
+
const incompatibleApiPlugin = {
|
|
100
|
+
...basePluginResource,
|
|
101
|
+
metadata: { [UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION]: '>=4.0.0' }
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
expect(shouldNotLoadPlugin(incompatibleApiPlugin, compatibleEnv, [])).toBe('plugins.error.api');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should return "plugins.error.primeOnly" if extension is Prime-only and Rancher is not Prime', () => {
|
|
108
|
+
const { shouldNotLoadPlugin } = require('./../uiplugins');
|
|
109
|
+
|
|
110
|
+
jest.spyOn(VersionModule, 'isRancherPrime').mockReturnValue(false);
|
|
111
|
+
|
|
112
|
+
const incompatibleApiPlugin = {
|
|
113
|
+
...basePluginResource,
|
|
114
|
+
metadata: {
|
|
115
|
+
[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION]: '>=1.0.0',
|
|
116
|
+
[UI_PLUGIN_CHART_ANNOTATIONS.PRIME_ONLY]: 'true'
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
expect(shouldNotLoadPlugin(incompatibleApiPlugin, compatibleEnv, [])).toBe('plugins.error.primeOnly');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should return "plugins.error.host" if HOST application value is incompatible', () => {
|
|
124
|
+
const { shouldNotLoadPlugin } = require('./../uiplugins');
|
|
125
|
+
|
|
126
|
+
const incompatibleApiPlugin = {
|
|
127
|
+
...basePluginResource,
|
|
128
|
+
metadata: {
|
|
129
|
+
[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION]: '>=1.0.0',
|
|
130
|
+
[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_HOST]: 'rancher-dummy-host'
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
expect(shouldNotLoadPlugin(incompatibleApiPlugin, compatibleEnv, [])).toBe('plugins.error.host');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should return "plugins.error.kubeVersion" if incompatible', () => {
|
|
138
|
+
const { shouldNotLoadPlugin } = require('./../uiplugins');
|
|
139
|
+
|
|
140
|
+
const incompatibleApiPlugin = {
|
|
141
|
+
...basePluginResource,
|
|
142
|
+
metadata: {
|
|
143
|
+
[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION]: '>=1.0.0',
|
|
144
|
+
[UI_PLUGIN_CHART_ANNOTATIONS.KUBE_VERSION]: '>= 2.0.0'
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
expect(shouldNotLoadPlugin(incompatibleApiPlugin, compatibleEnv, [])).toBe('plugins.error.kubeVersion');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should return "plugins.error.version" if incompatible', () => {
|
|
152
|
+
const { shouldNotLoadPlugin } = require('./../uiplugins');
|
|
153
|
+
|
|
154
|
+
const incompatibleApiPlugin = {
|
|
155
|
+
...basePluginResource,
|
|
156
|
+
metadata: {
|
|
157
|
+
[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION]: '>=1.0.0',
|
|
158
|
+
[UI_PLUGIN_CHART_ANNOTATIONS.RANCHER_VERSION]: '>= 3.0.0'
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
expect(shouldNotLoadPlugin(incompatibleApiPlugin, compatibleEnv, [])).toBe('plugins.error.version');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should return "plugins.error.developerPkg" if a builtin extension with the same name was loaded first', () => {
|
|
166
|
+
const { shouldNotLoadPlugin } = require('./../uiplugins');
|
|
167
|
+
|
|
168
|
+
const incompatibleApiPlugin = {
|
|
169
|
+
...basePluginResource,
|
|
170
|
+
metadata: { [UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION]: '>=1.0.0' }
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
expect(shouldNotLoadPlugin(incompatibleApiPlugin, compatibleEnv, [{ name: PLUGIN_NAME, builtin: true }])).toBe('plugins.error.developerPkg');
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe('fx: isSupportedChartVersion', () => {
|
|
178
|
+
beforeEach(() => {
|
|
179
|
+
jest.resetModules();
|
|
180
|
+
process.env.UI_EXTENSIONS_API_VERSION = MOCK_API_VERSION;
|
|
181
|
+
|
|
182
|
+
semver = require('semver');
|
|
183
|
+
originalCoerce = semver.coerce;
|
|
184
|
+
|
|
185
|
+
jest.spyOn(semver, 'coerce').mockImplementation((v) => {
|
|
186
|
+
if (v === MOCK_API_VERSION) {
|
|
187
|
+
return { version: '3.0.0' };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Use the original for all other inputs (Rancher, Kube)
|
|
191
|
+
return originalCoerce(v);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
afterEach(() => {
|
|
196
|
+
jest.restoreAllMocks();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const versionData = {
|
|
200
|
+
rancherVersion: 'v2.7.5',
|
|
201
|
+
kubeVersion: 'v1.25.10',
|
|
202
|
+
version: '1.0.0'
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
it('should return "isVersionCompatible" true when compatible', () => {
|
|
206
|
+
const { isSupportedChartVersion } = require('./../uiplugins');
|
|
207
|
+
|
|
208
|
+
const annotations = {
|
|
209
|
+
[UI_PLUGIN_CHART_ANNOTATIONS.KUBE_VERSION]: '>=1.0.0',
|
|
210
|
+
[UI_PLUGIN_CHART_ANNOTATIONS.RANCHER_VERSION]: '>=2.0.0',
|
|
211
|
+
[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION]: '>=2.0.0'
|
|
212
|
+
};
|
|
213
|
+
const data = { ...versionData, version: { annotations } };
|
|
214
|
+
const result = isSupportedChartVersion(data, true);
|
|
215
|
+
|
|
216
|
+
expect(result.isVersionCompatible).toBe(true);
|
|
217
|
+
expect(result.versionIncompatibilityData).toStrictEqual({});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should return incompatibility object for kube version mismatch', () => {
|
|
221
|
+
const annotations = { [UI_PLUGIN_CHART_ANNOTATIONS.KUBE_VERSION]: '>=2.0.0' };
|
|
222
|
+
const data = { ...versionData, version: { annotations } };
|
|
223
|
+
const result = isSupportedChartVersion(data, true);
|
|
224
|
+
|
|
225
|
+
expect(result.isVersionCompatible).toBe(false);
|
|
226
|
+
expect(result.versionIncompatibilityData.type).toBe(EXTENSIONS_INCOMPATIBILITY_TYPES.KUBE);
|
|
227
|
+
expect(result.versionIncompatibilityData.required).toBe('>=2.0.0');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('should return incompatibility object for Rancher version mismatch', () => {
|
|
231
|
+
const annotations = { [UI_PLUGIN_CHART_ANNOTATIONS.RANCHER_VERSION]: '>=3.0.0' };
|
|
232
|
+
const data = { ...versionData, version: { annotations } };
|
|
233
|
+
const result = isSupportedChartVersion(data, true);
|
|
234
|
+
|
|
235
|
+
expect(result.isVersionCompatible).toBe(false);
|
|
236
|
+
expect(result.versionIncompatibilityData.type).toBe(EXTENSIONS_INCOMPATIBILITY_TYPES.UI);
|
|
237
|
+
expect(result.versionIncompatibilityData.required).toBe('>=3.0.0');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should return incompatibility object for UI version mismatch', () => {
|
|
241
|
+
const annotations = { [UI_PLUGIN_CHART_ANNOTATIONS.UI_VERSION]: '>=3.0.0' };
|
|
242
|
+
const data = { ...versionData, version: { annotations } };
|
|
243
|
+
const result = isSupportedChartVersion(data, true);
|
|
244
|
+
|
|
245
|
+
expect(result.isVersionCompatible).toBe(false);
|
|
246
|
+
expect(result.versionIncompatibilityData.type).toBe(EXTENSIONS_INCOMPATIBILITY_TYPES.UI);
|
|
247
|
+
expect(result.versionIncompatibilityData.required).toBe('>=3.0.0');
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('should return incompatibility object for extensions api missing', () => {
|
|
251
|
+
const annotations = {};
|
|
252
|
+
const data = { ...versionData, version: { annotations } };
|
|
253
|
+
const result = isSupportedChartVersion(data, true);
|
|
254
|
+
|
|
255
|
+
expect(result.isVersionCompatible).toBe(false);
|
|
256
|
+
expect(result.versionIncompatibilityData.type).toBe(EXTENSIONS_INCOMPATIBILITY_TYPES.EXTENSIONS_API_MISSING);
|
|
257
|
+
expect(result.versionIncompatibilityData.required).toBeUndefined();
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('should return incompatibility object for extensions API version mismatch', () => {
|
|
261
|
+
const { isSupportedChartVersion } = require('./../uiplugins');
|
|
262
|
+
|
|
263
|
+
const annotations = { [UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION]: '>=4.0.0' };
|
|
264
|
+
const data = { ...versionData, version: { annotations } };
|
|
265
|
+
const result = isSupportedChartVersion(data, true);
|
|
266
|
+
|
|
267
|
+
expect(result.isVersionCompatible).toBe(false);
|
|
268
|
+
expect(result.versionIncompatibilityData.type).toBe(EXTENSIONS_INCOMPATIBILITY_TYPES.EXTENSIONS_API);
|
|
269
|
+
expect(result.versionIncompatibilityData.required).toBe('>=4.0.0');
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('should return incompatibility object for HOST_APP mismatch', () => {
|
|
273
|
+
const { isSupportedChartVersion } = require('./../uiplugins');
|
|
274
|
+
|
|
275
|
+
const annotations = { [UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION]: '>=1.0.0 < 5.0.0', [UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_HOST]: 'rancher-dummy-host' };
|
|
276
|
+
const data = { ...versionData, version: { annotations } };
|
|
277
|
+
const result = isSupportedChartVersion(data, true);
|
|
278
|
+
|
|
279
|
+
expect(result.isVersionCompatible).toBe(false);
|
|
280
|
+
expect(result.versionIncompatibilityData.type).toBe(EXTENSIONS_INCOMPATIBILITY_TYPES.HOST);
|
|
281
|
+
expect(result.versionIncompatibilityData.required).toBe('rancher-dummy-host');
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should return incompatibility object for Prime only extension running in non-prime env', () => {
|
|
285
|
+
const { isSupportedChartVersion } = require('./../uiplugins');
|
|
286
|
+
|
|
287
|
+
jest.spyOn(VersionModule, 'isRancherPrime').mockReturnValue(false);
|
|
288
|
+
|
|
289
|
+
const annotations = { [UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION]: '>=1.0.0 < 5.0.0', [UI_PLUGIN_CHART_ANNOTATIONS.PRIME_ONLY]: 'true' };
|
|
290
|
+
const data = { ...versionData, version: { annotations } };
|
|
291
|
+
const result = isSupportedChartVersion(data, true);
|
|
292
|
+
|
|
293
|
+
expect(result.isVersionCompatible).toBe(false);
|
|
294
|
+
expect(result.versionIncompatibilityData.type).toBe(EXTENSIONS_INCOMPATIBILITY_TYPES.PRIME_ONLY);
|
|
295
|
+
expect(result.versionIncompatibilityData.required).toBe(true);
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
describe('fx: isChartVersionHigher', () => {
|
|
300
|
+
// Tests now rely on the actual semver.gt implementation
|
|
301
|
+
it('should return true when version A is higher', () => {
|
|
302
|
+
expect(isChartVersionHigher('2.1.0', '2.0.0')).toBe(true);
|
|
303
|
+
});
|
|
304
|
+
it('should return false when version A is lower or equal', () => {
|
|
305
|
+
expect(isChartVersionHigher('2.0.0', '2.1.0')).toBe(false);
|
|
306
|
+
expect(isChartVersionHigher('2.0.0', '2.0.0')).toBe(false);
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
});
|
|
@@ -85,6 +85,7 @@ export const CATALOG = {
|
|
|
85
85
|
_PARTNER: 'partner',
|
|
86
86
|
_OTHER: 'other',
|
|
87
87
|
|
|
88
|
+
PRIME_ONLY: 'catalog.cattle.io/prime-only',
|
|
88
89
|
EXPERIMENTAL: 'catalog.cattle.io/experimental',
|
|
89
90
|
NAMESPACE: 'catalog.cattle.io/namespace',
|
|
90
91
|
RELEASE_NAME: 'catalog.cattle.io/release-name',
|
|
@@ -180,7 +180,8 @@ export function init(store) {
|
|
|
180
180
|
mapGroup(/^(.*\.)?(scc)\.cattle\.io$/, 'SCC');
|
|
181
181
|
|
|
182
182
|
const dePaginateBindings = configureConditionalDepaginate({ maxResourceCount: 5000 });
|
|
183
|
-
const dePaginateNormanBindings = configureConditionalDepaginate({ maxResourceCount: 5000, isNorman: true })
|
|
183
|
+
const dePaginateNormanBindings = configureConditionalDepaginate({ maxResourceCount: 5000, isNorman: true });
|
|
184
|
+
const dePaginateNormanUsers = configureConditionalDepaginate({ maxResourceCount: 5000, isNorman: true });
|
|
184
185
|
|
|
185
186
|
configureType(NODE, { isCreatable: false, isEditable: true });
|
|
186
187
|
configureType(WORKLOAD_TYPES.JOB, { isEditable: false, match: WORKLOAD_TYPES.JOB });
|
|
@@ -189,6 +190,7 @@ export function init(store) {
|
|
|
189
190
|
configureType(MANAGEMENT.PROJECT, { displayName: store.getters['i18n/t']('namespace.project.label') });
|
|
190
191
|
configureType(NORMAN.CLUSTER_ROLE_TEMPLATE_BINDING, { depaginate: dePaginateNormanBindings });
|
|
191
192
|
configureType(NORMAN.PROJECT_ROLE_TEMPLATE_BINDING, { depaginate: dePaginateNormanBindings });
|
|
193
|
+
configureType(NORMAN.USER, { depaginate: dePaginateNormanUsers });
|
|
192
194
|
configureType(SNAPSHOT, { depaginate: true });
|
|
193
195
|
|
|
194
196
|
configureType(SECRET, { showListMasthead: false });
|
package/config/router/routes.js
CHANGED
|
@@ -81,9 +81,13 @@ export default [
|
|
|
81
81
|
{
|
|
82
82
|
path: '/c/:cluster/uiplugins',
|
|
83
83
|
name: 'c-cluster-uiplugins',
|
|
84
|
-
component: () => interopDefault(import('@shell/pages/c/_cluster/uiplugins/index.vue'))
|
|
84
|
+
component: () => interopDefault(import('@shell/pages/c/_cluster/uiplugins/index.vue'))
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
path: '/c/:cluster/uiplugins/catalogs',
|
|
88
|
+
component: () => interopDefault(import('@shell/pages/c/_cluster/uiplugins/catalogs.vue')),
|
|
89
|
+
name: 'c-cluster-uiplugins-catalogs'
|
|
85
90
|
},
|
|
86
|
-
|
|
87
91
|
{
|
|
88
92
|
path: '/diagnostic',
|
|
89
93
|
component: () => interopDefault(import('@shell/pages/diagnostic.vue')),
|
package/config/types.js
CHANGED
|
@@ -222,6 +222,13 @@ export const MANAGEMENT = {
|
|
|
222
222
|
OIDC_CLIENT: 'management.cattle.io.oidcclient'
|
|
223
223
|
};
|
|
224
224
|
|
|
225
|
+
export const BRAND = {
|
|
226
|
+
SUSE: 'suse',
|
|
227
|
+
CSP: 'csp',
|
|
228
|
+
FEDERAL: 'federal',
|
|
229
|
+
RGS: 'rgs',
|
|
230
|
+
};
|
|
231
|
+
|
|
225
232
|
export const EXT = { USER_ACTIVITY: 'ext.cattle.io.useractivity' };
|
|
226
233
|
|
|
227
234
|
export const CAPI = {
|