@rancher/shell 3.0.11 → 3.0.12-rc.2

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 (219) hide show
  1. package/assets/images/providers/entraid-black.svg +4 -0
  2. package/assets/images/providers/entraid.svg +9 -0
  3. package/assets/images/vendor/entraid.svg +9 -0
  4. package/assets/styles/app.scss +0 -1
  5. package/assets/styles/base/_mixins.scss +31 -0
  6. package/assets/styles/base/_variables.scss +2 -0
  7. package/assets/styles/themes/_modern.scss +6 -5
  8. package/assets/translations/en-us.yaml +24 -21
  9. package/assets/translations/zh-hans.yaml +4 -11
  10. package/chart/__tests__/S3.test.ts +10 -3
  11. package/components/CountBox.vue +20 -0
  12. package/components/CreateDriver.vue +0 -12
  13. package/components/DetailText.vue +12 -3
  14. package/components/EmptyProductPage.vue +76 -0
  15. package/components/Resource/Detail/CopyToClipboard.vue +1 -2
  16. package/components/Resource/Detail/Metadata/KeyValueRow.vue +9 -3
  17. package/components/Resource/Detail/TitleBar/__tests__/__snapshots__/index.test.ts.snap +31 -0
  18. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +45 -1
  19. package/components/Resource/Detail/TitleBar/index.vue +1 -1
  20. package/components/Resource/Detail/ViewOptions/__tests__/__snapshots__/index.test.ts.snap +9 -0
  21. package/components/Resource/Detail/ViewOptions/__tests__/index.test.ts +62 -0
  22. package/components/Resource/Detail/ViewOptions/index.vue +2 -1
  23. package/components/ResourceList/Masthead.vue +25 -2
  24. package/components/SelectIconGrid.vue +5 -0
  25. package/components/SideNav.vue +13 -0
  26. package/components/__tests__/CountBox.test.ts +72 -0
  27. package/components/__tests__/DetailText.test.ts +113 -0
  28. package/components/__tests__/PromptModal.test.ts +2 -0
  29. package/components/fleet/FleetClusterTargets/index.vue +18 -1
  30. package/components/fleet/FleetClusters.vue +1 -0
  31. package/components/fleet/__tests__/FleetClusters.test.ts +71 -0
  32. package/components/form/InputWithSelect.vue +18 -10
  33. package/components/form/KeyValue.vue +17 -1
  34. package/components/form/LabeledSelect.vue +82 -24
  35. package/components/form/NodeScheduling.vue +17 -3
  36. package/components/form/PrivateRegistry.vue +69 -0
  37. package/components/form/Select.vue +73 -56
  38. package/components/form/ServiceNameSelect.vue +13 -11
  39. package/components/form/__tests__/KeyValue.test.ts +66 -0
  40. package/components/form/__tests__/NodeScheduling.test.ts +9 -0
  41. package/components/form/__tests__/PrivateRegistry.test.ts +133 -0
  42. package/components/form/labeled-select-utils/useLabeledSelectPagination.ts +138 -0
  43. package/components/formatter/WorkloadHealthScale.vue +3 -1
  44. package/components/nav/Group.vue +33 -9
  45. package/components/nav/Header.vue +56 -10
  46. package/components/nav/NotificationCenter/Notification.vue +4 -1
  47. package/components/nav/NotificationCenter/NotificationHeader.vue +20 -8
  48. package/components/nav/NotificationCenter/__tests__/NotificationHeader.test.ts +80 -0
  49. package/components/nav/TopLevelMenu.vue +15 -1
  50. package/components/nav/Type.vue +8 -7
  51. package/components/nav/WindowManager/index.vue +2 -1
  52. package/components/nav/WorkspaceSwitcher.vue +13 -0
  53. package/components/nav/__tests__/Group.test.ts +67 -0
  54. package/components/nav/__tests__/Header.test.ts +235 -0
  55. package/components/nav/__tests__/Type.test.ts +20 -3
  56. package/components/templates/default.vue +34 -4
  57. package/components/templates/home.vue +12 -25
  58. package/components/templates/plain.vue +13 -26
  59. package/composables/useLabeledFormElement.ts +10 -2
  60. package/composables/useLabeledSelect.ts +60 -0
  61. package/composables/useUserRetentionValidation.ts +1 -49
  62. package/config/cookies.js +0 -1
  63. package/config/labels-annotations.js +1 -0
  64. package/config/pagination-table-headers.js +8 -1
  65. package/config/product/apps.js +2 -1
  66. package/config/product/auth.js +1 -0
  67. package/config/product/backup.js +1 -0
  68. package/config/product/compliance.js +1 -1
  69. package/config/product/explorer.js +25 -6
  70. package/config/product/fleet.js +1 -0
  71. package/config/product/gatekeeper.js +1 -0
  72. package/config/product/istio.js +1 -0
  73. package/config/product/logging.js +1 -0
  74. package/config/product/longhorn.js +2 -1
  75. package/config/product/manager.js +1 -0
  76. package/config/product/monitoring.js +1 -0
  77. package/config/product/navlinks.js +1 -0
  78. package/config/product/neuvector.js +2 -1
  79. package/config/product/settings.js +1 -0
  80. package/config/product/uiplugins.js +1 -0
  81. package/config/query-params.js +1 -0
  82. package/config/router/routes.js +0 -8
  83. package/core/__tests__/plugin-products-helpers.test.ts +454 -0
  84. package/core/__tests__/plugin-products.test.ts +3810 -0
  85. package/core/extension-manager-impl.js +30 -1
  86. package/core/plugin-products-base.ts +392 -0
  87. package/core/plugin-products-extending.ts +44 -0
  88. package/core/plugin-products-helpers.ts +263 -0
  89. package/core/plugin-products-top-level.ts +66 -0
  90. package/core/plugin-products-type-guards.ts +33 -0
  91. package/core/plugin-products.ts +50 -0
  92. package/core/plugin-types.ts +237 -0
  93. package/core/plugin.ts +45 -10
  94. package/core/productDebugger.js +48 -0
  95. package/core/types.ts +97 -11
  96. package/detail/__tests__/__snapshots__/fleet.cattle.io.bundle.test.ts.snap +52 -0
  97. package/detail/__tests__/fleet.cattle.io.bundle.test.ts +171 -0
  98. package/detail/__tests__/management.cattle.io.fleetworkspace.test.ts +128 -0
  99. package/detail/fleet.cattle.io.bundle.vue +21 -34
  100. package/detail/management.cattle.io.fleetworkspace.vue +49 -0
  101. package/dialog/ExtensionCatalogInstallDialog.vue +1 -1
  102. package/dialog/InstallExtensionDialog.vue +6 -27
  103. package/dialog/UninstallExistingExtensionDialog.vue +141 -0
  104. package/dialog/UninstallExtensionDialog.vue +4 -26
  105. package/dialog/__tests__/UninstallExistingExtensionDialog.test.ts +114 -0
  106. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -0
  107. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +9 -0
  108. package/edit/__tests__/kontainerDriver.test.ts +0 -13
  109. package/edit/__tests__/nodeDriver.test.ts +5 -11
  110. package/edit/__tests__/resources.cattle.io.restore.test.ts +9 -0
  111. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
  112. package/edit/auth/__tests__/oidc.test.ts +54 -0
  113. package/edit/auth/azuread.vue +1 -1
  114. package/edit/auth/oidc.vue +8 -0
  115. package/edit/kontainerDriver.vue +1 -2
  116. package/edit/nodeDriver.vue +0 -2
  117. package/edit/provisioning.cattle.io.cluster/AgentEnv.vue +1 -0
  118. package/edit/provisioning.cattle.io.cluster/__tests__/AgentEnv.test.ts +25 -0
  119. package/edit/provisioning.cattle.io.cluster/__tests__/Ingress.test.ts +176 -0
  120. package/edit/provisioning.cattle.io.cluster/index.vue +70 -99
  121. package/edit/provisioning.cattle.io.cluster/rke2.vue +4 -1
  122. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +6 -0
  123. package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +7 -2
  124. package/initialize/App.vue +29 -2
  125. package/initialize/install-plugins.js +0 -2
  126. package/list/__tests__/management.cattle.io.feature.test.ts +105 -0
  127. package/list/catalog.cattle.io.app.vue +25 -5
  128. package/list/management.cattle.io.feature.vue +1 -1
  129. package/list/management.cattle.io.fleetworkspace.vue +8 -0
  130. package/list/provisioning.cattle.io.cluster.vue +0 -1
  131. package/list/workload.vue +11 -4
  132. package/machine-config/amazonec2.vue +1 -0
  133. package/mixins/chart.js +40 -9
  134. package/mixins/resource-fetch.js +12 -3
  135. package/models/__tests__/catalog.cattle.io.app.test.ts +15 -1
  136. package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +84 -0
  137. package/models/__tests__/chart.test.ts +99 -6
  138. package/models/__tests__/management.cattle.io.feature.test.ts +131 -0
  139. package/models/__tests__/monitoring.coreos.com.alertmanagerconfig.test.ts +98 -0
  140. package/models/catalog.cattle.io.app.js +21 -17
  141. package/models/catalog.cattle.io.clusterrepo.js +39 -11
  142. package/models/chart.js +33 -19
  143. package/models/fleet-application.js +1 -1
  144. package/models/fleet.cattle.io.bundle.js +1 -1
  145. package/models/kontainerdriver.js +11 -0
  146. package/models/management.cattle.io.authconfig.js +5 -1
  147. package/models/management.cattle.io.cluster.js +0 -53
  148. package/models/management.cattle.io.feature.js +3 -3
  149. package/models/management.cattle.io.kontainerdriver.js +1 -26
  150. package/models/monitoring.coreos.com.alertmanagerconfig.js +31 -17
  151. package/models/nodedriver.js +7 -0
  152. package/models/pod.js +18 -0
  153. package/models/workload.js +20 -2
  154. package/package.json +13 -13
  155. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +0 -1
  156. package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +189 -0
  157. package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +55 -0
  158. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +53 -0
  159. package/pages/c/_cluster/apps/charts/chart.vue +217 -33
  160. package/pages/c/_cluster/apps/charts/index.vue +2 -2
  161. package/pages/c/_cluster/apps/charts/install.vue +8 -3
  162. package/pages/c/_cluster/auth/user.retention/index.vue +55 -22
  163. package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -7
  164. package/pages/c/_cluster/settings/brand.vue +4 -4
  165. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +39 -2
  166. package/pages/c/_cluster/uiplugins/__tests__/PluginInfoPanel.test.ts +61 -0
  167. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +246 -23
  168. package/pages/c/_cluster/uiplugins/index.vue +166 -62
  169. package/plugins/dashboard-store/__tests__/resource-class.test.ts +1 -0
  170. package/plugins/dashboard-store/actions.js +3 -2
  171. package/plugins/dashboard-store/resource-class.js +62 -6
  172. package/plugins/plugin.js +16 -0
  173. package/plugins/steve/steve-pagination-utils.ts +7 -0
  174. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +205 -1
  175. package/rancher-components/Form/LabeledInput/LabeledInput.vue +82 -4
  176. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +1 -1
  177. package/scripts/test-plugins-build.sh +5 -2
  178. package/scripts/typegen.sh +13 -1
  179. package/server/server-middleware.js +2 -2
  180. package/static/humans.txt +1 -0
  181. package/static/robots.txt +34 -0
  182. package/static/welcome-cow.svg +18 -0
  183. package/store/__tests__/catalog.test.ts +161 -11
  184. package/store/__tests__/type-map.test.ts +84 -24
  185. package/store/auth.js +0 -3
  186. package/store/catalog.js +60 -8
  187. package/store/type-map.js +42 -3
  188. package/tsconfig.paths.json +1 -0
  189. package/types/resources/pod.ts +18 -0
  190. package/types/shell/index.d.ts +8539 -2938
  191. package/types/store/dashboard-store.types.ts +5 -0
  192. package/types/store/pagination.types.ts +6 -0
  193. package/utils/__tests__/git.test.ts +270 -0
  194. package/utils/__tests__/inactivity.test.ts +316 -0
  195. package/utils/__tests__/object.test.ts +77 -0
  196. package/utils/__tests__/time.test.ts +14 -1
  197. package/utils/__tests__/url.test.ts +246 -0
  198. package/utils/axios.js +1 -4
  199. package/utils/dynamic-importer.js +3 -2
  200. package/utils/object.js +33 -2
  201. package/utils/pagination-utils.ts +1 -1
  202. package/utils/time.ts +5 -0
  203. package/utils/uiplugins.ts +12 -16
  204. package/utils/validators/__tests__/private-registry.test.ts +76 -0
  205. package/utils/validators/private-registry.ts +28 -0
  206. package/vue.config.js +0 -9
  207. package/assets/images/providers/azuread-black.svg +0 -22
  208. package/assets/images/providers/azuread.svg +0 -25
  209. package/assets/images/vendor/azuread.svg +0 -18
  210. package/assets/styles/fonts/_dots.scss +0 -18
  211. package/components/EmberPage.vue +0 -622
  212. package/components/EmberPageView.vue +0 -39
  213. package/components/form/labeled-select-utils/labeled-select-pagination.ts +0 -116
  214. package/mixins/labeled-form-element.ts +0 -225
  215. package/pages/c/_cluster/explorer/tools/pages/_page.vue +0 -28
  216. package/pages/c/_cluster/manager/pages/_page.vue +0 -22
  217. package/pages/c/_cluster/mcapps/pages/_page.vue +0 -22
  218. package/plugins/ember-cookie.js +0 -17
  219. package/utils/ember-page.js +0 -30
@@ -4,7 +4,6 @@ import Loading from '@shell/components/Loading';
4
4
  import { Banner } from '@components/Banner';
5
5
  import CruResource from '@shell/components/CruResource';
6
6
  import SelectIconGrid from '@shell/components/SelectIconGrid';
7
- import EmberPage from '@shell/components/EmberPage';
8
7
  import {
9
8
  CHART, FROM_CLUSTER, SUB_TYPE, RKE_TYPE, _EDIT, _IMPORT, _CONFIG, _VIEW
10
9
  } from '@shell/config/query-params';
@@ -18,8 +17,8 @@ import { mapFeature, RKE2 as RKE2_FEATURE } from '@shell/store/features';
18
17
  import { allHash } from '@shell/utils/promise';
19
18
  import { BLANK_CLUSTER } from '@shell/store/store-types.js';
20
19
  import { ELEMENTAL_PRODUCT_NAME, ELEMENTAL_CLUSTER_PROVIDER } from '../../config/elemental-types';
20
+ import { KONTAINER_TO_DRIVER } from '@shell/models/management.cattle.io.kontainerdriver';
21
21
  import Rke2Config from './rke2';
22
- import { DRIVER_TO_IMPORT } from '@shell/models/management.cattle.io.kontainerdriver';
23
22
  import { requireAsset } from '@shell/utils/require-asset';
24
23
 
25
24
  const SORT_GROUPS = {
@@ -44,7 +43,6 @@ export default {
44
43
 
45
44
  components: {
46
45
  CruResource,
47
- EmberPage,
48
46
  Loading,
49
47
  Rke2Config,
50
48
  SelectIconGrid,
@@ -103,19 +101,6 @@ export default {
103
101
  hash.kontainerDrivers = this.$store.dispatch('management/findAll', { type: MANAGEMENT.KONTAINER_DRIVER });
104
102
  }
105
103
 
106
- // Not sure if needed for legacy hosted cluster?
107
- if ( this.value.id && !this.value.isRke2 ) {
108
- // These are needed to resolve references in the mgmt cluster -> node pool -> node template to figure out what provider the cluster is using
109
- // so that the edit iframe for ember pages can go to the right place.
110
- if (this.$store.getters[`management/canList`](MANAGEMENT.NODE_POOL)) {
111
- hash.rke1NodePools = this.$store.dispatch('management/findAll', { type: MANAGEMENT.NODE_POOL });
112
- }
113
-
114
- if (this.$store.getters[`management/canList`](MANAGEMENT.NODE_TEMPLATE)) {
115
- hash.rke1NodeTemplates = this.$store.dispatch('management/findAll', { type: MANAGEMENT.NODE_TEMPLATE });
116
- }
117
- }
118
-
119
104
  const res = await allHash(hash);
120
105
 
121
106
  this.nodeDrivers = res.nodeDrivers || [];
@@ -172,11 +157,50 @@ export default {
172
157
  if ( this.$route.query[SUB_TYPE]) {
173
158
  subType = this.$route.query[SUB_TYPE];
174
159
  } else if (this.value.isImported) {
160
+ // Default imported clusters to the generic imported subType.
161
+ // Imported hosted clusters (e.g. AKS, EKS, GKE) that have an extension-provided
162
+ // component will be overridden below to load the correct custom form.
175
163
  subType = IMPORTED;
176
164
  } else if (this.value.isLocal) {
177
165
  subType = LOCAL;
178
166
  }
179
167
 
168
+ // For imported hosted clusters, check if the provisioner has a matching extension
169
+ // component and override the subType so the correct custom form loads instead of
170
+ // the generic imported configuration page.
171
+ if (subType === IMPORTED && this.value?.id && this.value.provisioner) {
172
+ const provisionerLower = this.value.provisioner.toLowerCase();
173
+ const hasExtension = this.extensions.some((ext) => ext.id === provisionerLower);
174
+
175
+ if (hasExtension) {
176
+ subType = provisionerLower;
177
+ }
178
+ }
179
+
180
+ // Auto-detect subType for existing clusters being edited
181
+ if ( !subType && this.value?.id ) {
182
+ // Check for extension annotation first
183
+ const fromAnnotation = this.value.annotations?.[CAPI_ANNOTATIONS.UI_CUSTOM_PROVIDER];
184
+
185
+ if (fromAnnotation) {
186
+ subType = fromAnnotation;
187
+ } else if ( this.value.isRke2 ) {
188
+ // For custom RKE2 clusters
189
+ if ( this.value.isCustom && (this.realMode === _EDIT || (this.as === _CONFIG && this.realMode === _VIEW)) ) {
190
+ subType = 'custom';
191
+ } else if ( this.value.machineProvider ) {
192
+ // For RKE2/K3s clusters provisioned in Rancher, use the machine pool provisioner
193
+ subType = this.value.machineProvider;
194
+ }
195
+ } else if ( this.value.provisioner ) {
196
+ // For non-RKE2 clusters, try to match against extension-provided subtypes
197
+ const provisionerLower = this.value.provisioner.toLowerCase();
198
+
199
+ // This will be checked against available subtypes after they're computed
200
+ subType = provisionerLower;
201
+ }
202
+ }
203
+
180
204
  this.subType = subType;
181
205
  },
182
206
 
@@ -217,74 +241,6 @@ export default {
217
241
  },
218
242
  _RKE2: () => _RKE2,
219
243
 
220
- emberLink() {
221
- if (this.value) {
222
- // set subtype if editing EKS/GKE/AKS cluster -- this ensures that the component provided by extension is loaded instead of iframing old ember ui
223
- if (this.value.provisioner) {
224
- const matchingSubtype = this.subTypes.find((st) => {
225
- const typeLower = st.id.toLowerCase();
226
- const provisionerLower = this.value.provisioner.toLowerCase();
227
-
228
- // This allows extensions to provide type for edit without breaking edit for Ember kontainer providers
229
- return (!!st.component && (typeLower === provisionerLower)) || (DRIVER_TO_IMPORT[typeLower] === provisionerLower);
230
- });
231
-
232
- if (matchingSubtype) {
233
- this.selectType(matchingSubtype.id, false);
234
- }
235
- }
236
-
237
- // subType set by the ui during cluster creation
238
- // this is likely from a ui extension trying to load custom ui to edit the cluster
239
- const fromAnnotation = this.value.annotations?.[CAPI_ANNOTATIONS.UI_CUSTOM_PROVIDER];
240
-
241
- if (fromAnnotation) {
242
- this.selectType(fromAnnotation, false);
243
-
244
- return '';
245
- }
246
-
247
- // For custom RKE2 clusters, don't load an Ember page.
248
- // It should be the dashboard.
249
- if ( this.value.isRke2 && ((this.value.isCustom && this.mode === _EDIT) || (this.value.isCustom && this.as === _CONFIG && this.mode === _VIEW) || (this.subType || '').toLowerCase() === 'custom')) {
250
- // For admins, this.value.isCustom is used to check if it is a custom cluster.
251
- // For cluster owners, this.subtype is used.
252
- this.selectType('custom', false);
253
-
254
- return '';
255
- }
256
- // For existing RKE2/K3s clusters provisioned in Rancher,
257
- // set the subtype using the machine pool provisioner
258
- // do not use an iFramed Ember page.
259
- if ( this.value.isRke2 && this.value.machineProvider ) {
260
- this.selectType(this.value.machineProvider, false);
261
-
262
- return '';
263
- }
264
-
265
- if ( this.subType ) {
266
- // if driver type has a custom form component, don't load an ember page
267
- if (this.selectedSubType?.component) {
268
- return '';
269
- }
270
- // For RKE1 and hosted Kubernetes Clusters, set the ember link
271
- // so that we load the page rather than using RKE2 create
272
- if (this.selectedSubType?.emberLink) {
273
- return this.selectedSubType.emberLink;
274
- }
275
-
276
- return '';
277
- }
278
-
279
- if ( this.value.mgmt?.emberEditPath ) {
280
- // Iframe an old page
281
- return this.value.mgmt.emberEditPath;
282
- }
283
- }
284
-
285
- return '';
286
- },
287
-
288
244
  rke2Enabled: mapFeature(RKE2_FEATURE),
289
245
 
290
246
  // todo nb is this info stored anywhere else..?
@@ -310,6 +266,24 @@ export default {
310
266
  return this.value.isRke2;
311
267
  },
312
268
 
269
+ isEmberKontainerDriver() {
270
+ // RKE2/K3s clusters are never legacy Ember kontainer drivers
271
+ if (!this.value?.id || !this.value?.provisioner || this.value.isRke2) {
272
+ return false;
273
+ }
274
+
275
+ const provisioner = this.value.provisioner.toLowerCase();
276
+ // Resolve the provisioner to a driver name using the KONTAINER_TO_DRIVER map
277
+ const resolvedName = KONTAINER_TO_DRIVER[provisioner] || provisioner;
278
+
279
+ const driver = this.kontainerDrivers.find((d) => {
280
+ return d.driverName === resolvedName || d.driverName === provisioner || d.id === provisioner;
281
+ });
282
+
283
+ // If the driver exists and is not built-in, it's a legacy ember driver
284
+ return !!driver && !driver.spec?.builtIn;
285
+ },
286
+
313
287
  templateOptions() {
314
288
  if ( !this.rke2Enabled ) {
315
289
  return [];
@@ -327,16 +301,14 @@ export default {
327
301
  let out = [];
328
302
 
329
303
  const templates = this.templateOptions;
330
- const vueKontainerTypes = getters['plugins/clusterDrivers'];
331
304
  const machineTypes = this.nodeDrivers.filter((x) => x.spec.active && x.state === 'active');
332
305
 
333
- // Keeping this for non Rancher-managed kontainer drivers
306
+ // Kontainer drivers that don't have an extension-provided component are legacy Ember-based
307
+ // and no longer functional. Show them as disabled with an informational tooltip.
308
+ const emberRemovalTooltip = getters['i18n/t']('drivers.kontainer.emberRemovalTooltip');
309
+
334
310
  this.kontainerDrivers.filter((x) => (isImport ? x.showImport : x.showCreate)).forEach((obj) => {
335
- if ( vueKontainerTypes.includes(obj.driverName) ) {
336
- addType(this.$extension, obj.driverName, 'hosted', false);
337
- } else {
338
- addType(this.$extension, obj.driverName, 'hosted', false, (isImport ? obj.emberImportPath : obj.emberCreatePath));
339
- }
311
+ addType(this.$extension, obj.driverName, 'hosted', true, undefined, undefined, emberRemovalTooltip);
340
312
  });
341
313
  if (!isImport) {
342
314
  templates.forEach((chart) => {
@@ -360,7 +332,7 @@ export default {
360
332
  machineTypes.forEach((type) => {
361
333
  const id = type.spec.displayName || type.id;
362
334
 
363
- addType(this.$extension, id, _RKE2, false, null, undefined, type);
335
+ addType(this.$extension, id, _RKE2, false, undefined, type);
364
336
  });
365
337
 
366
338
  addType(this.$extension, 'custom', 'custom2', false);
@@ -409,7 +381,7 @@ export default {
409
381
  out.push(subtype);
410
382
  }
411
383
 
412
- function addType(plugin, id, group, disabled = false, emberLink = null, iconClass = undefined, providerConfig = undefined) {
384
+ function addType(plugin, id, group, disabled = false, iconClass = undefined, providerConfig = undefined, tooltip = undefined) {
413
385
  const label = getters['i18n/withFallback'](`cluster.provider."${ id }"`, null, id);
414
386
  const description = getters['i18n/withFallback'](`cluster.providerDescription."${ id }"`, null, '');
415
387
  const tag = '';
@@ -439,8 +411,8 @@ export default {
439
411
  iconClass,
440
412
  group,
441
413
  disabled,
442
- emberLink,
443
414
  tag,
415
+ tooltip,
444
416
  providerConfig
445
417
  };
446
418
 
@@ -594,12 +566,11 @@ export default {
594
566
  />
595
567
  </div>
596
568
  <div
597
- v-else-if="emberLink"
598
- class="embed"
569
+ v-else-if="isEmberKontainerDriver"
599
570
  >
600
- <EmberPage
601
- :force-new="true"
602
- :src="emberLink"
571
+ <Banner
572
+ color="warning"
573
+ label-key="drivers.kontainer.emberRemovalMessage"
603
574
  />
604
575
  </div>
605
576
  <CruResource
@@ -297,7 +297,8 @@ export default {
297
297
  originalKubeVersion: null,
298
298
  isEmpty,
299
299
  AGENT_CONFIGURATION_TYPES,
300
- basicsValid: true
300
+ basicsValid: true,
301
+ originalIngressController: this.value.spec.rkeConfig.machineGlobalConfig?.[INGRESS_CONTROLLER] || INGRESS_NONE,
301
302
  };
302
303
  },
303
304
 
@@ -2522,6 +2523,7 @@ export default {
2522
2523
  <Tab
2523
2524
  v-if="!obj.remove"
2524
2525
  :key="obj.id"
2526
+ :weight="-1 * idx"
2525
2527
  :name="obj.id"
2526
2528
  :label="obj.pool.name || '(Not Named)'"
2527
2529
  :show-header="false"
@@ -2592,6 +2594,7 @@ export default {
2592
2594
  :is-azure-provider-unsupported="isAzureProviderUnsupported"
2593
2595
  :can-azure-migrate-on-edit="canAzureMigrateOnEdit"
2594
2596
  :has-some-ipv6-pools="hasOnlyIpv6Pools"
2597
+ :original-ingress-controller="originalIngressController"
2595
2598
  @update:value="$emit('input', $event)"
2596
2599
  @cilium-values-changed="handleCiliumValuesChanged"
2597
2600
  @enabled-system-services-changed="handleEnabledSystemServicesChanged"
@@ -120,6 +120,11 @@ export default {
120
120
  canAzureMigrateOnEdit: {
121
121
  type: Boolean,
122
122
  required: true
123
+ },
124
+ originalIngressController: {
125
+ type: [String, Array],
126
+ required: false,
127
+ default: INGRESS_NONE
123
128
  }
124
129
  },
125
130
 
@@ -699,6 +704,7 @@ export default {
699
704
  :traefik-chart="traefikChart"
700
705
  :user-chart-values="userChartValues"
701
706
  :version-info="versionInfo"
707
+ :original-ingress-controller="originalIngressController"
702
708
  @update-values="(name, val) => $emit('update-values', name, val)"
703
709
  @error="$emit('error', $event)"
704
710
  @yaml-validation-changed="e => $emit('yaml-validation-changed', e)"
@@ -24,6 +24,7 @@ interface Props {
24
24
  traefikChart: string;
25
25
  userChartValues: any;
26
26
  versionInfo: any;
27
+ originalIngressController?: string | string[];
27
28
  }
28
29
  const {
29
30
  mode = _CREATE,
@@ -33,7 +34,8 @@ const {
33
34
  nginxSupported,
34
35
  traefikSupported,
35
36
  userChartValues,
36
- versionInfo
37
+ versionInfo,
38
+ originalIngressController = INGRESS_NONE
37
39
  } = defineProps<Props>();
38
40
 
39
41
  const emit = defineEmits(['update:value', 'error', 'config-validation-changed', 'yaml-validation-changed', 'update-values']);
@@ -179,7 +181,10 @@ const compatibilityMode = computed({
179
181
 
180
182
  function selectIngress(id: string) {
181
183
  if ( id === INGRESS_DUAL) {
182
- emit('update:value', [TRAEFIK, INGRESS_NGINX]);
184
+ const newValue: string | string[] = !Array.isArray(originalIngressController) ? (originalIngressController === TRAEFIK ? [TRAEFIK, INGRESS_NGINX] : [INGRESS_NGINX, TRAEFIK]) : originalIngressController;
185
+
186
+ emit('update:value', newValue);
187
+
183
188
  preconfigureTraefik();
184
189
  } else {
185
190
  emit('update:value', id);
@@ -1,10 +1,16 @@
1
1
  <script>
2
2
  import GlobalLoading from '@shell/components/nav/GlobalLoading.vue';
3
+ import WindowManager from '@shell/components/nav/WindowManager';
3
4
 
4
5
  import '@shell/assets/styles/app.scss';
5
6
 
6
7
  export default {
7
- data: () => ({ isOnline: true }),
8
+ data() {
9
+ return {
10
+ isOnline: true,
11
+ currentLayout: null,
12
+ };
13
+ },
8
14
 
9
15
  created() {
10
16
  // add to window so we can listen when ready
@@ -36,6 +42,14 @@ export default {
36
42
  this.$loading = this.$refs.loading;
37
43
  },
38
44
 
45
+ provide() {
46
+ return {
47
+ notifyWmContainerReady: (layout) => {
48
+ this.currentLayout = layout;
49
+ }
50
+ };
51
+ },
52
+
39
53
  computed: {
40
54
  isOffline() {
41
55
  return !this.isOnline;
@@ -55,7 +69,10 @@ export default {
55
69
  },
56
70
  },
57
71
 
58
- components: { GlobalLoading }
72
+ components: {
73
+ GlobalLoading,
74
+ WindowManager,
75
+ }
59
76
  };
60
77
  </script>
61
78
  <template>
@@ -65,6 +82,16 @@ export default {
65
82
  id="__layout"
66
83
  >
67
84
  <router-view />
85
+ <!--
86
+ WindowManager is teleported into each template's wm-container
87
+ This keeps a single instance that never re-mounts while appearing in each template
88
+ -->
89
+ <Teleport
90
+ v-if="currentLayout"
91
+ :to="`#wm-container-${currentLayout}`"
92
+ >
93
+ <WindowManager :layout="currentLayout" />
94
+ </Teleport>
68
95
  </div>
69
96
  </div>
70
97
  </template>
@@ -21,7 +21,6 @@ import plugin from '@shell/plugins/plugin';
21
21
  import pluginsLoader from '@shell/core/plugins-loader.js';
22
22
  import replaceAll from '@shell/plugins/replaceall';
23
23
  import steveCreateWorker from '@shell/plugins/steve-create-worker';
24
- import emberCookie from '@shell/plugins/ember-cookie';
25
24
  import ShortKey from '@shell/plugins/shortkey';
26
25
  import { initUiApis } from '@shell/apis/impl/apis';
27
26
 
@@ -57,7 +56,6 @@ export async function installInjectedPlugins(app, vueApp) {
57
56
  replaceAll,
58
57
  plugin,
59
58
  steveCreateWorker,
60
- emberCookie,
61
59
  dynamicContent,
62
60
  ];
63
61
 
@@ -0,0 +1,105 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+
3
+ jest.mock('@shell/mixins/resource-fetch', () => ({
4
+ __esModule: true,
5
+ default: {
6
+ data() {
7
+ return {
8
+ forceUpdateLiveAndDelayed: 0, loading: false, rows: []
9
+ };
10
+ },
11
+ async $fetchType() {}
12
+ }
13
+ }));
14
+
15
+ // eslint-disable-next-line import/first
16
+ import ManagementFeature from '@shell/list/management.cattle.io.feature.vue';
17
+
18
+ const createMockStore = () => ({
19
+ getters: {
20
+ 'i18n/t': (key: string) => key,
21
+ 'management/schemaFor': () => ({ resourceMethods: ['PUT'] }),
22
+ },
23
+ dispatch: jest.fn(),
24
+ });
25
+
26
+ const createWrapper = (rows: any[]) => {
27
+ return shallowMount(ManagementFeature, {
28
+ props: {
29
+ resource: 'management.cattle.io.feature',
30
+ schema: { id: 'management.cattle.io.feature' } as any,
31
+ },
32
+ data: () => ({ rows }),
33
+ global: {
34
+ mocks: {
35
+ $store: createMockStore(),
36
+ $fetchState: { pending: false },
37
+ $fetchType: jest.fn(),
38
+ },
39
+ stubs: {
40
+ // Render the cell:name slot directly so we can assert on the lock icon
41
+ ResourceTable: {
42
+ props: ['rows'],
43
+ template: '<div><slot name="cell:name" :row="rows[0]" /></div>',
44
+ },
45
+ },
46
+ }
47
+ });
48
+ };
49
+
50
+ describe('list/management.cattle.io.feature', () => {
51
+ describe('locked icon rendering in cell:name slot', () => {
52
+ it('should render the lock icon when status.lockedValue is not null', () => {
53
+ const row = {
54
+ metadata: { name: 'feature-a' },
55
+ nameDisplay: 'feature-a',
56
+ status: { lockedValue: true },
57
+ };
58
+
59
+ const wrapper = createWrapper([row]);
60
+
61
+ expect(wrapper.find('i.icon-lock').exists()).toBe(true);
62
+ });
63
+
64
+ it('should not render the lock icon when status.lockedValue is null', () => {
65
+ const row = {
66
+ metadata: { name: 'feature-a' },
67
+ nameDisplay: 'feature-a',
68
+ status: { lockedValue: null },
69
+ };
70
+
71
+ const wrapper = createWrapper([row]);
72
+
73
+ expect(wrapper.find('i.icon-lock').exists()).toBe(false);
74
+ });
75
+
76
+ it('should not throw and should not render the lock icon when status is missing (malformed feature flag)', () => {
77
+ const row = {
78
+ metadata: { name: 'feature-a' },
79
+ nameDisplay: 'feature-a',
80
+ };
81
+
82
+ expect(() => createWrapper([row])).not.toThrow();
83
+
84
+ const wrapper = createWrapper([row]);
85
+
86
+ expect(wrapper.find('i.icon-lock').exists()).toBe(false);
87
+ });
88
+ });
89
+
90
+ describe('filteredRows', () => {
91
+ it('should filter out hidden feature flags', () => {
92
+ const rows = [
93
+ { metadata: { name: 'fleet' } },
94
+ { metadata: { name: 'some-feature' } },
95
+ ];
96
+
97
+ const wrapper = createWrapper(rows);
98
+
99
+ const filtered = (wrapper.vm as any).filteredRows;
100
+
101
+ expect(filtered).toHaveLength(1);
102
+ expect(filtered[0].metadata.name).toBe('some-feature');
103
+ });
104
+ });
105
+ });
@@ -40,14 +40,15 @@ export default {
40
40
  data-testid="installed-app-catalog-list"
41
41
  >
42
42
  <template #cell:upgrade="{row}">
43
- <span
43
+ <div
44
44
  v-if="row.upgradeAvailable === APP_UPGRADE_STATUS.SINGLE_UPGRADE"
45
- class="badge-state bg-warning hand"
45
+ v-clean-tooltip="row.upgradeAvailableVersion"
46
+ class="badge-state bg-warning hand app-upgrade-badge"
46
47
  @click="row.goToUpgrade(row.upgradeAvailableVersion)"
47
48
  >
48
- {{ row.upgradeAvailableVersion }}
49
+ <div>{{ row.upgradeAvailableVersion }}</div>
49
50
  <i class="icon icon-upload" />
50
- </span>
51
+ </div>
51
52
  <span
52
53
  v-else-if="row.upgradeAvailable === APP_UPGRADE_STATUS.NOT_APPLICABLE"
53
54
  v-t="'catalog.app.managed'"
@@ -69,8 +70,27 @@ export default {
69
70
  </PaginatedResourceTable>
70
71
  </template>
71
72
 
72
- <style scoped>
73
+ <style scoped lang="scss">
73
74
  .apps :deep() .state-description{
74
75
  color: var(--error)
75
76
  }
77
+
78
+ .badge-state.app-upgrade-badge {
79
+ display: inline-flex;
80
+ align-items: center;
81
+ border-radius: var(--border-radius);
82
+ padding: 2px 4px;
83
+
84
+ > div {
85
+ overflow: hidden;
86
+ text-overflow: ellipsis;
87
+ white-space: nowrap;
88
+ min-width: 0;
89
+ }
90
+
91
+ > .icon {
92
+ flex-shrink: 0;
93
+ }
94
+ }
95
+
76
96
  </style>
@@ -69,7 +69,7 @@ export default {
69
69
  <div class="feature-name">
70
70
  <div>{{ scope.row.nameDisplay }}</div>
71
71
  <i
72
- v-if="scope.row.status.lockedValue !== null"
72
+ v-if="scope.row.status && scope.row.status.lockedValue !== null"
73
73
  class="icon icon-lock"
74
74
  />
75
75
  </div>
@@ -21,6 +21,14 @@ export default {
21
21
  }
22
22
  },
23
23
 
24
+ created() {
25
+ this.$store.dispatch('showWorkspaceSwitcher', false);
26
+ },
27
+
28
+ beforeUnmount() {
29
+ this.$store.dispatch('showWorkspaceSwitcher', true);
30
+ },
31
+
24
32
  async fetch() {
25
33
  try {
26
34
  await this.$fetchType(this.resource);
@@ -220,7 +220,6 @@ export default {
220
220
  :use-query-params-for-simple-filtering="useQueryParamsForSimpleFiltering"
221
221
  :data-testid="'cluster-list'"
222
222
  :force-update-live-and-delayed="forceUpdateLiveAndDelayed"
223
- :sub-rows="true"
224
223
  >
225
224
  <!-- Why are state column and subrow overwritten here? -->
226
225
  <!-- for rke1 clusters, where they try to use the mgmt cluster stateObj instead of prov cluster stateObj, -->
package/list/workload.vue CHANGED
@@ -99,13 +99,21 @@ export default {
99
99
  const schema = type !== workloadSchema.id ? this.$store.getters['cluster/schemaFor'](type) : workloadSchema;
100
100
  const paginationEnabled = !allTypes && this.$store.getters[`cluster/paginationEnabled`]?.({ id: type });
101
101
 
102
+ const workloadIncludeAssociatedData = paginationEnabled && [
103
+ WORKLOAD_TYPES.DEPLOYMENT,
104
+ WORKLOAD_TYPES.DAEMON_SET,
105
+ WORKLOAD_TYPES.STATEFUL_SET,
106
+ WORKLOAD_TYPES.JOB,
107
+ ].includes(type);
108
+
102
109
  return {
103
110
  allTypes,
104
111
  schema,
105
112
  paginationEnabled,
106
113
  resources: [],
107
114
  loadResources,
108
- loadIndeterminate
115
+ loadIndeterminate,
116
+ workloadIncludeAssociatedData
109
117
  };
110
118
  },
111
119
 
@@ -143,10 +151,8 @@ export default {
143
151
  * Fetch resources required to populate POD_RESTARTS and WORKLOAD_HEALTH_SCALE columns
144
152
  */
145
153
  loadHeathResources() {
146
- // See https://github.com/rancher/dashboard/issues/10417, health comes from selectors applied locally to all pods (bad)
147
154
  if (this.paginationEnabled) {
148
- // Unfortunately with SSP enabled we cannot fetch all pods to then let each row find applicable pods by locally applied selectors (bad for scaling)
149
- // See https://github.com/rancher/dashboard/issues/14211
155
+ // When SSP is enabled we efficiently fetch stats for health column imbedded in the original resource type by supplying `includeAssociatedData` param
150
156
  return;
151
157
  }
152
158
 
@@ -184,6 +190,7 @@ export default {
184
190
  v-if="paginationEnabled"
185
191
  :schema="schema"
186
192
  :use-query-params-for-simple-filtering="useQueryParamsForSimpleFiltering"
193
+ :includeAssociatedData="workloadIncludeAssociatedData"
187
194
  />
188
195
  <ResourceTable
189
196
  v-else
@@ -329,6 +329,7 @@ export default {
329
329
  :searchable="true"
330
330
  :disabled="disabled"
331
331
  :label="t('cluster.machineConfig.amazonEc2.region')"
332
+ data-testid="amazonEc2__region"
332
333
  />
333
334
  </div>
334
335
  <div class="col span-6">