@rancher/shell 2.0.0 → 2.0.2-rc.1
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/translations/en-us.yaml +69 -29
- package/assets/translations/zh-hans.yaml +1 -0
- package/components/AlertTable.vue +17 -7
- package/components/AssignTo.vue +2 -0
- package/components/GrafanaDashboard.vue +6 -4
- package/components/PromptRemove.vue +1 -0
- package/components/Questions/index.vue +2 -2
- package/components/auth/RoleDetailEdit.vue +5 -4
- package/components/form/KeyValue.vue +1 -0
- package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
- package/components/form/ProjectMemberEditor.vue +1 -1
- package/components/form/ResourceLabeledSelect.vue +11 -3
- package/components/form/Taints.vue +13 -7
- package/components/form/__tests__/Taints.test.ts +70 -0
- package/components/form/labeled-select-utils/labeled-select.utils.ts +1 -1
- package/components/nav/Header.vue +1 -1
- package/components/nav/TopLevelMenu.vue +1 -4
- package/config/pagination-table-headers.js +5 -4
- package/config/product/auth.js +1 -1
- package/config/roles.ts +34 -19
- package/config/router/navigation-guards/attempt-first-login.js +1 -1
- package/config/router/navigation-guards/authentication.js +1 -1
- package/config/router/navigation-guards/i18n.js +13 -0
- package/config/router/navigation-guards/index.js +3 -1
- package/config/router/navigation-guards/load-initial-settings.js +1 -1
- package/config/router/navigation-guards/runtime-extension-route.js +31 -0
- package/config/router/routes.js +10 -1
- package/config/uiplugins.js +130 -61
- package/core/plugin.ts +5 -0
- package/core/plugins.js +7 -1
- package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +42 -0
- package/detail/provisioning.cattle.io.cluster.vue +4 -4
- package/dialog/DeactivateDriverDialog.vue +30 -11
- package/edit/auth/__tests__/oidc.test.ts +2 -2
- package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +86 -13
- package/edit/provisioning.cattle.io.cluster/__tests__/DirectoryConfig.test.ts +3 -134
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +209 -0
- package/edit/provisioning.cattle.io.cluster/index.vue +8 -4
- package/edit/provisioning.cattle.io.cluster/rke2.vue +115 -17
- package/edit/provisioning.cattle.io.cluster/tabs/AddOnAdditionalManifest.vue +50 -0
- package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +29 -64
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +42 -3
- package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +22 -86
- package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +8 -2
- package/edit/provisioning.cattle.io.cluster/tabs/registries/__tests__/RegistryConfigs.test.ts +61 -0
- package/edit/token.vue +2 -1
- package/initialize/entry-helpers.js +4 -24
- package/list/management.cattle.io.feature.vue +4 -2
- package/middleware/authenticated.js +0 -19
- package/mixins/__tests__/chart.test.ts +4 -1
- package/mixins/auth-config.js +1 -1
- package/mixins/chart.js +30 -14
- package/models/__tests__/apps.deployment.test.ts +93 -0
- package/models/apps.deployment.js +18 -4
- package/models/driver.js +3 -2
- package/models/kontainerdriver.js +30 -13
- package/models/management.cattle.io.authconfig.js +2 -2
- package/models/management.cattle.io.cluster.js +2 -2
- package/models/management.cattle.io.user.js +3 -3
- package/models/nodedriver.js +35 -13
- package/models/provisioning.cattle.io.cluster.js +4 -0
- package/package.json +3 -2
- package/pages/404.vue +15 -0
- package/pages/auth/login.vue +4 -1
- package/pages/auth/setup.vue +4 -1
- package/pages/c/_cluster/apps/charts/install.vue +3 -2
- package/pages/c/_cluster/explorer/index.vue +5 -0
- package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +0 -3
- package/pages/c/_cluster/manager/drivers/nodeDriver/index.vue +1 -4
- package/pages/c/_cluster/manager/jwt.authentication/index.vue +10 -4
- package/pages/c/_cluster/settings/performance.vue +2 -2
- package/pages/c/_cluster/uiplugins/InstallDialog.vue +2 -1
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +7 -10
- package/pages/c/_cluster/uiplugins/index.vue +24 -16
- package/pages/home.vue +1 -13
- package/plugins/dashboard-store/actions.js +1 -1
- package/plugins/dashboard-store/getters.js +1 -1
- package/plugins/steve/__tests__/getters.test.ts +5 -5
- package/plugins/steve/getters.js +6 -4
- package/plugins/steve/hybrid-class.js +1 -5
- package/promptRemove/pod.vue +15 -7
- package/scripts/extension/helm/charts/ui-plugin-server/Chart.yaml +1 -1
- package/scripts/publish-shell.sh +54 -55
- package/scripts/test-plugins-build.sh +45 -39
- package/shell/types/shell/index.d.ts +2 -0
- package/store/auth.js +1 -1
- package/store/index.js +1 -1
- package/store/type-map.js +4 -2
- package/types/store/pagination.types.ts +1 -1
- package/utils/__tests__/kontainer.test.ts +89 -1
- package/utils/auth.js +1 -1
- package/utils/cluster.js +9 -0
- package/utils/kontainer.ts +5 -1
- package/utils/settings.ts +3 -1
- package/utils/version.js +2 -1
- package/creators/app/app.package.json +0 -13
- package/creators/app/files/.eslintignore +0 -16
- package/creators/app/files/.eslintrc.js +0 -173
- package/creators/app/files/.gitignore +0 -70
- package/creators/app/files/.gitlab-ci.yml +0 -14
- package/creators/app/files/.vscode/settings.json +0 -21
- package/creators/app/files/babel.config.js +0 -1
- package/creators/app/files/tsconfig.json +0 -42
- package/creators/app/files/vue.config.js +0 -6
- package/creators/app/init +0 -120
- package/creators/app/package.json +0 -25
- package/creators/pkg/files/.github/workflows/build-extension-catalog.yml +0 -24
- package/creators/pkg/files/.github/workflows/build-extension-charts.yml +0 -22
- package/creators/pkg/files/babel.config.js +0 -1
- package/creators/pkg/files/index.ts +0 -14
- package/creators/pkg/files/tsconfig.json +0 -53
- package/creators/pkg/files/vue.config.js +0 -1
- package/creators/pkg/init +0 -286
- package/creators/pkg/package.json +0 -19
- package/creators/pkg/pkg.package.json +0 -21
- package/creators/pkg/vue-shim.ts +0 -4
- package/creators/update/init +0 -56
- package/creators/update/package.json +0 -20
- package/creators/update/upgrade +0 -56
- package/rancher-components/components/Accordion/Accordion.test.ts +0 -45
- package/rancher-components/components/Accordion/Accordion.vue +0 -86
- package/rancher-components/components/Accordion/index.ts +0 -1
- package/rancher-components/components/BadgeState/BadgeState.test.ts +0 -12
- package/rancher-components/components/BadgeState/BadgeState.vue +0 -111
- package/rancher-components/components/BadgeState/index.ts +0 -1
- package/rancher-components/components/Banner/Banner.test.ts +0 -59
- package/rancher-components/components/Banner/Banner.vue +0 -244
- package/rancher-components/components/Banner/index.ts +0 -1
- package/rancher-components/components/Card/Card.test.ts +0 -37
- package/rancher-components/components/Card/Card.vue +0 -167
- package/rancher-components/components/Card/index.ts +0 -1
- package/rancher-components/components/Form/Checkbox/Checkbox.test.ts +0 -68
- package/rancher-components/components/Form/Checkbox/Checkbox.vue +0 -421
- package/rancher-components/components/Form/Checkbox/index.ts +0 -1
- package/rancher-components/components/Form/LabeledInput/LabeledInput.test.ts +0 -40
- package/rancher-components/components/Form/LabeledInput/LabeledInput.vue +0 -402
- package/rancher-components/components/Form/LabeledInput/index.ts +0 -1
- package/rancher-components/components/Form/Radio/RadioButton.test.ts +0 -33
- package/rancher-components/components/Form/Radio/RadioButton.vue +0 -293
- package/rancher-components/components/Form/Radio/RadioGroup.test.ts +0 -30
- package/rancher-components/components/Form/Radio/RadioGroup.vue +0 -259
- package/rancher-components/components/Form/Radio/index.ts +0 -2
- package/rancher-components/components/Form/TextArea/TextAreaAutoGrow.vue +0 -172
- package/rancher-components/components/Form/TextArea/index.ts +0 -1
- package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.test.ts +0 -94
- package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.vue +0 -152
- package/rancher-components/components/Form/ToggleSwitch/index.ts +0 -1
- package/rancher-components/components/Form/index.ts +0 -5
- package/rancher-components/components/LabeledTooltip/LabeledTooltip.vue +0 -156
- package/rancher-components/components/LabeledTooltip/index.ts +0 -1
- package/rancher-components/components/StringList/StringList.test.ts +0 -754
- package/rancher-components/components/StringList/StringList.vue +0 -650
- package/rancher-components/components/StringList/index.ts +0 -1
- package/types/shell/index.d.ts +0 -4585
package/config/roles.ts
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
export const enum SCOPED_RESOURCE_GROUPS {
|
|
2
|
+
GLOBAL = 'globalScopedApiGroups', // eslint-disable-line no-unused-vars
|
|
3
|
+
CLUSTER = 'clusterScopedApiGroups', // eslint-disable-line no-unused-vars
|
|
4
|
+
PROJECT_NAMESPACE = 'projectScopedApiGroups', // eslint-disable-line no-unused-vars
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Resources users can select when creating grants when managing global, cluster and project/namespace roles
|
|
9
|
+
*
|
|
10
|
+
* **************NOTE*****************
|
|
11
|
+
* Global roles will show ALL entries
|
|
12
|
+
* Cluster roles will show cluster AND project/namespace entries
|
|
13
|
+
* Project/Namespace roles will show ONLY project/namespace entries
|
|
14
|
+
*/
|
|
1
15
|
export const SCOPED_RESOURCES = {
|
|
2
16
|
// With this hardcoded list, it will be easier to curate a more useful
|
|
3
17
|
// and human-understandable list of resources to choose from
|
|
@@ -13,7 +27,7 @@ export const SCOPED_RESOURCES = {
|
|
|
13
27
|
// the global scoped list, and the project role creation form includes a
|
|
14
28
|
// subset of the cluster scoped list.
|
|
15
29
|
|
|
16
|
-
|
|
30
|
+
[SCOPED_RESOURCE_GROUPS.GLOBAL]: {
|
|
17
31
|
// Global scoped resources are resources for
|
|
18
32
|
// Rancher's global apps, mainly Cluster
|
|
19
33
|
// Management and Continuous Delivery.
|
|
@@ -130,9 +144,9 @@ export const SCOPED_RESOURCES = {
|
|
|
130
144
|
resources: [
|
|
131
145
|
'Clusters'
|
|
132
146
|
]
|
|
133
|
-
}
|
|
147
|
+
},
|
|
134
148
|
},
|
|
135
|
-
|
|
149
|
+
[SCOPED_RESOURCE_GROUPS.CLUSTER]: {
|
|
136
150
|
// Cluster scoped resources are for non-namespaced
|
|
137
151
|
// resources at the cluster level, for example,
|
|
138
152
|
// storage resources.
|
|
@@ -201,15 +215,16 @@ export const SCOPED_RESOURCES = {
|
|
|
201
215
|
},
|
|
202
216
|
neuvectorApi: {
|
|
203
217
|
resources: [
|
|
204
|
-
'
|
|
205
|
-
'
|
|
206
|
-
'
|
|
207
|
-
'
|
|
208
|
-
'
|
|
218
|
+
'AdmissionControl',
|
|
219
|
+
'Authentication',
|
|
220
|
+
'CIScan',
|
|
221
|
+
'Cluster',
|
|
222
|
+
'Federation',
|
|
223
|
+
'Vulnerability',
|
|
209
224
|
]
|
|
210
225
|
}
|
|
211
226
|
},
|
|
212
|
-
|
|
227
|
+
[SCOPED_RESOURCE_GROUPS.PROJECT_NAMESPACE]: {
|
|
213
228
|
// Project scoped resources include all other namespaced
|
|
214
229
|
// resources.
|
|
215
230
|
coreKubernetesApi: {
|
|
@@ -378,16 +393,16 @@ export const SCOPED_RESOURCES = {
|
|
|
378
393
|
},
|
|
379
394
|
neuvectorApi: {
|
|
380
395
|
resources: [
|
|
381
|
-
'
|
|
382
|
-
'
|
|
383
|
-
'
|
|
384
|
-
'
|
|
385
|
-
'
|
|
386
|
-
'
|
|
387
|
-
'
|
|
388
|
-
'
|
|
389
|
-
'
|
|
390
|
-
'
|
|
396
|
+
'AuditEvents',
|
|
397
|
+
'Authorization',
|
|
398
|
+
'Compliance',
|
|
399
|
+
'Events',
|
|
400
|
+
'Namespace',
|
|
401
|
+
'RegistryScan',
|
|
402
|
+
'RuntimePolicy',
|
|
403
|
+
'RuntimeScan',
|
|
404
|
+
'SecurityEvents',
|
|
405
|
+
'SystemConfig',
|
|
391
406
|
]
|
|
392
407
|
}
|
|
393
408
|
}
|
|
@@ -5,7 +5,7 @@ import { tryInitialSetup } from '@shell/utils/auth';
|
|
|
5
5
|
import { routeRequiresAuthentication } from '@shell/utils/router';
|
|
6
6
|
|
|
7
7
|
export function install(router, context) {
|
|
8
|
-
router.beforeEach((to, from, next) => attemptFirstLogin(to, from, next, context));
|
|
8
|
+
router.beforeEach(async(to, from, next) => await attemptFirstLogin(to, from, next, context));
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export async function attemptFirstLogin(to, from, next, { store }) {
|
|
@@ -2,7 +2,7 @@ import { routeRequiresAuthentication } from '@shell/utils/router';
|
|
|
2
2
|
import { isLoggedIn, notLoggedIn, noAuth, findMe } from '@shell/utils/auth';
|
|
3
3
|
|
|
4
4
|
export function install(router, context) {
|
|
5
|
-
router.beforeEach((to, from, next) => authenticate(to, from, next, context));
|
|
5
|
+
router.beforeEach(async(to, from, next) => await authenticate(to, from, next, context));
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export async function authenticate(to, from, next, { store }) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function install(router, context) {
|
|
2
|
+
router.beforeEach(async(to, from, next) => await loadI18n(to, from, next, context));
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export async function loadI18n(to, from, next, { store }) {
|
|
6
|
+
try {
|
|
7
|
+
await store.dispatch('i18n/init');
|
|
8
|
+
} catch (e) {
|
|
9
|
+
console.error('Failed to initialize i18n', e); // eslint-disable-line no-console
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
next();
|
|
13
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { install as installLoadInitialSettings } from '@shell/config/router/navigation-guards/load-initial-settings';
|
|
2
2
|
import { install as installAttemptFirstLogin } from '@shell/config/router/navigation-guards/attempt-first-login';
|
|
3
3
|
import { install as installAuthentication } from '@shell/config/router/navigation-guards/authentication';
|
|
4
|
+
import { install as installRuntimeExtensionRoute } from '@shell/config/router/navigation-guards/runtime-extension-route';
|
|
5
|
+
import { install as installI18N } from '@shell/config/router/navigation-guards/i18n';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Install our router navigation guards. i.e. router.beforeEach(), router.afterEach()
|
|
@@ -9,7 +11,7 @@ export function installNavigationGuards(router, context) {
|
|
|
9
11
|
// NOTE: the order of the installation matters.
|
|
10
12
|
// Be intentional when adding, removing or modifying the guards that are installed.
|
|
11
13
|
|
|
12
|
-
const navigationGuardInstallers = [installLoadInitialSettings, installAttemptFirstLogin, installAuthentication];
|
|
14
|
+
const navigationGuardInstallers = [installLoadInitialSettings, installAttemptFirstLogin, installAuthentication, installRuntimeExtensionRoute, installI18N];
|
|
13
15
|
|
|
14
16
|
navigationGuardInstallers.forEach((installer) => installer(router, context));
|
|
15
17
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { fetchInitialSettings } from '@shell/utils/settings';
|
|
2
2
|
|
|
3
3
|
export function install(router, context) {
|
|
4
|
-
router.beforeEach((to, from, next) => loadInitialSettings(to, from, next, context));
|
|
4
|
+
router.beforeEach(async(to, from, next) => await loadInitialSettings(to, from, next, context));
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export async function loadInitialSettings(to, from, next, { store }) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import dynamicPluginLoader from '@shell/pkg/dynamic-plugin-loader';
|
|
2
|
+
import { routeRequiresAuthentication } from '@shell/utils/router';
|
|
3
|
+
|
|
4
|
+
export function install(router, context) {
|
|
5
|
+
router.beforeEach((to, from, next) => runtimeExtensionRoute(to, from, next, context));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export async function runtimeExtensionRoute(to, from, next, { store }) {
|
|
9
|
+
if (!routeRequiresAuthentication(to) || to.name !== '404') {
|
|
10
|
+
return next();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
// Handle the loading of dynamic plugins (Harvester) because we only want to attempt to load those plugins and routes if we first couldn't find a page.
|
|
15
|
+
// We should probably get rid of this concept entirely and just load plugins at the start.
|
|
16
|
+
await store.dispatch('loadManagement');
|
|
17
|
+
const newLocation = await dynamicPluginLoader.check({ route: { path: window.location.pathname }, store });
|
|
18
|
+
|
|
19
|
+
// If we have a new location, double check that it's actually valid
|
|
20
|
+
const resolvedRoute = newLocation?.path ? store.app.router.resolve({ path: newLocation.path.replace(/^\/{0,1}dashboard/, '') }) : null;
|
|
21
|
+
|
|
22
|
+
if (resolvedRoute?.route.matched.length) {
|
|
23
|
+
// Note - don't use `redirect` or `store.app.route` (breaks feature by failing to run middleware in default layout)
|
|
24
|
+
return next(resolvedRoute.resolved.path);
|
|
25
|
+
}
|
|
26
|
+
} catch (e) {
|
|
27
|
+
console.error('Failed to load harvester', e); // eslint-disable-line no-console
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
next();
|
|
31
|
+
}
|
package/config/router/routes.js
CHANGED
|
@@ -41,6 +41,7 @@ export default [
|
|
|
41
41
|
path: '',
|
|
42
42
|
component: () => interopDefault(import('@shell/components/templates/home.vue')),
|
|
43
43
|
meta: { requiresAuthentication: true },
|
|
44
|
+
name: 'home_layout',
|
|
44
45
|
children: [
|
|
45
46
|
{
|
|
46
47
|
path: '/home',
|
|
@@ -484,4 +485,12 @@ export default [
|
|
|
484
485
|
component: () => interopDefault(import('@shell/pages/c/_cluster/_product/_resource/_namespace/_id.vue')),
|
|
485
486
|
name: 'c-cluster-product-resource-namespace-id'
|
|
486
487
|
}]
|
|
487
|
-
}
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
path: '*',
|
|
491
|
+
name: '404',
|
|
492
|
+
component: () => interopDefault(import('@shell/pages/404.vue')),
|
|
493
|
+
meta: { requiresAuthentication: true },
|
|
494
|
+
},
|
|
495
|
+
|
|
496
|
+
];
|
package/config/uiplugins.js
CHANGED
|
@@ -68,6 +68,36 @@ export const UI_PLUGIN_METADATA = {
|
|
|
68
68
|
DISPLAY_NAME: 'displayName',
|
|
69
69
|
};
|
|
70
70
|
|
|
71
|
+
export const EXTENSIONS_INCOMPATIBILITY_TYPES = {
|
|
72
|
+
UI: 'uiVersion',
|
|
73
|
+
EXTENSIONS_API: 'extensionsApiVersion',
|
|
74
|
+
KUBE: 'kubeVersion',
|
|
75
|
+
HOST: 'host'
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const EXTENSIONS_INCOMPATIBILITY_DATA = {
|
|
79
|
+
UI: {
|
|
80
|
+
type: EXTENSIONS_INCOMPATIBILITY_TYPES.UI,
|
|
81
|
+
cardMessageKey: 'plugins.incompatibleRancherVersion',
|
|
82
|
+
tooltipKey: 'plugins.info.requiresRancherVersion',
|
|
83
|
+
},
|
|
84
|
+
EXTENSIONS_API: {
|
|
85
|
+
type: EXTENSIONS_INCOMPATIBILITY_TYPES.EXTENSIONS_API,
|
|
86
|
+
cardMessageKey: 'plugins.incompatibleUiExtensionsApiVersion',
|
|
87
|
+
tooltipKey: 'plugins.info.requiresExtensionApiVersion',
|
|
88
|
+
},
|
|
89
|
+
KUBE: {
|
|
90
|
+
type: EXTENSIONS_INCOMPATIBILITY_TYPES.KUBE,
|
|
91
|
+
cardMessageKey: 'plugins.incompatibleKubeVersion',
|
|
92
|
+
tooltipKey: 'plugins.info.requiresKubeVersion',
|
|
93
|
+
},
|
|
94
|
+
HOST: {
|
|
95
|
+
type: EXTENSIONS_INCOMPATIBILITY_TYPES.HOST,
|
|
96
|
+
cardMessageKey: 'plugins.incompatibleHost',
|
|
97
|
+
tooltipKey: 'plugins.info.requiresHost',
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
71
101
|
export function isUIPlugin(chart) {
|
|
72
102
|
return !!chart?.versions.find((v) => {
|
|
73
103
|
return v.annotations && v.annotations[UI_PLUGIN_ANNOTATION_NAME] === UI_PLUGIN_ANNOTATION_VALUE;
|
|
@@ -91,130 +121,169 @@ export function uiPluginAnnotation(chart, name) {
|
|
|
91
121
|
return undefined;
|
|
92
122
|
}
|
|
93
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Parse the rancher version string
|
|
126
|
+
*/
|
|
127
|
+
function parseRancherVersion(v) {
|
|
128
|
+
let parsedRancherVersion = semver.coerce(v)?.version;
|
|
129
|
+
const splitArr = parsedRancherVersion.split('.');
|
|
130
|
+
|
|
131
|
+
// this is a scenario where we are on a "head" version of some sort... we can't infer the patch version from it
|
|
132
|
+
// so we apply a big patch version number to make sure we follow through with the minor
|
|
133
|
+
if (v.includes('-') && splitArr?.length === 3) {
|
|
134
|
+
parsedRancherVersion = `${ splitArr[0] }.${ splitArr[1] }.999`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return parsedRancherVersion;
|
|
138
|
+
}
|
|
139
|
+
|
|
94
140
|
// i18n-uses plugins.error.generic, plugins.error.api, plugins.error.host
|
|
95
141
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
142
|
+
/**
|
|
143
|
+
* Whether an extension should be loaded based on the metadata returned by the backend in the UIPlugins resource instance
|
|
144
|
+
* @returns String || Boolean
|
|
145
|
+
*/
|
|
146
|
+
export function shouldNotLoadPlugin(UIPluginResource, rancherVersion, loadedPlugins) {
|
|
147
|
+
if (!UIPluginResource.name || !UIPluginResource.version || !UIPluginResource.endpoint) {
|
|
100
148
|
return 'plugins.error.generic';
|
|
101
149
|
}
|
|
102
150
|
|
|
103
|
-
//
|
|
151
|
+
// Extension chart specified a required extension API version
|
|
104
152
|
// we are propagating the annotations in pkg/package.json for any extension
|
|
105
153
|
// inside the "spec.plugin.metadata" property of UIPlugin resource
|
|
106
|
-
const
|
|
154
|
+
const requiredUiExtensionsVersion = UIPluginResource.spec?.plugin?.metadata?.[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION];
|
|
155
|
+
// semver.coerce will get rid of any suffix on the version numbering (-rc, -head, etc)
|
|
156
|
+
const parsedUiExtensionsApiVersion = semver.coerce(UI_EXTENSIONS_API_VERSION)?.version || UI_EXTENSIONS_API_VERSION;
|
|
157
|
+
const parsedRancherVersion = rancherVersion ? parseRancherVersion(rancherVersion) : '';
|
|
107
158
|
|
|
108
|
-
if (
|
|
159
|
+
if (requiredUiExtensionsVersion && !semver.satisfies(parsedUiExtensionsApiVersion, requiredUiExtensionsVersion)) {
|
|
109
160
|
return 'plugins.error.api';
|
|
110
161
|
}
|
|
111
162
|
|
|
112
163
|
// Host application
|
|
113
|
-
const requiredHost =
|
|
164
|
+
const requiredHost = UIPluginResource.metadata?.[UI_PLUGIN_METADATA.EXTENSIONS_HOST];
|
|
114
165
|
|
|
115
166
|
if (requiredHost && requiredHost !== UI_PLUGIN_HOST_APP) {
|
|
116
167
|
return 'plugins.error.host';
|
|
117
168
|
}
|
|
118
169
|
|
|
119
170
|
// Rancher version
|
|
120
|
-
if (
|
|
121
|
-
const requiredRancherVersion =
|
|
171
|
+
if (parsedRancherVersion) {
|
|
172
|
+
const requiredRancherVersion = UIPluginResource.metadata?.[UI_PLUGIN_METADATA.RANCHER_VERSION];
|
|
122
173
|
|
|
123
|
-
if (requiredRancherVersion && !semver.satisfies(
|
|
174
|
+
if (requiredRancherVersion && !semver.satisfies(parsedRancherVersion, requiredRancherVersion)) {
|
|
124
175
|
return 'plugins.error.version';
|
|
125
176
|
}
|
|
126
177
|
}
|
|
127
178
|
|
|
128
179
|
// check if a builtin extension has been loaded before - improve developer experience
|
|
129
|
-
const checkLoaded = loadedPlugins.find((p) => p?.name ===
|
|
180
|
+
const checkLoaded = loadedPlugins.find((p) => p?.name === UIPluginResource?.name);
|
|
130
181
|
|
|
131
182
|
if (checkLoaded && checkLoaded.builtin) {
|
|
132
183
|
return 'plugins.error.developerPkg';
|
|
133
184
|
}
|
|
134
185
|
|
|
135
|
-
if (
|
|
186
|
+
if (UIPluginResource.metadata?.[UI_PLUGIN_LABELS.CATALOG]) {
|
|
136
187
|
return true;
|
|
137
188
|
}
|
|
138
189
|
|
|
139
190
|
return false;
|
|
140
191
|
}
|
|
141
192
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
193
|
+
/**
|
|
194
|
+
* Wether an extension version is available to be installed, based on the annotations present in the Helm chart version
|
|
195
|
+
* backend may not automatically "limit" a particular version but dashboard will disable that version for install with this check
|
|
196
|
+
* @returns Boolean || Object
|
|
197
|
+
*/
|
|
198
|
+
export function isSupportedChartVersion(versionData, returnObj = false) {
|
|
199
|
+
const { version, rancherVersion, kubeVersion } = versionData;
|
|
145
200
|
|
|
146
|
-
//
|
|
147
|
-
const
|
|
201
|
+
// semver.coerce will get rid of any suffix on the version numbering (-rc, -head, etc)
|
|
202
|
+
const parsedRancherVersion = rancherVersion ? parseRancherVersion(rancherVersion) : '';
|
|
203
|
+
const requiredUiVersion = version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.UI_VERSION];
|
|
204
|
+
const requiredKubeVersion = version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.KUBE_VERSION];
|
|
205
|
+
const versionObj = { ...version };
|
|
148
206
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
207
|
+
// reset compatibility property
|
|
208
|
+
versionObj.isVersionCompatible = true;
|
|
209
|
+
versionObj.versionIncompatibilityData = {};
|
|
152
210
|
|
|
153
|
-
//
|
|
154
|
-
|
|
211
|
+
// check "catalog.cattle.io/kube-version" annotation
|
|
212
|
+
// we keep it as first check since there is a card notification to be displayed
|
|
213
|
+
// in case an extension version installed has an incompatibility with the kube version and is not loaded
|
|
214
|
+
if (kubeVersion && requiredKubeVersion && !semver.satisfies(kubeVersion, requiredKubeVersion)) {
|
|
215
|
+
if (!returnObj) {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
155
218
|
|
|
156
|
-
|
|
157
|
-
|
|
219
|
+
versionObj.isVersionCompatible = false;
|
|
220
|
+
versionObj.versionIncompatibilityData = Object.assign({}, EXTENSIONS_INCOMPATIBILITY_DATA.KUBE);
|
|
221
|
+
versionObj.versionIncompatibilityData.required = requiredKubeVersion;
|
|
222
|
+
|
|
223
|
+
return versionObj;
|
|
158
224
|
}
|
|
159
225
|
|
|
160
|
-
// Rancher
|
|
161
|
-
|
|
226
|
+
// we aren't on a "published" version of Rancher and therefore in a "-head" or similar
|
|
227
|
+
// Backend will NOT block an extension version from being available IF we are on HEAD versions!!
|
|
228
|
+
// we need to enforce that check if we are on a HEAD world
|
|
229
|
+
if (rancherVersion && rancherVersion.includes('-')) {
|
|
162
230
|
const requiredRancherVersion = version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.RANCHER_VERSION];
|
|
163
231
|
|
|
164
|
-
if (
|
|
165
|
-
|
|
232
|
+
if (parsedRancherVersion && !semver.satisfies(parsedRancherVersion, requiredRancherVersion)) {
|
|
233
|
+
if (!returnObj) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
versionObj.isVersionCompatible = false;
|
|
238
|
+
versionObj.versionIncompatibilityData = Object.assign({}, EXTENSIONS_INCOMPATIBILITY_DATA.UI);
|
|
239
|
+
versionObj.versionIncompatibilityData.required = requiredRancherVersion;
|
|
240
|
+
|
|
241
|
+
return versionObj;
|
|
166
242
|
}
|
|
167
243
|
}
|
|
168
244
|
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
const requiredKubeVersion = version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.KUBE_VERSION];
|
|
245
|
+
// check host application
|
|
246
|
+
const requiredHost = version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_HOST];
|
|
172
247
|
|
|
173
|
-
|
|
248
|
+
if (requiredHost && requiredHost !== UI_PLUGIN_HOST_APP) {
|
|
249
|
+
if (!returnObj) {
|
|
174
250
|
return false;
|
|
175
251
|
}
|
|
176
|
-
}
|
|
177
252
|
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export function isChartVersionAvailableForInstall(versionsData, returnObj = false) {
|
|
182
|
-
const { version, rancherVersion, kubeVersion } = versionsData;
|
|
253
|
+
versionObj.isVersionCompatible = false;
|
|
254
|
+
versionObj.versionIncompatibilityData = Object.assign({}, EXTENSIONS_INCOMPATIBILITY_DATA.HOST);
|
|
255
|
+
versionObj.versionIncompatibilityData.required = requiredHost;
|
|
183
256
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const isRancherVersionHashString = regexHashString.test(rancherVersion);
|
|
187
|
-
const requiredUiVersion = version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.UI_VERSION];
|
|
188
|
-
const requiredKubeVersion = version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.KUBE_VERSION];
|
|
189
|
-
const versionObj = { ...version };
|
|
257
|
+
return versionObj;
|
|
258
|
+
}
|
|
190
259
|
|
|
191
|
-
|
|
192
|
-
|
|
260
|
+
// check "catalog.cattle.io/ui-extensions-version" annotation
|
|
261
|
+
const requiredUiExtensionsApiVersion = version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION];
|
|
262
|
+
const parsedUiExtensionsApiVersion = semver.coerce(UI_EXTENSIONS_API_VERSION)?.version || UI_EXTENSIONS_API_VERSION;
|
|
193
263
|
|
|
194
|
-
|
|
195
|
-
if (!isRancherVersionHashString && requiredUiVersion && !semver.satisfies(parsedRancherVersion, requiredUiVersion)) {
|
|
264
|
+
if (requiredUiExtensionsApiVersion && parsedUiExtensionsApiVersion && !semver.satisfies(parsedUiExtensionsApiVersion, requiredUiExtensionsApiVersion)) {
|
|
196
265
|
if (!returnObj) {
|
|
197
266
|
return false;
|
|
198
267
|
}
|
|
199
|
-
versionObj.isCompatibleWithUi = false;
|
|
200
|
-
versionObj.requiredUiVersion = requiredUiVersion;
|
|
201
268
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
269
|
+
versionObj.isVersionCompatible = false;
|
|
270
|
+
versionObj.versionIncompatibilityData = Object.assign({}, EXTENSIONS_INCOMPATIBILITY_DATA.EXTENSIONS_API);
|
|
271
|
+
versionObj.versionIncompatibilityData.required = requiredUiExtensionsApiVersion;
|
|
272
|
+
|
|
273
|
+
return versionObj;
|
|
205
274
|
}
|
|
206
275
|
|
|
207
|
-
// check
|
|
208
|
-
if (
|
|
276
|
+
// check "catalog.cattle.io/ui-version" annotation
|
|
277
|
+
if (requiredUiVersion && parsedRancherVersion && !semver.satisfies(parsedRancherVersion, requiredUiVersion)) {
|
|
209
278
|
if (!returnObj) {
|
|
210
279
|
return false;
|
|
211
280
|
}
|
|
212
|
-
versionObj.isCompatibleWithKubeVersion = false;
|
|
213
|
-
versionObj.requiredKubeVersion = requiredKubeVersion;
|
|
214
281
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
282
|
+
versionObj.isVersionCompatible = false;
|
|
283
|
+
versionObj.versionIncompatibilityData = Object.assign({}, EXTENSIONS_INCOMPATIBILITY_DATA.UI);
|
|
284
|
+
versionObj.versionIncompatibilityData.required = requiredUiVersion;
|
|
285
|
+
|
|
286
|
+
return versionObj;
|
|
218
287
|
}
|
|
219
288
|
|
|
220
289
|
if (returnObj) {
|
package/core/plugin.ts
CHANGED
|
@@ -132,6 +132,11 @@ export class Plugin implements IPlugin {
|
|
|
132
132
|
console.warn(`Layouts have been deprecated. We still have parent routes which use the same name and styling as the previous layouts. You should specify a parent, we're currently setting the parent to 'default'`); // eslint-disable-line no-console
|
|
133
133
|
parentOverride = 'default';
|
|
134
134
|
}
|
|
135
|
+
|
|
136
|
+
// Fix for Home page components with wrong layout - need to ensure the parentOverride is set
|
|
137
|
+
if (typelessRoute.component && typelessRoute.name === 'home' && typelessRoute.path === '/home') {
|
|
138
|
+
parentOverride = 'home_layout';
|
|
139
|
+
}
|
|
135
140
|
}
|
|
136
141
|
|
|
137
142
|
route.meta = {
|
package/core/plugins.js
CHANGED
|
@@ -90,6 +90,10 @@ export default function(context, inject, vueApp) {
|
|
|
90
90
|
delete window[oldPlugin.id];
|
|
91
91
|
|
|
92
92
|
delete plugins[oldPlugin.id];
|
|
93
|
+
|
|
94
|
+
const oldElement = document.getElementById(oldPlugin.id);
|
|
95
|
+
|
|
96
|
+
oldElement.parentElement.removeChild(oldElement);
|
|
93
97
|
});
|
|
94
98
|
}
|
|
95
99
|
|
|
@@ -124,7 +128,9 @@ export default function(context, inject, vueApp) {
|
|
|
124
128
|
};
|
|
125
129
|
|
|
126
130
|
element.onerror = (e) => {
|
|
127
|
-
|
|
131
|
+
element.parentElement.removeChild(element);
|
|
132
|
+
|
|
133
|
+
// Massage the error into something useful
|
|
128
134
|
const errorMessage = `Failed to load script from '${ e.target.src }'`;
|
|
129
135
|
|
|
130
136
|
console.error(errorMessage, e); // eslint-disable-line no-console
|
|
@@ -73,5 +73,47 @@ describe('view: provisioning.cattle.io.cluster', () => {
|
|
|
73
73
|
|
|
74
74
|
expect(wrapper.vm.showRegistration).toStrictEqual(false);
|
|
75
75
|
});
|
|
76
|
+
|
|
77
|
+
it('should SHOW if custom/imported cluster and the cluster is active', async() => {
|
|
78
|
+
const value = {
|
|
79
|
+
isCustom: true,
|
|
80
|
+
isImported: true,
|
|
81
|
+
mgmt: {
|
|
82
|
+
hasLink: () => jest.fn(),
|
|
83
|
+
linkFor: () => '',
|
|
84
|
+
isReady: true
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const wrapper = shallowMount(ProvisioningCattleIoCluster, {
|
|
89
|
+
mocks,
|
|
90
|
+
propsData: { value },
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
await wrapper.setData({ clusterToken: {} });
|
|
94
|
+
|
|
95
|
+
expect(wrapper.vm.showRegistration).toStrictEqual(true);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should NOT show if imported cluster and the cluster is active', async() => {
|
|
99
|
+
const value = {
|
|
100
|
+
isCustom: false,
|
|
101
|
+
isImported: true,
|
|
102
|
+
mgmt: {
|
|
103
|
+
hasLink: () => jest.fn(),
|
|
104
|
+
linkFor: () => '',
|
|
105
|
+
isReady: true
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const wrapper = shallowMount(ProvisioningCattleIoCluster, {
|
|
110
|
+
mocks,
|
|
111
|
+
propsData: { value },
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
await wrapper.setData({ clusterToken: {} });
|
|
115
|
+
|
|
116
|
+
expect(wrapper.vm.showRegistration).toStrictEqual(false);
|
|
117
|
+
});
|
|
76
118
|
});
|
|
77
119
|
});
|
|
@@ -512,14 +512,14 @@ export default {
|
|
|
512
512
|
return false;
|
|
513
513
|
}
|
|
514
514
|
|
|
515
|
-
if ( this.value.isImported ) {
|
|
516
|
-
return !this.value.mgmt?.isReady && this.extDetailTabs.registration;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
515
|
if ( this.value.isCustom ) {
|
|
520
516
|
return this.extDetailTabs.registration;
|
|
521
517
|
}
|
|
522
518
|
|
|
519
|
+
if ( this.value.isImported ) {
|
|
520
|
+
return !this.value.mgmt?.isReady && this.extDetailTabs.registration;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
523
|
// Hosted kubernetes providers with private endpoints need the registration tab
|
|
524
524
|
// https://github.com/rancher/dashboard/issues/6036
|
|
525
525
|
// https://github.com/rancher/dashboard/issues/4545
|
|
@@ -3,6 +3,8 @@ import AsyncButton from '@shell/components/AsyncButton';
|
|
|
3
3
|
import { Card } from '@components/Card';
|
|
4
4
|
import { Banner } from '@components/Banner';
|
|
5
5
|
import { exceptionToErrorsArray } from '@shell/utils/error';
|
|
6
|
+
import { resourceNames } from '@shell/utils/string';
|
|
7
|
+
import { mapGetters } from 'vuex';
|
|
6
8
|
|
|
7
9
|
export default {
|
|
8
10
|
components: {
|
|
@@ -12,20 +14,35 @@ export default {
|
|
|
12
14
|
},
|
|
13
15
|
|
|
14
16
|
props: {
|
|
15
|
-
|
|
16
|
-
type:
|
|
17
|
-
|
|
17
|
+
drivers: {
|
|
18
|
+
type: Array,
|
|
19
|
+
required: true
|
|
18
20
|
},
|
|
19
|
-
|
|
20
|
-
type:
|
|
21
|
-
|
|
21
|
+
driverType: {
|
|
22
|
+
type: String,
|
|
23
|
+
required: true
|
|
22
24
|
}
|
|
23
25
|
},
|
|
24
26
|
|
|
25
27
|
data() {
|
|
26
28
|
return { errors: [] };
|
|
27
29
|
},
|
|
30
|
+
computed: {
|
|
31
|
+
formattedText() {
|
|
32
|
+
const namesSliced = this.drivers.map((obj) => obj.nameDisplay).slice(0, 5);
|
|
33
|
+
const remaining = this.drivers.length - namesSliced.length;
|
|
34
|
+
|
|
35
|
+
const plusMore = this.t('drivers.deactivate.andOthers', { count: remaining });
|
|
36
|
+
const names = resourceNames(namesSliced, plusMore, this.t);
|
|
37
|
+
const count = remaining || namesSliced.length;
|
|
38
|
+
const warningDrivers = this.t('drivers.deactivate.warningDrivers', { names, count });
|
|
39
|
+
|
|
40
|
+
return this.t('drivers.deactivate.warning', { warningDrivers, count: namesSliced.length });
|
|
41
|
+
},
|
|
42
|
+
...mapGetters({ t: 'i18n/t' }),
|
|
43
|
+
},
|
|
28
44
|
methods: {
|
|
45
|
+
resourceNames,
|
|
29
46
|
close(buttonDone) {
|
|
30
47
|
if (buttonDone && typeof buttonDone === 'function') {
|
|
31
48
|
buttonDone(true);
|
|
@@ -34,10 +51,12 @@ export default {
|
|
|
34
51
|
},
|
|
35
52
|
async apply(buttonDone) {
|
|
36
53
|
try {
|
|
37
|
-
await this
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
54
|
+
await Promise.all(this.drivers.map(
|
|
55
|
+
(driver) => this.$store.dispatch('rancher/request', {
|
|
56
|
+
url: `v3/${ this.driverType }/${ escape(driver.id) }?action=deactivate`,
|
|
57
|
+
method: 'POST'
|
|
58
|
+
})
|
|
59
|
+
));
|
|
41
60
|
|
|
42
61
|
this.close(buttonDone);
|
|
43
62
|
} catch (err) {
|
|
@@ -64,7 +83,7 @@ export default {
|
|
|
64
83
|
<template #body>
|
|
65
84
|
<div class="pl-10 pr-10">
|
|
66
85
|
<div class="text info mb-10 mt-20">
|
|
67
|
-
<span v-clean-html="
|
|
86
|
+
<span v-clean-html="formattedText" />
|
|
68
87
|
</div>
|
|
69
88
|
<Banner
|
|
70
89
|
v-for="(err, i) in errors"
|