@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
@@ -1,30 +1,54 @@
1
1
  import { CATALOG } from '@shell/config/types';
2
+ import { CATALOG as CATALOG_ANNOTATIONS } from '@shell/config/labels-annotations';
2
3
  import {
3
4
  state, getters, actions, mutations, filterAndArrangeCharts
4
5
  } from '../catalog';
5
6
  import { createStore } from 'vuex';
6
7
 
7
8
  const clusterRepo = { _key: 'testClusterRepo' };
8
- const repoChartName = 'abc';
9
+ const repoChartName = 'regular-chart';
9
10
  const repoChart = {
10
11
  name: repoChartName,
11
12
  type: 'namespaced',
12
13
  version: 1,
13
14
  metadata: { name: repoChartName }
14
15
  };
15
- const repoCharts = [repoChart];
16
+ const deprecatedByFieldChartName = 'deprecated-by-field-chart';
17
+ const deprecatedByFieldChart = {
18
+ name: deprecatedByFieldChartName,
19
+ type: 'namespaced',
20
+ version: 1,
21
+ deprecated: true,
22
+ metadata: { name: deprecatedByFieldChartName }
23
+ };
24
+ const deprecatedByAnnotationChartName = 'deprecated-by-annotation-chart';
25
+ const deprecatedByAnnotationChart = {
26
+ name: deprecatedByAnnotationChartName,
27
+ type: 'namespaced',
28
+ version: 1,
29
+ annotations: { [CATALOG_ANNOTATIONS.DEPRECATED]: 'true' },
30
+ metadata: { name: deprecatedByAnnotationChartName }
31
+ };
16
32
  const repo = {
17
33
  metadata: { name: 'testRepo' },
18
34
  _key: 'testRepo',
19
35
  links: { index: 'fetchindex' },
20
36
  canLoad: true,
21
- followLink: () => ({ entries: { [repoChartName]: repoCharts } })
37
+ followLink: () => ({
38
+ entries: {
39
+ [repoChartName]: [repoChart],
40
+ [deprecatedByFieldChartName]: [deprecatedByFieldChart],
41
+ [deprecatedByAnnotationChartName]: [deprecatedByAnnotationChart]
42
+ }
43
+ })
22
44
  };
23
45
 
24
46
  const catalogStoreName = 'catalog';
25
47
  const clusterStore = 'cluster';
26
48
 
27
49
  const expectedChartKey = `namespace/${ repo._key }/${ repoChartName }`;
50
+ const expectedDeprecatedByFieldChartKey = `namespace/${ repo._key }/${ deprecatedByFieldChartName }`;
51
+ const expectedDeprecatedByAnnotationChartKey = `namespace/${ repo._key }/${ deprecatedByAnnotationChartName }`;
28
52
  const initialVersionInfo = { junk: true };
29
53
  const initialRawChartName = 'cde';
30
54
  const initialRawChart = {
@@ -160,10 +184,20 @@ describe('catalog', () => {
160
184
  expect(rawCharts[expectedChartKey]).toBeDefined();
161
185
  expect(rawCharts[expectedChartKey].id).toBe(expectedChartKey);
162
186
  expect(rawCharts[expectedChartKey].versions[0].version).toBe(repoChart.version);
163
- expect(charts[0]).toBeDefined();
164
- expect(charts[0].id).toBe(initialRawChart.id);
165
- expect(charts[1]).toBeDefined();
166
- expect(charts[1].id).toBe(expectedChartKey);
187
+
188
+ expect(rawCharts[expectedDeprecatedByFieldChartKey]).toBeDefined();
189
+ expect(rawCharts[expectedDeprecatedByFieldChartKey].id).toBe(expectedDeprecatedByFieldChartKey);
190
+ expect(rawCharts[expectedDeprecatedByFieldChartKey].deprecated).toBe(true);
191
+
192
+ expect(rawCharts[expectedDeprecatedByAnnotationChartKey]).toBeDefined();
193
+ expect(rawCharts[expectedDeprecatedByAnnotationChartKey].id).toBe(expectedDeprecatedByAnnotationChartKey);
194
+ expect(rawCharts[expectedDeprecatedByAnnotationChartKey].deprecated).toBe(true);
195
+
196
+ expect(charts).toHaveLength(4);
197
+ expect(charts.find((c: any) => c.id === initialRawChart.id)).toBeDefined();
198
+ expect(charts.find((c: any) => c.id === expectedChartKey)).toBeDefined();
199
+ expect(charts.find((c: any) => c.id === expectedDeprecatedByFieldChartKey)).toBeDefined();
200
+ expect(charts.find((c: any) => c.id === expectedDeprecatedByAnnotationChartKey)).toBeDefined();
167
201
 
168
202
  // Version info should be unchanged
169
203
  expect(store.state[catalogStoreName].versionInfos).toStrictEqual(initialVersionInfo);
@@ -197,14 +231,130 @@ describe('catalog', () => {
197
231
  expect(rawCharts[expectedChartKey].id).toBe(expectedChartKey);
198
232
  expect(rawCharts[expectedChartKey].versions[0].version).toBe(repoChart.version);
199
233
 
200
- expect(charts).toHaveLength(1);
201
- expect(charts[0]).toBeDefined();
202
- expect(charts[0].id).toBe(expectedChartKey);
203
- expect(charts[0].versions[0].version).toBe(repoChart.version);
234
+ expect(rawCharts[expectedDeprecatedByFieldChartKey]).toBeDefined();
235
+ expect(rawCharts[expectedDeprecatedByFieldChartKey].id).toBe(expectedDeprecatedByFieldChartKey);
236
+ expect(rawCharts[expectedDeprecatedByFieldChartKey].deprecated).toBe(true);
237
+
238
+ expect(rawCharts[expectedDeprecatedByAnnotationChartKey]).toBeDefined();
239
+ expect(rawCharts[expectedDeprecatedByAnnotationChartKey].id).toBe(expectedDeprecatedByAnnotationChartKey);
240
+ expect(rawCharts[expectedDeprecatedByAnnotationChartKey].deprecated).toBe(true);
241
+
242
+ expect(charts).toHaveLength(3);
243
+ expect(charts.find((c: any) => c.id === expectedChartKey)).toBeDefined();
244
+ expect(charts.find((c: any) => c.id === expectedDeprecatedByFieldChartKey)).toBeDefined();
245
+ expect(charts.find((c: any) => c.id === expectedDeprecatedByAnnotationChartKey)).toBeDefined();
204
246
 
205
247
  // Version info should be changed (it's now empty given reset)
206
248
  expect(store.state[catalogStoreName].versionInfos).toStrictEqual({ });
207
249
  });
250
+
251
+ it('repoKeys provided', async() => {
252
+ const store = createStore(constructStore());
253
+
254
+ // Validate initial state of store
255
+ expect(store.getters[`${ catalogStoreName }/rawCharts`]).toStrictEqual(initialRawCharts);
256
+ expect(store.getters[`${ catalogStoreName }/charts`]).toStrictEqual([]);
257
+
258
+ // Make the request targeting specific repo (we provide repo._key)
259
+ await store.dispatch(`${ catalogStoreName }/load`, {
260
+ force: true,
261
+ repoKeys: [repo._key]
262
+ });
263
+
264
+ const rawCharts = store.getters[`${ catalogStoreName }/rawCharts`];
265
+ const charts = store.getters[`${ catalogStoreName }/charts`];
266
+
267
+ // We expect the old chart belonging to this repo to be wiped out
268
+ // and replaced entirely by the newly fetched chart.
269
+ expect(rawCharts[initialRawChart.id]).not.toBeDefined();
270
+ expect(rawCharts[expectedChartKey]).toBeDefined();
271
+ expect(rawCharts[expectedChartKey].id).toBe(expectedChartKey);
272
+ expect(rawCharts[expectedChartKey].versions[0].version).toBe(repoChart.version);
273
+
274
+ expect(rawCharts[expectedDeprecatedByFieldChartKey]).toBeDefined();
275
+ expect(rawCharts[expectedDeprecatedByFieldChartKey].id).toBe(expectedDeprecatedByFieldChartKey);
276
+ expect(rawCharts[expectedDeprecatedByFieldChartKey].deprecated).toBe(true);
277
+
278
+ expect(rawCharts[expectedDeprecatedByAnnotationChartKey]).toBeDefined();
279
+ expect(rawCharts[expectedDeprecatedByAnnotationChartKey].id).toBe(expectedDeprecatedByAnnotationChartKey);
280
+ expect(rawCharts[expectedDeprecatedByAnnotationChartKey].deprecated).toBe(true);
281
+
282
+ expect(charts).toHaveLength(3);
283
+ expect(charts.find((c: any) => c.id === expectedChartKey)).toBeDefined();
284
+ expect(charts.find((c: any) => c.id === expectedDeprecatedByFieldChartKey)).toBeDefined();
285
+ expect(charts.find((c: any) => c.id === expectedDeprecatedByAnnotationChartKey)).toBeDefined();
286
+ });
287
+
288
+ it('repoKeys provided, ignoring unrelated charts and versions', async() => {
289
+ // We will manually inject an unrelated chart to prove it isn't wiped out
290
+ const store = createStore(constructStore());
291
+ const unrelatedChart = {
292
+ id: `namespace/unrelatedRepo/unrelatedChart`,
293
+ repoKey: 'unrelatedRepo',
294
+ type: 'namespaced',
295
+ name: 'unrelatedChart',
296
+ version: 1,
297
+ metadata: { name: 'unrelatedChart' }
298
+ };
299
+
300
+ const targetedVersionKey = `namespace/testRepo/myChart/1.0.0`;
301
+ const unrelatedVersionKey = `namespace/unrelatedRepo/unrelatedChart/1.0.0`;
302
+
303
+ store.state[catalogStoreName].charts = {
304
+ ...initialRawCharts,
305
+ [unrelatedChart.id]: unrelatedChart
306
+ };
307
+
308
+ store.state[catalogStoreName].versionInfos = {
309
+ [targetedVersionKey]: { data: 'old-data' },
310
+ [unrelatedVersionKey]: { data: 'keep-me' }
311
+ };
312
+
313
+ // Make the request targeting specific repo
314
+ await store.dispatch(`${ catalogStoreName }/load`, {
315
+ force: true,
316
+ repoKeys: [repo._key]
317
+ });
318
+
319
+ const rawCharts = store.getters[`${ catalogStoreName }/rawCharts`];
320
+ const versionInfos = store.state[catalogStoreName].versionInfos;
321
+
322
+ // The unrelated chart should NOT be wiped
323
+ expect(rawCharts[unrelatedChart.id]).toBeDefined();
324
+ expect(rawCharts[unrelatedChart.id].repoKey).toBe('unrelatedRepo');
325
+
326
+ // The old initialRawChart (with repoKey: repo._key) SHOULD be wiped and replaced
327
+ expect(rawCharts[initialRawChart.id]).not.toBeDefined();
328
+ expect(rawCharts[expectedChartKey]).toBeDefined();
329
+
330
+ expect(rawCharts[expectedDeprecatedByFieldChartKey]).toBeDefined();
331
+ expect(rawCharts[expectedDeprecatedByAnnotationChartKey]).toBeDefined();
332
+
333
+ // The targeted version info SHOULD be wiped
334
+ expect(versionInfos[targetedVersionKey]).not.toBeDefined();
335
+ // The unrelated version info should NOT be wiped
336
+ expect(versionInfos[unrelatedVersionKey]).toBeDefined();
337
+ expect(versionInfos[unrelatedVersionKey].data).toBe('keep-me');
338
+ });
339
+ });
340
+
341
+ describe('refresh', () => {
342
+ it('calls refresh(false) on all repos and then dispatches a reset load', async() => {
343
+ const mockRepo1 = { refresh: jest.fn().mockResolvedValue(true) };
344
+ const mockRepo2 = { refresh: jest.fn().mockResolvedValue(true) };
345
+
346
+ const getters = { repos: [mockRepo1, mockRepo2] };
347
+ const dispatch = jest.fn().mockResolvedValue(true);
348
+ const commit = jest.fn();
349
+
350
+ await actions.refresh({
351
+ getters, commit, dispatch
352
+ });
353
+
354
+ expect(mockRepo1.refresh).toHaveBeenCalledWith(false);
355
+ expect(mockRepo2.refresh).toHaveBeenCalledWith(false);
356
+ expect(dispatch).toHaveBeenCalledWith('load', { force: true, reset: true });
357
+ });
208
358
  });
209
359
 
210
360
  describe('filterAndArrangeCharts', () => {
@@ -7,6 +7,17 @@ import {
7
7
  SCHEMA,
8
8
  } from '@shell/config/types';
9
9
 
10
+ // Type definitions for test data
11
+ interface TestSchema {
12
+ id: string;
13
+ type: string;
14
+ attributes?: { kind: string };
15
+ _group?: string;
16
+ _id?: string;
17
+ }
18
+
19
+ type SchemaOrType = TestSchema | string;
20
+
10
21
  /**
11
22
  * types in the store
12
23
  */
@@ -115,9 +126,9 @@ describe('type-map', () => {
115
126
  /**
116
127
  * Stick in the required mode param to the expected menu items
117
128
  */
118
- const setTypeMode = (modes, resourcesById) => {
119
- return modes.reduce((res, mode) => {
120
- const newResource = { };
129
+ const setTypeMode = (modes: string[], resourcesById: Record<string, any>) => {
130
+ return modes.reduce((res: Record<string, any>, mode: string) => {
131
+ const newResource: Record<string, any> = { };
121
132
 
122
133
  Object.entries(resourcesById).forEach(([id, resource]: [string, any]) => {
123
134
  newResource[id] = {
@@ -146,17 +157,17 @@ describe('type-map', () => {
146
157
  spoofedTypes: { [productName]: [] }
147
158
  },
148
159
  typeMapGetters: {
149
- labelFor: (schema, count) => '',
150
- optionsFor: (schema) => {},
160
+ labelFor: (schema: SchemaOrType, count: number) => '',
161
+ optionsFor: (schema: SchemaOrType) => ({}),
151
162
  groupForBasicType: () => {},
152
- typeWeightFor: (label, isBasic) => 1
163
+ typeWeightFor: (label: string, isBasic: boolean) => 1
153
164
  },
154
165
  rootState: {},
155
166
  rootGetters: {
156
167
  [`${ productStore }/all`]: (schema: string) => {
157
168
  return [];
158
169
  },
159
- 'prefs/get': (pref) => {},
170
+ 'prefs/get': (pref: string) => {},
160
171
 
161
172
  },
162
173
 
@@ -201,9 +212,9 @@ describe('type-map', () => {
201
212
 
202
213
  const testTypeMapGetters = {
203
214
  ...typeMapGetters,
204
- labelFor: (schema, count) => 'Pod',
215
+ labelFor: (schema: SchemaOrType, count: number) => 'Pod',
205
216
  groupForBasicType: () => true,
206
- optionsFor: (schema) => ({
217
+ optionsFor: (schema: SchemaOrType) => ({
207
218
  namespaced: true,
208
219
  customRoute: 'cde'
209
220
  }),
@@ -320,7 +331,7 @@ describe('type-map', () => {
320
331
 
321
332
  const testTypeMapGetters = {
322
333
  ...typeMapGetters,
323
- groupForBasicType: (product, id) => false
334
+ groupForBasicType: (product: string, id: string) => false
324
335
  };
325
336
 
326
337
  const groups = getters.allTypes(state, testTypeMapGetters, rootState, rootGetters)(productName, modes);
@@ -473,7 +484,7 @@ describe('type-map', () => {
473
484
 
474
485
  const testTypeMapGetters = {
475
486
  ...typeMapGetters,
476
- optionsFor: (schema) => ({
487
+ optionsFor: (schema: SchemaOrType) => ({
477
488
  namespaced: true,
478
489
  customRoute: 'cde',
479
490
  ifRancherCluster: true
@@ -497,7 +508,7 @@ describe('type-map', () => {
497
508
 
498
509
  const testTypeMapGetters = {
499
510
  ...typeMapGetters,
500
- optionsFor: (schema) => ({
511
+ optionsFor: (schema: SchemaOrType) => ({
501
512
  namespaced: true,
502
513
  customRoute: 'cde',
503
514
  ifRancherCluster: false
@@ -521,7 +532,7 @@ describe('type-map', () => {
521
532
 
522
533
  const testTypeMapGetters = {
523
534
  ...typeMapGetters,
524
- optionsFor: (schema) => ({
535
+ optionsFor: (schema: SchemaOrType) => ({
525
536
  namespaced: true,
526
537
  customRoute: 'cde',
527
538
  ifRancherCluster: true
@@ -545,7 +556,7 @@ describe('type-map', () => {
545
556
 
546
557
  const testTypeMapGetters = {
547
558
  ...typeMapGetters,
548
- optionsFor: (schema) => ({
559
+ optionsFor: (schema: SchemaOrType) => ({
549
560
  namespaced: true,
550
561
  customRoute: 'cde',
551
562
  localOnly: true
@@ -569,7 +580,7 @@ describe('type-map', () => {
569
580
 
570
581
  const testTypeMapGetters = {
571
582
  ...typeMapGetters,
572
- optionsFor: (schema) => ({
583
+ optionsFor: (schema: SchemaOrType) => ({
573
584
  namespaced: true,
574
585
  customRoute: 'cde',
575
586
  localOnly: true
@@ -630,9 +641,9 @@ describe('type-map', () => {
630
641
 
631
642
  const testTypeMapGetters = {
632
643
  ...typeMapGetters,
633
- labelFor: (schema, count) => 'Secret',
644
+ labelFor: (schema: SchemaOrType, count: number) => 'Secret',
634
645
  groupForBasicType: () => true,
635
- optionsFor: (schema) => ({
646
+ optionsFor: (schema: SchemaOrType) => ({
636
647
  namespaced: true,
637
648
  customRoute: 'cde'
638
649
  }),
@@ -882,8 +893,10 @@ describe('type-map', () => {
882
893
 
883
894
  const testTypeMapGetters = {
884
895
  ...typeMapGetters,
885
- labelFor: (schema, count) => {
886
- switch (schema.id) {
896
+ labelFor: (schema: SchemaOrType, count: number) => {
897
+ const schemaId = typeof schema === 'object' ? schema.id : schema;
898
+
899
+ switch (schemaId) {
887
900
  case 'secret':
888
901
  return 'Secret';
889
902
  default:
@@ -891,11 +904,11 @@ describe('type-map', () => {
891
904
  }
892
905
  },
893
906
  groupForBasicType: () => true,
894
- optionsFor: (schema) => ({
907
+ optionsFor: (schema: SchemaOrType) => ({
895
908
  namespaced: true,
896
909
  customRoute: 'cde'
897
910
  }),
898
- isFavorite: (id) => id === 'secret',
911
+ isFavorite: (id: string) => id === 'secret',
899
912
  };
900
913
 
901
914
  return {
@@ -1134,14 +1147,14 @@ describe('type-map', () => {
1134
1147
  },
1135
1148
  };
1136
1149
 
1137
- const createProductState = (products) => ({
1150
+ const createProductState = (products: any) => ({
1138
1151
  products,
1139
1152
  schemaGeneration: 1,
1140
1153
  });
1141
1154
 
1142
- const createProductRootGetters = (moduleSchemas = [], moduleName = 'cluster') => ({
1155
+ const createProductRootGetters = (moduleSchemas: any[] = [], moduleName = 'cluster') => ({
1143
1156
  'prefs/get': () => false,
1144
- [`${ moduleName }/all`]: (resource) => {
1157
+ [`${ moduleName }/all`]: (resource: any) => {
1145
1158
  if (resource === SCHEMA) {
1146
1159
  return moduleSchemas;
1147
1160
  }
@@ -1281,4 +1294,51 @@ describe('type-map', () => {
1281
1294
  });
1282
1295
  });
1283
1296
  });
1297
+
1298
+ describe('groupLabel', () => {
1299
+ it('should return groupLabel when it exists in state', () => {
1300
+ const state = { groupLabels: { mygroup: { label: 'My Group Label', labelKey: undefined } } };
1301
+
1302
+ const result = getters.groupLabel(state)('mygroup');
1303
+
1304
+ expect(result).toStrictEqual({ label: 'My Group Label', labelKey: undefined });
1305
+ });
1306
+
1307
+ it('should return groupLabel with labelKey when set', () => {
1308
+ const state = { groupLabels: { anothergroup: { label: undefined, labelKey: 'typeLabel.myKey' } } };
1309
+
1310
+ const result = getters.groupLabel(state)('anothergroup');
1311
+
1312
+ expect(result).toStrictEqual({ label: undefined, labelKey: 'typeLabel.myKey' });
1313
+ });
1314
+
1315
+ it('should handle case-insensitive group names', () => {
1316
+ const state = { groupLabels: { mygroup: { label: 'My Group', labelKey: undefined } } };
1317
+
1318
+ const result = getters.groupLabel(state)('MyGroup');
1319
+
1320
+ expect(result).toStrictEqual({ label: 'My Group', labelKey: undefined });
1321
+ });
1322
+
1323
+ it('should return undefined when group label does not exist', () => {
1324
+ const state = { groupLabels: {} };
1325
+
1326
+ const result = getters.groupLabel(state)('nonexistent');
1327
+
1328
+ expect(result).toBeUndefined();
1329
+ });
1330
+
1331
+ it('should return undefined for empty, null, undefined, or non-existent group name', () => {
1332
+ const state = { groupLabels: { mygroup: { label: 'My Group', labelKey: undefined } } };
1333
+
1334
+ // Empty string fails the groupName check and returns undefined
1335
+ expect(getters.groupLabel(state)('')).toBeUndefined();
1336
+ // null returns undefined
1337
+ expect(getters.groupLabel(state)(null)).toBeUndefined();
1338
+ // undefined returns undefined
1339
+ expect(getters.groupLabel(state)(undefined)).toBeUndefined();
1340
+ // Non-existent group returns undefined
1341
+ expect(getters.groupLabel(state)('nonexistent')).toBeUndefined();
1342
+ });
1343
+ });
1284
1344
  });
package/store/auth.js CHANGED
@@ -3,7 +3,6 @@ import { MANAGEMENT, EXT } from '@shell/config/types';
3
3
  import { addObjects, findBy, joinStringList } from '@shell/utils/array';
4
4
  import { openAuthPopup, returnTo } from '@shell/utils/auth';
5
5
  import { base64Encode } from '@shell/utils/crypto';
6
- import { removeEmberPage } from '@shell/utils/ember-page';
7
6
  import { randomStr } from '@shell/utils/string';
8
7
  import { addParams, parse as parseUrl, removeParam } from '@shell/utils/url';
9
8
 
@@ -429,8 +428,6 @@ export const actions = {
429
428
  },
430
429
 
431
430
  uiLogout({ commit, dispatch }, options = {}) {
432
- removeEmberPage();
433
-
434
431
  commit('loggedOut');
435
432
  dispatch('onLogout', options, { root: true });
436
433
 
package/store/catalog.js CHANGED
@@ -325,8 +325,12 @@ export const actions = {
325
325
  * force: Always refresh catalog's helm repo by re-fetching index.yaml
326
326
  *
327
327
  * reset: clear existing charts and version cache
328
+ *
329
+ * repoKeys: Optional array of specific repo keys (IDs) to refresh. When provided, only these specific
330
+ * repos will be fetched, and only their existing charts will be cleared from the cache to avoid
331
+ * duplicate chart entries or wiping out unrelated chart data.
328
332
  */
329
- async load(ctx, { force, reset } = {}) {
333
+ async load(ctx, { force, reset, repoKeys = [] } = {}) {
330
334
  const {
331
335
  state, getters, rootGetters, commit, dispatch
332
336
  } = ctx;
@@ -360,14 +364,34 @@ export const actions = {
360
364
  promises = {};
361
365
 
362
366
  for ( const repo of repos ) {
363
- if ( (force === true || !getters.isLoaded(repo)) && repo.canLoad ) {
367
+ let shouldLoad = false;
368
+
369
+ if (repoKeys.length) {
370
+ // If repoKeys are explicitly provided (e.g. refreshing a single repo from the UI),
371
+ // we ONLY want to load the repos in that array. We intentionally ignore `!getters.isLoaded(repo)`
372
+ // here so we don't accidentally fetch other unrelated repos just because they haven't loaded yet.
373
+ shouldLoad = repoKeys.includes(repo._key);
374
+ } else {
375
+ // Default behavior: load if explicitly forced, OR if the repo hasn't been loaded into state yet.
376
+ shouldLoad = force === true || !getters.isLoaded(repo);
377
+ }
378
+
379
+ if ( shouldLoad && repo.canLoad ) {
364
380
  console.info('Loading index for repo', repo.name, `(${ repo._key })`); // eslint-disable-line no-console
365
381
  promises[repo._key] = repo.followLink('index');
366
382
  }
367
383
  }
368
384
 
369
385
  const res = await allHashSettled(promises);
370
- const charts = reset ? {} : state.charts;
386
+ const charts = reset ? {} : { ...state.charts };
387
+ let versionInfos = null;
388
+
389
+ if (reset) {
390
+ versionInfos = {};
391
+ } else if (repoKeys.length) {
392
+ versionInfos = { ...state.versionInfos };
393
+ }
394
+
371
395
  const errors = [];
372
396
 
373
397
  for ( const key of Object.keys(res) ) {
@@ -379,6 +403,28 @@ export const actions = {
379
403
  continue;
380
404
  }
381
405
 
406
+ // We are targeting specific repos. To prevent duplicate chart versions from appearing,
407
+ // we must remove the old charts for this specific repo before appending the newly fetched ones,
408
+ // but ONLY if the fetch was successful.
409
+ if (repoKeys.length && repoKeys.includes(key)) {
410
+ for (const chartKey in charts) {
411
+ if (charts[chartKey].repoKey === key) {
412
+ delete charts[chartKey];
413
+ }
414
+ }
415
+
416
+ // Also clear out cached version info for this repo so we don't display stale READMEs/values
417
+ const repoType = repo.type === CATALOG.CLUSTER_REPO ? 'cluster' : 'namespace';
418
+ const repoName = repo.metadata.name;
419
+ const versionPrefix = `${ repoType }/${ repoName }/`;
420
+
421
+ for (const versionKey in versionInfos) {
422
+ if (versionKey.startsWith(versionPrefix)) {
423
+ delete versionInfos[versionKey];
424
+ }
425
+ }
426
+ }
427
+
382
428
  for ( const k in obj.value.entries ) {
383
429
  for ( const entry of obj.value.entries[k] ) {
384
430
  addChart(ctx, charts, entry, repo);
@@ -394,13 +440,17 @@ export const actions = {
394
440
  loaded,
395
441
  });
396
442
 
397
- if (reset) {
398
- commit('setVersions', {});
443
+ if (versionInfos) {
444
+ commit('setVersions', versionInfos);
399
445
  }
400
446
  },
401
447
 
448
+ /**
449
+ * Globally refreshes all loaded repositories by triggering their refresh actions concurrently,
450
+ * bypassing individual catalog loads, and then performs a single, global catalog/load.
451
+ */
402
452
  async refresh({ getters, commit, dispatch }) {
403
- const promises = getters.repos.map((x) => x.refresh());
453
+ const promises = getters.repos.map((x) => x.refresh(false));
404
454
 
405
455
  // @TODO wait for repo state to indicate they're done once the API has that
406
456
 
@@ -488,7 +538,9 @@ function addChart(ctx, map, chart, repo) {
488
538
  certified = CATALOG_ANNOTATIONS._OTHER;
489
539
  }
490
540
 
491
- if ( chart.deprecated ) {
541
+ const isDeprecated = !!chart.deprecated || chart.annotations?.[CATALOG_ANNOTATIONS.DEPRECATED] === 'true';
542
+
543
+ if ( isDeprecated ) {
492
544
  sideLabel = DEPRECATED;
493
545
  } else if ( chart.annotations?.[CATALOG_ANNOTATIONS.EXPERIMENTAL] ) {
494
546
  sideLabel = EXPERIMENTAL;
@@ -546,7 +598,7 @@ function addChart(ctx, map, chart, repo) {
546
598
  versions: [],
547
599
  keywords: chart.keywords || [],
548
600
  categories: filterCategories(chart.keywords),
549
- deprecated: !!chart.deprecated,
601
+ deprecated: isDeprecated,
550
602
  primeOnly,
551
603
  experimental,
552
604
  hidden: !!chart.annotations?.[CATALOG_ANNOTATIONS.HIDDEN],
package/store/type-map.js CHANGED
@@ -317,6 +317,12 @@ export function DSL(store, product, module = 'type-map') {
317
317
  }
318
318
  },
319
319
 
320
+ labelGroup(group, label, labelKey) {
321
+ store.commit(`${ module }/labelGroup`, {
322
+ group, label, labelKey
323
+ });
324
+ },
325
+
320
326
  setGroupDefaultType(input, defaultType) {
321
327
  if ( isArray(input) ) {
322
328
  store.commit(`${ module }/setGroupDefaultType`, { groups: input, defaultType });
@@ -397,6 +403,7 @@ export const state = function() {
397
403
  groupIgnore: [],
398
404
  groupWeights: {},
399
405
  groupDefaultTypes: {},
406
+ groupLabels: {},
400
407
  basicGroupWeights: { [ROOT]: 1000 },
401
408
  groupMappings: [],
402
409
  typeIgnore: [],
@@ -499,6 +506,23 @@ export const getters = {
499
506
  };
500
507
  },
501
508
 
509
+ groupLabel(state) {
510
+ return (group) => {
511
+ // Handle null/undefined
512
+ if (!group) {
513
+ return;
514
+ }
515
+
516
+ // commit is done with lowercase group names, so lowercase here to match
517
+ const groupName = group.toLowerCase();
518
+
519
+ // If this has been explicitly set, use that
520
+ if (groupName) {
521
+ return state.groupLabels[groupName];
522
+ }
523
+ };
524
+ },
525
+
502
526
  groupForBasicType(state) {
503
527
  return (product, schemaId) => {
504
528
  return state.basicTypes?.[product]?.[schemaId];
@@ -740,10 +764,21 @@ export const getters = {
740
764
 
741
765
  // Translate if an entry exists
742
766
  let label = name;
743
- // i18n-uses nav.group.*
744
- const key = `nav.group."${ name }"`;
767
+ let key;
768
+
769
+ // See if we have a configured label for this group
770
+ const groupLabel = getters['groupLabel'](name);
771
+
772
+ if (groupLabel?.label) {
773
+ label = groupLabel.label;
774
+ } else if (groupLabel?.labelKey) {
775
+ key = groupLabel.labelKey;
776
+ } else {
777
+ // i18n-uses nav.group.*
778
+ key = `nav.group."${ name }"`;
779
+ }
745
780
 
746
- if ( rootGetters['i18n/exists'](key) ) {
781
+ if (key && rootGetters['i18n/exists'](key) ) {
747
782
  label = rootGetters['i18n/t'](key);
748
783
  }
749
784
 
@@ -1680,6 +1715,10 @@ export const mutations = {
1680
1715
  }
1681
1716
  },
1682
1717
 
1718
+ labelGroup(state, { group, label, labelKey }) {
1719
+ state.groupLabels[group.toLowerCase()] = { label, labelKey };
1720
+ },
1721
+
1683
1722
  // setGroupDefaultType({group: 'core', defaultType: 'name'});
1684
1723
  // By default when a group is clicked, the first item is selected - this allows
1685
1724
  // this behaviour to be changed and a named child type can be chosen
@@ -14,6 +14,7 @@
14
14
  "../shell/pkg/*"
15
15
  ],
16
16
  "@components/*": [
17
+ "./rancher-components/*",
17
18
  "../pkg/rancher-components/src/components/*"
18
19
  ]
19
20
  },