@rancher/shell 3.0.5-rc.1 → 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 (200) hide show
  1. package/assets/images/providers/sks.svg +1 -0
  2. package/assets/styles/base/_helpers.scss +4 -0
  3. package/assets/styles/base/_variables.scss +1 -0
  4. package/assets/translations/en-us.yaml +31 -15
  5. package/assets/translations/zh-hans.yaml +4 -3
  6. package/chart/monitoring/index.vue +3 -1
  7. package/components/ActionDropdownShell.vue +71 -0
  8. package/components/AppModal.vue +18 -4
  9. package/components/CommunityLinks.vue +3 -58
  10. package/components/CruResource.vue +6 -1
  11. package/components/ExplorerProjectsNamespaces.vue +12 -4
  12. package/components/GlobalRoleBindings.vue +5 -1
  13. package/components/GrowlManager.vue +1 -0
  14. package/components/LandingPagePreference.vue +2 -0
  15. package/components/LocaleSelector.vue +1 -1
  16. package/components/ModalManager.vue +55 -0
  17. package/components/PromptModal.vue +47 -8
  18. package/components/ResourceDetail/Masthead.vue +38 -12
  19. package/components/ResourceDetail/__tests__/Masthead.test.ts +5 -1
  20. package/components/ResourceDetail/index.vue +47 -12
  21. package/components/ResourceTable.vue +54 -19
  22. package/components/SideNav.vue +5 -1
  23. package/components/SlideInPanelManager.vue +126 -0
  24. package/components/SortableTable/THead.vue +5 -2
  25. package/components/SortableTable/actions.js +1 -1
  26. package/components/SortableTable/index.vue +54 -40
  27. package/components/SortableTable/paging.js +16 -19
  28. package/components/SortableTable/selection.js +0 -11
  29. package/components/Wizard.vue +2 -2
  30. package/components/__tests__/ModalManager.spec.ts +176 -0
  31. package/components/__tests__/PromptModal.test.ts +148 -0
  32. package/components/__tests__/SlideInPanelManager.spec.ts +166 -0
  33. package/components/auth/AuthBanner.vue +13 -11
  34. package/components/auth/Principal.vue +1 -0
  35. package/components/auth/login/ldap.vue +1 -1
  36. package/components/fleet/FleetResources.vue +21 -6
  37. package/components/form/ArrayList.vue +10 -6
  38. package/components/form/BannerSettings.vue +17 -2
  39. package/components/form/ColorInput.vue +35 -6
  40. package/components/form/EnvVars.vue +1 -0
  41. package/components/form/LabeledSelect.vue +18 -23
  42. package/components/form/MatchExpressions.vue +4 -1
  43. package/components/form/NameNsDescription.vue +5 -1
  44. package/components/form/NotificationSettings.vue +15 -1
  45. package/components/form/Password.vue +1 -0
  46. package/components/form/Probe.vue +1 -0
  47. package/components/form/SSHKnownHosts/__tests__/KnownHostsEditDialog.test.ts +15 -34
  48. package/components/form/SSHKnownHosts/index.vue +14 -11
  49. package/components/form/Select.vue +1 -15
  50. package/components/form/ValueFromResource.vue +12 -12
  51. package/components/form/__tests__/ArrayList.test.ts +2 -2
  52. package/components/form/__tests__/ColorInput.test.ts +35 -0
  53. package/components/form/__tests__/LabeledSelect.test.ts +40 -0
  54. package/components/form/__tests__/SSHKnownHosts.test.ts +11 -2
  55. package/components/nav/Group.vue +12 -4
  56. package/components/nav/Header.vue +16 -43
  57. package/components/nav/NamespaceFilter.vue +134 -86
  58. package/components/nav/TopLevelMenu.vue +4 -5
  59. package/components/nav/WindowManager/ContainerLogs.vue +87 -61
  60. package/components/nav/WindowManager/ContainerLogsActions.vue +76 -0
  61. package/components/templates/default.vue +6 -3
  62. package/components/templates/home.vue +6 -0
  63. package/components/templates/plain.vue +6 -3
  64. package/composables/focusTrap.ts +12 -4
  65. package/config/store.js +4 -0
  66. package/config/uiplugins.js +5 -1
  67. package/core/types.ts +7 -6
  68. package/detail/catalog.cattle.io.app.vue +6 -1
  69. package/detail/fleet.cattle.io.bundle.vue +70 -6
  70. package/detail/fleet.cattle.io.gitrepo.vue +1 -1
  71. package/detail/namespace.vue +0 -3
  72. package/detail/node.vue +17 -13
  73. package/detail/provisioning.cattle.io.cluster.vue +72 -6
  74. package/dialog/AddCustomBadgeDialog.vue +0 -1
  75. package/{pages/c/_cluster/uiplugins/AddExtensionRepos.vue → dialog/AddExtensionReposDialog.vue} +72 -42
  76. package/dialog/AssignToDialog.vue +176 -0
  77. package/dialog/ChangePasswordDialog.vue +106 -0
  78. package/{pages/c/_cluster/uiplugins/DeveloperInstallDialog.vue → dialog/DeveloperLoadExtensionDialog.vue} +74 -71
  79. package/dialog/DisableAuthProviderDialog.vue +101 -0
  80. package/dialog/DrainNode.vue +1 -1
  81. package/{pages/c/_cluster/uiplugins/CatalogList/CatalogLoadDialog.vue → dialog/ExtensionCatalogInstallDialog.vue} +100 -88
  82. package/{pages/c/_cluster/uiplugins/CatalogList/CatalogUninstallDialog.vue → dialog/ExtensionCatalogUninstallDialog.vue} +69 -57
  83. package/dialog/FeatureFlagListDialog.vue +288 -0
  84. package/dialog/ForceMachineRemoveDialog.vue +1 -1
  85. package/{components/Import.vue → dialog/ImportDialog.vue} +0 -5
  86. package/{pages/c/_cluster/uiplugins/InstallDialog.vue → dialog/InstallExtensionDialog.vue} +124 -106
  87. package/{components/form/SSHKnownHosts → dialog}/KnownHostsEditDialog.vue +52 -62
  88. package/dialog/MoveNamespaceDialog.vue +157 -0
  89. package/dialog/ScalePoolDownDialog.vue +1 -1
  90. package/{components/nav/Jump.vue → dialog/SearchDialog.vue} +34 -14
  91. package/{pages/c/_cluster/uiplugins/UninstallDialog.vue → dialog/UninstallExtensionDialog.vue} +67 -58
  92. package/dialog/WechatDialog.vue +57 -0
  93. package/edit/auth/azuread.vue +1 -1
  94. package/edit/auth/github.vue +1 -1
  95. package/edit/auth/googleoauth.vue +1 -1
  96. package/edit/auth/ldap/index.vue +1 -1
  97. package/edit/auth/oidc.vue +1 -1
  98. package/edit/auth/saml.vue +1 -1
  99. package/edit/cloudcredential.vue +24 -10
  100. package/edit/management.cattle.io.user.vue +28 -3
  101. package/edit/namespace.vue +1 -4
  102. package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +4 -1
  103. package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +26 -10
  104. package/edit/provisioning.cattle.io.cluster/__tests__/Advanced.test.ts +8 -8
  105. package/edit/provisioning.cattle.io.cluster/__tests__/DirectoryConfig.test.ts +26 -12
  106. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +66 -0
  107. package/edit/provisioning.cattle.io.cluster/__tests__/utils/rke2-test-data.ts +58 -0
  108. package/edit/provisioning.cattle.io.cluster/rke2.vue +24 -7
  109. package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +5 -3
  110. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +4 -1
  111. package/initialize/install-plugins.js +2 -1
  112. package/list/harvesterhci.io.management.cluster.vue +4 -1
  113. package/list/management.cattle.io.feature.vue +4 -288
  114. package/machine-config/azure.vue +16 -4
  115. package/mixins/vue-select-overrides.js +0 -4
  116. package/models/fleet.cattle.io.cluster.js +8 -2
  117. package/models/fleet.cattle.io.gitrepo.js +8 -34
  118. package/models/management.cattle.io.feature.js +7 -1
  119. package/models/namespace.js +7 -1
  120. package/package.json +1 -1
  121. package/pages/about.vue +13 -3
  122. package/pages/account/index.vue +12 -5
  123. package/pages/auth/login.vue +7 -4
  124. package/pages/auth/setup.vue +1 -0
  125. package/pages/auth/verify.vue +9 -7
  126. package/pages/c/_cluster/apps/charts/install.vue +26 -26
  127. package/pages/c/_cluster/auth/config/index.vue +10 -12
  128. package/pages/c/_cluster/explorer/EventsTable.vue +38 -33
  129. package/pages/c/_cluster/explorer/index.vue +17 -15
  130. package/pages/c/_cluster/istio/index.vue +2 -2
  131. package/pages/c/_cluster/longhorn/index.vue +1 -1
  132. package/pages/c/_cluster/monitoring/index.vue +1 -1
  133. package/pages/c/_cluster/monitoring/monitor/_namespace/_id.vue +4 -2
  134. package/pages/c/_cluster/monitoring/monitor/create.vue +4 -2
  135. package/pages/c/_cluster/monitoring/route-receiver/_id.vue +4 -2
  136. package/pages/c/_cluster/monitoring/route-receiver/create.vue +5 -2
  137. package/pages/c/_cluster/neuvector/index.vue +1 -1
  138. package/pages/c/_cluster/settings/banners.vue +4 -3
  139. package/pages/c/_cluster/uiplugins/CatalogList/index.vue +8 -10
  140. package/pages/c/_cluster/uiplugins/__tests__/AddExtensionRepos.test.ts +4 -7
  141. package/pages/c/_cluster/uiplugins/index.vue +98 -55
  142. package/pages/diagnostic.vue +12 -9
  143. package/pages/fail-whale.vue +8 -5
  144. package/pages/prefs.vue +7 -6
  145. package/plugins/internal-api/index.ts +37 -0
  146. package/plugins/internal-api/shared/base-api.ts +13 -0
  147. package/plugins/internal-api/shell/shell.api.ts +108 -0
  148. package/plugins/steve/actions.js +0 -12
  149. package/public/index.html +1 -0
  150. package/rancher-components/Card/Card.vue +1 -1
  151. package/rancher-components/Form/Checkbox/Checkbox.test.ts +59 -1
  152. package/rancher-components/Form/Checkbox/Checkbox.vue +27 -3
  153. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +47 -0
  154. package/rancher-components/Form/LabeledInput/LabeledInput.vue +20 -2
  155. package/rancher-components/Form/Radio/RadioButton.test.ts +36 -1
  156. package/rancher-components/Form/Radio/RadioButton.vue +20 -4
  157. package/rancher-components/Form/Radio/RadioGroup.test.ts +60 -0
  158. package/rancher-components/Form/Radio/RadioGroup.vue +75 -35
  159. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.test.ts +17 -0
  160. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +5 -0
  161. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +10 -1
  162. package/rancher-components/RcButton/RcButton.vue +2 -1
  163. package/rancher-components/RcButton/types.ts +1 -0
  164. package/rancher-components/RcDropdown/RcDropdown.vue +17 -6
  165. package/rancher-components/RcDropdown/RcDropdownItem.vue +3 -56
  166. package/rancher-components/RcDropdown/RcDropdownItemCheckbox.vue +68 -0
  167. package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +92 -0
  168. package/rancher-components/RcDropdown/index.ts +2 -0
  169. package/rancher-components/RcDropdown/useDropdownItem.ts +63 -0
  170. package/scripts/extension/bundle +20 -0
  171. package/scripts/extension/helm/charts/ui-plugin-server/templates/cr.yaml +2 -1
  172. package/scripts/extension/helm/charts/ui-plugin-server/values.yaml +2 -0
  173. package/scripts/extension/helmpatch +44 -31
  174. package/scripts/extension/publish +12 -13
  175. package/scripts/typegen.sh +2 -4
  176. package/store/action-menu.js +26 -56
  177. package/store/index.js +5 -0
  178. package/store/modal.ts +71 -0
  179. package/store/slideInPanel.ts +47 -0
  180. package/store/type-map.js +8 -1
  181. package/store/type-map.utils.ts +4 -4
  182. package/types/global-vue.d.ts +5 -0
  183. package/types/internal-api/shell/growl.d.ts +25 -0
  184. package/types/internal-api/shell/modal.d.ts +77 -0
  185. package/types/internal-api/shell/slideIn.d.ts +15 -0
  186. package/types/resources/fleet.d.ts +0 -14
  187. package/types/shell/index.d.ts +35 -23
  188. package/types/vue-shim.d.ts +4 -1
  189. package/utils/__mocks__/tabbable.js +13 -0
  190. package/utils/__tests__/object.test.ts +38 -4
  191. package/utils/fleet.ts +15 -73
  192. package/utils/object.js +48 -5
  193. package/utils/validators/formRules/__tests__/index.test.ts +10 -1
  194. package/utils/validators/formRules/index.ts +27 -3
  195. package/components/AssignTo.vue +0 -199
  196. package/components/DisableAuthProviderModal.vue +0 -115
  197. package/components/MoveModal.vue +0 -167
  198. package/components/PromptChangePassword.vue +0 -123
  199. package/components/fleet/FleetBundleResources.vue +0 -86
  200. package/types/vue-shim.d +0 -20
@@ -209,7 +209,7 @@ export default {
209
209
  </template>
210
210
  </AuthBanner>
211
211
 
212
- <hr>
212
+ <hr role="none">
213
213
 
214
214
  <AllowedPrincipals
215
215
  :provider="NAME"
@@ -223,7 +223,7 @@ export default {
223
223
  </template>
224
224
  </AuthBanner>
225
225
 
226
- <hr>
226
+ <hr role="none">
227
227
 
228
228
  <AllowedPrincipals
229
229
  :provider="NAME"
@@ -100,6 +100,30 @@ export default {
100
100
  computed: {
101
101
  rke2Enabled: mapFeature(RKE2_FEATURE),
102
102
 
103
+ hasCustomCloudCredentialComponent() {
104
+ const driverName = this.driverName;
105
+
106
+ return this.$store.getters['type-map/hasCustomCloudCredentialComponent'](driverName);
107
+ },
108
+
109
+ cloudCredentialComponent() {
110
+ const driverName = this.driverName;
111
+
112
+ return this.$store.getters['type-map/importCloudCredential'](driverName);
113
+ },
114
+
115
+ genericCloudCredentialComponent() {
116
+ return this.$store.getters['type-map/importCloudCredential']('generic');
117
+ },
118
+
119
+ cloudComponent() {
120
+ if (this.hasCustomCloudCredentialComponent) {
121
+ return this.cloudCredentialComponent;
122
+ }
123
+
124
+ return this.genericCloudCredentialComponent;
125
+ },
126
+
103
127
  validationPassed() {
104
128
  return this.credCustomComponentValidation && this.nameRequiredValidation;
105
129
  },
@@ -112,14 +136,6 @@ export default {
112
136
  return this.value?.provider;
113
137
  },
114
138
 
115
- cloudComponent() {
116
- if (this.$store.getters['type-map/hasCustomCloudCredentialComponent'](this.driverName)) {
117
- return this.$store.getters['type-map/importCloudCredential'](this.driverName);
118
- }
119
-
120
- return this.$store.getters['type-map/importCloudCredential']('generic');
121
- },
122
-
123
139
  // array of id, label, description, initials for type selection step
124
140
  secretSubTypes() {
125
141
  const out = [];
@@ -194,7 +210,6 @@ export default {
194
210
  },
195
211
 
196
212
  methods: {
197
-
198
213
  createValidationChanged(passed) {
199
214
  this.credCustomComponentValidation = passed;
200
215
  },
@@ -276,7 +291,6 @@ export default {
276
291
  <Loading v-if="$fetchState.pending" />
277
292
  <CruResource
278
293
  v-else
279
- :done-params="$attrs['done-params'] /* Without this, changes to the validationPassed prop end up propagating all the way to the root of the app and force a re-render when the input becomes valid. I haven't found a reasonable explanation for why this happens. */"
280
294
  :mode="mode"
281
295
  :validation-passed="validationPassed"
282
296
  :selected-subtype="value._type"
@@ -15,6 +15,8 @@ export default {
15
15
  ChangePassword, GlobalRoleBindings, CruResource, LabeledInput, Loading
16
16
  },
17
17
 
18
+ emits: ['update:mode'],
19
+
18
20
  mixins: [
19
21
  CreateEditView
20
22
  ],
@@ -40,6 +42,7 @@ export default {
40
42
  roles: !showGlobalRoles,
41
43
  rolesChanged: false,
42
44
  },
45
+ watchOverride: false,
43
46
  };
44
47
  },
45
48
 
@@ -115,7 +118,11 @@ export default {
115
118
  this.$router.replace({ name: this.doneRoute });
116
119
  buttonDone(true);
117
120
  } catch (err) {
118
- this.errors = exceptionToErrorsArray(err);
121
+ if (err?.message?.includes('errors due to escalation')) {
122
+ this.errors = [this.t('rbac.errors.escalation')];
123
+ } else {
124
+ this.errors = exceptionToErrorsArray(err);
125
+ }
119
126
  buttonDone(false);
120
127
  }
121
128
  },
@@ -150,7 +157,7 @@ export default {
150
157
 
151
158
  const normanUser = await this.$store.dispatch('rancher/find', {
152
159
  type: NORMAN.USER,
153
- id: this.value.id,
160
+ id: this.value.id || this.user?.id,
154
161
  });
155
162
 
156
163
  // Save change of password
@@ -181,8 +188,25 @@ export default {
181
188
  },
182
189
 
183
190
  async updateRoles(userId) {
184
- if (this.$refs.grb) {
191
+ if (!this.$refs.grb) {
192
+ return;
193
+ }
194
+
195
+ try {
185
196
  await this.$refs.grb.save(userId);
197
+ } catch (err) {
198
+ if (this.isCreate) {
199
+ this.watchOverride = true;
200
+ this.$emit(
201
+ 'update:mode',
202
+ {
203
+ userId,
204
+ mode: _EDIT,
205
+ resource: 'management.cattle.io.user',
206
+ }
207
+ );
208
+ }
209
+ throw err;
186
210
  }
187
211
  }
188
212
  }
@@ -255,6 +279,7 @@ export default {
255
279
  :user-id="value.id || liveValue.id"
256
280
  :mode="mode"
257
281
  :real-mode="realMode"
282
+ :watch-override="watchOverride"
258
283
  type="user"
259
284
  @hasChanges="validation.rolesChanged = $event"
260
285
  @canLogIn="validation.roles = $event"
@@ -11,7 +11,6 @@ import Tab from '@shell/components/Tabbed/Tab';
11
11
  import ResourceTabs from '@shell/components/form/ResourceTabs/index.vue';
12
12
  import CruResource from '@shell/components/CruResource';
13
13
  import { PROJECT_ID, _VIEW, FLAT_VIEW, _CREATE } from '@shell/config/query-params';
14
- import MoveModal from '@shell/components/MoveModal';
15
14
  import ResourceQuota from '@shell/components/form/ResourceQuota/Namespace';
16
15
  import Loading from '@shell/components/Loading';
17
16
  import { HARVESTER_TYPES, RANCHER_TYPES } from '@shell/components/form/ResourceQuota/shared';
@@ -31,8 +30,7 @@ export default {
31
30
  PodSecurityAdmission,
32
31
  ResourceQuota,
33
32
  Tab,
34
- ResourceTabs,
35
- MoveModal
33
+ ResourceTabs
36
34
  },
37
35
 
38
36
  mixins: [CreateEditView],
@@ -277,6 +275,5 @@ export default {
277
275
  />
278
276
  </Tab>
279
277
  </ResourceTabs>
280
- <MoveModal v-if="projects" />
281
278
  </CruResource>
282
279
  </template>
@@ -260,7 +260,10 @@ export default {
260
260
  />
261
261
 
262
262
  <template v-if="cluster.supportsWindows">
263
- <hr class="mt-20 mb-20">
263
+ <hr
264
+ class="mt-20 mb-20"
265
+ role="none"
266
+ >
264
267
  <h4 v-t="'cluster.custom.registrationCommand.windowsDetail'" />
265
268
  <Banner
266
269
  v-if="readyForWindows"
@@ -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);
@@ -236,7 +253,6 @@ export default {
236
253
  <Loading v-if="$fetchState.pending" />
237
254
  <CruResource
238
255
  v-else
239
- :done-params="$attrs['done-params'] /* Without this, changes to the validationPassed prop end up propagating all the way to the root of the app and force a re-render when the input becomes valid. I haven't found a reasonable explanation for why this happens. */"
240
256
  :mode="mode"
241
257
  :validation-passed="validationPassed"
242
258
  :resource="newCredential"
@@ -267,7 +283,7 @@ export default {
267
283
  />
268
284
 
269
285
  <component
270
- :is="createComponent"
286
+ :is="cloudComponent"
271
287
  ref="create"
272
288
  v-model:value="newCredential"
273
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,7 +22,7 @@ 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
28
  import {
@@ -297,6 +297,21 @@ export default {
297
297
  return this.value.spec.rkeConfig.chartValues;
298
298
  },
299
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
+
300
315
  serverConfig() {
301
316
  return this.value.spec.rkeConfig.machineGlobalConfig;
302
317
  },
@@ -576,7 +591,7 @@ export default {
576
591
  out.tooltip[role] = this.t(`cluster.machinePool.nodeTotals.tooltip.${ role }`, { count: counts[role] });
577
592
  }
578
593
 
579
- if (counts.etcd === 0) {
594
+ if (counts.etcd <= 0) {
580
595
  out.color.etcd = NODE_TOTAL.error.color;
581
596
  out.icon.etcd = NODE_TOTAL.error.icon;
582
597
  } else if (counts.etcd === 1 || counts.etcd % 2 === 0 || counts.etcd > 7) {
@@ -584,7 +599,7 @@ export default {
584
599
  out.icon.etcd = NODE_TOTAL.warning.icon;
585
600
  }
586
601
 
587
- if (counts.controlPlane === 0) {
602
+ if (counts.controlPlane <= 0) {
588
603
  out.color.controlPlane = NODE_TOTAL.error.color;
589
604
  out.icon.controlPlane = NODE_TOTAL.error.icon;
590
605
  } else if (counts.controlPlane === 1) {
@@ -592,7 +607,7 @@ export default {
592
607
  out.icon.controlPlane = NODE_TOTAL.warning.icon;
593
608
  }
594
609
 
595
- if (counts.worker === 0) {
610
+ if (counts.worker <= 0) {
596
611
  out.color.worker = NODE_TOTAL.error.color;
597
612
  out.icon.worker = NODE_TOTAL.error.icon;
598
613
  } else if (counts.worker === 1) {
@@ -1305,7 +1320,7 @@ export default {
1305
1320
  delete clonedCurrentConfig.metadata;
1306
1321
 
1307
1322
  if (this.provider === VMWARE_VSPHERE) {
1308
- machinePool.config = mergeWithReplaceArrays(clonedLatestConfig, clonedCurrentConfig);
1323
+ machinePool.config = mergeWithReplace(clonedLatestConfig, clonedCurrentConfig, { mutateOriginal: true });
1309
1324
  } else {
1310
1325
  machinePool.config = merge(clonedLatestConfig, clonedCurrentConfig);
1311
1326
  }
@@ -1691,7 +1706,7 @@ export default {
1691
1706
  const defaultChartValue = this.versionInfo[name];
1692
1707
  const key = this.chartVersionKey(name);
1693
1708
 
1694
- return merge({}, defaultChartValue?.values || {}, this.userChartValues[key] || {});
1709
+ return mergeWithReplace(defaultChartValue?.values, this.userChartValues[key]);
1695
1710
  },
1696
1711
 
1697
1712
  initServerAgentArgs() {
@@ -1850,7 +1865,9 @@ export default {
1850
1865
 
1851
1866
  applyChartValues(rkeConfig) {
1852
1867
  rkeConfig.chartValues = {};
1853
- this.addonNames.forEach((name) => {
1868
+ const charts = [...this.addonNames, ...this.rke2Charts];
1869
+
1870
+ charts.forEach((name) => {
1854
1871
  const key = this.chartVersionKey(name);
1855
1872
  const userValues = this.userChartValues[key];
1856
1873
 
@@ -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
  }
@@ -292,7 +292,10 @@ export default {
292
292
  />
293
293
  </div>
294
294
  </div>
295
- <hr class="mt-10">
295
+ <hr
296
+ class="mt-10"
297
+ role="none"
298
+ >
296
299
  <component
297
300
  :is="configComponent"
298
301
  v-if="value.config && configComponent"
@@ -26,6 +26,7 @@ import replaceAll from '@shell/plugins/replaceall';
26
26
  import steveCreateWorker from '@shell/plugins/steve-create-worker';
27
27
  import emberCookie from '@shell/plugins/ember-cookie';
28
28
  import ShortKey from '@shell/plugins/shortkey';
29
+ import internalApiPlugin from '@shell/plugins/internal-api';
29
30
 
30
31
  import 'floating-vue/dist/style.css';
31
32
  import { floatingVueOptions } from '@shell/plugins/floating-vue';
@@ -46,7 +47,7 @@ export async function installPlugins(vueApp) {
46
47
  }
47
48
 
48
49
  export async function installInjectedPlugins(app, vueApp) {
49
- const pluginDefinitions = [config, cookieUniversal, axios, plugins, pluginsLoader, axiosShell, intNumber, codeMirror, nuxtClientInit, replaceAll, plugin, steveCreateWorker, emberCookie];
50
+ const pluginDefinitions = [config, cookieUniversal, axios, plugins, pluginsLoader, axiosShell, intNumber, codeMirror, nuxtClientInit, replaceAll, plugin, steveCreateWorker, emberCookie, internalApiPlugin];
50
51
 
51
52
  const installations = pluginDefinitions.map(async(pluginDefinition) => {
52
53
  if (typeof pluginDefinition === 'function') {
@@ -178,7 +178,10 @@ export default {
178
178
  <div class="no-clusters">
179
179
  {{ t('harvesterManager.cluster.none') }}
180
180
  </div>
181
- <hr class="info-section">
181
+ <hr
182
+ class="info-section"
183
+ role="none"
184
+ >
182
185
  <div class="logo">
183
186
  <BrandImage
184
187
  file-name="harvester.png"