@rancher/shell 3.0.12-rc.2 → 3.0.12-rc.3

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 (272) hide show
  1. package/apis/impl/apis.ts +6 -0
  2. package/apis/index.ts +26 -0
  3. package/apis/intf/resources-api/cluster-api.ts +18 -0
  4. package/apis/intf/resources-api/mgmt-api.ts +15 -0
  5. package/apis/intf/resources-api/resource-base.ts +107 -0
  6. package/apis/intf/resources-api/resource-constants.ts +147 -0
  7. package/apis/intf/resources-api/resources-api.ts +143 -0
  8. package/apis/intf/resources.ts +49 -0
  9. package/apis/intf/{modal.ts → shell-api/modal.ts} +21 -26
  10. package/apis/intf/shell-api/proxy.ts +216 -0
  11. package/apis/intf/{slide-in.ts → shell-api/slide-in.ts} +4 -3
  12. package/apis/intf/{system.ts → shell-api/system.ts} +4 -1
  13. package/apis/intf/shell.ts +12 -6
  14. package/apis/resources/__tests__/resources-api-class.test.ts +550 -0
  15. package/apis/resources/index.ts +22 -0
  16. package/apis/resources/resources-api-class.ts +187 -0
  17. package/apis/shell/__tests__/proxy.test.ts +369 -0
  18. package/apis/shell/index.ts +8 -1
  19. package/apis/shell/modal.ts +4 -1
  20. package/apis/shell/notifications.ts +9 -6
  21. package/apis/shell/proxy.ts +256 -0
  22. package/apis/shell/slide-in.ts +4 -1
  23. package/apis/vue-shim.d.ts +2 -1
  24. package/assets/data/aws-regions.json +4 -0
  25. package/assets/fonts/lato/LatoLatin-Black.woff +0 -0
  26. package/assets/fonts/lato/LatoLatin-Black.woff2 +0 -0
  27. package/assets/fonts/lato/LatoLatin-BlackItalic.woff +0 -0
  28. package/assets/fonts/lato/LatoLatin-BlackItalic.woff2 +0 -0
  29. package/assets/fonts/lato/LatoLatin-Bold.woff +0 -0
  30. package/assets/fonts/lato/LatoLatin-Bold.woff2 +0 -0
  31. package/assets/fonts/lato/LatoLatin-BoldItalic.woff +0 -0
  32. package/assets/fonts/lato/LatoLatin-BoldItalic.woff2 +0 -0
  33. package/assets/fonts/lato/LatoLatin-Heavy.woff +0 -0
  34. package/assets/fonts/lato/LatoLatin-Heavy.woff2 +0 -0
  35. package/assets/fonts/lato/LatoLatin-HeavyItalic.woff +0 -0
  36. package/assets/fonts/lato/LatoLatin-HeavyItalic.woff2 +0 -0
  37. package/assets/fonts/lato/LatoLatin-Italic.woff +0 -0
  38. package/assets/fonts/lato/LatoLatin-Italic.woff2 +0 -0
  39. package/assets/fonts/lato/LatoLatin-Light.woff +0 -0
  40. package/assets/fonts/lato/LatoLatin-Light.woff2 +0 -0
  41. package/assets/fonts/lato/LatoLatin-LightItalic.woff +0 -0
  42. package/assets/fonts/lato/LatoLatin-LightItalic.woff2 +0 -0
  43. package/assets/fonts/lato/LatoLatin-Medium.woff +0 -0
  44. package/assets/fonts/lato/LatoLatin-Medium.woff2 +0 -0
  45. package/assets/fonts/lato/LatoLatin-MediumItalic.woff +0 -0
  46. package/assets/fonts/lato/LatoLatin-MediumItalic.woff2 +0 -0
  47. package/assets/fonts/lato/LatoLatin-Regular.woff +0 -0
  48. package/assets/fonts/lato/LatoLatin-Regular.woff2 +0 -0
  49. package/assets/fonts/lato/LatoLatin-Semibold.woff +0 -0
  50. package/assets/fonts/lato/LatoLatin-Semibold.woff2 +0 -0
  51. package/assets/fonts/lato/LatoLatin-SemiboldItalic.woff +0 -0
  52. package/assets/fonts/lato/LatoLatin-SemiboldItalic.woff2 +0 -0
  53. package/assets/styles/base/_variables.scss +2 -0
  54. package/assets/styles/fonts/_fontstack.scss +132 -8
  55. package/assets/translations/en-us.yaml +22 -5
  56. package/chart/monitoring/index.vue +10 -1
  57. package/components/ActionDropdownShell.vue +2 -1
  58. package/components/CruResourceFooter.vue +9 -5
  59. package/components/ExplorerProjectsNamespaces.vue +1 -1
  60. package/components/InstallHelmCharts.vue +2 -2
  61. package/components/LandingPagePreference.vue +14 -5
  62. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +15 -1
  63. package/components/Resource/Detail/Metadata/index.vue +6 -0
  64. package/components/Resource/Detail/ResourcePopover/index.vue +12 -1
  65. package/components/Resource/Detail/SpacedRow.vue +3 -1
  66. package/components/Resource/Detail/TitleBar/index.vue +10 -11
  67. package/components/ResourceList/Masthead.vue +12 -8
  68. package/components/SelectIconGrid.vue +0 -10
  69. package/components/SingleClusterInfo.vue +1 -0
  70. package/components/SortableTable/__tests__/sorting.test.ts +126 -0
  71. package/components/SortableTable/index.vue +6 -9
  72. package/components/SortableTable/selection.js +23 -5
  73. package/components/SortableTable/sorting.js +6 -3
  74. package/components/Wizard.vue +14 -13
  75. package/components/fleet/FleetBundles.vue +100 -12
  76. package/components/fleet/FleetClusterTargets/index.vue +37 -15
  77. package/components/fleet/__tests__/FleetClusterTargets.test.ts +149 -115
  78. package/components/fleet/__tests__/FleetClusters.test.ts +12 -12
  79. package/components/form/LabeledSelect.vue +20 -3
  80. package/components/form/NameNsDescription.vue +11 -0
  81. package/components/form/Security.vue +6 -2
  82. package/components/form/WorkloadPorts.vue +2 -7
  83. package/components/form/__tests__/Security.test.ts +76 -0
  84. package/components/formatter/Autoscaler.vue +4 -4
  85. package/components/formatter/ClusterKubeVersion.vue +27 -0
  86. package/components/formatter/ClusterLink.vue +1 -7
  87. package/components/formatter/ClusterProvider.vue +6 -10
  88. package/components/formatter/FleetSummaryGraph.vue +0 -3
  89. package/components/formatter/MachineSummaryGraph.vue +1 -1
  90. package/components/formatter/PodsUsage.vue +2 -2
  91. package/components/formatter/__tests__/Autoscaler.test.ts +19 -22
  92. package/components/formatter/__tests__/FleetSummaryGraph.test.ts +216 -0
  93. package/components/formatter/__tests__/PodsUsage.test.ts +6 -10
  94. package/components/nav/NamespaceFilter.vue +2 -2
  95. package/components/nav/TopLevelMenu.helper.ts +15 -3
  96. package/components/nav/TopLevelMenu.vue +16 -5
  97. package/components/nav/__tests__/TopLevelMenu.test.ts +145 -21
  98. package/components/templates/home.vue +18 -0
  99. package/components/templates/plain.vue +18 -0
  100. package/components/templates/standalone.vue +17 -0
  101. package/composables/useFormValidation.ts +93 -0
  102. package/composables/useVeeValidateField.test.ts +159 -0
  103. package/composables/useVeeValidateField.ts +67 -0
  104. package/config/pagination-table-headers.js +18 -1
  105. package/config/product/manager.js +82 -21
  106. package/config/router/routes.js +6 -0
  107. package/config/table-headers.js +20 -1
  108. package/config/types.js +2 -1
  109. package/core/__tests__/plugin-products.test.ts +904 -20
  110. package/core/plugin-products-base.ts +107 -7
  111. package/core/plugin-products.ts +4 -0
  112. package/core/plugin-types.ts +111 -1
  113. package/core/plugin.ts +15 -7
  114. package/core/productDebugger.js +9 -4
  115. package/core/types-provisioning.ts +43 -30
  116. package/core/types.ts +57 -20
  117. package/detail/__tests__/pod.test.ts +41 -0
  118. package/detail/harvesterhci.io.management.cluster.vue +6 -2
  119. package/detail/pod.vue +1 -1
  120. package/detail/provisioning.cattle.io.cluster.vue +4 -10
  121. package/edit/auth/__tests__/azuread.test.ts +217 -34
  122. package/edit/auth/azuread.vue +122 -14
  123. package/edit/auth/oidc.vue +2 -2
  124. package/edit/networking.k8s.io.ingress/DefaultBackend.vue +13 -4
  125. package/edit/networking.k8s.io.ingress/RulePath.vue +8 -4
  126. package/edit/networking.k8s.io.ingress/index.vue +75 -20
  127. package/edit/provisioning.cattle.io.cluster/__tests__/MachinePool.test.ts +104 -0
  128. package/edit/provisioning.cattle.io.cluster/index.vue +11 -7
  129. package/edit/provisioning.cattle.io.cluster/rke2.vue +8 -4
  130. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
  131. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +37 -4
  132. package/edit/provisioning.cattle.io.cluster/tabs/registries/__tests__/RegistryConfigs.test.ts +132 -7
  133. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +2 -1
  134. package/edit/secret/__tests__/ssh.test.ts +5 -6
  135. package/edit/secret/basic.vue +31 -0
  136. package/edit/secret/index.vue +68 -17
  137. package/edit/secret/registry.vue +38 -0
  138. package/edit/secret/ssh.vue +29 -0
  139. package/edit/secret/tls.vue +30 -0
  140. package/edit/service.vue +4 -4
  141. package/edit/workload/Upgrading.vue +3 -3
  142. package/edit/workload/__tests__/Upgrading.test.ts +6 -9
  143. package/edit/workload/mixins/workload.js +2 -1
  144. package/list/fleet.cattle.io.bundle.vue +7 -104
  145. package/list/fleet.cattle.io.clusterregistrationtoken.vue +20 -0
  146. package/list/provisioning.cattle.io.cluster.vue +262 -180
  147. package/list/utils/management.cattle.io.cluster.utils.ts +128 -0
  148. package/mixins/__tests__/chart.test.ts +112 -0
  149. package/mixins/brand.js +2 -1
  150. package/mixins/chart.js +12 -8
  151. package/mixins/resource-fetch-api-pagination.js +41 -5
  152. package/models/__tests__/ext.cattle.io.kubeconfig.test.ts +67 -67
  153. package/models/__tests__/management.cattle.io.cluster.test.ts +1 -1
  154. package/models/__tests__/management.cattle.io.node.ts +6 -5
  155. package/models/__tests__/management.cattle.io.nodepool.ts +5 -4
  156. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +32 -11
  157. package/models/base-cluster.x-k8s.io.js +26 -0
  158. package/models/cluster.js +1 -1
  159. package/models/cluster.x-k8s.io.machine.js +4 -22
  160. package/models/cluster.x-k8s.io.machinedeployment.js +2 -20
  161. package/models/cluster.x-k8s.io.machineset.js +2 -20
  162. package/models/compliance.cattle.io.clusterscan.js +130 -2
  163. package/models/ext.cattle.io.kubeconfig.ts +4 -7
  164. package/models/fleet-application.js +3 -1
  165. package/models/management.cattle.io.cluster.js +417 -40
  166. package/models/management.cattle.io.node.js +6 -4
  167. package/models/management.cattle.io.nodepool.js +1 -1
  168. package/models/networking.k8s.io.ingress.js +12 -4
  169. package/models/provisioning.cattle.io.cluster.js +47 -330
  170. package/models/rke.cattle.io.etcdsnapshot.js +1 -2
  171. package/package.json +11 -29
  172. package/pages/__tests__/readme.test.ts +49 -0
  173. package/pages/auth/setup.vue +2 -3
  174. package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +76 -0
  175. package/pages/c/_cluster/apps/charts/chart.vue +60 -8
  176. package/pages/c/_cluster/apps/charts/install.vue +10 -7
  177. package/pages/c/_cluster/explorer/__tests__/index.test.ts +23 -25
  178. package/pages/c/_cluster/explorer/index.vue +5 -49
  179. package/pages/c/_cluster/istio/__tests__/istio.index.test.ts +194 -0
  180. package/pages/c/_cluster/istio/index.vue +21 -6
  181. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -0
  182. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +719 -2
  183. package/pages/c/_cluster/uiplugins/index.vue +203 -197
  184. package/pages/diagnostic.vue +13 -17
  185. package/pages/fail-whale.vue +18 -0
  186. package/pages/home.vue +77 -260
  187. package/pages/readme.vue +88 -0
  188. package/plugins/dashboard-store/__tests__/resource-class.test.ts +88 -0
  189. package/plugins/dashboard-store/actions.js +40 -18
  190. package/plugins/dashboard-store/resource-class.js +5 -2
  191. package/plugins/steve/__tests__/subscribe.spec.ts +6 -3
  192. package/plugins/steve/steve-pagination-utils.ts +11 -3
  193. package/plugins/steve/subscribe.js +35 -5
  194. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +10 -4
  195. package/rancher-components/Form/LabeledInput/LabeledInput.vue +7 -52
  196. package/rancher-components/RcButton/RcButton.test.ts +37 -1
  197. package/rancher-components/RcButton/RcButton.vue +38 -8
  198. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +10 -8
  199. package/store/__tests__/catalog.test.ts +115 -1
  200. package/store/__tests__/type-map.test.ts +556 -1
  201. package/store/action-menu.js +8 -3
  202. package/store/auth.js +1 -1
  203. package/store/aws.js +27 -16
  204. package/store/catalog.js +27 -3
  205. package/store/digitalocean.js +20 -38
  206. package/store/index.js +2 -0
  207. package/store/linode.js +25 -40
  208. package/store/pnap.js +1 -0
  209. package/store/type-map.js +111 -29
  210. package/tsconfig.paths.json +8 -8
  211. package/types/kube/kube-api.ts +14 -1
  212. package/types/rancher/steve.api.ts +12 -12
  213. package/types/resources/settings.d.ts +2 -1
  214. package/types/shell/index.d.ts +102 -2
  215. package/types/store/dashboard-store.types.ts +108 -11
  216. package/types/store/pagination.types.ts +6 -3
  217. package/utils/__tests__/alertmanagerconfig.test.ts +117 -0
  218. package/utils/__tests__/async.test.ts +87 -0
  219. package/utils/__tests__/aws.test.ts +140 -0
  220. package/utils/__tests__/banners.test.ts +176 -0
  221. package/utils/__tests__/chart.test.ts +64 -1
  222. package/utils/__tests__/color.test.ts +226 -0
  223. package/utils/__tests__/duration.test.ts +140 -0
  224. package/utils/__tests__/fleet.test.ts +340 -0
  225. package/utils/__tests__/ingress.test.ts +553 -0
  226. package/utils/__tests__/kube.test.ts +68 -0
  227. package/utils/__tests__/namespace-filter.test.ts +109 -0
  228. package/utils/__tests__/pagination-utils.test.ts +361 -0
  229. package/utils/__tests__/parse-externalid.test.ts +137 -0
  230. package/utils/__tests__/perf-setting.utils.test.ts +98 -0
  231. package/utils/__tests__/poller-sequential.test.ts +177 -0
  232. package/utils/__tests__/poller.test.ts +170 -0
  233. package/utils/__tests__/promise.test.ts +346 -0
  234. package/utils/__tests__/settings.test.ts +140 -0
  235. package/utils/__tests__/sort-utils.test.ts +301 -0
  236. package/utils/__tests__/string-utils.test.ts +798 -0
  237. package/utils/__tests__/string.test.ts +23 -1
  238. package/utils/__tests__/style.test.ts +154 -0
  239. package/utils/__tests__/svg-filter.test.ts +184 -0
  240. package/utils/__tests__/units.test.ts +417 -0
  241. package/utils/__tests__/versions.test.ts +128 -0
  242. package/utils/__tests__/xccdf.test.ts +391 -0
  243. package/utils/chart.js +36 -0
  244. package/utils/fleet.ts +13 -3
  245. package/utils/gatekeeper/__tests__/util.test.ts +174 -0
  246. package/utils/gc/__tests__/gc-interval.test.ts +119 -0
  247. package/utils/gc/__tests__/gc-root-store.test.ts +225 -0
  248. package/utils/gc/__tests__/gc-route-changed.test.ts +96 -0
  249. package/utils/gc/__tests__/gc.test.ts +487 -0
  250. package/utils/ingress.ts +9 -1
  251. package/utils/pagination-utils.ts +2 -1
  252. package/utils/string.js +25 -2
  253. package/utils/uiplugins.ts +5 -5
  254. package/utils/validators/__tests__/cluster-name.test.ts +110 -0
  255. package/utils/validators/__tests__/cron-schedule.test.ts +79 -0
  256. package/utils/validators/__tests__/index.test.ts +481 -0
  257. package/utils/validators/__tests__/kubernetes-name.test.ts +163 -0
  258. package/utils/validators/__tests__/misc-validators.test.ts +246 -0
  259. package/utils/validators/__tests__/pod-affinity.test.ts +382 -0
  260. package/utils/validators/__tests__/prometheusrule.test.ts +211 -0
  261. package/utils/validators/__tests__/role-template.test.ts +149 -0
  262. package/utils/validators/__tests__/service.test.ts +283 -0
  263. package/utils/validators/__tests__/setting.test.js +32 -0
  264. package/utils/validators/formRules/__tests__/index.test.ts +50 -0
  265. package/utils/validators/formRules/index.ts +5 -5
  266. package/utils/validators/machine-pool.ts +1 -1
  267. package/utils/validators/setting.js +18 -3
  268. package/utils/xccdf.ts +418 -0
  269. package/assets/fonts/lato/lato-v17-latin-700.woff +0 -0
  270. package/assets/fonts/lato/lato-v17-latin-700.woff2 +0 -0
  271. package/assets/fonts/lato/lato-v17-latin-regular.woff +0 -0
  272. package/assets/fonts/lato/lato-v17-latin-regular.woff2 +0 -0
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable jest/max-nested-describe */
2
2
 
3
- import { TYPE_MODES, getters } from '../type-map';
3
+ import { TYPE_MODES, getters, mutations, DSL } from '../type-map';
4
4
  import { NAME as EXPLORER } from '@shell/config/product/explorer';
5
5
  import {
6
6
  COUNT,
@@ -1341,4 +1341,559 @@ describe('type-map', () => {
1341
1341
  expect(getters.groupLabel(state)('nonexistent')).toBeUndefined();
1342
1342
  });
1343
1343
  });
1344
+
1345
+ describe('configureType - product scoping', () => {
1346
+ describe('mutations.configureType', () => {
1347
+ it('should scope typeOptions by product', () => {
1348
+ const state = { typeOptions: {} } as any;
1349
+
1350
+ mutations.configureType(state, {
1351
+ product: 'product-1',
1352
+ match: 'pod',
1353
+ customRoute: { name: 'product-1-route' }
1354
+ });
1355
+
1356
+ mutations.configureType(state, {
1357
+ product: 'explorer',
1358
+ match: 'pod',
1359
+ customRoute: { name: 'explorer-route' }
1360
+ });
1361
+
1362
+ expect(state.typeOptions['product-1']).toHaveLength(1);
1363
+ expect(state.typeOptions['product-1'][0].customRoute.name).toBe('product-1-route');
1364
+ expect(state.typeOptions['explorer']).toHaveLength(1);
1365
+ expect(state.typeOptions['explorer'][0].customRoute.name).toBe('explorer-route');
1366
+ });
1367
+
1368
+ it('should merge multiple configures for same type in same product', () => {
1369
+ const state = { typeOptions: {} } as any;
1370
+
1371
+ mutations.configureType(state, {
1372
+ product: 'product-1',
1373
+ match: 'pod',
1374
+ isCreatable: false
1375
+ });
1376
+
1377
+ mutations.configureType(state, {
1378
+ product: 'product-1',
1379
+ match: 'pod',
1380
+ customRoute: { name: 'custom' }
1381
+ });
1382
+
1383
+ expect(state.typeOptions['product-1']).toHaveLength(1);
1384
+ expect(state.typeOptions['product-1'][0].isCreatable).toBe(false);
1385
+ expect(state.typeOptions['product-1'][0].customRoute.name).toBe('custom');
1386
+ });
1387
+
1388
+ it('should merge custom data objects when configuring same type multiple times', () => {
1389
+ const state = { typeOptions: {} } as any;
1390
+
1391
+ mutations.configureType(state, {
1392
+ product: 'product-1',
1393
+ match: 'pod',
1394
+ custom: { setting1: 'value1' }
1395
+ });
1396
+
1397
+ mutations.configureType(state, {
1398
+ product: 'product-1',
1399
+ match: 'pod',
1400
+ custom: { setting2: 'value2' }
1401
+ });
1402
+
1403
+ expect(state.typeOptions['product-1'][0].custom).toStrictEqual({
1404
+ setting1: 'value1',
1405
+ setting2: 'value2'
1406
+ });
1407
+ });
1408
+
1409
+ it('should throw error when product parameter is missing in new format (2.15+)', () => {
1410
+ const state = { typeOptions: {} } as any;
1411
+
1412
+ expect(() => {
1413
+ mutations.configureType(state, {
1414
+ match: 'pod',
1415
+ customRoute: { name: 'route' }
1416
+ });
1417
+ }).toThrow(/product parameter is required/);
1418
+ });
1419
+
1420
+ it('should throw error with type info when product parameter is missing', () => {
1421
+ const state = { typeOptions: {} } as any;
1422
+
1423
+ expect(() => {
1424
+ mutations.configureType(state, {
1425
+ match: 'pod',
1426
+ customRoute: { name: 'route' }
1427
+ });
1428
+ }).toThrow(/for type/);
1429
+ });
1430
+
1431
+ it('should allow different types in same product', () => {
1432
+ const state = { typeOptions: {} } as any;
1433
+
1434
+ mutations.configureType(state, {
1435
+ product: 'product-1',
1436
+ match: 'pod',
1437
+ customRoute: { name: 'pod-route' }
1438
+ });
1439
+
1440
+ mutations.configureType(state, {
1441
+ product: 'product-1',
1442
+ match: 'deployment',
1443
+ customRoute: { name: 'deployment-route' }
1444
+ });
1445
+
1446
+ expect(state.typeOptions['product-1']).toHaveLength(2);
1447
+ expect(state.typeOptions['product-1'][0].customRoute.name).toBe('pod-route');
1448
+ expect(state.typeOptions['product-1'][1].customRoute.name).toBe('deployment-route');
1449
+ });
1450
+ });
1451
+
1452
+ describe('getters.optionsFor - product filtering', () => {
1453
+ it('should return product-scoped options', () => {
1454
+ const state = {
1455
+ typeOptions: {
1456
+ 'product-1': [{ match: 'pod', customRoute: { name: 'product-1-route' } }],
1457
+ explorer: [{ match: 'pod', customRoute: { name: 'explorer-route' } }]
1458
+ }
1459
+ } as any;
1460
+
1461
+ const rootGetters = { productId: 'product-1' };
1462
+
1463
+ const optionsFn = getters.optionsFor(state, {}, {}, rootGetters);
1464
+ const opts = optionsFn('pod', false);
1465
+
1466
+ expect(opts.customRoute.name).toBe('product-1-route');
1467
+ });
1468
+
1469
+ it('should return defaults when type not configured in current product', () => {
1470
+ const state = { typeOptions: { explorer: [{ match: 'pod', customRoute: { name: 'explorer-route' } }] } } as any;
1471
+
1472
+ const rootGetters = { productId: 'product-1' };
1473
+
1474
+ const optionsFn = getters.optionsFor(state, {}, {}, rootGetters);
1475
+ const opts = optionsFn('pod', false);
1476
+
1477
+ expect(opts.customRoute).toBeUndefined();
1478
+ expect(opts.isCreatable).toBe(true); // default value
1479
+ });
1480
+
1481
+ it('should handle missing product gracefully', () => {
1482
+ const state = { typeOptions: {} } as any;
1483
+ const rootGetters = { productId: 'nonexistent' };
1484
+
1485
+ const optionsFn = getters.optionsFor(state, {}, {}, rootGetters);
1486
+ const opts = optionsFn('pod', false);
1487
+
1488
+ expect(opts.isCreatable).toBe(true); // defaults
1489
+ expect(opts.customRoute).toBeUndefined();
1490
+ });
1491
+
1492
+ it('should not apply explorer product config to other products', () => {
1493
+ const state = {
1494
+ typeOptions: {
1495
+ explorer: [{ match: 'pod', customRoute: { name: 'explorer-pod-route' } }],
1496
+ 'product-1': [{ match: 'pod', customRoute: { name: 'product-1-pod-route' } }]
1497
+ }
1498
+ } as any;
1499
+
1500
+ // Check explorer product
1501
+ const explorerOptions = getters.optionsFor(state, {}, {}, { productId: 'explorer' });
1502
+
1503
+ expect(explorerOptions('pod', false).customRoute.name).toBe('explorer-pod-route');
1504
+
1505
+ // Check other product
1506
+ const product1Options = getters.optionsFor(state, {}, {}, { productId: 'product-1' });
1507
+
1508
+ expect(product1Options('pod', false).customRoute.name).toBe('product-1-pod-route');
1509
+
1510
+ // Check unconfigured product
1511
+ const product2Options = getters.optionsFor(state, {}, {}, { productId: 'product-2' });
1512
+
1513
+ expect(product2Options('pod', false).customRoute).toBeUndefined();
1514
+ });
1515
+
1516
+ it('should use explicit product parameter when provided', () => {
1517
+ const state = {
1518
+ typeOptions: {
1519
+ explorer: [
1520
+ {
1521
+ match: 'pod',
1522
+ customRoute: { name: 'explorer-route' },
1523
+ localOnly: true
1524
+ }
1525
+ ],
1526
+ 'product-1': [
1527
+ {
1528
+ match: 'pod',
1529
+ customRoute: { name: 'product-1-route' },
1530
+ localOnly: false
1531
+ }
1532
+ ]
1533
+ }
1534
+ } as any;
1535
+
1536
+ const optionsFn = getters.optionsFor(state, {}, {}, { productId: 'product-1' });
1537
+
1538
+ // Without product override, should use current product (product-1)
1539
+ const defaultOpts = optionsFn('pod', false);
1540
+
1541
+ expect(defaultOpts.customRoute.name).toBe('product-1-route');
1542
+ expect(defaultOpts.localOnly).toBe(false);
1543
+
1544
+ // With product override to explorer, should use explorer's config
1545
+ const explorerOpts = optionsFn('pod', false, 'explorer');
1546
+
1547
+ expect(explorerOpts.customRoute.name).toBe('explorer-route');
1548
+ expect(explorerOpts.localOnly).toBe(true);
1549
+ });
1550
+
1551
+ it('should handle product override for unconfigured types', () => {
1552
+ const state = {
1553
+ typeOptions: {
1554
+ explorer: [
1555
+ {
1556
+ match: 'pod',
1557
+ customRoute: { name: 'explorer-route' }
1558
+ }
1559
+ ]
1560
+ }
1561
+ } as any;
1562
+
1563
+ const optionsFn = getters.optionsFor(state, {}, {}, { productId: 'product-1' });
1564
+
1565
+ // Override to explorer which has config
1566
+ const explorerOpts = optionsFn('secret', false, 'explorer');
1567
+
1568
+ expect(explorerOpts.customRoute).toBeUndefined(); // secret not configured in explorer
1569
+ expect(explorerOpts.isCreatable).toBe(true); // uses default
1570
+
1571
+ // Override to product-2 which has no config
1572
+ const product2Opts = optionsFn('pod', false, 'product-2');
1573
+
1574
+ expect(product2Opts.customRoute).toBeUndefined();
1575
+ expect(product2Opts.isCreatable).toBe(true);
1576
+ });
1577
+
1578
+ describe('backward compatibility - legacy array format (Rancher 2.14)', () => {
1579
+ it('should handle legacy array format with product field', () => {
1580
+ const state = {
1581
+ typeOptions: [
1582
+ {
1583
+ match: 'pod', product: 'explorer', customRoute: { name: 'explorer-route' }
1584
+ },
1585
+ {
1586
+ match: 'pod', product: 'product-1', customRoute: { name: 'product-1-route' }
1587
+ }
1588
+ ]
1589
+ } as any;
1590
+
1591
+ const explorerOptions = getters.optionsFor(state, {}, {}, { productId: 'explorer' });
1592
+ const product1Options = getters.optionsFor(state, {}, {}, { productId: 'product-1' });
1593
+
1594
+ expect(explorerOptions('pod', false).customRoute.name).toBe('explorer-route');
1595
+ expect(product1Options('pod', false).customRoute.name).toBe('product-1-route');
1596
+ });
1597
+
1598
+ it('should handle legacy array format with entries without product field', () => {
1599
+ const state = {
1600
+ typeOptions: [
1601
+ {
1602
+ match: 'pod', product: 'explorer', customRoute: { name: 'explorer-route' }
1603
+ },
1604
+ { match: 'pod', customRoute: { name: 'shared-route' } }
1605
+ ]
1606
+ } as any;
1607
+
1608
+ const explorerOptions = getters.optionsFor(state, {}, {}, { productId: 'explorer' });
1609
+ const otherProductOptions = getters.optionsFor(state, {}, {}, { productId: 'other' });
1610
+
1611
+ // explorer gets its product-specific entry (matches first)
1612
+ expect(explorerOptions('pod', false).customRoute.name).toBe('explorer-route');
1613
+
1614
+ // other product gets only unscoped entries
1615
+ expect(otherProductOptions('pod', false).customRoute.name).toBe('shared-route');
1616
+ });
1617
+ });
1618
+
1619
+ describe('backward compatibility - missing product parameter (old extensions in 2.15)', () => {
1620
+ it('should reject unscoped config (requires product parameter in 2.15+)', () => {
1621
+ const state = { typeOptions: {} } as any;
1622
+
1623
+ expect(() => {
1624
+ mutations.configureType(state, {
1625
+ match: 'pod',
1626
+ customRoute: { name: 'legacy-route' }
1627
+ });
1628
+ }).toThrow(/product parameter is required/);
1629
+ });
1630
+
1631
+ it('should retrieve legacyCompatibilityProdRegistration entries for products without specific config', () => {
1632
+ const state = {
1633
+ typeOptions: {
1634
+ 'product-1': [{ match: 'pod', customRoute: { name: 'product-1-route' } }],
1635
+ legacyCompatibilityProdRegistration: [{ match: 'pod', customRoute: { name: 'legacy-route' } }]
1636
+ }
1637
+ } as any;
1638
+
1639
+ const product1Options = getters.optionsFor(state, {}, {}, { productId: 'product-1' });
1640
+ const product2Options = getters.optionsFor(state, {}, {}, { productId: 'product-2' });
1641
+
1642
+ // product-1 gets its own config (takes precedence)
1643
+ expect(product1Options('pod', false).customRoute.name).toBe('product-1-route');
1644
+
1645
+ // product-2 gets legacy entries
1646
+ expect(product2Options('pod', false).customRoute.name).toBe('legacy-route');
1647
+ });
1648
+
1649
+ it('should merge product-specific and legacyCompatibilityProdRegistration buckets in getter', () => {
1650
+ const state = {
1651
+ typeOptions: {
1652
+ 'product-1': [{ match: 'deployment', customRoute: { name: 'product-1-deployment-route' } }],
1653
+ legacyCompatibilityProdRegistration: [{ match: 'pod', customRoute: { name: 'legacy-pod-route' } }]
1654
+ }
1655
+ } as any;
1656
+
1657
+ const product1Options = getters.optionsFor(state, {}, {}, { productId: 'product-1' });
1658
+
1659
+ // pod from legacy bucket (product-1 doesn't have pod config)
1660
+ expect(product1Options('pod', false).customRoute.name).toBe('legacy-pod-route');
1661
+
1662
+ // deployment from product-specific bucket
1663
+ expect(product1Options('deployment', false).customRoute.name).toBe('product-1-deployment-route');
1664
+ });
1665
+ });
1666
+
1667
+ describe('mixed extensions scenario (2.15 with old + new extensions)', () => {
1668
+ it('should handle old extension (2.14 shell) + new extension (2.15 shell) in same product', () => {
1669
+ const state = {
1670
+ typeOptions: {
1671
+ explorer: [{ match: 'pod', customRoute: { name: 'explorer-pod-route' } }],
1672
+ legacyCompatibilityProdRegistration: [
1673
+ { match: 'secret', customRoute: { name: 'legacy-secret-route' } },
1674
+ { match: 'configmap', customRoute: { name: 'legacy-configmap-route' } }
1675
+ ]
1676
+ }
1677
+ } as any;
1678
+
1679
+ const explorerOptions = getters.optionsFor(state, {}, {}, { productId: 'explorer' });
1680
+
1681
+ // New extension config (product-specific)
1682
+ expect(explorerOptions('pod', false).customRoute.name).toBe('explorer-pod-route');
1683
+
1684
+ // Old extension configs (from legacy bucket)
1685
+ expect(explorerOptions('secret', false).customRoute.name).toBe('legacy-secret-route');
1686
+ expect(explorerOptions('configmap', false).customRoute.name).toBe('legacy-configmap-route');
1687
+ });
1688
+
1689
+ it('should prioritize product-specific config over legacy bucket when same type in both', () => {
1690
+ const state = {
1691
+ typeOptions: {
1692
+ 'product-1': [{ match: 'pod', customRoute: { name: 'product-1-route' } }],
1693
+ legacyCompatibilityProdRegistration: [{ match: 'pod', customRoute: { name: 'legacy-route' } }]
1694
+ }
1695
+ } as any;
1696
+
1697
+ const product1Options = getters.optionsFor(state, {}, {}, { productId: 'product-1' });
1698
+
1699
+ // product-specific should come first in the array and match first
1700
+ expect(product1Options('pod', false).customRoute.name).toBe('product-1-route');
1701
+ });
1702
+
1703
+ it('should work with multiple products having both specific and legacy configs', () => {
1704
+ const state = {
1705
+ typeOptions: {
1706
+ 'product-1': [{ match: 'pod', customRoute: { name: 'p1-pod' } }],
1707
+ 'product-2': [{ match: 'pod', customRoute: { name: 'p2-pod' } }],
1708
+ legacyCompatibilityProdRegistration: [
1709
+ { match: 'secret', customRoute: { name: 'legacy-secret' } },
1710
+ { match: 'pod', customRoute: { name: 'legacy-pod' } }
1711
+ ]
1712
+ }
1713
+ } as any;
1714
+
1715
+ const p1Options = getters.optionsFor(state, {}, {}, { productId: 'product-1' });
1716
+ const p2Options = getters.optionsFor(state, {}, {}, { productId: 'product-2' });
1717
+ const p3Options = getters.optionsFor(state, {}, {}, { productId: 'product-3' });
1718
+
1719
+ // product-1: gets its own pod config, ignores legacy pod, gets legacy secret
1720
+ expect(p1Options('pod', false).customRoute.name).toBe('p1-pod');
1721
+ expect(p1Options('secret', false).customRoute.name).toBe('legacy-secret');
1722
+
1723
+ // product-2: gets its own pod config, ignores legacy pod, gets legacy secret
1724
+ expect(p2Options('pod', false).customRoute.name).toBe('p2-pod');
1725
+ expect(p2Options('secret', false).customRoute.name).toBe('legacy-secret');
1726
+
1727
+ // product-3: gets only legacy configs
1728
+ expect(p3Options('pod', false).customRoute.name).toBe('legacy-pod');
1729
+ expect(p3Options('secret', false).customRoute.name).toBe('legacy-secret');
1730
+ });
1731
+ });
1732
+
1733
+ describe('product name validation', () => {
1734
+ it('should throw error when product name equals reserved legacyCompatibilityProdRegistration', () => {
1735
+ const state = { typeOptions: {} } as any;
1736
+
1737
+ expect(() => {
1738
+ mutations.configureType(state, {
1739
+ product: 'legacyCompatibilityProdRegistration',
1740
+ match: 'pod',
1741
+ customRoute: { name: 'route' }
1742
+ });
1743
+ }).toThrow(/cannot be "legacyCompatibilityProdRegistration"/);
1744
+ });
1745
+
1746
+ it('should allow empty string in getter (falsy, bypasses validation)', () => {
1747
+ const state = { typeOptions: { 'product-1': [{ match: 'pod', customRoute: { name: 'p1-route' } }] } } as any;
1748
+ const optionsFn = getters.optionsFor(state, {}, {}, { productId: 'product-1' });
1749
+
1750
+ // Empty string is falsy so it skips validation and uses current product context
1751
+ expect(() => {
1752
+ optionsFn('pod', false, '');
1753
+ }).not.toThrow();
1754
+ });
1755
+
1756
+ it('should throw error in getter when product override is the reserved name', () => {
1757
+ const state = { typeOptions: { 'product-1': [] } } as any;
1758
+ const optionsFn = getters.optionsFor(state, {}, {}, { productId: 'product-1' });
1759
+
1760
+ expect(() => {
1761
+ optionsFn('pod', false, 'legacyCompatibilityProdRegistration');
1762
+ }).toThrow(/cannot be "legacyCompatibilityProdRegistration"/);
1763
+ });
1764
+
1765
+ it('should allow undefined product (triggers default context in getter)', () => {
1766
+ const state = { typeOptions: { 'product-1': [{ match: 'pod', customRoute: { name: 'p1-route' } }] } } as any;
1767
+ const optionsFn = getters.optionsFor(state, {}, {}, { productId: 'product-1' });
1768
+
1769
+ // undefined product should use current product context
1770
+ expect(() => {
1771
+ optionsFn('pod', false, undefined);
1772
+ }).not.toThrow();
1773
+ });
1774
+
1775
+ it('should allow valid product names', () => {
1776
+ const state = { typeOptions: {} } as any;
1777
+
1778
+ expect(() => {
1779
+ mutations.configureType(state, {
1780
+ product: 'my-product',
1781
+ match: 'pod',
1782
+ customRoute: { name: 'route' }
1783
+ });
1784
+ }).not.toThrow();
1785
+
1786
+ expect(state.typeOptions['my-product']).toHaveLength(1);
1787
+ });
1788
+ });
1789
+
1790
+ describe('edge cases and potential issues', () => {
1791
+ it('should handle explicit product override with product parameter in getter', () => {
1792
+ const state = {
1793
+ typeOptions: {
1794
+ 'product-1': [{ match: 'pod', customRoute: { name: 'p1-route' } }],
1795
+ 'product-2': [{ match: 'pod', customRoute: { name: 'p2-route' } }]
1796
+ }
1797
+ } as any;
1798
+
1799
+ const product1Options = getters.optionsFor(state, {}, {}, { productId: 'product-1' });
1800
+
1801
+ // Without override, should use product-1
1802
+ expect(product1Options('pod', false).customRoute.name).toBe('p1-route');
1803
+
1804
+ // With explicit override to product-2, should use product-2
1805
+ expect(product1Options('pod', false, 'product-2').customRoute.name).toBe('p2-route');
1806
+ });
1807
+ });
1808
+
1809
+ describe('dsl headers', () => {
1810
+ function createMockStore() {
1811
+ const committed: { type: string; payload: any }[] = [];
1812
+
1813
+ return {
1814
+ committed,
1815
+ commit(mutation: string, payload: any) {
1816
+ committed.push({ type: mutation, payload });
1817
+ }
1818
+ };
1819
+ }
1820
+
1821
+ it('should commit only paginationHeaders when regular headers are not provided', () => {
1822
+ const mockStore = createMockStore();
1823
+ const { headers } = DSL(mockStore as any, 'test-product');
1824
+ const paginationHeaders = [
1825
+ {
1826
+ name: 'name', labelKey: 'tableHeaders.name', value: 'name', sort: ['name']
1827
+ }
1828
+ ];
1829
+
1830
+ headers('pod', undefined as any, paginationHeaders);
1831
+
1832
+ expect(mockStore.committed).toHaveLength(1);
1833
+ expect(mockStore.committed[0].type).toBe('type-map/paginationHeaders');
1834
+ expect(mockStore.committed[0].payload).toStrictEqual({ type: 'pod', paginationHeaders });
1835
+ });
1836
+
1837
+ it('should commit only paginationHeaders when regular headers are an empty array', () => {
1838
+ const mockStore = createMockStore();
1839
+ const { headers } = DSL(mockStore as any, 'test-product');
1840
+ const paginationHeaders = [
1841
+ {
1842
+ name: 'state', labelKey: 'tableHeaders.state', value: 'state', sort: ['state']
1843
+ }
1844
+ ];
1845
+
1846
+ headers('pod', [], paginationHeaders);
1847
+
1848
+ expect(mockStore.committed).toHaveLength(1);
1849
+ expect(mockStore.committed[0].type).toBe('type-map/paginationHeaders');
1850
+ });
1851
+
1852
+ it('should commit both headers and paginationHeaders when both are provided', () => {
1853
+ const mockStore = createMockStore();
1854
+ const { headers } = DSL(mockStore as any, 'test-product');
1855
+ const regularHeaders = [
1856
+ {
1857
+ name: 'name', labelKey: 'tableHeaders.name', value: 'nameDisplay', sort: ['nameSort']
1858
+ }
1859
+ ];
1860
+ const paginationHeaders = [
1861
+ {
1862
+ name: 'name', labelKey: 'tableHeaders.name', value: 'name', sort: ['name']
1863
+ }
1864
+ ];
1865
+
1866
+ headers('pod', regularHeaders, paginationHeaders);
1867
+
1868
+ expect(mockStore.committed).toHaveLength(2);
1869
+ expect(mockStore.committed[0].type).toBe('type-map/headers');
1870
+ expect(mockStore.committed[1].type).toBe('type-map/paginationHeaders');
1871
+ });
1872
+
1873
+ it('should commit only regular headers when paginationHeaders are not provided', () => {
1874
+ const mockStore = createMockStore();
1875
+ const { headers } = DSL(mockStore as any, 'test-product');
1876
+ const regularHeaders = [
1877
+ {
1878
+ name: 'name', labelKey: 'tableHeaders.name', value: 'nameDisplay', sort: ['nameSort']
1879
+ }
1880
+ ];
1881
+
1882
+ headers('pod', regularHeaders);
1883
+
1884
+ expect(mockStore.committed).toHaveLength(1);
1885
+ expect(mockStore.committed[0].type).toBe('type-map/headers');
1886
+ });
1887
+
1888
+ it('should not commit anything when both headers and paginationHeaders are empty', () => {
1889
+ const mockStore = createMockStore();
1890
+ const { headers } = DSL(mockStore as any, 'test-product');
1891
+
1892
+ headers('pod', [], []);
1893
+
1894
+ expect(mockStore.committed).toHaveLength(0);
1895
+ });
1896
+ });
1897
+ });
1898
+ });
1344
1899
  });
@@ -191,6 +191,9 @@ function _filter(map, disableAll = false) {
191
191
  function _execute(resources, action, args, opts = {}) {
192
192
  args = args || [];
193
193
  if ( resources.length > 1 && action.bulkAction && !opts.alt ) {
194
+ // I think this is dead code. This is only run from the action menu where there's only ever a single resource
195
+ // For bulk actions shell/components/SortableTable/selection.js _execute runs
196
+ // If we do ever need to run this it should use that version
194
197
  const fn = resources[0][action.bulkAction];
195
198
 
196
199
  if ( fn ) {
@@ -203,14 +206,16 @@ function _execute(resources, action, args, opts = {}) {
203
206
  for ( const resource of resources ) {
204
207
  let fn;
205
208
 
209
+ const applyResource = action.altResource || resource;
210
+
206
211
  if (opts.alt && action.altAction) {
207
- fn = resource[action.altAction];
212
+ fn = applyResource[action.altAction];
208
213
  } else {
209
- fn = resource[action.action];
214
+ fn = applyResource[action.action];
210
215
  }
211
216
 
212
217
  if ( fn ) {
213
- promises.push(fn.apply(resource, args));
218
+ promises.push(fn.apply(applyResource, args));
214
219
  }
215
220
  }
216
221
 
package/store/auth.js CHANGED
@@ -8,7 +8,7 @@ import { addParams, parse as parseUrl, removeParam } from '@shell/utils/url';
8
8
 
9
9
  // configuration for Single Logout/SLO
10
10
  // admissable auth providers compatible with SLO, based on shell/models/management.cattle.io.authconfig "configType"
11
- export const SLO_AUTH_PROVIDERS = ['oidc', 'saml'];
11
+ export const SLO_AUTH_PROVIDERS = ['oidc', 'saml', 'oauth'];
12
12
 
13
13
  // this is connected to the redirect url, for which the logic can be found in "shell/store/auth"
14
14
  const SLO_TOKENS_ENDPOINT_LOGOUT_RES_BASETYPE = ['authConfigLogoutOutput'];