@rancher/shell 3.0.4 → 3.0.5-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 (270) hide show
  1. package/assets/images/providers/sks.svg +1 -0
  2. package/assets/styles/base/_basic.scss +6 -0
  3. package/assets/styles/base/_helpers.scss +4 -0
  4. package/assets/styles/base/_variables.scss +1 -0
  5. package/assets/styles/global/_button.scss +1 -0
  6. package/assets/translations/en-us.yaml +65 -15
  7. package/assets/translations/zh-hans.yaml +4 -3
  8. package/chart/monitoring/index.vue +3 -1
  9. package/cloud-credential/aws.vue +2 -0
  10. package/components/ActionDropdownShell.vue +71 -0
  11. package/components/AppModal.vue +18 -4
  12. package/components/AsyncButton.vue +24 -7
  13. package/components/BannerGraphic.vue +1 -0
  14. package/components/CommunityLinks.vue +4 -59
  15. package/components/CopyToClipboardText.vue +2 -1
  16. package/components/CruResource.vue +6 -1
  17. package/components/DetailText.vue +5 -0
  18. package/components/ExplorerMembers.vue +1 -1
  19. package/components/ExplorerProjectsNamespaces.vue +68 -18
  20. package/components/GlobalRoleBindings.vue +5 -1
  21. package/components/GrowlManager.vue +1 -0
  22. package/components/LandingPagePreference.vue +7 -3
  23. package/components/LocaleSelector.vue +39 -95
  24. package/components/ModalManager.vue +55 -0
  25. package/components/ModalWithCard.vue +1 -0
  26. package/components/PromptModal.vue +47 -8
  27. package/components/PromptRemove.vue +1 -0
  28. package/components/PromptRestore.vue +1 -0
  29. package/components/ResourceCancelModal.vue +1 -0
  30. package/components/ResourceDetail/Masthead.vue +38 -12
  31. package/components/ResourceDetail/__tests__/Masthead.test.ts +5 -1
  32. package/components/ResourceDetail/index.vue +47 -12
  33. package/components/ResourceTable.vue +54 -19
  34. package/components/SideNav.vue +5 -1
  35. package/components/SlideInPanelManager.vue +126 -0
  36. package/components/SortableTable/THead.vue +5 -2
  37. package/components/SortableTable/actions.js +1 -1
  38. package/components/SortableTable/index.vue +64 -51
  39. package/components/SortableTable/paging.js +16 -19
  40. package/components/SortableTable/selection.js +0 -11
  41. package/components/Wizard.vue +2 -2
  42. package/components/__tests__/AsyncButton.test.ts +2 -2
  43. package/components/__tests__/ModalManager.spec.ts +176 -0
  44. package/components/__tests__/PromptModal.test.ts +148 -0
  45. package/components/__tests__/SlideInPanelManager.spec.ts +166 -0
  46. package/components/auth/AuthBanner.vue +13 -11
  47. package/components/auth/Principal.vue +1 -0
  48. package/components/auth/__tests__/RoleDetailEdit.test.ts +3 -2
  49. package/components/auth/login/ldap.vue +1 -1
  50. package/components/fleet/FleetResources.vue +21 -6
  51. package/components/form/ArrayList.vue +76 -60
  52. package/components/form/BannerSettings.vue +17 -2
  53. package/components/form/ColorInput.vue +35 -6
  54. package/components/form/Command.vue +6 -15
  55. package/components/form/EnvVars.vue +16 -8
  56. package/components/form/HealthCheck.vue +3 -3
  57. package/components/form/HookOption.vue +11 -16
  58. package/components/form/LabeledSelect.vue +18 -22
  59. package/components/form/LifecycleHooks.vue +3 -3
  60. package/components/form/MatchExpressions.vue +14 -8
  61. package/components/form/NameNsDescription.vue +128 -104
  62. package/components/form/Networking.vue +20 -12
  63. package/components/form/NodeAffinity.vue +31 -23
  64. package/components/form/NodeScheduling.vue +13 -3
  65. package/components/form/NotificationSettings.vue +15 -1
  66. package/components/form/Password.vue +1 -0
  67. package/components/form/PodAffinity.vue +43 -43
  68. package/components/form/Probe.vue +68 -66
  69. package/components/form/ResourceQuota/Project.vue +5 -1
  70. package/components/form/ResourceSelector.vue +7 -9
  71. package/components/form/SSHKnownHosts/__tests__/KnownHostsEditDialog.test.ts +16 -24
  72. package/components/form/SSHKnownHosts/index.vue +30 -13
  73. package/components/form/Security.vue +54 -56
  74. package/components/form/Select.vue +32 -21
  75. package/components/form/ShellInput.vue +5 -1
  76. package/components/form/Tolerations.vue +5 -1
  77. package/components/form/ValueFromResource.vue +134 -121
  78. package/components/form/WorkloadPorts.vue +18 -18
  79. package/components/form/__tests__/ArrayList.test.ts +5 -2
  80. package/components/form/__tests__/ColorInput.test.ts +35 -0
  81. package/components/form/__tests__/LabeledSelect.test.ts +40 -0
  82. package/components/form/__tests__/MatchExpressions.test.ts +12 -12
  83. package/components/form/__tests__/NameNsDescription.test.ts +115 -14
  84. package/components/form/__tests__/Probe.test.ts +12 -8
  85. package/components/form/__tests__/SSHKnownHosts.test.ts +22 -2
  86. package/components/form/__tests__/Select.test.ts +37 -0
  87. package/components/formatter/InternalExternalIP.vue +2 -0
  88. package/components/formatter/SecretData.vue +20 -7
  89. package/components/nav/Group.vue +27 -5
  90. package/components/nav/Header.vue +17 -43
  91. package/components/nav/NamespaceFilter.vue +134 -86
  92. package/components/nav/TopLevelMenu.vue +4 -5
  93. package/components/nav/Type.vue +12 -1
  94. package/components/nav/WindowManager/ContainerLogs.vue +87 -61
  95. package/components/nav/WindowManager/ContainerLogsActions.vue +76 -0
  96. package/components/templates/blank.vue +4 -1
  97. package/components/templates/default.vue +8 -3
  98. package/components/templates/home.vue +10 -1
  99. package/components/templates/plain.vue +10 -4
  100. package/composables/focusTrap.ts +12 -4
  101. package/composables/useRuntimeFlag.ts +29 -0
  102. package/config/router/routes.js +20 -13
  103. package/config/store.js +4 -0
  104. package/config/uiplugins.js +5 -1
  105. package/core/types.ts +12 -6
  106. package/detail/catalog.cattle.io.app.vue +6 -1
  107. package/detail/fleet.cattle.io.bundle.vue +70 -6
  108. package/detail/fleet.cattle.io.gitrepo.vue +1 -1
  109. package/detail/namespace.vue +0 -3
  110. package/detail/node.vue +17 -13
  111. package/detail/provisioning.cattle.io.cluster.vue +72 -6
  112. package/dialog/AddCustomBadgeDialog.vue +1 -1
  113. package/{pages/c/_cluster/uiplugins/AddExtensionRepos.vue → dialog/AddExtensionReposDialog.vue} +72 -42
  114. package/{components/AssignTo.vue → dialog/AssignToDialog.vue} +71 -80
  115. package/dialog/ChangePasswordDialog.vue +106 -0
  116. package/dialog/DeactivateDriverDialog.vue +1 -0
  117. package/{pages/c/_cluster/uiplugins/DeveloperInstallDialog.vue → dialog/DeveloperLoadExtensionDialog.vue} +74 -71
  118. package/dialog/DisableAuthProviderDialog.vue +101 -0
  119. package/dialog/DrainNode.vue +1 -1
  120. package/{pages/c/_cluster/uiplugins/CatalogList/CatalogLoadDialog.vue → dialog/ExtensionCatalogInstallDialog.vue} +100 -88
  121. package/{pages/c/_cluster/uiplugins/CatalogList/CatalogUninstallDialog.vue → dialog/ExtensionCatalogUninstallDialog.vue} +69 -57
  122. package/dialog/FeatureFlagListDialog.vue +288 -0
  123. package/dialog/ForceMachineRemoveDialog.vue +5 -2
  124. package/{components/Import.vue → dialog/ImportDialog.vue} +0 -5
  125. package/{pages/c/_cluster/uiplugins/InstallDialog.vue → dialog/InstallExtensionDialog.vue} +124 -106
  126. package/{components/form/SSHKnownHosts → dialog}/KnownHostsEditDialog.vue +52 -59
  127. package/dialog/MoveNamespaceDialog.vue +157 -0
  128. package/dialog/ScalePoolDownDialog.vue +1 -1
  129. package/{components/nav/Jump.vue → dialog/SearchDialog.vue} +34 -14
  130. package/{pages/c/_cluster/uiplugins/UninstallDialog.vue → dialog/UninstallExtensionDialog.vue} +67 -58
  131. package/dialog/WechatDialog.vue +57 -0
  132. package/edit/__tests__/monitoring.coreos.com.prometheusrule.test.ts +16 -3
  133. package/edit/auth/__tests__/oidc.test.ts +152 -109
  134. package/edit/auth/azuread.vue +2 -1
  135. package/edit/auth/github.vue +1 -1
  136. package/edit/auth/googleoauth.vue +5 -1
  137. package/edit/auth/ldap/index.vue +1 -1
  138. package/edit/auth/oidc.vue +38 -5
  139. package/edit/auth/saml.vue +1 -1
  140. package/edit/cloudcredential.vue +24 -9
  141. package/edit/logging.banzaicloud.io.output/__tests__/logging.banzaicloud.io.output.test.ts +40 -9
  142. package/edit/management.cattle.io.user.vue +28 -3
  143. package/edit/namespace.vue +1 -4
  144. package/edit/networking.k8s.io.ingress/IngressClass.vue +7 -3
  145. package/edit/networking.k8s.io.ingress/__tests__/IngressClass.test.ts +58 -0
  146. package/edit/persistentvolume/__tests__/persistentvolume.test.ts +14 -2
  147. package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +4 -1
  148. package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +26 -9
  149. package/edit/provisioning.cattle.io.cluster/__tests__/Advanced.test.ts +8 -8
  150. package/edit/provisioning.cattle.io.cluster/__tests__/DirectoryConfig.test.ts +26 -12
  151. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +66 -0
  152. package/edit/provisioning.cattle.io.cluster/__tests__/utils/rke2-test-data.ts +58 -0
  153. package/edit/provisioning.cattle.io.cluster/rke2.vue +49 -41
  154. package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +6 -1
  155. package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +5 -3
  156. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +33 -2
  157. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +2 -2
  158. package/edit/token.vue +2 -0
  159. package/edit/workload/index.vue +1 -0
  160. package/edit/workload/mixins/workload.js +0 -2
  161. package/initialize/install-plugins.js +2 -1
  162. package/list/harvesterhci.io.management.cluster.vue +4 -1
  163. package/list/management.cattle.io.feature.vue +4 -287
  164. package/list/provisioning.cattle.io.cluster.vue +20 -12
  165. package/machine-config/azure.vue +16 -4
  166. package/mixins/vue-select-overrides.js +0 -4
  167. package/models/__tests__/namespace.test.ts +25 -1
  168. package/models/cloudcredential.js +5 -0
  169. package/models/fleet.cattle.io.cluster.js +8 -2
  170. package/models/fleet.cattle.io.gitrepo.js +8 -34
  171. package/models/kontainerdriver.js +6 -3
  172. package/models/management.cattle.io.feature.js +7 -1
  173. package/models/management.cattle.io.node.js +3 -3
  174. package/models/namespace.js +11 -6
  175. package/models/nodedriver.js +6 -3
  176. package/models/workload.js +4 -1
  177. package/package.json +3 -3
  178. package/pages/about.vue +13 -3
  179. package/pages/account/index.vue +16 -6
  180. package/pages/auth/login.vue +18 -7
  181. package/pages/auth/logout.vue +4 -1
  182. package/pages/auth/setup.vue +2 -0
  183. package/pages/auth/verify.vue +13 -8
  184. package/pages/c/_cluster/apps/charts/chart.vue +1 -1
  185. package/pages/c/_cluster/apps/charts/install.vue +26 -26
  186. package/pages/c/_cluster/auth/config/index.vue +10 -12
  187. package/pages/c/_cluster/explorer/EventsTable.vue +38 -33
  188. package/pages/c/_cluster/explorer/index.vue +17 -15
  189. package/pages/c/_cluster/istio/index.vue +2 -2
  190. package/pages/c/_cluster/longhorn/index.vue +1 -1
  191. package/pages/c/_cluster/monitoring/index.vue +1 -1
  192. package/pages/c/_cluster/monitoring/monitor/_namespace/_id.vue +4 -2
  193. package/pages/c/_cluster/monitoring/monitor/create.vue +4 -2
  194. package/pages/c/_cluster/monitoring/route-receiver/_id.vue +4 -2
  195. package/pages/c/_cluster/monitoring/route-receiver/create.vue +5 -2
  196. package/pages/c/_cluster/neuvector/index.vue +1 -1
  197. package/pages/c/_cluster/settings/banners.vue +4 -3
  198. package/pages/c/_cluster/uiplugins/CatalogList/index.vue +8 -10
  199. package/pages/c/_cluster/uiplugins/__tests__/AddExtensionRepos.test.ts +4 -7
  200. package/pages/c/_cluster/uiplugins/index.vue +98 -55
  201. package/pages/diagnostic.vue +59 -11
  202. package/pages/fail-whale.vue +14 -8
  203. package/pages/home.vue +24 -18
  204. package/pages/prefs.vue +7 -6
  205. package/pages/support/index.vue +4 -1
  206. package/plugins/internal-api/index.ts +37 -0
  207. package/plugins/internal-api/shared/base-api.ts +13 -0
  208. package/plugins/internal-api/shell/shell.api.ts +108 -0
  209. package/plugins/steve/actions.js +0 -12
  210. package/public/index.html +1 -0
  211. package/rancher-components/Card/Card.vue +1 -1
  212. package/rancher-components/Form/Checkbox/Checkbox.test.ts +59 -1
  213. package/rancher-components/Form/Checkbox/Checkbox.vue +27 -3
  214. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +47 -0
  215. package/rancher-components/Form/LabeledInput/LabeledInput.vue +20 -2
  216. package/rancher-components/Form/Radio/RadioButton.test.ts +36 -1
  217. package/rancher-components/Form/Radio/RadioButton.vue +20 -4
  218. package/rancher-components/Form/Radio/RadioGroup.test.ts +60 -0
  219. package/rancher-components/Form/Radio/RadioGroup.vue +52 -10
  220. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.test.ts +17 -0
  221. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +5 -0
  222. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +10 -1
  223. package/rancher-components/RcButton/RcButton.vue +2 -1
  224. package/rancher-components/RcButton/types.ts +1 -0
  225. package/rancher-components/RcDropdown/RcDropdown.vue +18 -6
  226. package/rancher-components/RcDropdown/RcDropdownItem.vue +3 -56
  227. package/rancher-components/RcDropdown/RcDropdownItemCheckbox.vue +68 -0
  228. package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +92 -0
  229. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +10 -0
  230. package/rancher-components/RcDropdown/index.ts +2 -0
  231. package/rancher-components/RcDropdown/useDropdownCollection.ts +8 -0
  232. package/rancher-components/RcDropdown/useDropdownContext.ts +9 -3
  233. package/rancher-components/RcDropdown/useDropdownItem.ts +63 -0
  234. package/scripts/extension/bundle +20 -0
  235. package/scripts/extension/helm/charts/ui-plugin-server/templates/cr.yaml +2 -1
  236. package/scripts/extension/helm/charts/ui-plugin-server/values.yaml +2 -0
  237. package/scripts/extension/helmpatch +44 -31
  238. package/scripts/extension/publish +12 -12
  239. package/scripts/typegen.sh +2 -4
  240. package/server/har-file.js +25 -3
  241. package/store/action-menu.js +26 -56
  242. package/store/features.js +2 -1
  243. package/store/index.js +5 -0
  244. package/store/modal.ts +71 -0
  245. package/store/slideInPanel.ts +47 -0
  246. package/store/type-map.js +12 -1
  247. package/store/type-map.utils.ts +4 -4
  248. package/types/global-vue.d.ts +5 -0
  249. package/types/internal-api/shell/growl.d.ts +25 -0
  250. package/types/internal-api/shell/modal.d.ts +77 -0
  251. package/types/internal-api/shell/slideIn.d.ts +15 -0
  252. package/types/resources/fleet.d.ts +0 -14
  253. package/types/shell/index.d.ts +43 -24
  254. package/types/vue-shim.d.ts +4 -1
  255. package/utils/__mocks__/tabbable.js +13 -0
  256. package/utils/__tests__/object.test.ts +38 -4
  257. package/utils/cluster.js +35 -0
  258. package/utils/fleet.ts +15 -73
  259. package/utils/object.js +48 -5
  260. package/utils/validators/formRules/__tests__/index.test.ts +10 -1
  261. package/utils/validators/formRules/index.ts +27 -3
  262. package/utils/validators/machine-pool.ts +20 -0
  263. package/components/DisableAuthProviderModal.vue +0 -114
  264. package/components/MoveModal.vue +0 -166
  265. package/components/PromptChangePassword.vue +0 -123
  266. package/components/fleet/FleetBundleResources.vue +0 -86
  267. package/components/formatter/ExtensionCache.vue +0 -74
  268. package/components/formatter/Port.vue +0 -24
  269. package/components/formatter/SecretType.vue +0 -41
  270. package/types/vue-shim.d +0 -20
@@ -81,6 +81,30 @@ export default {
81
81
  },
82
82
 
83
83
  computed: {
84
+ hasCustomCloudCredentialComponent() {
85
+ const driverName = this.driverName;
86
+
87
+ return this.$store.getters['type-map/hasCustomCloudCredentialComponent'](driverName);
88
+ },
89
+
90
+ cloudCredentialComponent() {
91
+ const driverName = this.driverName;
92
+
93
+ return this.$store.getters['type-map/importCloudCredential'](driverName);
94
+ },
95
+
96
+ genericCloudCredentialComponent() {
97
+ return this.$store.getters['type-map/importCloudCredential']('generic');
98
+ },
99
+
100
+ cloudComponent() {
101
+ if (this.hasCustomCloudCredentialComponent) {
102
+ return this.cloudCredentialComponent;
103
+ }
104
+
105
+ return this.genericCloudCredentialComponent;
106
+ },
107
+
84
108
  isNone() {
85
109
  return this.credentialId === null || this.credentialId === _NONE;
86
110
  },
@@ -140,14 +164,6 @@ export default {
140
164
  return out;
141
165
  },
142
166
 
143
- createComponent() {
144
- if (this.$store.getters['type-map/hasCustomCloudCredentialComponent'](this.driverName)) {
145
- return this.$store.getters['type-map/importCloudCredential'](this.driverName);
146
- }
147
-
148
- return this.$store.getters['type-map/importCloudCredential']('generic');
149
- },
150
-
151
167
  validationPassed() {
152
168
  if ( this.credentialId === _NONE ) {
153
169
  return false;
@@ -175,6 +191,7 @@ export default {
175
191
  },
176
192
 
177
193
  methods: {
194
+
178
195
  async save(btnCb) {
179
196
  if ( this.errors ) {
180
197
  clear(this.errors);
@@ -266,7 +283,7 @@ export default {
266
283
  />
267
284
 
268
285
  <component
269
- :is="createComponent"
286
+ :is="cloudComponent"
270
287
  ref="create"
271
288
  v-model:value="newCredential"
272
289
  mode="create"
@@ -176,7 +176,7 @@ describe('component: Advanced', () => {
176
176
  const selectorContainer = wrapper.find('[data-testid="selector-kubelet-arg"]');
177
177
 
178
178
  const globalInputElem = globalContainer;
179
- const selectorInputElem = selectorContainer.find('[data-testid="input-0"]').element as HTMLInputElement;
179
+ const selectorInputElem = selectorContainer.find('[data-testid="array-list-input-0"]').element as HTMLInputElement;
180
180
 
181
181
  expect(globalInputElem.exists()).toBe(false);
182
182
  expect(selectorInputElem.value).toContain('config-from-machineSelectorConfig');
@@ -199,7 +199,7 @@ describe('component: Advanced', () => {
199
199
  const selectorContainer = wrapper.find('[data-testid="selector-kubelet-arg"]');
200
200
 
201
201
  const selectorInputElem = selectorContainer;
202
- const globalInputElem = globalContainer.find('[data-testid="input-0"]').element as HTMLInputElement;
202
+ const globalInputElem = globalContainer.find('[data-testid="array-list-input-0"]').element as HTMLInputElement;
203
203
 
204
204
  expect(selectorInputElem.exists()).toBe(false);
205
205
  expect(globalInputElem.value).toContain('config-from-machineGlobalConfig');
@@ -222,8 +222,8 @@ describe('component: Advanced', () => {
222
222
  const globalContainer = wrapper.find('[data-testid="global-kubelet-arg"]');
223
223
  const selectorContainer = wrapper.find('[data-testid="selector-kubelet-arg"]');
224
224
 
225
- const selectorInputElem = selectorContainer.find('[data-testid="input-0"]').element as HTMLInputElement;
226
- const globalInputElem = globalContainer.find('[data-testid="input-0"]').element as HTMLInputElement;
225
+ const selectorInputElem = selectorContainer.find('[data-testid="array-list-input-0"]').element as HTMLInputElement;
226
+ const globalInputElem = globalContainer.find('[data-testid="array-list-input-0"]').element as HTMLInputElement;
227
227
 
228
228
  expect(selectorInputElem.value).toContain('config-from-machineSelectorConfig');
229
229
  expect(globalInputElem.value).toContain('config-from-machineGlobalConfig');
@@ -247,8 +247,8 @@ describe('component: Advanced', () => {
247
247
  const globalContainer = wrapper.find('[data-testid="global-kubelet-arg"]');
248
248
  const selectorContainer = wrapper.find('[data-testid="selector-kubelet-arg"]');
249
249
 
250
- const selectorInputElem = selectorContainer.find('[data-testid="input-0"]');
251
- const globalInputElem = globalContainer.find('[data-testid="input-0"]');
250
+ const selectorInputElem = selectorContainer.find('[data-testid="array-list-input-0"]');
251
+ const globalInputElem = globalContainer.find('[data-testid="array-list-input-0"]');
252
252
 
253
253
  const emptyCharacter = wrapper.find('.info-box').find('.text-muted').element;
254
254
 
@@ -282,8 +282,8 @@ describe('component: Advanced', () => {
282
282
  const globalContainer = wrapper.find('[data-testid="global-kubelet-arg"]');
283
283
  const selectorContainer = wrapper.find('[data-testid="selector-kubelet-arg"]');
284
284
 
285
- const selectorInputElem = selectorContainer.find('[data-testid="input-0"]');
286
- const globalInputElem = globalContainer.find('[data-testid="input-0"]');
285
+ const selectorInputElem = selectorContainer.find('[data-testid="array-list-input-0"]');
286
+ const globalInputElem = globalContainer.find('[data-testid="array-list-input-0"]');
287
287
 
288
288
  const emptyCharacter = wrapper.find('.info-box').find('.text-muted');
289
289
 
@@ -1,7 +1,7 @@
1
1
  import { nextTick } from 'vue';
2
2
  /* eslint-disable jest/no-hooks */
3
3
  import { mount, Wrapper } from '@vue/test-utils';
4
- import DirectoryConfig, { DATA_DIR_RADIO_OPTIONS, DEFAULT_SUBDIRS } from '@shell/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue';
4
+ import DirectoryConfig, { DATA_DIR_RADIO_OPTIONS, DEFAULT_SUBDIRS, DEFAULT_COMMON_BASE_PATH } from '@shell/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue';
5
5
  import { _EDIT, _CREATE } from '@shell/config/query-params';
6
6
  import { clone } from '@shell/utils/object';
7
7
 
@@ -54,7 +54,7 @@ describe('component: DirectoryConfig', () => {
54
54
  expect(k8sDistroInput.exists()).toBe(false);
55
55
  });
56
56
 
57
- it('updating common base directory should set the correct values on each data dir variable', async() => {
57
+ it('setting the radio option to "common" should set the correct values on each data dir variable', async() => {
58
58
  const newMountOptions = clone(mountOptions);
59
59
 
60
60
  wrapper = mount(
@@ -68,17 +68,12 @@ describe('component: DirectoryConfig', () => {
68
68
  }
69
69
  );
70
70
 
71
- const inputPath = 'some-data-dir';
72
- const commonInput = wrapper.find('[data-testid="rke2-directory-config-common-data-dir"]');
71
+ // update radio to the "common" option
72
+ await wrapper.vm.handleRadioInput(DATA_DIR_RADIO_OPTIONS.COMMON);
73
73
 
74
- // update base dir value
75
- expect(commonInput.exists()).toBe(true);
76
- commonInput.setValue(inputPath);
77
- await nextTick();
78
-
79
- expect(wrapper.vm.value.systemAgent).toStrictEqual(`${ inputPath }/${ DEFAULT_SUBDIRS.AGENT }`);
80
- expect(wrapper.vm.value.provisioning).toStrictEqual(`${ inputPath }/${ DEFAULT_SUBDIRS.PROVISIONING }`);
81
- expect(wrapper.vm.value.k8sDistro).toStrictEqual(`${ inputPath }/${ DEFAULT_SUBDIRS.K8S_DISTRO_K3S }`);
74
+ expect(wrapper.vm.value.systemAgent).toStrictEqual(`${ DEFAULT_COMMON_BASE_PATH }/${ DEFAULT_SUBDIRS.AGENT }`);
75
+ expect(wrapper.vm.value.provisioning).toStrictEqual(`${ DEFAULT_COMMON_BASE_PATH }/${ DEFAULT_SUBDIRS.PROVISIONING }`);
76
+ expect(wrapper.vm.value.k8sDistro).toStrictEqual(`${ DEFAULT_COMMON_BASE_PATH }/${ DEFAULT_SUBDIRS.K8S_DISTRO_K3S }`);
82
77
  });
83
78
 
84
79
  it('updating each individual data dir should set the correct values on each data dir variable', async() => {
@@ -179,4 +174,23 @@ describe('component: DirectoryConfig', () => {
179
174
  expect(wrapper.vm.value.provisioning).toStrictEqual(`${ inputPath }/${ DEFAULT_SUBDIRS.PROVISIONING }`);
180
175
  expect(wrapper.vm.value.k8sDistro).toStrictEqual(`${ inputPath }/${ DEFAULT_SUBDIRS.K8S_DISTRO_K3S }`);
181
176
  });
177
+
178
+ it('updating the k8s version for the "common" config should only update the sub dir for the k8sDistro value', async() => {
179
+ wrapper = mount(
180
+ DirectoryConfig,
181
+ mountOptions
182
+ );
183
+
184
+ // update radio to the "common" option
185
+ await wrapper.vm.handleRadioInput(DATA_DIR_RADIO_OPTIONS.COMMON);
186
+
187
+ expect(wrapper.vm.value.systemAgent).toStrictEqual(`${ DEFAULT_COMMON_BASE_PATH }/${ DEFAULT_SUBDIRS.AGENT }`);
188
+ expect(wrapper.vm.value.provisioning).toStrictEqual(`${ DEFAULT_COMMON_BASE_PATH }/${ DEFAULT_SUBDIRS.PROVISIONING }`);
189
+ expect(wrapper.vm.value.k8sDistro).toStrictEqual(`${ DEFAULT_COMMON_BASE_PATH }/${ DEFAULT_SUBDIRS.K8S_DISTRO_K3S }`);
190
+
191
+ // let's update the k8s version
192
+ await wrapper.setProps({ k8sVersion: 'v1.32.4+rke2r1' });
193
+
194
+ expect(wrapper.vm.value.k8sDistro).toStrictEqual(`${ DEFAULT_COMMON_BASE_PATH }/${ DEFAULT_SUBDIRS.K8S_DISTRO_RKE2 }`);
195
+ });
182
196
  });
@@ -3,6 +3,7 @@ import { SECRET } from '@shell/config/types';
3
3
  import { _CREATE } from '@shell/config/query-params';
4
4
  import rke2 from '@shell/edit/provisioning.cattle.io.cluster/rke2.vue';
5
5
  import { get } from '@shell/utils/object';
6
+ import { rke2TestTable } from './utils/rke2-test-data';
6
7
 
7
8
  /**
8
9
  * DISCLAIMER ***************************************************************************************
@@ -543,4 +544,69 @@ describe('component: rke2', () => {
543
544
 
544
545
  expect(azureOption.disabled).toBe(value);
545
546
  });
547
+
548
+ it.each(rke2TestTable)('should preserve valid user-supplied chart values', (chartValues, expected) => {
549
+ const wrapper = mount(rke2, {
550
+ props: {
551
+ mode: _CREATE,
552
+ value: {
553
+ spec: {
554
+ ...defaultSpec,
555
+ chartValues,
556
+ kubernetesVersion: 'v1.32.3+rke2r1',
557
+ rkeConfig: {
558
+ machineGlobalConfig: {
559
+ cni: 'calico',
560
+ 'disable-kube-proxy': false,
561
+ 'etcd-expose-metrics': false
562
+ },
563
+ }
564
+ },
565
+ agentConfig: { 'cloud-provider-name': 'any' }
566
+ },
567
+ provider: 'custom'
568
+ },
569
+ data: () => ({
570
+ credentialId: 'I am authenticated',
571
+ userChartValues: chartValues,
572
+ rke2Versions: [
573
+ {
574
+ id: 'v1.32.3+rke2r1',
575
+ type: 'release',
576
+ links: { self: 'https://127.0.0.1:8005/v1-rke2-release/releases/v1.32.3+rke2r1' },
577
+ version: 'v1.32.3+rke2r1',
578
+ minChannelServerVersion: 'v2.11.0-alpha1',
579
+ maxChannelServerVersion: 'v2.11.99',
580
+ serverArgs: {},
581
+ agentArgs: {},
582
+ featureVersions: { 'encryption-key-rotation': '2.0.0' },
583
+ charts: {
584
+ 'rke2-ingress-nginx': {
585
+ repo: 'rancher-rke2-charts',
586
+ version: '4.12.100'
587
+ },
588
+ 'rke2-metrics-server': {
589
+ repo: 'rancher-rke2-charts',
590
+ version: '3.12.200'
591
+ },
592
+ }
593
+ }
594
+ ]
595
+ }),
596
+
597
+ global: {
598
+ mocks: {
599
+ ...defaultMocks,
600
+ $store: { dispatch: () => jest.fn(), getters: defaultGetters },
601
+ $plugin: { getDynamic: jest.fn(() => undefined ) },
602
+ },
603
+
604
+ stubs: defaultStubs,
605
+ },
606
+ });
607
+
608
+ wrapper.vm.applyChartValues(wrapper.vm.value.spec.rkeConfig);
609
+
610
+ expect(wrapper.vm.value.spec.rkeConfig.chartValues).toStrictEqual(expected);
611
+ });
546
612
  });
@@ -0,0 +1,58 @@
1
+ export const rke2TestTable = [
2
+ [
3
+ {
4
+ 'rke2-calico': {},
5
+ 'rke2-ingress-nginx': {
6
+ controller: {
7
+ extraArgs: {
8
+ 'enable-ssl-passthrough': true,
9
+ 'watch-ingress-without-class': true
10
+ }
11
+ }
12
+ }
13
+ },
14
+ {
15
+ 'rke2-calico': {},
16
+ 'rke2-ingress-nginx': {
17
+ controller: {
18
+ extraArgs: {
19
+ 'enable-ssl-passthrough': true,
20
+ 'watch-ingress-without-class': true
21
+ }
22
+ }
23
+ }
24
+ },
25
+ ],
26
+ [
27
+ {
28
+ 'rke2-calico': {},
29
+ 'rke2-ingress-nginx': {
30
+ controller: {
31
+ extraArgs: {
32
+ 'enable-ssl-passthrough': true,
33
+ 'watch-ingress-without-class': true
34
+ }
35
+ }
36
+ },
37
+ 'rke2-ingress-nginx-invalid': {
38
+ controller: {
39
+ extraArgs: {
40
+ 'enable-ssl-passthrough': true,
41
+ 'watch-ingress-without-class': true
42
+ }
43
+ }
44
+ }
45
+ },
46
+ {
47
+ 'rke2-calico': {},
48
+ 'rke2-ingress-nginx': {
49
+ controller: {
50
+ extraArgs: {
51
+ 'enable-ssl-passthrough': true,
52
+ 'watch-ingress-without-class': true
53
+ }
54
+ }
55
+ }
56
+ },
57
+ ],
58
+ ];
@@ -22,10 +22,12 @@ import { _CREATE, _EDIT, _VIEW } from '@shell/config/query-params';
22
22
  import { findBy, removeObject, clear } from '@shell/utils/array';
23
23
  import { createYaml } from '@shell/utils/create-yaml';
24
24
  import {
25
- clone, diff, set, get, isEmpty, mergeWithReplaceArrays
25
+ clone, diff, set, get, isEmpty, mergeWithReplace
26
26
  } from '@shell/utils/object';
27
27
  import { allHash } from '@shell/utils/promise';
28
- import { getAllOptionsAfterCurrentVersion, filterOutDeprecatedPatchVersions, isHarvesterSatisfiesVersion, labelForAddon } from '@shell/utils/cluster';
28
+ import {
29
+ getAllOptionsAfterCurrentVersion, filterOutDeprecatedPatchVersions, isHarvesterSatisfiesVersion, labelForAddon, initSchedulingCustomization
30
+ } from '@shell/utils/cluster';
29
31
 
30
32
  import { BadgeState } from '@components/BadgeState';
31
33
  import { Banner } from '@components/Banner';
@@ -63,7 +65,6 @@ import ClusterAppearance from '@shell/components/form/ClusterAppearance';
63
65
  import AddOnAdditionalManifest from '@shell/edit/provisioning.cattle.io.cluster/tabs/AddOnAdditionalManifest';
64
66
  import VsphereUtils, { VMWARE_VSPHERE } from '@shell/utils/v-sphere';
65
67
  import { mapGetters } from 'vuex';
66
- import { SCHEDULING_CUSTOMIZATION } from '@shell/store/features';
67
68
  const HARVESTER = 'harvester';
68
69
  const HARVESTER_CLOUD_PROVIDER = 'harvester-cloud-provider';
69
70
  const NETBIOS_TRUNCATION_LENGTH = 15;
@@ -149,7 +150,13 @@ export default {
149
150
  await this.initSpecs();
150
151
  await this.initAddons();
151
152
  await this.initRegistry();
152
- await this.initSchedulingCustomization();
153
+ const sc = await initSchedulingCustomization(this.value.spec, this.features, this.$store, this.mode);
154
+
155
+ this.clusterAgentDefaultPC = sc.clusterAgentDefaultPC;
156
+ this.clusterAgentDefaultPDB = sc.clusterAgentDefaultPDB;
157
+ this.schedulingCustomizationFeatureEnabled = sc.schedulingCustomizationFeatureEnabled;
158
+ this.schedulingCustomizationOriginallyEnabled = sc.schedulingCustomizationOriginallyEnabled;
159
+ this.errors = this.errors.concat(sc.errors);
153
160
 
154
161
  Object.entries(this.chartValues).forEach(([name, value]) => {
155
162
  const key = this.chartVersionKey(name);
@@ -243,20 +250,21 @@ export default {
243
250
  fvFormRuleSets: [{
244
251
  path: 'metadata.name', rules: ['subDomain'], translationKey: 'nameNsDescription.name.label'
245
252
  }],
246
- harvesterVersionRange: {},
247
- cisOverride: false,
253
+ harvesterVersionRange: {},
254
+ cisOverride: false,
248
255
  truncateLimit,
249
- busy: false,
250
- machinePoolValidation: {}, // map of validation states for each machine pool
251
- machinePoolErrors: {},
252
- addonConfigValidation: {}, // validation state of each addon config (boolean of whether codemirror's yaml lint passed)
253
- allNamespaces: [],
254
- extensionTabs: getApplicableExtensionEnhancements(this, ExtensionPoint.TAB, TabLocation.CLUSTER_CREATE_RKE2, this.$route, this),
255
- clusterAgentDeploymentCustomization: null,
256
- schedulingCustomizationFeatureEnabled: false,
257
- clusterAgentDefaultPC: null,
258
- clusterAgentDefaultPDB: null,
259
- activeTab: null,
256
+ busy: false,
257
+ machinePoolValidation: {}, // map of validation states for each machine pool
258
+ machinePoolErrors: {},
259
+ addonConfigValidation: {}, // validation state of each addon config (boolean of whether codemirror's yaml lint passed)
260
+ allNamespaces: [],
261
+ extensionTabs: getApplicableExtensionEnhancements(this, ExtensionPoint.TAB, TabLocation.CLUSTER_CREATE_RKE2, this.$route, this),
262
+ clusterAgentDeploymentCustomization: null,
263
+ schedulingCustomizationFeatureEnabled: false,
264
+ schedulingCustomizationOriginallyEnabled: false,
265
+ clusterAgentDefaultPC: null,
266
+ clusterAgentDefaultPDB: null,
267
+ activeTab: null,
260
268
  REGISTRIES_TAB_NAME,
261
269
  labelForAddon
262
270
 
@@ -289,6 +297,21 @@ export default {
289
297
  return this.value.spec.rkeConfig.chartValues;
290
298
  },
291
299
 
300
+ kubernetesVersion() {
301
+ return this.value.spec.kubernetesVersion;
302
+ },
303
+
304
+ rke2Charts() {
305
+ const rke2Versions = this.rke2Versions || [];
306
+ const kubernetesVersion = this.kubernetesVersion;
307
+
308
+ const charts = rke2Versions
309
+ .find((version) => version.id === kubernetesVersion)
310
+ ?.charts ?? {};
311
+
312
+ return Object.keys(charts);
313
+ },
314
+
292
315
  serverConfig() {
293
316
  return this.value.spec.rkeConfig.machineGlobalConfig;
294
317
  },
@@ -568,7 +591,7 @@ export default {
568
591
  out.tooltip[role] = this.t(`cluster.machinePool.nodeTotals.tooltip.${ role }`, { count: counts[role] });
569
592
  }
570
593
 
571
- if (counts.etcd === 0) {
594
+ if (counts.etcd <= 0) {
572
595
  out.color.etcd = NODE_TOTAL.error.color;
573
596
  out.icon.etcd = NODE_TOTAL.error.icon;
574
597
  } else if (counts.etcd === 1 || counts.etcd % 2 === 0 || counts.etcd > 7) {
@@ -576,7 +599,7 @@ export default {
576
599
  out.icon.etcd = NODE_TOTAL.warning.icon;
577
600
  }
578
601
 
579
- if (counts.controlPlane === 0) {
602
+ if (counts.controlPlane <= 0) {
580
603
  out.color.controlPlane = NODE_TOTAL.error.color;
581
604
  out.icon.controlPlane = NODE_TOTAL.error.icon;
582
605
  } else if (counts.controlPlane === 1) {
@@ -584,7 +607,7 @@ export default {
584
607
  out.icon.controlPlane = NODE_TOTAL.warning.icon;
585
608
  }
586
609
 
587
- if (counts.worker === 0) {
610
+ if (counts.worker <= 0) {
588
611
  out.color.worker = NODE_TOTAL.error.color;
589
612
  out.icon.worker = NODE_TOTAL.error.icon;
590
613
  } else if (counts.worker === 1) {
@@ -1062,24 +1085,6 @@ export default {
1062
1085
  }
1063
1086
  },
1064
1087
 
1065
- async initSchedulingCustomization() {
1066
- this.schedulingCustomizationFeatureEnabled = this.features(SCHEDULING_CUSTOMIZATION);
1067
- try {
1068
- this.clusterAgentDefaultPC = JSON.parse((await this.$store.dispatch('management/find', { type: MANAGEMENT.SETTING, id: SETTING.CLUSTER_AGENT_DEFAULT_PRIORITY_CLASS })).value) || null;
1069
- } catch (e) {
1070
- this.errors.push(e);
1071
- }
1072
- try {
1073
- this.clusterAgentDefaultPDB = JSON.parse((await this.$store.dispatch('management/find', { type: MANAGEMENT.SETTING, id: SETTING.CLUSTER_AGENT_DEFAULT_POD_DISTRIBUTION_BUDGET })).value) || null;
1074
- } catch (e) {
1075
- this.errors.push(e);
1076
- }
1077
-
1078
- if (this.schedulingCustomizationFeatureEnabled && this.mode === _CREATE && isEmpty(this.value?.spec?.clusterAgentDeploymentCustomization?.schedulingCustomization)) {
1079
- set(this.value, 'spec.clusterAgentDeploymentCustomization.schedulingCustomization', { priorityClass: this.clusterAgentDefaultPC, podDisruptionBudget: this.clusterAgentDefaultPDB });
1080
- }
1081
- },
1082
-
1083
1088
  setSchedulingCustomization(val) {
1084
1089
  if (val) {
1085
1090
  set(this.value, 'spec.clusterAgentDeploymentCustomization.schedulingCustomization', { priorityClass: this.clusterAgentDefaultPC, podDisruptionBudget: this.clusterAgentDefaultPDB });
@@ -1315,7 +1320,7 @@ export default {
1315
1320
  delete clonedCurrentConfig.metadata;
1316
1321
 
1317
1322
  if (this.provider === VMWARE_VSPHERE) {
1318
- machinePool.config = mergeWithReplaceArrays(clonedLatestConfig, clonedCurrentConfig);
1323
+ machinePool.config = mergeWithReplace(clonedLatestConfig, clonedCurrentConfig, { mutateOriginal: true });
1319
1324
  } else {
1320
1325
  machinePool.config = merge(clonedLatestConfig, clonedCurrentConfig);
1321
1326
  }
@@ -1701,7 +1706,7 @@ export default {
1701
1706
  const defaultChartValue = this.versionInfo[name];
1702
1707
  const key = this.chartVersionKey(name);
1703
1708
 
1704
- return merge({}, defaultChartValue?.values || {}, this.userChartValues[key] || {});
1709
+ return mergeWithReplace(defaultChartValue?.values, this.userChartValues[key]);
1705
1710
  },
1706
1711
 
1707
1712
  initServerAgentArgs() {
@@ -1860,7 +1865,9 @@ export default {
1860
1865
 
1861
1866
  applyChartValues(rkeConfig) {
1862
1867
  rkeConfig.chartValues = {};
1863
- this.addonNames.forEach((name) => {
1868
+ const charts = [...this.addonNames, ...this.rke2Charts];
1869
+
1870
+ charts.forEach((name) => {
1864
1871
  const key = this.chartVersionKey(name);
1865
1872
  const userValues = this.userChartValues[key];
1866
1873
 
@@ -2450,6 +2457,7 @@ export default {
2450
2457
  :scheduling-customization-feature-enabled="schedulingCustomizationFeatureEnabled"
2451
2458
  :default-p-c="clusterAgentDefaultPC"
2452
2459
  :default-p-d-b="clusterAgentDefaultPDB"
2460
+ :scheduling-customization-originally-enabled="schedulingCustomizationOriginallyEnabled"
2453
2461
  @scheduling-customization-changed="setSchedulingCustomization"
2454
2462
  />
2455
2463
  </Tab>
@@ -48,6 +48,11 @@ export default {
48
48
  type: Boolean,
49
49
  required: false
50
50
  },
51
+ schedulingCustomizationOriginallyEnabled: {
52
+ type: Boolean,
53
+ default: false
54
+ },
55
+
51
56
  defaultPC: {
52
57
  type: Object,
53
58
  default: () => {},
@@ -149,7 +154,7 @@ export default {
149
154
  },
150
155
 
151
156
  schedulingCustomizationVisible() {
152
- return this.schedulingCustomizationFeatureEnabled || (this.isEdit && this.value.schedulingCustomization );
157
+ return this.schedulingCustomizationFeatureEnabled || this.schedulingCustomizationOriginallyEnabled;
153
158
  },
154
159
 
155
160
  affinityOptions() {
@@ -78,8 +78,8 @@ export default {
78
78
  this.k8sDistroSubDir = DEFAULT_SUBDIRS.K8S_DISTRO_RKE2;
79
79
  }
80
80
 
81
- if (this.value.k8sDistro) {
82
- this.value.k8sDistro = `${ neu }/${ this.k8sDistroSubDir }`;
81
+ if (this.dataConfigRadioValue === DATA_DIR_RADIO_OPTIONS.COMMON && this.commonConfig) {
82
+ this.value.k8sDistro = `${ this.commonConfig }/${ this.k8sDistroSubDir }`;
83
83
  }
84
84
  }
85
85
  }
@@ -129,9 +129,11 @@ export default {
129
129
  this.commonConfig = DEFAULT_COMMON_BASE_PATH;
130
130
 
131
131
  this.dataConfigRadioValue = DATA_DIR_RADIO_OPTIONS.COMMON;
132
+
133
+ // individual data for each field is set on the watcher for commonConfig a bit further above
132
134
  break;
133
- // default is custom config
134
135
  default:
136
+ // switch "default" is for the "custom" config
135
137
  if (this.mode === _CREATE) {
136
138
  this.commonConfig = '';
137
139
  }
@@ -8,6 +8,8 @@ import AdvancedSection from '@shell/components/AdvancedSection.vue';
8
8
  import { Banner } from '@components/Banner';
9
9
  import UnitInput from '@shell/components/form/UnitInput.vue';
10
10
  import { randomStr } from '@shell/utils/string';
11
+ import FormValidation from '@shell/mixins/form-validation';
12
+ import { MACHINE_POOL_VALIDATION } from '@shell/utils/validators/machine-pool';
11
13
 
12
14
  export default {
13
15
 
@@ -25,6 +27,8 @@ export default {
25
27
  UnitInput
26
28
  },
27
29
 
30
+ mixins: [FormValidation],
31
+
28
32
  props: {
29
33
  value: {
30
34
  type: Object,
@@ -122,6 +126,12 @@ export default {
122
126
  uuid: randomStr(),
123
127
 
124
128
  unhealthyNodeTimeoutInteger: this.value.pool.unhealthyNodeTimeout ? parseDuration(this.value.pool.unhealthyNodeTimeout) : 0,
129
+
130
+ validationErrors: [],
131
+
132
+ MACHINE_POOL_VALIDATION,
133
+
134
+ fvFormRuleSets: MACHINE_POOL_VALIDATION.RULESETS,
125
135
  };
126
136
  },
127
137
 
@@ -136,7 +146,7 @@ export default {
136
146
 
137
147
  isWindows() {
138
148
  return this.value?.config?.os === 'windows';
139
- },
149
+ }
140
150
  },
141
151
 
142
152
  watch: {
@@ -148,6 +158,20 @@ export default {
148
158
  } else {
149
159
  this.value.pool.machineOS = 'linux';
150
160
  }
161
+ },
162
+
163
+ validationErrors: {
164
+ handler(newValue) {
165
+ this.$emit('validationChanged', newValue.length === 0);
166
+ },
167
+ deep: true
168
+ },
169
+
170
+ fvFormIsValid: {
171
+ handler(newValue) {
172
+ this.$emit('validationChanged', newValue);
173
+ },
174
+ deep: true
151
175
  }
152
176
  },
153
177
 
@@ -227,6 +251,8 @@ export default {
227
251
  :label="t('cluster.machinePool.name.label')"
228
252
  :required="true"
229
253
  :disabled="!value.config || !!value.config.id || busy"
254
+ :rules="fvGetAndReportPathRules(MACHINE_POOL_VALIDATION.FIELDS.NAME)"
255
+ data-testid="machine-pool-name-input"
230
256
  />
231
257
  </div>
232
258
  <div class="col span-4">
@@ -238,6 +264,8 @@ export default {
238
264
  type="number"
239
265
  min="0"
240
266
  :required="true"
267
+ :rules="fvGetAndReportPathRules(MACHINE_POOL_VALIDATION.FIELDS.QUANTITY)"
268
+ data-testid="machine-pool-quantity-input"
241
269
  />
242
270
  </div>
243
271
  <div class="col span-4 pt-5">
@@ -264,7 +292,10 @@ export default {
264
292
  />
265
293
  </div>
266
294
  </div>
267
- <hr class="mt-10">
295
+ <hr
296
+ class="mt-10"
297
+ role="none"
298
+ >
268
299
  <component
269
300
  :is="configComponent"
270
301
  v-if="value.config && configComponent"
@@ -90,8 +90,8 @@ export default {
90
90
  v-model:value="etcd.snapshotRetention"
91
91
  :mode="mode"
92
92
  :label="t('cluster.rke2.etcd.snapshotRetention.label')"
93
- :suffix="t('cluster.rke2.snapshots.suffix')"
94
- :tooltip="t('cluster.rke2.etcd.snapshotRetention.tooltip')"
93
+ :suffix="s3Backup ? t('cluster.rke2.snapshots.s3Suffix') : t('cluster.rke2.snapshots.suffix')"
94
+ :tooltip="s3Backup ? t('cluster.rke2.etcd.snapshotRetention.tooltip') : undefined"
95
95
  />
96
96
  </div>
97
97
  </div>
package/edit/token.vue CHANGED
@@ -210,6 +210,7 @@ export default {
210
210
  :disabled="form.expiryType !== 'custom'"
211
211
  type="number"
212
212
  :mode="mode"
213
+ :aria-label="t('accountAndKeys.apiKeys.add.ariaLabel.expiration')"
213
214
  >
214
215
  <Select
215
216
  v-model:value="form.customExpiryUnits"
@@ -217,6 +218,7 @@ export default {
217
218
  :options="expiryUnitsOptions"
218
219
  :clearable="false"
219
220
  :reduce="opt=>opt.value"
221
+ :aria-label="t('accountAndKeys.apiKeys.add.ariaLabel.expirationUnits')"
220
222
  />
221
223
  </div>
222
224
  </div>
@@ -599,6 +599,7 @@ export default {
599
599
  v-if="!isView"
600
600
  type="button"
601
601
  class="btn-sm role-link"
602
+ data-testid="workload-button-add-container"
602
603
  @click="addContainerBtn"
603
604
  >
604
605
  <i class="icon icon-plus pr-5" /> {{ t('workload.container.addContainer') }}