@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,6 +4,7 @@ import HelmOp from '@shell/models/fleet.cattle.io.helmop';
4
4
  import HelmOpComponent from '@shell/edit/fleet.cattle.io.helmop.vue';
5
5
  import FleetSecretSelector from '@shell/components/fleet/FleetSecretSelector.vue';
6
6
  import FleetConfigMapSelector from '@shell/components/fleet/FleetConfigMapSelector.vue';
7
+ import { createStore } from 'vuex';
7
8
 
8
9
  const mockStore = {
9
10
  dispatch: jest.fn(),
@@ -85,6 +86,14 @@ const initHelmOp = (props: any, options = {}) => {
85
86
  value,
86
87
  ...props
87
88
  },
89
+ provide: {
90
+ store: createStore({
91
+ getters: {
92
+ currentStore: () => 'current_store',
93
+ 'management/paginationEnabled': () => () => false
94
+ }
95
+ })
96
+ },
88
97
  computed: mockComputed,
89
98
  global: { mocks },
90
99
  };
@@ -30,7 +30,6 @@ describe('view: kontainerdriver should', () => {
30
30
  active: true,
31
31
  checksum: '',
32
32
  url: '',
33
- uiUrl: '',
34
33
  whitelistDomains: []
35
34
  }
36
35
  },
@@ -65,32 +64,22 @@ describe('view: kontainerdriver should', () => {
65
64
 
66
65
  it('have "Create" button enabled and disabled depending on validation results', async() => {
67
66
  const urlField = wrapper.find('[data-testid="driver-create-url-field"]');
68
- const uiurlField = wrapper.find('[data-testid="driver-create-uiurl-field"]');
69
67
  const checksumField = wrapper.find('[data-testid="driver-create-checksum-field"]');
70
68
  const saveButton = wrapper.find('[data-testid="kontainer-driver-edit-save"]').element as HTMLInputElement;
71
69
 
72
70
  const testCases = [
73
71
  {
74
72
  url: '1111',
75
- uiurl: 'http://test.com',
76
73
  checksum: 'aaaaaBBBBdddd',
77
74
  result: true
78
75
  },
79
76
  {
80
77
  url: 'http://test.com',
81
- uiurl: '1111',
82
- checksum: 'aaaaaBBBBdddd',
83
- result: true
84
- },
85
- {
86
- url: 'http://test.com',
87
- uiurl: 'http://test.com',
88
78
  checksum: '!!!',
89
79
  result: true
90
80
  },
91
81
  {
92
82
  url: 'http://test.com',
93
- uiurl: 'http://test.com',
94
83
  checksum: 'aaaaaBBBBdddd',
95
84
  result: false
96
85
  }
@@ -99,8 +88,6 @@ describe('view: kontainerdriver should', () => {
99
88
  for (const testCase of testCases) {
100
89
  urlField.setValue(testCase.url);
101
90
  await nextTick();
102
- uiurlField.setValue(testCase.uiurl);
103
- await nextTick();
104
91
  checksumField.setValue(testCase.checksum);
105
92
  await nextTick();
106
93
 
@@ -31,7 +31,6 @@ describe('view: nodedriver should', () => {
31
31
  active: true,
32
32
  checksum: '',
33
33
  url: '',
34
- uiUrl: '',
35
34
  whitelistDomains: []
36
35
  }
37
36
  },
@@ -65,21 +64,16 @@ describe('view: nodedriver should', () => {
65
64
  });
66
65
 
67
66
  it.each`
68
- url | uiurl | checksum | expected
69
- ${ '1111' } | ${ 'http://test.com' } | ${ 'aaaaaBBBBdddd' } | ${ true }
70
- ${ 'http://test.com' } | ${ '1111' } | ${ 'aaaaaBBBBdddd' } | ${ true }
71
- ${ 'http://test.com' } | ${ 'http://test.com' } | ${ '!!!' } | ${ true }
72
- ${ 'http://test.com' } | ${ 'http://test.com' } | ${ 'aaaaaBBBBdddd' } | ${ false }
73
- `('have "Create" button enabled and disabled depending on validation results', async({
74
- url, uiurl, checksum, expected
75
- }) => {
67
+ url | checksum | expected
68
+ ${ '1111' } | ${ 'aaaaaBBBBdddd' } | ${ true }
69
+ ${ 'http://test.com' } | ${ '!!!' } | ${ true }
70
+ ${ 'http://test.com' } | ${ 'aaaaaBBBBdddd' } | ${ false }
71
+ `('have "Create" button enabled and disabled depending on validation results', async({ url, checksum, expected }) => {
76
72
  const urlField = wrapper.find('[data-testid="driver-create-url-field"]');
77
- const uiurlField = wrapper.find('[data-testid="driver-create-uiurl-field"]');
78
73
  const checksumField = wrapper.find('[data-testid="driver-create-checksum-field"]');
79
74
  const saveButton = wrapper.find('[data-testid="node-driver-edit-save"]').element as HTMLInputElement;
80
75
 
81
76
  urlField.setValue(url);
82
- uiurlField.setValue(uiurl);
83
77
  checksumField.setValue(checksum);
84
78
 
85
79
  await nextTick();
@@ -2,6 +2,7 @@ import { nextTick } from 'vue';
2
2
  import { mount } from '@vue/test-utils';
3
3
  import RestoreComponent from '@shell/edit/resources.cattle.io.restore.vue';
4
4
  import { _CREATE } from '@shell/config/query-params';
5
+ import { createStore } from 'vuex';
5
6
 
6
7
  describe('view: restore storage source switching', () => {
7
8
  let wrapper: any;
@@ -30,6 +31,14 @@ describe('view: restore storage source switching', () => {
30
31
  };
31
32
 
32
33
  return mount(RestoreComponent, {
34
+ provide: {
35
+ store: createStore({
36
+ getters: {
37
+ currentStore: () => 'current_store',
38
+ 'cluster/paginationEnabled': () => () => false
39
+ }
40
+ })
41
+ },
33
42
  global: {
34
43
  mocks: {
35
44
  $store: mockStore,
@@ -65,7 +65,9 @@ exports[`component: General rendering & initial state should render with default
65
65
  clearable="false"
66
66
  closeonselect="true"
67
67
  disabled="false"
68
+ filterable="true"
68
69
  hovertooltip="true"
70
+ instore="cluster"
69
71
  label="auditPolicy.general.verbosity.level.label"
70
72
  loading="false"
71
73
  localizedlabel="false"
@@ -73,8 +75,12 @@ exports[`component: General rendering & initial state should render with default
73
75
  nooptionslabelkey="labelSelect.noOptions.empty"
74
76
  optionlabel="label"
75
77
  options="[object Object],[object Object],[object Object],[object Object]"
78
+ placeholder=""
76
79
  reduce="[Function]"
77
80
  required="false"
81
+ requiredirty="true"
82
+ rules=""
83
+ searchable="false"
78
84
  selectable="[Function]"
79
85
  value="0"
80
86
  />
@@ -297,5 +297,59 @@ describe('oidc.vue', () => {
297
297
  expect(groupsClaim.exists()).toBe(false);
298
298
  expect(emailClaim.exists()).toBe(false);
299
299
  });
300
+
301
+ describe('clientAuthenticatedSearch checkbox', () => {
302
+ it('is not rendered for genericoidc', async() => {
303
+ const checkbox = wrapper.find('[data-testid="input-client-authenticated-group-search"]');
304
+
305
+ expect(checkbox.exists()).toBe(false);
306
+ });
307
+
308
+ it('is not rendered for cognito', async() => {
309
+ await wrapper.setData({ model: { ...mockModel, id: 'cognito' } });
310
+
311
+ const checkbox = wrapper.find('[data-testid="input-client-authenticated-group-search"]');
312
+
313
+ expect(checkbox.exists()).toBe(false);
314
+ });
315
+
316
+ it('is rendered for keycloakoidc', async() => {
317
+ await wrapper.setData({ model: { ...mockModel, id: 'keycloakoidc' } });
318
+
319
+ const checkbox = wrapper.find('[data-testid="input-client-authenticated-group-search"]');
320
+
321
+ expect(checkbox.exists()).toBe(true);
322
+ });
323
+
324
+ it('defaults to falsy when not set on keycloakoidc', async() => {
325
+ await wrapper.setData({ model: { ...mockModel, id: 'keycloakoidc' } });
326
+
327
+ expect(wrapper.vm.model.clientAuthenticatedSearch).toBeFalsy();
328
+ });
329
+
330
+ it('updates model when checkbox is clicked', async() => {
331
+ await wrapper.setData({
332
+ model: {
333
+ ...mockModel, id: 'keycloakoidc', clientAuthenticatedSearch: false
334
+ }
335
+ });
336
+
337
+ const checkbox = wrapper.getComponent('[data-testid="input-client-authenticated-group-search"]');
338
+
339
+ await checkbox.find('[role="checkbox"]').trigger('click');
340
+
341
+ expect(wrapper.vm.model.clientAuthenticatedSearch).toBe(true);
342
+ });
343
+
344
+ it('reflects a pre-existing true value from the model', async() => {
345
+ await wrapper.setData({
346
+ model: {
347
+ ...mockModel, id: 'keycloakoidc', clientAuthenticatedSearch: true
348
+ }
349
+ });
350
+
351
+ expect(wrapper.vm.model.clientAuthenticatedSearch).toBe(true);
352
+ });
353
+ });
300
354
  });
301
355
  });
@@ -144,7 +144,7 @@ export default {
144
144
  config: {
145
145
  ...this.model,
146
146
  enabled: true,
147
- description: 'Enable AzureAD'
147
+ description: 'Enable Microsoft Entra ID'
148
148
  }
149
149
  };
150
150
  },
@@ -452,6 +452,14 @@ export default {
452
452
  :tooltip="t('authConfig.oidc.groupSearch.tooltip')"
453
453
  :mode="mode"
454
454
  />
455
+ <Checkbox
456
+ v-if="isKeycloak"
457
+ v-model:value="model.clientAuthenticatedSearch"
458
+ data-testid="input-client-authenticated-group-search"
459
+ :label="t('authConfig.oidc.clientAuthenticatedSearch.label')"
460
+ :tooltip="t('authConfig.oidc.clientAuthenticatedSearch.tooltip')"
461
+ :mode="mode"
462
+ />
455
463
  <Checkbox
456
464
  v-if="supportsCustomClaims"
457
465
  v-model:value="addCustomClaims"
@@ -21,7 +21,6 @@ export default {
21
21
  return {
22
22
  fvFormRuleSets: [
23
23
  { path: 'url', rules: ['required', 'url'] },
24
- { path: 'uiUrl', rules: ['url'] },
25
24
  { path: 'checksum', rules: ['alphanumeric'] },
26
25
  { path: 'whitelistDomains', rules: ['wildcardHostname'] }
27
26
  ]
@@ -61,7 +60,7 @@ export default {
61
60
  <CreateDriver
62
61
  :mode="mode"
63
62
  :value="value"
64
- :rules="{url:fvGetAndReportPathRules('url'), uiUrl:fvGetAndReportPathRules('uiUrl'), checksum:fvGetAndReportPathRules('checksum'), whitelistDomains:fvGetAndReportPathRules('whitelistDomains')}"
63
+ :rules="{url:fvGetAndReportPathRules('url'), checksum:fvGetAndReportPathRules('checksum'), whitelistDomains:fvGetAndReportPathRules('whitelistDomains')}"
65
64
  />
66
65
  </CruResource>
67
66
  </template>
@@ -21,7 +21,6 @@ export default {
21
21
  return {
22
22
  fvFormRuleSets: [
23
23
  { path: 'url', rules: ['required', 'url'] },
24
- { path: 'uiUrl', rules: ['url'] },
25
24
  { path: 'checksum', rules: ['alphanumeric'] },
26
25
  { path: 'whitelistDomains', rules: ['wildcardHostname'] }
27
26
  ]
@@ -63,7 +62,6 @@ export default {
63
62
  :value="value"
64
63
  :rules="{
65
64
  url: fvGetAndReportPathRules('url'),
66
- uiUrl: fvGetAndReportPathRules('uiUrl'),
67
65
  checksum: fvGetAndReportPathRules('checksum'),
68
66
  whitelistDomains: fvGetAndReportPathRules('whitelistDomains')
69
67
  }"
@@ -33,6 +33,7 @@ export default {
33
33
  :supported="(row) => typeof row.valueFrom === 'undefined'"
34
34
  :read-allowed="true"
35
35
  :value-can-be-empty="true"
36
+ :read-accept="'text/plain'"
36
37
  :key-label="t('cluster.agentEnvVars.keyLabel')"
37
38
  :parse-lines-from-file="true"
38
39
  />
@@ -0,0 +1,25 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import AgentEnv from '@shell/edit/provisioning.cattle.io.cluster/AgentEnv.vue';
3
+
4
+ describe('component: AgentEnv', () => {
5
+ it('should only accept text files (not binary) on the KeyValue file upload', () => {
6
+ const wrapper = mount(AgentEnv, {
7
+ props: {
8
+ mode: 'edit',
9
+ value: { spec: { agentEnvVars: [] } },
10
+ },
11
+ global: {
12
+ mocks: { t: (key: string) => key },
13
+ stubs: {
14
+ Tab: { template: '<div><slot /></div>' },
15
+ KeyValue: true,
16
+ },
17
+ },
18
+ });
19
+
20
+ const keyValue = wrapper.findComponent({ name: 'KeyValue' });
21
+
22
+ expect(keyValue.exists()).toBe(true);
23
+ expect(keyValue.props('readAccept')).toBe('text/plain');
24
+ });
25
+ });
@@ -0,0 +1,176 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import Ingress from '@shell/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue';
3
+ import { _EDIT } from '@shell/config/query-params';
4
+ import { INGRESS_DUAL, TRAEFIK, INGRESS_NGINX, INGRESS_NONE } from '@shell/edit/provisioning.cattle.io.cluster/shared';
5
+
6
+ jest.mock('vuex', () => ({
7
+ useStore: () => ({ getters: { 'i18n/t': (key: string) => key } }),
8
+ mapGetters: () => ({ t: (key: string) => key })
9
+ }));
10
+
11
+ jest.mock('@shell/edit/provisioning.cattle.io.cluster/shared', () => ({
12
+
13
+ INGRESS_NGINX: 'ingress-nginx',
14
+ TRAEFIK: 'traefik',
15
+ INGRESS_DUAL: 'dual',
16
+ INGRESS_NONE: 'none',
17
+ INGRESS_OPTIONS: [{
18
+ id: 'traefik',
19
+ image: { src: '', alt: 'Traefik' },
20
+ header: { title: { key: 'cluster.ingress.traefik.header' } },
21
+ subHeader: { label: { key: 'cluster.ingress.recommended' } },
22
+ content: { key: 'cluster.ingress.traefik.content' },
23
+ doc: { url: 'https://docs.rke2.io/networking/networking_services?_highlight=ingress#ingress-controller' }
24
+ },
25
+ {
26
+ id: 'ingress-nginx',
27
+ image: { src: '', alt: 'NGINX' },
28
+ header: { title: { key: 'cluster.ingress.nginx.header' } },
29
+ subHeader: { label: { key: 'cluster.ingress.legacy' } },
30
+ content: { key: 'cluster.ingress.nginx.content' },
31
+ doc: { url: 'https://www.kubernetes.dev/blog/2025/11/12/ingress-nginx-retirement/' }
32
+ },
33
+ {
34
+ id: 'dual',
35
+ header: { title: { key: 'cluster.ingress.dual.header' } },
36
+ subHeader: { label: { key: 'cluster.ingress.migration' } },
37
+ content: { key: 'cluster.ingress.dual.content' }
38
+ }],
39
+ INGRESS_MIGRATION_KB_LINK: 'mock-link',
40
+ INGRESS_CONTROLLER_CLASS_MIGRATION: 'rke2.cattle.io/ingress-nginx-migration',
41
+ INGRESS_CLASS_DEFAULT: 'rke2.cattle.io/ingress-nginx-default',
42
+ INGRESS_CONTROLLER_CLASS_DEFAULT: 'rke2.cattle.io/ingress-nginx-controller-default',
43
+ INGRESS_CLASS_MIGRATION: 'rke2.cattle.io/ingress-nginx-migration'
44
+ }));
45
+
46
+ describe('ingress.vue', () => {
47
+ const defaultProps = {
48
+ mode: _EDIT,
49
+ value: INGRESS_NONE,
50
+ nginxSupported: true,
51
+ traefikSupported: true,
52
+ nginxChart: 'rancher-ingress-nginx',
53
+ traefikChart: 'traefik',
54
+ userChartValues: {},
55
+ versionInfo: {
56
+ 'rancher-ingress-nginx': { values: {} },
57
+ traefik: { values: {} }
58
+ }
59
+ };
60
+
61
+ const createWrapper = (props = {}) => mount(Ingress, {
62
+ props: { ...defaultProps, ...props },
63
+ global: {
64
+ stubs: {
65
+ Checkbox: true,
66
+ Banner: true,
67
+ IngressCards: true,
68
+ IngressConfiguration: true,
69
+ YamlEditor: true,
70
+ RichTranslation: true
71
+ }
72
+ }
73
+ });
74
+
75
+ it('renders checkbox to enable/disable ingress', () => {
76
+ const wrapper = createWrapper();
77
+ const checkbox = wrapper.findComponent({ name: 'Checkbox' });
78
+
79
+ expect(checkbox.exists()).toBe(true);
80
+ });
81
+
82
+ it('emits update:value with INGRESS_NONE when ingress is disabled', async() => {
83
+ const wrapper = createWrapper({ value: TRAEFIK });
84
+ const checkbox = wrapper.findComponent({ name: 'Checkbox' });
85
+
86
+ await checkbox.vm.$emit('update:value', false);
87
+
88
+ expect(wrapper.emitted('update:value')).toBeTruthy();
89
+ expect(wrapper.emitted('update:value')?.[0]).toStrictEqual([INGRESS_NONE]);
90
+ });
91
+
92
+ it('emits update:value with TRAEFIK when ingress is enabled and traefik is supported', async() => {
93
+ const wrapper = createWrapper({ value: INGRESS_NONE });
94
+ const checkbox = wrapper.findComponent({ name: 'Checkbox' });
95
+
96
+ await checkbox.vm.$emit('update:value', true);
97
+
98
+ expect(wrapper.emitted('update:value')).toBeTruthy();
99
+ expect(wrapper.emitted('update:value')?.[0]).toStrictEqual([TRAEFIK]);
100
+ });
101
+
102
+ it('emits update:value with INGRESS_NGINX when ingress is enabled, traefik is NOT supported, and nginx IS supported', async() => {
103
+ const wrapper = createWrapper({ value: INGRESS_NONE, traefikSupported: false });
104
+ const checkbox = wrapper.findComponent({ name: 'Checkbox' });
105
+
106
+ await checkbox.vm.$emit('update:value', true);
107
+
108
+ expect(wrapper.emitted('update:value')).toBeTruthy();
109
+ expect(wrapper.emitted('update:value')?.[0]).toStrictEqual([INGRESS_NGINX]);
110
+ });
111
+
112
+ it('selectIngress emits [INGRESS_NGINX, TRAEFIK] string value when INGRESS_DUAL is selected and previous value was ingress-nginx', () => {
113
+ const wrapper = createWrapper({ value: INGRESS_NGINX, originalIngressController: INGRESS_NGINX });
114
+ const ingressCards = wrapper.findComponent({ name: 'IngressCards' });
115
+
116
+ ingressCards.vm.$emit('select', INGRESS_DUAL);
117
+
118
+ expect(wrapper.emitted('update:value')).toBeTruthy();
119
+ expect(wrapper.emitted('update:value')?.[0]).toStrictEqual([[INGRESS_NGINX, TRAEFIK]]);
120
+ });
121
+
122
+ it('selectIngress emits [TRAEFIK, INGRESS_NGINX] string value when INGRESS_DUAL is selected and previous value was traefik', () => {
123
+ const wrapper = createWrapper({ value: TRAEFIK, originalIngressController: TRAEFIK });
124
+ const ingressCards = wrapper.findComponent({ name: 'IngressCards' });
125
+
126
+ ingressCards.vm.$emit('select', INGRESS_DUAL);
127
+
128
+ expect(wrapper.emitted('update:value')).toBeTruthy();
129
+ expect(wrapper.emitted('update:value')?.[0]).toStrictEqual([[TRAEFIK, INGRESS_NGINX]]);
130
+ });
131
+
132
+ it('selectIngress emits [TRAEFIK, INGRESS_NGINX] string value when INGRESS_DUAL is selected and value went traefik -> nginx -> dual', () => {
133
+ const wrapper = createWrapper({ value: TRAEFIK, originalIngressController: TRAEFIK });
134
+ const ingressCards = wrapper.findComponent({ name: 'IngressCards' });
135
+
136
+ ingressCards.vm.$emit('select', INGRESS_NGINX);
137
+ expect(wrapper.emitted('update:value')).toBeTruthy();
138
+ expect(wrapper.emitted('update:value')?.[0]).toStrictEqual([INGRESS_NGINX]);
139
+
140
+ ingressCards.vm.$emit('select', INGRESS_DUAL);
141
+
142
+ expect(wrapper.emitted('update:value')).toBeTruthy();
143
+ expect(wrapper.emitted('update:value')?.[1]).toStrictEqual([[TRAEFIK, INGRESS_NGINX]]);
144
+ });
145
+
146
+ it('selectIngress emits string value when a single ingress is selected', () => {
147
+ const wrapper = createWrapper({ value: TRAEFIK });
148
+ const ingressCards = wrapper.findComponent({ name: 'IngressCards' });
149
+
150
+ ingressCards.vm.$emit('select', INGRESS_NGINX);
151
+
152
+ expect(wrapper.emitted('update:value')).toBeTruthy();
153
+ expect(wrapper.emitted('update:value')?.[0]).toStrictEqual([INGRESS_NGINX]);
154
+ });
155
+
156
+ it('renders IngressConfiguration when versionInfo contains chart values', () => {
157
+ const wrapper = createWrapper({ value: TRAEFIK });
158
+ const config = wrapper.findComponent({ name: 'IngressConfiguration' });
159
+
160
+ expect(config.exists()).toBe(true);
161
+ });
162
+
163
+ it('toggles advanced configuration visibility and renders YamlEditor', async() => {
164
+ const wrapper = createWrapper({ value: TRAEFIK });
165
+
166
+ expect(wrapper.findComponent({ name: 'YamlEditor' }).exists()).toBe(false);
167
+
168
+ const advancedButton = wrapper.find('.advanced-toggle');
169
+
170
+ await advancedButton.trigger('click');
171
+
172
+ const yamlEditor = wrapper.find('[data-testid="traefik-yaml-editor"]');
173
+
174
+ expect(yamlEditor.exists()).toBe(true);
175
+ });
176
+ });