@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.
Files changed (154) hide show
  1. package/assets/translations/en-us.yaml +69 -29
  2. package/assets/translations/zh-hans.yaml +1 -0
  3. package/components/AlertTable.vue +17 -7
  4. package/components/AssignTo.vue +2 -0
  5. package/components/GrafanaDashboard.vue +6 -4
  6. package/components/PromptRemove.vue +1 -0
  7. package/components/Questions/index.vue +2 -2
  8. package/components/auth/RoleDetailEdit.vue +5 -4
  9. package/components/form/KeyValue.vue +1 -0
  10. package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
  11. package/components/form/ProjectMemberEditor.vue +1 -1
  12. package/components/form/ResourceLabeledSelect.vue +11 -3
  13. package/components/form/Taints.vue +13 -7
  14. package/components/form/__tests__/Taints.test.ts +70 -0
  15. package/components/form/labeled-select-utils/labeled-select.utils.ts +1 -1
  16. package/components/nav/Header.vue +1 -1
  17. package/components/nav/TopLevelMenu.vue +1 -4
  18. package/config/pagination-table-headers.js +5 -4
  19. package/config/product/auth.js +1 -1
  20. package/config/roles.ts +34 -19
  21. package/config/router/navigation-guards/attempt-first-login.js +1 -1
  22. package/config/router/navigation-guards/authentication.js +1 -1
  23. package/config/router/navigation-guards/i18n.js +13 -0
  24. package/config/router/navigation-guards/index.js +3 -1
  25. package/config/router/navigation-guards/load-initial-settings.js +1 -1
  26. package/config/router/navigation-guards/runtime-extension-route.js +31 -0
  27. package/config/router/routes.js +10 -1
  28. package/config/uiplugins.js +130 -61
  29. package/core/plugin.ts +5 -0
  30. package/core/plugins.js +7 -1
  31. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +42 -0
  32. package/detail/provisioning.cattle.io.cluster.vue +4 -4
  33. package/dialog/DeactivateDriverDialog.vue +30 -11
  34. package/edit/auth/__tests__/oidc.test.ts +2 -2
  35. package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +86 -13
  36. package/edit/provisioning.cattle.io.cluster/__tests__/DirectoryConfig.test.ts +3 -134
  37. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +209 -0
  38. package/edit/provisioning.cattle.io.cluster/index.vue +8 -4
  39. package/edit/provisioning.cattle.io.cluster/rke2.vue +115 -17
  40. package/edit/provisioning.cattle.io.cluster/tabs/AddOnAdditionalManifest.vue +50 -0
  41. package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +29 -64
  42. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +42 -3
  43. package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +22 -86
  44. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +8 -2
  45. package/edit/provisioning.cattle.io.cluster/tabs/registries/__tests__/RegistryConfigs.test.ts +61 -0
  46. package/edit/token.vue +2 -1
  47. package/initialize/entry-helpers.js +4 -24
  48. package/list/management.cattle.io.feature.vue +4 -2
  49. package/middleware/authenticated.js +0 -19
  50. package/mixins/__tests__/chart.test.ts +4 -1
  51. package/mixins/auth-config.js +1 -1
  52. package/mixins/chart.js +30 -14
  53. package/models/__tests__/apps.deployment.test.ts +93 -0
  54. package/models/apps.deployment.js +18 -4
  55. package/models/driver.js +3 -2
  56. package/models/kontainerdriver.js +30 -13
  57. package/models/management.cattle.io.authconfig.js +2 -2
  58. package/models/management.cattle.io.cluster.js +2 -2
  59. package/models/management.cattle.io.user.js +3 -3
  60. package/models/nodedriver.js +35 -13
  61. package/models/provisioning.cattle.io.cluster.js +4 -0
  62. package/package.json +3 -2
  63. package/pages/404.vue +15 -0
  64. package/pages/auth/login.vue +4 -1
  65. package/pages/auth/setup.vue +4 -1
  66. package/pages/c/_cluster/apps/charts/install.vue +3 -2
  67. package/pages/c/_cluster/explorer/index.vue +5 -0
  68. package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +0 -3
  69. package/pages/c/_cluster/manager/drivers/nodeDriver/index.vue +1 -4
  70. package/pages/c/_cluster/manager/jwt.authentication/index.vue +10 -4
  71. package/pages/c/_cluster/settings/performance.vue +2 -2
  72. package/pages/c/_cluster/uiplugins/InstallDialog.vue +2 -1
  73. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +7 -10
  74. package/pages/c/_cluster/uiplugins/index.vue +24 -16
  75. package/pages/home.vue +1 -13
  76. package/plugins/dashboard-store/actions.js +1 -1
  77. package/plugins/dashboard-store/getters.js +1 -1
  78. package/plugins/steve/__tests__/getters.test.ts +5 -5
  79. package/plugins/steve/getters.js +6 -4
  80. package/plugins/steve/hybrid-class.js +1 -5
  81. package/promptRemove/pod.vue +15 -7
  82. package/scripts/extension/helm/charts/ui-plugin-server/Chart.yaml +1 -1
  83. package/scripts/publish-shell.sh +54 -55
  84. package/scripts/test-plugins-build.sh +45 -39
  85. package/shell/types/shell/index.d.ts +2 -0
  86. package/store/auth.js +1 -1
  87. package/store/index.js +1 -1
  88. package/store/type-map.js +4 -2
  89. package/types/store/pagination.types.ts +1 -1
  90. package/utils/__tests__/kontainer.test.ts +89 -1
  91. package/utils/auth.js +1 -1
  92. package/utils/cluster.js +9 -0
  93. package/utils/kontainer.ts +5 -1
  94. package/utils/settings.ts +3 -1
  95. package/utils/version.js +2 -1
  96. package/creators/app/app.package.json +0 -13
  97. package/creators/app/files/.eslintignore +0 -16
  98. package/creators/app/files/.eslintrc.js +0 -173
  99. package/creators/app/files/.gitignore +0 -70
  100. package/creators/app/files/.gitlab-ci.yml +0 -14
  101. package/creators/app/files/.vscode/settings.json +0 -21
  102. package/creators/app/files/babel.config.js +0 -1
  103. package/creators/app/files/tsconfig.json +0 -42
  104. package/creators/app/files/vue.config.js +0 -6
  105. package/creators/app/init +0 -120
  106. package/creators/app/package.json +0 -25
  107. package/creators/pkg/files/.github/workflows/build-extension-catalog.yml +0 -24
  108. package/creators/pkg/files/.github/workflows/build-extension-charts.yml +0 -22
  109. package/creators/pkg/files/babel.config.js +0 -1
  110. package/creators/pkg/files/index.ts +0 -14
  111. package/creators/pkg/files/tsconfig.json +0 -53
  112. package/creators/pkg/files/vue.config.js +0 -1
  113. package/creators/pkg/init +0 -286
  114. package/creators/pkg/package.json +0 -19
  115. package/creators/pkg/pkg.package.json +0 -21
  116. package/creators/pkg/vue-shim.ts +0 -4
  117. package/creators/update/init +0 -56
  118. package/creators/update/package.json +0 -20
  119. package/creators/update/upgrade +0 -56
  120. package/rancher-components/components/Accordion/Accordion.test.ts +0 -45
  121. package/rancher-components/components/Accordion/Accordion.vue +0 -86
  122. package/rancher-components/components/Accordion/index.ts +0 -1
  123. package/rancher-components/components/BadgeState/BadgeState.test.ts +0 -12
  124. package/rancher-components/components/BadgeState/BadgeState.vue +0 -111
  125. package/rancher-components/components/BadgeState/index.ts +0 -1
  126. package/rancher-components/components/Banner/Banner.test.ts +0 -59
  127. package/rancher-components/components/Banner/Banner.vue +0 -244
  128. package/rancher-components/components/Banner/index.ts +0 -1
  129. package/rancher-components/components/Card/Card.test.ts +0 -37
  130. package/rancher-components/components/Card/Card.vue +0 -167
  131. package/rancher-components/components/Card/index.ts +0 -1
  132. package/rancher-components/components/Form/Checkbox/Checkbox.test.ts +0 -68
  133. package/rancher-components/components/Form/Checkbox/Checkbox.vue +0 -421
  134. package/rancher-components/components/Form/Checkbox/index.ts +0 -1
  135. package/rancher-components/components/Form/LabeledInput/LabeledInput.test.ts +0 -40
  136. package/rancher-components/components/Form/LabeledInput/LabeledInput.vue +0 -402
  137. package/rancher-components/components/Form/LabeledInput/index.ts +0 -1
  138. package/rancher-components/components/Form/Radio/RadioButton.test.ts +0 -33
  139. package/rancher-components/components/Form/Radio/RadioButton.vue +0 -293
  140. package/rancher-components/components/Form/Radio/RadioGroup.test.ts +0 -30
  141. package/rancher-components/components/Form/Radio/RadioGroup.vue +0 -259
  142. package/rancher-components/components/Form/Radio/index.ts +0 -2
  143. package/rancher-components/components/Form/TextArea/TextAreaAutoGrow.vue +0 -172
  144. package/rancher-components/components/Form/TextArea/index.ts +0 -1
  145. package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.test.ts +0 -94
  146. package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.vue +0 -152
  147. package/rancher-components/components/Form/ToggleSwitch/index.ts +0 -1
  148. package/rancher-components/components/Form/index.ts +0 -5
  149. package/rancher-components/components/LabeledTooltip/LabeledTooltip.vue +0 -156
  150. package/rancher-components/components/LabeledTooltip/index.ts +0 -1
  151. package/rancher-components/components/StringList/StringList.test.ts +0 -754
  152. package/rancher-components/components/StringList/StringList.vue +0 -650
  153. package/rancher-components/components/StringList/index.ts +0 -1
  154. 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
- globalScopedApiGroups: {
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
- clusterScopedApiGroups: {
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
- 'nv-perm.admctrl',
205
- 'nv-perm.authentication',
206
- 'nv-perm.ci-scan',
207
- 'nv-perm.fed',
208
- 'nv-perm.vulnerability'
218
+ 'AdmissionControl',
219
+ 'Authentication',
220
+ 'CIScan',
221
+ 'Cluster',
222
+ 'Federation',
223
+ 'Vulnerability',
209
224
  ]
210
225
  }
211
226
  },
212
- projectScopedApiGroups: {
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
- 'nv-perm.all-permissions',
382
- 'nv-perm.audit-events',
383
- 'nv-perm.authorization',
384
- 'nv-perm.compliance',
385
- 'nv-perm.events',
386
- 'nv-perm.reg-scan',
387
- 'nv-perm.rt-policy',
388
- 'nv-perm.rt-scan',
389
- 'nv-perm.security-events',
390
- 'nv-perm.config',
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
+ }
@@ -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
+ ];
@@ -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
- // Should we load a plugin, based on the metadata returned by the backend?
97
- // Returns error key string or false
98
- export function shouldNotLoadPlugin(plugin, rancherVersion, loadedPlugins) {
99
- if (!plugin.name || !plugin.version || !plugin.endpoint) {
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
- // Plugin specified a required extension API version
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 requiredAPI = plugin.spec?.plugin?.metadata?.[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION];
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 (requiredAPI && !semver.satisfies(UI_EXTENSIONS_API_VERSION, requiredAPI)) {
159
+ if (requiredUiExtensionsVersion && !semver.satisfies(parsedUiExtensionsApiVersion, requiredUiExtensionsVersion)) {
109
160
  return 'plugins.error.api';
110
161
  }
111
162
 
112
163
  // Host application
113
- const requiredHost = plugin.metadata?.[UI_PLUGIN_METADATA.EXTENSIONS_HOST];
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 (rancherVersion) {
121
- const requiredRancherVersion = plugin.metadata?.[UI_PLUGIN_METADATA.RANCHER_VERSION];
171
+ if (parsedRancherVersion) {
172
+ const requiredRancherVersion = UIPluginResource.metadata?.[UI_PLUGIN_METADATA.RANCHER_VERSION];
122
173
 
123
- if (requiredRancherVersion && !semver.satisfies(rancherVersion, requiredRancherVersion)) {
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 === plugin?.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 (plugin.metadata?.[UI_PLUGIN_LABELS.CATALOG]) {
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
- // Can a chart version be used for this Rancher (based on the annotations on the chart)?
143
- export function isSupportedChartVersion(versionsData) {
144
- const { version, rancherVersion, kubeVersion } = versionsData;
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
- // Plugin specified a required extension API version
147
- const requiredAPI = version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION];
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
- if (requiredAPI && !semver.satisfies(UI_EXTENSIONS_API_VERSION, requiredAPI)) {
150
- return false;
151
- }
207
+ // reset compatibility property
208
+ versionObj.isVersionCompatible = true;
209
+ versionObj.versionIncompatibilityData = {};
152
210
 
153
- // Host application
154
- const requiredHost = version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_HOST];
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
- if (requiredHost && requiredHost !== UI_PLUGIN_HOST_APP) {
157
- return false;
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 version
161
- if (rancherVersion) {
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 (requiredRancherVersion && !semver.satisfies(rancherVersion, requiredRancherVersion)) {
165
- return false;
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
- // Kube version
170
- if (kubeVersion) {
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
- if (requiredKubeVersion && !semver.satisfies(kubeVersion, requiredKubeVersion)) {
248
+ if (requiredHost && requiredHost !== UI_PLUGIN_HOST_APP) {
249
+ if (!returnObj) {
174
250
  return false;
175
251
  }
176
- }
177
252
 
178
- return true;
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
- const parsedRancherVersion = rancherVersion.split('-')?.[0];
185
- const regexHashString = new RegExp('^[A-Za-z0-9]{9}$');
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
- versionObj.isCompatibleWithUi = true;
192
- versionObj.isCompatibleWithKubeVersion = true;
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
- // if it's a head version of Rancher, then we skip the validation and enable them all
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
- if (returnObj) {
203
- return versionObj;
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 kube version
208
- if (kubeVersion && requiredKubeVersion && !semver.satisfies(kubeVersion, requiredKubeVersion)) {
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
- if (returnObj) {
216
- return versionObj;
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
- // Massage the error into something useful
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
- url: {
16
- type: String,
17
- default: null,
17
+ drivers: {
18
+ type: Array,
19
+ required: true
18
20
  },
19
- name: {
20
- type: String,
21
- default: null,
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.$store.dispatch('rancher/request', {
38
- url: this.url,
39
- method: 'post'
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="t('drivers.deactivate.warning', {name})" />
86
+ <span v-clean-html="formattedText" />
68
87
  </div>
69
88
  <Banner
70
89
  v-for="(err, i) in errors"