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

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 (258) hide show
  1. package/assets/styles/global/_layout.scss +4 -0
  2. package/assets/translations/en-us.yaml +144 -41
  3. package/assets/translations/zh-hans.yaml +1 -7
  4. package/chart/monitoring/ClusterSelector.vue +0 -21
  5. package/chart/monitoring/prometheus/index.vue +6 -3
  6. package/components/CruResource.vue +161 -14
  7. package/components/ExplorerMembers.vue +8 -4
  8. package/components/ExplorerProjectsNamespaces.vue +10 -6
  9. package/components/GrowlManager.vue +4 -0
  10. package/components/MgmtNodeList.vue +184 -0
  11. package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +90 -1
  12. package/components/Resource/Detail/Card/StateCard/composables.ts +57 -87
  13. package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +61 -0
  14. package/components/Resource/Detail/Card/StatusCard/index.vue +61 -15
  15. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +2 -0
  16. package/components/Resource/Detail/Metadata/KeyValue.vue +5 -2
  17. package/components/Resource/Detail/Metadata/KeyValueRow.vue +2 -6
  18. package/components/ResourceDetail/index.vue +1 -1
  19. package/components/ResourceList/Masthead.vue +7 -1
  20. package/components/ResourceList/index.vue +82 -1
  21. package/components/RichTranslation.vue +5 -2
  22. package/components/Setting.vue +1 -0
  23. package/components/SubtleLink.vue +31 -6
  24. package/components/Tabbed/Tab.vue +29 -3
  25. package/components/Tabbed/index.vue +25 -3
  26. package/components/TableOfContents/TableOfContents.vue +109 -0
  27. package/components/TableOfContents/composables.ts +258 -0
  28. package/components/Window/ContainerShell.vue +21 -11
  29. package/components/Window/__tests__/ContainerShell.test.ts +107 -37
  30. package/components/Wizard.vue +9 -4
  31. package/components/fleet/AppCoChartGrid.vue +401 -0
  32. package/components/fleet/AppCoEmptyState.vue +127 -0
  33. package/components/fleet/AppCoPageHeader.vue +119 -0
  34. package/components/fleet/AppCoVersionSelect.vue +70 -0
  35. package/components/fleet/FleetClusterTargets/ClusterSelectionFields.vue +217 -0
  36. package/components/fleet/FleetClusterTargets/TargetsList.vue +123 -35
  37. package/components/fleet/FleetClusterTargets/index.vue +189 -146
  38. package/components/fleet/FleetIntro.vue +7 -3
  39. package/components/fleet/FleetNoWorkspaces.vue +7 -3
  40. package/components/fleet/FleetSecretSelector.vue +5 -3
  41. package/components/fleet/FleetValuesFrom.vue +8 -2
  42. package/components/fleet/GitRepoTargetTab.vue +0 -2
  43. package/components/fleet/HelmOpAdvancedTab.vue +19 -53
  44. package/components/fleet/HelmOpAppCoConfigTab.vue +593 -0
  45. package/components/fleet/HelmOpAppCoResourcesSection.vue +162 -0
  46. package/components/fleet/HelmOpResourcesSection.vue +82 -0
  47. package/components/fleet/HelmOpTargetOptionsSection.vue +89 -0
  48. package/components/fleet/HelmOpTargetTab.vue +64 -60
  49. package/components/fleet/HelmOpValuesTab.vue +129 -105
  50. package/components/fleet/__tests__/AppCoEmptyState.test.ts +71 -0
  51. package/components/fleet/__tests__/AppCoVersionSelect.test.ts +36 -0
  52. package/components/fleet/__tests__/ClusterSelectionFields.test.ts +62 -0
  53. package/components/fleet/__tests__/FleetClusterTargets.test.ts +253 -0
  54. package/components/fleet/__tests__/FleetSecretSelector.test.ts +16 -0
  55. package/components/fleet/__tests__/FleetValuesFrom.test.ts +44 -0
  56. package/components/fleet/__tests__/HelmOpAppCoConfigTab.test.ts +59 -0
  57. package/components/fleet/__tests__/HelmOpAppCoResourcesSection.test.ts +62 -0
  58. package/components/fleet/__tests__/HelmOpResourcesSection.test.ts +43 -0
  59. package/components/fleet/__tests__/HelmOpTargetOptionsSection.test.ts +34 -0
  60. package/components/fleet/__tests__/HelmOpValuesTab.test.ts +39 -0
  61. package/components/fleet/__tests__/__snapshots__/AppCoEmptyState.test.ts.snap +97 -0
  62. package/components/fleet/__tests__/__snapshots__/AppCoVersionSelect.test.ts.snap +30 -0
  63. package/components/fleet/__tests__/__snapshots__/ClusterSelectionFields.test.ts.snap +209 -0
  64. package/components/fleet/__tests__/__snapshots__/HelmOpTargetOptionsSection.test.ts.snap +140 -0
  65. package/components/fleet/dashboard/Empty.vue +8 -4
  66. package/components/fleet/dashboard/ResourceCard.vue +28 -0
  67. package/components/fleet/dashboard/ResourceDetails.vue +28 -0
  68. package/components/fleet/dashboard/__tests__/ResourceCard.test.ts +87 -0
  69. package/components/form/ArrayList.vue +61 -4
  70. package/components/form/KeyValue.vue +23 -2
  71. package/components/form/LabeledSelect.vue +39 -1
  72. package/components/form/Labels.vue +22 -3
  73. package/components/form/NameNsDescription.vue +13 -5
  74. package/components/form/ResourceTabs/index.vue +1 -0
  75. package/components/form/__tests__/NameNsDescription.test.ts +75 -0
  76. package/components/formatter/InternalExternalIP.vue +10 -4
  77. package/components/formatter/ServiceTargets.vue +26 -7
  78. package/components/formatter/__tests__/InternalExternalIP.test.ts +132 -0
  79. package/components/formatter/__tests__/ServiceTargets.test.ts +412 -0
  80. package/components/nav/Header.vue +4 -0
  81. package/components/nav/TopLevelMenu.vue +7 -2
  82. package/components/nav/__tests__/Header.test.ts +15 -0
  83. package/components/nav/__tests__/TopLevelMenu.test.ts +120 -2
  84. package/components/templates/default.vue +9 -4
  85. package/components/templates/home.vue +9 -4
  86. package/components/templates/plain.vue +9 -4
  87. package/composables/useHelmOpResources.test.ts +56 -0
  88. package/composables/useHelmOpResources.ts +32 -0
  89. package/composables/useStateColor.test.ts +325 -0
  90. package/composables/useStateColor.ts +128 -0
  91. package/config/home-links.js +1 -1
  92. package/config/labels-annotations.js +1 -0
  93. package/config/product/explorer.js +17 -4
  94. package/config/product/manager.js +2 -0
  95. package/config/router/index.js +16 -0
  96. package/config/router/navigation-guards/__tests__/authentication.test.ts +130 -0
  97. package/config/router/navigation-guards/authentication.js +10 -4
  98. package/config/router/routes.js +20 -6
  99. package/config/settings.ts +0 -2
  100. package/config/table-headers.js +3 -4
  101. package/config/types.js +9 -0
  102. package/core/plugin-products-base.ts +3 -3
  103. package/core/plugin-types.ts +83 -30
  104. package/core/plugin.ts +3 -0
  105. package/core/types-provisioning.ts +34 -1
  106. package/core/types.ts +15 -2
  107. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +114 -0
  108. package/detail/__tests__/workload.test.ts +3 -152
  109. package/detail/catalog.cattle.io.clusterrepo.vue +1 -1
  110. package/detail/provisioning.cattle.io.cluster.vue +30 -4
  111. package/detail/workload/index.vue +12 -55
  112. package/edit/__tests__/catalog.cattle.io.clusterrepo.test.ts +248 -0
  113. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +105 -0
  114. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
  115. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +1 -0
  116. package/edit/auth/__tests__/azuread.test.ts +34 -9
  117. package/edit/auth/__tests__/github.test.ts +234 -0
  118. package/edit/auth/__tests__/oidc.test.ts +26 -6
  119. package/edit/auth/__tests__/saml.test.ts +196 -0
  120. package/edit/auth/azuread.vue +128 -95
  121. package/edit/auth/github.vue +72 -13
  122. package/edit/auth/ldap/__tests__/index.test.ts +206 -0
  123. package/edit/auth/ldap/config.vue +8 -0
  124. package/edit/auth/ldap/index.vue +75 -1
  125. package/edit/auth/oidc.vue +119 -73
  126. package/edit/auth/saml.vue +76 -12
  127. package/edit/catalog.cattle.io.clusterrepo.vue +140 -32
  128. package/edit/fleet.cattle.io.helmop.vue +491 -136
  129. package/edit/management.cattle.io.user.vue +5 -2
  130. package/edit/provisioning.cattle.io.cluster/rke2.vue +84 -10
  131. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
  132. package/list/group.principal.vue +5 -4
  133. package/list/harvesterhci.io.management.cluster.vue +8 -9
  134. package/list/management.cattle.io.user.vue +12 -9
  135. package/list/provisioning.cattle.io.cluster.vue +16 -10
  136. package/mixins/__tests__/auth-config.test.ts +90 -0
  137. package/mixins/__tests__/chart.test.ts +94 -0
  138. package/mixins/__tests__/resource-fetch-api-pagination.test.ts +48 -0
  139. package/mixins/auth-config.js +7 -0
  140. package/mixins/chart.js +11 -2
  141. package/mixins/child-hook.js +12 -6
  142. package/mixins/create-edit-view/impl.js +5 -3
  143. package/mixins/resource-fetch-api-pagination.js +21 -1
  144. package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +57 -0
  145. package/models/__tests__/compliance.cattle.io.clusterscan.test.ts +144 -0
  146. package/models/__tests__/fleet-application.test.ts +175 -0
  147. package/models/__tests__/fleet.cattle.io.bundle.test.ts +169 -0
  148. package/models/__tests__/fleet.cattle.io.helmop.test.ts +84 -0
  149. package/models/__tests__/management.cattle.io.node.ts +22 -0
  150. package/models/__tests__/namespace.test.ts +36 -0
  151. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +49 -0
  152. package/models/__tests__/workload.test.ts +401 -26
  153. package/models/catalog.cattle.io.clusterrepo.js +28 -4
  154. package/models/compliance.cattle.io.clusterscan.js +39 -4
  155. package/models/fleet-application.js +4 -0
  156. package/models/fleet.cattle.io.helmop.js +20 -1
  157. package/models/management.cattle.io.cluster.js +18 -2
  158. package/models/management.cattle.io.node.js +44 -3
  159. package/models/namespace.js +1 -1
  160. package/models/pod.js +33 -1
  161. package/models/provisioning.cattle.io.cluster.js +5 -5
  162. package/models/workload.js +108 -13
  163. package/models/workload.service.js +5 -0
  164. package/package.json +14 -13
  165. package/pages/about.vue +5 -6
  166. package/pages/auth/login.vue +0 -35
  167. package/pages/auth/setup.vue +11 -0
  168. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +2 -2
  169. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +10 -1
  170. package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +93 -0
  171. package/pages/c/_cluster/apps/charts/chart.vue +2 -1
  172. package/pages/c/_cluster/apps/charts/index.vue +48 -10
  173. package/pages/c/_cluster/apps/charts/install.vue +122 -116
  174. package/pages/c/_cluster/auth/roles/index.vue +5 -4
  175. package/pages/c/_cluster/explorer/workload-dashboard/ByNamespaceSection.vue +31 -0
  176. package/pages/c/_cluster/explorer/workload-dashboard/ByStateSection.vue +138 -0
  177. package/pages/c/_cluster/explorer/workload-dashboard/ByTypeSection.vue +30 -0
  178. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadCard.vue +155 -0
  179. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadNamespaceCard.vue +142 -0
  180. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadTypeCard.vue +159 -0
  181. package/pages/c/_cluster/explorer/workload-dashboard/__tests__/composable.test.ts +561 -0
  182. package/pages/c/_cluster/explorer/workload-dashboard/composable.ts +440 -0
  183. package/pages/c/_cluster/explorer/workload-dashboard/index.vue +187 -0
  184. package/pages/c/_cluster/explorer/workload-dashboard/types.ts +80 -0
  185. package/pages/c/_cluster/fleet/application/create.vue +187 -136
  186. package/pages/c/_cluster/fleet/application/index.vue +5 -3
  187. package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailBody.vue +338 -0
  188. package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailHeader.vue +121 -0
  189. package/pages/c/_cluster/fleet/application/suse-app-collection/chart.vue +369 -0
  190. package/pages/c/_cluster/fleet/application/suse-app-collection/charts.vue +248 -0
  191. package/pages/c/_cluster/fleet/application/suse-app-collection/credentials.vue +310 -0
  192. package/pages/c/_cluster/fleet/index.vue +2 -2
  193. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +96 -0
  194. package/pages/c/_cluster/uiplugins/index.vue +15 -0
  195. package/pages/fail-whale.vue +16 -11
  196. package/pages/home.vue +16 -46
  197. package/plugins/clean-html.d.ts +9 -0
  198. package/plugins/dashboard-store/__tests__/resource-class.test.ts +93 -0
  199. package/plugins/dashboard-store/resource-class.js +62 -7
  200. package/plugins/steve/__tests__/actions.test.ts +212 -0
  201. package/plugins/steve/actions.js +96 -0
  202. package/plugins/steve/steve-pagination-utils.ts +1 -1
  203. package/rancher-components/Accordion/Accordion.vue +53 -9
  204. package/rancher-components/Form/Checkbox/Checkbox.vue +14 -0
  205. package/rancher-components/Form/Radio/RadioButton.vue +17 -1
  206. package/rancher-components/Form/Radio/RadioGroup.vue +10 -0
  207. package/rancher-components/Pill/RcTag/RcTag.vue +3 -2
  208. package/rancher-components/RcButton/RcButton.test.ts +103 -0
  209. package/rancher-components/RcButton/RcButton.vue +94 -15
  210. package/rancher-components/RcButton/types.ts +3 -0
  211. package/rancher-components/RcItemCard/RcItemCard.test.ts +18 -0
  212. package/rancher-components/RcItemCard/RcItemCard.vue +2 -2
  213. package/rancher-components/RcSection/RcSection.vue +28 -3
  214. package/scripts/extension/helm/package/Dockerfile +1 -1
  215. package/scripts/test-plugins-build.sh +2 -1
  216. package/store/__tests__/notifications.test.ts +434 -0
  217. package/store/catalog.js +57 -0
  218. package/store/plugins.js +7 -4
  219. package/types/components/buttonGroup.ts +5 -0
  220. package/types/shell/index.d.ts +104 -70
  221. package/utils/__tests__/auth.test.ts +273 -0
  222. package/utils/__tests__/computed.test.ts +193 -0
  223. package/utils/__tests__/cspAdaptor.test.ts +163 -0
  224. package/utils/__tests__/dom.test.ts +81 -0
  225. package/utils/__tests__/duration.test.ts +37 -1
  226. package/utils/__tests__/dynamic-importer.test.ts +102 -0
  227. package/utils/__tests__/fleet-appco.test.ts +312 -0
  228. package/utils/__tests__/monitoring.test.ts +130 -0
  229. package/utils/__tests__/object.test.ts +22 -0
  230. package/utils/__tests__/platform.test.ts +91 -0
  231. package/utils/__tests__/position.test.ts +237 -0
  232. package/utils/__tests__/provider.test.ts +51 -1
  233. package/utils/__tests__/queue.test.ts +232 -0
  234. package/utils/__tests__/release-notes.test.ts +221 -0
  235. package/utils/__tests__/router.test.js +254 -1
  236. package/utils/__tests__/select.test.ts +208 -0
  237. package/utils/__tests__/time.test.ts +265 -1
  238. package/utils/__tests__/title.test.ts +47 -0
  239. package/utils/__tests__/width.test.ts +53 -0
  240. package/utils/__tests__/window.test.ts +158 -0
  241. package/utils/__tests__/xccdf.test.ts +126 -6
  242. package/utils/crypto/__tests__/browserHashUtils.test.ts +98 -0
  243. package/utils/crypto/__tests__/index.test.ts +144 -0
  244. package/utils/duration.ts +104 -0
  245. package/utils/dynamic-content/__tests__/notification-handler.test.ts +196 -0
  246. package/utils/dynamic-content/info.ts +2 -1
  247. package/utils/error.js +13 -0
  248. package/utils/fleet-appco.ts +323 -0
  249. package/utils/object.js +22 -2
  250. package/utils/provider.ts +12 -0
  251. package/utils/validators/__tests__/container-images.test.ts +104 -0
  252. package/utils/validators/__tests__/flow-output.test.ts +91 -0
  253. package/utils/validators/__tests__/logging-outputs.test.ts +58 -0
  254. package/utils/validators/__tests__/monitoring-route.test.ts +119 -0
  255. package/utils/xccdf.ts +39 -42
  256. package/vue.config.js +1 -1
  257. package/pages/support/index.vue +0 -264
  258. package/utils/duration.js +0 -43
@@ -174,7 +174,7 @@ export default {
174
174
  await wait(5000);
175
175
  }
176
176
 
177
- const user = this.value.save();
177
+ const user = await this.value.save();
178
178
 
179
179
  return user;
180
180
  },
@@ -214,8 +214,10 @@ export default {
214
214
  return;
215
215
  }
216
216
 
217
+ const userId = user?.id || this.value.id;
218
+
217
219
  try {
218
- await this.$refs.grb.save(user.id);
220
+ await this.$refs.grb.save(userId);
219
221
  } catch (err) {
220
222
  if (this.isCreate) {
221
223
  try {
@@ -245,6 +247,7 @@ export default {
245
247
  :can-yaml="false"
246
248
  class="create-edit"
247
249
  @finish="save"
250
+ @error="e=>errors = e"
248
251
  >
249
252
  <div class="credentials">
250
253
  <h2> {{ t("user.edit.credentials.label") }}</h2>
@@ -94,8 +94,8 @@ const NODE_TOTAL = {
94
94
  };
95
95
  const CLUSTER_AGENT_CUSTOMIZATION = 'clusterAgentDeploymentCustomization';
96
96
  const FLEET_AGENT_CUSTOMIZATION = 'fleetAgentDeploymentCustomization';
97
-
98
97
  const REGISTRIES_TAB_NAME = 'registry';
98
+ const INIT_HOOKS = '_initHooks';
99
99
 
100
100
  const isAzureK8sUnsupported = (version) => semver.gte(version, '1.30.0');
101
101
 
@@ -274,6 +274,7 @@ export default {
274
274
  truncateLimit: this.value.defaultHostnameLengthLimit || 0,
275
275
  busy: false,
276
276
  machinePoolValidation: {}, // map of validation states for each machine pool
277
+ infrastructureClusterValid: true,
277
278
  machinePoolErrors: {},
278
279
  addonConfigValidation: {}, // validation state of each addon config (boolean of whether codemirror's yaml lint passed)
279
280
  stackPreferenceError: false, // spec.networking.stackPreference is validated in conjunction with hasOnlyIpv6Pools
@@ -300,6 +301,7 @@ export default {
300
301
  basicsValid: true,
301
302
  registryConfigValid: true,
302
303
  originalIngressController: this.value.spec.rkeConfig.machineGlobalConfig?.[INGRESS_CONTROLLER] || INGRESS_NONE,
304
+ infrastructureCluster: null,
303
305
  };
304
306
  },
305
307
 
@@ -329,6 +331,14 @@ export default {
329
331
  return this.provider === ELEMENTAL_CLUSTER_PROVIDER || this.value?.machineProvider?.toLowerCase() === KIND.MACHINE_INV_SELECTOR_TEMPLATES.toLowerCase();
330
332
  },
331
333
 
334
+ isUpstreamCAPIProvider() {
335
+ if (this.extensionProvider?.isUpstreamCAPIProvider !== undefined) {
336
+ return !!this.extensionProvider.isUpstreamCAPIProvider;
337
+ }
338
+
339
+ return false;
340
+ },
341
+
332
342
  chartValues() {
333
343
  return this.value.spec.rkeConfig.chartValues;
334
344
  },
@@ -548,7 +558,7 @@ export default {
548
558
  getters: this.$store.getters,
549
559
  axios: this.$store.$axios,
550
560
  $extension: this.$store.app.$extension,
551
- $t: this.t,
561
+ t: (...args) => this.t.apply(this, args),
552
562
  isCreate: this.isCreate
553
563
  });
554
564
  }
@@ -826,6 +836,10 @@ export default {
826
836
  return null;
827
837
  },
828
838
 
839
+ extensionInfrastructureSection() {
840
+ return this.extensionProvider?.extensionInfrastructureSection || null;
841
+ },
842
+
829
843
  showForm() {
830
844
  return !!this.credentialId || !this.needCredential;
831
845
  },
@@ -879,7 +893,9 @@ export default {
879
893
 
880
894
  const hasAddonConfigErrors = Object.values(this.addonConfigValidation).filter((v) => v === false).length > 0;
881
895
 
882
- return validRequiredPools && base && !hasAddonConfigErrors && !this.stackPreferenceError;
896
+ const hasInfrastructureClusterError = this.isUpstreamCAPIProvider ? !this.infrastructureClusterValid : false;
897
+
898
+ return validRequiredPools && base && !hasAddonConfigErrors && !hasInfrastructureClusterError && !this.stackPreferenceError;
883
899
  },
884
900
 
885
901
  currentCluster() {
@@ -1015,6 +1031,11 @@ export default {
1015
1031
  },
1016
1032
 
1017
1033
  created() {
1034
+ // Hooks to be run when cluster is getting initialized
1035
+ if (this.extensionProvider?.registerInitHooks) {
1036
+ this.extensionProvider.registerInitHooks(this.registerHook.bind(this, INIT_HOOKS), this.value);
1037
+ }
1038
+ // Other hooks to be run before/after saving the cluster
1018
1039
  this.registerBeforeHook(this.showIpv6Warning, 'show-ipv6-warning', 1);
1019
1040
  this.registerBeforeHook(this.saveMachinePools, 'save-machine-pools', 2);
1020
1041
  this.registerBeforeHook(this.setRegistryConfig, 'set-registry-config');
@@ -1033,6 +1054,21 @@ export default {
1033
1054
  methods: {
1034
1055
  set,
1035
1056
 
1057
+ updateExtensionInfrastructureSection(neu) {
1058
+ if (!neu || typeof neu !== 'object') {
1059
+ return;
1060
+ }
1061
+
1062
+ if (!this.infrastructureCluster || typeof this.infrastructureCluster !== 'object') {
1063
+ this.infrastructureCluster = neu;
1064
+
1065
+ return;
1066
+ }
1067
+
1068
+ // Preserve the original resource model instance while applying extension updates.
1069
+ mergeWithReplace(this.infrastructureCluster, neu, { mutateOriginal: true });
1070
+ },
1071
+
1036
1072
  async handleVsphereCpiSecret() {
1037
1073
  return VsphereUtils.handleVsphereCpiSecret(this);
1038
1074
  },
@@ -1056,8 +1092,8 @@ export default {
1056
1092
  if (!this.value.spec.machineSelectorConfig.find((x) => !x.machineLabelSelector)) {
1057
1093
  this.value.spec.machineSelectorConfig.unshift({ config: {} });
1058
1094
  }
1059
-
1060
- if (this.value.spec.cloudCredentialSecretName) {
1095
+ // TODO handle upstream capi once credentials part is clear
1096
+ if (this.value.spec.cloudCredentialSecretName ) {
1061
1097
  await this.$store.dispatch('rancher/findAll', { type: NORMAN.CLOUD_CREDENTIAL });
1062
1098
  this.credentialId = `${ this.value.spec.cloudCredentialSecretName }`;
1063
1099
  }
@@ -1106,6 +1142,9 @@ export default {
1106
1142
  if ( isEmpty(this.value?.spec?.localClusterAuthEndpoint) ) {
1107
1143
  set(this.value, 'spec.localClusterAuthEndpoint', { enabled: false });
1108
1144
  }
1145
+
1146
+ await this.applyHooks(INIT_HOOKS, this.value);
1147
+ this.localValue = this.value;
1109
1148
  },
1110
1149
 
1111
1150
  /**
@@ -1281,6 +1320,10 @@ export default {
1281
1320
 
1282
1321
  if (this.isElementalCluster) {
1283
1322
  type = ELEMENTAL_SCHEMA_IDS.MACHINE_INV_SELECTOR_TEMPLATES;
1323
+ } else if (this.isUpstreamCAPIProvider && pool.machineConfigRef?.apiVersion) {
1324
+ const [group] = (pool.machineConfigRef.apiVersion || '').split('/');
1325
+
1326
+ type = `${ group }.${ pool.machineConfigRef.kind.toLowerCase() }`;
1284
1327
  } else {
1285
1328
  type = `${ CAPI.MACHINE_CONFIG_GROUP }.${ pool.machineConfigRef.kind.toLowerCase() }`;
1286
1329
  }
@@ -1386,10 +1429,20 @@ export default {
1386
1429
  pool.pool.machineOS = 'linux';
1387
1430
  }
1388
1431
 
1389
- if (this.isElementalCluster) {
1432
+ if (this.isElementalCluster && this.machineConfigSchema?.attributes) {
1390
1433
  pool.pool.machineConfigRef.apiVersion = `${ this.machineConfigSchema.attributes.group }/${ this.machineConfigSchema.attributes.version }`;
1391
1434
  }
1392
1435
 
1436
+ // Upstream CAPI MachineTemplate resources are referenced by full apiVersion so that
1437
+ // initMachinePools can resolve the correct management store type on subsequent loads.
1438
+ if (this.isUpstreamCAPIProvider && this.machineConfigSchema?.attributes) {
1439
+ const { group, version } = this.machineConfigSchema.attributes;
1440
+
1441
+ if (group && version) {
1442
+ pool.pool.machineConfigRef.apiVersion = `${ group }/${ version }`;
1443
+ }
1444
+ }
1445
+
1393
1446
  this.machinePools.push(pool);
1394
1447
 
1395
1448
  this.$nextTick(() => {
@@ -1538,6 +1591,10 @@ export default {
1538
1591
  },
1539
1592
 
1540
1593
  async cleanupMachinePools() {
1594
+ // Allow the extension provider to handle its own resource cleanup
1595
+ if (this.extensionProvider?.cleanupMachinePools) {
1596
+ return await this.extensionProvider.cleanupMachinePools(this.machinePools);
1597
+ }
1541
1598
  for (const entry of this.machinePools) {
1542
1599
  if (entry.remove && entry.config) {
1543
1600
  try {
@@ -2349,7 +2406,8 @@ export default {
2349
2406
  if (this.errors) {
2350
2407
  clear(this.errors);
2351
2408
  }
2352
- if (this.value.cloudProvider === 'aws') {
2409
+
2410
+ if ( this.value.cloudProvider === 'aws') {
2353
2411
  const missingProfileName = this.machinePools.some((mp) => !mp.config.iamInstanceProfile);
2354
2412
 
2355
2413
  if (missingProfileName) {
@@ -2358,7 +2416,7 @@ export default {
2358
2416
  }
2359
2417
 
2360
2418
  for (const [index] of this.machinePools.entries()) { // validator machine config
2361
- if (typeof this.$refs.pool[index]?.test === 'function') {
2419
+ if (typeof this.$refs.pool?.[index]?.test === 'function') {
2362
2420
  try {
2363
2421
  const res = await this.$refs.pool[index].test();
2364
2422
 
@@ -2475,7 +2533,22 @@ export default {
2475
2533
  >
2476
2534
  {{ appsOSWarning }}
2477
2535
  </Banner>
2478
-
2536
+ <div class="span-12">
2537
+ <component
2538
+ :is="extensionInfrastructureSection"
2539
+ v-if="extensionInfrastructureSection"
2540
+ :value="infrastructureCluster"
2541
+ :mode="mode"
2542
+ :provider="provider"
2543
+ :credential-id="credentialId"
2544
+ :provisioning-cluster="value"
2545
+ data-testid="extension-top-section"
2546
+ class="span-12"
2547
+ @update:value="updateExtensionInfrastructureSection"
2548
+ @error="e => errors.push(e)"
2549
+ @validationChanged="(val) => infrastructureClusterValid = val"
2550
+ />
2551
+ </div>
2479
2552
  <!-- Pools Extras -->
2480
2553
  <template v-if="hasMachinePools">
2481
2554
  <div class="clearfix">
@@ -2509,7 +2582,6 @@ export default {
2509
2582
  />
2510
2583
  </div>
2511
2584
  </div>
2512
-
2513
2585
  <!-- Extra Tabs for Machine Pool -->
2514
2586
  <Tabbed
2515
2587
  ref="pools"
@@ -2544,6 +2616,8 @@ export default {
2544
2616
  :busy="busy"
2545
2617
  :pool-id="obj.id"
2546
2618
  :pool-create-mode="obj.create"
2619
+ :infrastructure-cluster="infrastructureCluster"
2620
+ :hide-advanced="isUpstreamCAPIProvider"
2547
2621
  @error="handleMachinePoolError"
2548
2622
  @validationChanged="v => machinePoolValidationChanged(obj.id, v)"
2549
2623
  />
@@ -41,6 +41,11 @@ export default {
41
41
  default: () => ({})
42
42
  },
43
43
 
44
+ infrastructureCluster: {
45
+ type: Object,
46
+ default: () => ({})
47
+ },
48
+
44
49
  // no credentials are required for elemental machine pools
45
50
  credentialId: {
46
51
  type: String,
@@ -86,6 +91,10 @@ export default {
86
91
  poolCreateMode: {
87
92
  type: Boolean,
88
93
  required: true,
94
+ },
95
+ hideAdvanced: {
96
+ type: Boolean,
97
+ default: false
89
98
  }
90
99
  },
91
100
 
@@ -368,6 +377,7 @@ export default {
368
377
  v-model:is-ipv6="value.isIpv6"
369
378
  v-model:is-dualStack="value.isDualStack"
370
379
  :cluster="cluster"
380
+ :infrastructure-cluster="infrastructureCluster"
371
381
  :uuid="uuid"
372
382
  :mode="mode"
373
383
  :value="value.config"
@@ -396,6 +406,7 @@ export default {
396
406
  />
397
407
 
398
408
  <AdvancedSection
409
+ v-if="!hideAdvanced"
399
410
  ref="advanced"
400
411
  :mode="mode"
401
412
  class="advanced"
@@ -9,10 +9,11 @@ import { NAME } from '@shell/config/product/auth';
9
9
  import { MODE, _EDIT } from '@shell/config/query-params';
10
10
  import { mapState } from 'vuex';
11
11
  import { BLANK_CLUSTER } from '@shell/store/store-types.js';
12
+ import { RcButton } from '@components/RcButton';
12
13
 
13
14
  export default {
14
15
  components: {
15
- AsyncButton, ResourceTable, Masthead, Loading
16
+ AsyncButton, ResourceTable, Masthead, Loading, RcButton
16
17
  },
17
18
  props: {
18
19
  resource: {
@@ -130,13 +131,13 @@ export default {
130
131
  :class="{'mr-5': canCreateGlobalRoleBinding}"
131
132
  @click="refreshGroupMemberships"
132
133
  />
133
- <router-link
134
+ <rc-button
134
135
  v-if="canCreateGlobalRoleBinding"
136
+ size="large"
135
137
  :to="assignLocation"
136
- class="btn role-primary"
137
138
  >
138
139
  {{ t("authGroups.actions.assignRoles") }}
139
- </router-link>
140
+ </rc-button>
140
141
  </template>
141
142
  </Masthead>
142
143
 
@@ -8,6 +8,7 @@ import { HARVESTER_NAME as VIRTUAL } from '@shell/config/features';
8
8
  import { CAPI, HCI, MANAGEMENT } from '@shell/config/types';
9
9
  import { isHarvesterCluster } from '@shell/utils/cluster';
10
10
  import { allHash } from '@shell/utils/promise';
11
+ import { RcButton } from '@components/RcButton';
11
12
 
12
13
  export default {
13
14
  components: {
@@ -15,7 +16,8 @@ export default {
15
16
  ResourceTable,
16
17
  Masthead,
17
18
  TypeDescription,
18
- Loading
19
+ Loading,
20
+ RcButton,
19
21
  },
20
22
 
21
23
  props: {
@@ -128,12 +130,12 @@ export default {
128
130
  v-if="canCreateCluster"
129
131
  slot="extraActions"
130
132
  >
131
- <n-link
133
+ <rc-button
132
134
  :to="importLocation"
133
- class="btn role-primary"
135
+ size="large"
134
136
  >
135
137
  {{ t('cluster.importAction') }}
136
- </n-link>
138
+ </rc-button>
137
139
  </template>
138
140
  </Masthead>
139
141
 
@@ -166,12 +168,9 @@ export default {
166
168
  </template>
167
169
 
168
170
  <template #cell:harvester="{row}">
169
- <n-link
170
- class="btn btn-sm role-primary"
171
- :to="row.detailLocation"
172
- >
171
+ <rc-button :to="row.detailLocation">
173
172
  {{ t('harvesterManager.manage') }}
174
- </n-link>
173
+ </rc-button>
175
174
  </template>
176
175
  </ResourceTable>
177
176
  <div v-else>
@@ -7,6 +7,7 @@ import Masthead from '@shell/components/ResourceList/Masthead';
7
7
  import ResourceFetch from '@shell/mixins/resource-fetch';
8
8
  import { isAdminUser } from '@shell/store/type-map';
9
9
  import TableDataUserIcon from '@shell/components/TableDataUserIcon';
10
+ import { RcButton } from '@components/RcButton';
10
11
 
11
12
  export default {
12
13
  components: {
@@ -14,6 +15,7 @@ export default {
14
15
  ResourceTable,
15
16
  Masthead,
16
17
  TableDataUserIcon,
18
+ RcButton
17
19
  },
18
20
  mixins: [ResourceFetch],
19
21
  props: {
@@ -128,14 +130,17 @@ export default {
128
130
  v-if="isAdmin"
129
131
  #subHeader
130
132
  >
131
- <router-link
133
+ <rc-button
134
+ variant="link"
135
+ class="btn-user-retention"
132
136
  :to="{ name: 'c-cluster-auth-user.retention'}"
133
- class="btn role-link btn-sm btn-user-retention"
134
137
  data-testid="router-link-user-retention"
135
138
  >
136
- <i class="icon icon-gear" />
139
+ <template #before>
140
+ <i class="icon icon-gear" />
141
+ </template>
137
142
  {{ t('user.retention.button.label') }}
138
- </router-link>
143
+ </rc-button>
139
144
  </template>
140
145
  </Masthead>
141
146
 
@@ -159,10 +164,8 @@ export default {
159
164
  </div>
160
165
  </template>
161
166
 
162
- <style lang="scss">
163
- .btn-user-retention {
164
- display: flex;
165
- gap: 0.25rem;
166
- padding: 0;
167
+ <style lang="scss" scoped>
168
+ a.rc-button.variant-link.btn-user-retention {
169
+ padding: 0; //retain the padding override for left-alignment with the header
167
170
  }
168
171
  </style>
@@ -17,10 +17,15 @@ import ProvCluster from '@shell/models/provisioning.cattle.io.cluster';
17
17
  import ManagementClusterUtils from '@shell/list/utils/management.cattle.io.cluster.utils';
18
18
  import { STEVE_AUTOSCALER_ENABLED } from '@shell/config/pagination-table-headers';
19
19
  import { filterHiddenLocalCluster, filterOnlyKubernetesClusters } from '@shell/utils/cluster';
20
+ import { RcButton } from '@components/RcButton';
20
21
 
21
22
  export default {
22
23
  components: {
23
- Banner, PaginatedResourceTable, Masthead, MachineSummaryGraph
24
+ Banner,
25
+ PaginatedResourceTable,
26
+ Masthead,
27
+ MachineSummaryGraph,
28
+ RcButton,
24
29
  },
25
30
 
26
31
  data() {
@@ -289,13 +294,14 @@ export default {
289
294
  v-if="canImport"
290
295
  #extraActions
291
296
  >
292
- <router-link
297
+ <rc-button
298
+ size="large"
299
+ class="mr-10"
293
300
  :to="importLocation"
294
- class="btn role-primary mr-10"
295
301
  data-testid="cluster-manager-list-import"
296
302
  >
297
303
  {{ t('cluster.importAction') }}
298
- </router-link>
304
+ </rc-button>
299
305
  </template>
300
306
  </Masthead>
301
307
 
@@ -329,22 +335,22 @@ export default {
329
335
  </template>
330
336
  <template #cell:explorer="{row}">
331
337
  <!-- Align side nav cluster, home page name link and cluster management cluster explor buttons on canExplore -->
332
- <router-link
338
+ <rc-button
333
339
  v-if="row.canExplore"
340
+ variant="secondary"
334
341
  data-testid="cluster-manager-list-explore-management"
335
- class="btn btn-sm role-secondary"
336
342
  :to="{name: 'c-cluster', params: {cluster: row.id}}"
337
343
  >
338
344
  {{ t('cluster.explore') }}
339
- </router-link>
340
- <button
345
+ </rc-button>
346
+ <rc-button
341
347
  v-else
348
+ variant="secondary"
342
349
  data-testid="cluster-manager-list-explore"
343
350
  :disabled="true"
344
- class="btn btn-sm role-secondary"
345
351
  >
346
352
  {{ t('cluster.explore') }}
347
- </button>
353
+ </rc-button>
348
354
  </template>
349
355
  </PaginatedResourceTable>
350
356
  </template>
@@ -69,4 +69,94 @@ describe('mixin: authConfigMixin', () => {
69
69
  expect(instance.model.scope).toStrictEqual(scope);
70
70
  });
71
71
  });
72
+
73
+ describe('accessMode on enable', () => {
74
+ const FakeComponent = {
75
+ render() {},
76
+ mixins: [authConfigMixin, childHook],
77
+ methods: { applyHooks: jest.fn() },
78
+ };
79
+
80
+ const createMock = (model: any, overrides: Record<string, any> = {}) => ({
81
+ data: () => ({
82
+ value: { configType: 'oidc' },
83
+ model,
84
+ ...overrides,
85
+ }),
86
+ computed: { principal: () => ({ me: {} }) },
87
+ global: {
88
+ mocks: {
89
+ $store: { dispatch: () => model },
90
+ $route: {
91
+ params: { id: model.id || '123' },
92
+ query: { mode: 'edit' },
93
+ },
94
+ }
95
+ }
96
+ });
97
+
98
+ it.each([
99
+ 'github',
100
+ 'githubapp',
101
+ ])('should default accessMode to restricted for %s on enable', async(id) => {
102
+ const model = {
103
+ id,
104
+ enabled: false,
105
+ authConfigName: 'whatever',
106
+ doAction: jest.fn(),
107
+ save: jest.fn(),
108
+ };
109
+ const instance = mount(FakeComponent, createMock(model)).vm as any;
110
+
111
+ await instance.save(jest.fn());
112
+
113
+ expect(model.save).toHaveBeenCalled();
114
+ expect(instance.model.accessMode).toStrictEqual('required');
115
+ });
116
+
117
+ it('should default accessMode to unrestricted for non-github oauth on enable', async() => {
118
+ const model = {
119
+ id: 'googleoauth',
120
+ enabled: false,
121
+ authConfigName: 'whatever',
122
+ doAction: jest.fn(),
123
+ save: jest.fn(),
124
+ };
125
+ const instance = mount(FakeComponent, createMock(model)).vm as any;
126
+
127
+ await instance.save(jest.fn());
128
+
129
+ expect(instance.model.accessMode).toStrictEqual('required');
130
+ });
131
+
132
+ it('should set accessMode to required after enabling a provider', async() => {
133
+ const model = {
134
+ enabled: false,
135
+ accessMode: 'unrestricted',
136
+ authConfigName: 'whatever',
137
+ doAction: jest.fn(),
138
+ save: async() => {}
139
+ };
140
+ const instance = mount(FakeComponent, createMock(model)).vm as any;
141
+
142
+ await instance.save(jest.fn());
143
+
144
+ expect(instance.model.accessMode).toStrictEqual('required');
145
+ });
146
+
147
+ it('should not change accessMode when editing an already enabled provider', async() => {
148
+ const model = {
149
+ enabled: true,
150
+ accessMode: 'unrestricted',
151
+ authConfigName: 'whatever',
152
+ doAction: jest.fn(),
153
+ save: async() => {}
154
+ };
155
+ const instance = mount(FakeComponent, createMock(model, { editConfig: true })).vm as any;
156
+
157
+ await instance.save(jest.fn());
158
+
159
+ expect(instance.model.accessMode).toStrictEqual('unrestricted');
160
+ });
161
+ });
72
162
  });
@@ -581,5 +581,99 @@ describe('chartMixin', () => {
581
581
  '0.0.1'
582
582
  ]);
583
583
  });
584
+
585
+ it('should correctly format current version when it is Linux-only', () => {
586
+ const versions = [
587
+ {
588
+ version: '1.0.0',
589
+ created: '2026-01-01',
590
+ annotations: { 'catalog.cattle.io/permits-os': 'linux' }
591
+ }
592
+ ];
593
+
594
+ const mockStore = {
595
+ dispatch: jest.fn(() => Promise.resolve()),
596
+ getters: {
597
+ currentCluster: () => ({ workerOSs: ['linux'] }),
598
+ isRancher: () => true,
599
+ 'catalog/repo': () => () => 'repo',
600
+ 'catalog/chart': () => ({ versions }),
601
+ 'prefs/get': () => () => true,
602
+ 'i18n/t': () => jest.fn()
603
+ }
604
+ };
605
+
606
+ const DummyComponent = {
607
+ mixins: [ChartMixin],
608
+ template: '<div></div>',
609
+ };
610
+
611
+ const wrapper = mount(
612
+ DummyComponent,
613
+ {
614
+ data: () => ({
615
+ chart: { versions },
616
+ existing: { spec: { chart: { metadata: { version: '1.0.0' } } } }
617
+ }),
618
+ global: {
619
+ mocks: {
620
+ $store: mockStore,
621
+ $route: { query: { version: '1.0.0' } },
622
+ t: (key: string, args: any) => `${ args?.ver } (Current, Linux-only)`
623
+ }
624
+ }
625
+ });
626
+
627
+ const result = (wrapper.vm as any).mappedVersions;
628
+
629
+ expect(result[0].label).toBe('1.0.0 (Current, Linux-only)');
630
+ });
631
+
632
+ it('should correctly format current version when it is not OS-restricted', () => {
633
+ const versions = [
634
+ {
635
+ version: '1.0.0',
636
+ created: '2026-01-01',
637
+ annotations: {}
638
+ }
639
+ ];
640
+
641
+ const mockStore = {
642
+ dispatch: jest.fn(() => Promise.resolve()),
643
+ getters: {
644
+ currentCluster: () => ({ workerOSs: ['linux'] }),
645
+ isRancher: () => true,
646
+ 'catalog/repo': () => () => 'repo',
647
+ 'catalog/chart': () => ({ versions }),
648
+ 'prefs/get': () => () => true,
649
+ 'i18n/t': () => jest.fn()
650
+ }
651
+ };
652
+
653
+ const DummyComponent = {
654
+ mixins: [ChartMixin],
655
+ template: '<div></div>',
656
+ };
657
+
658
+ const wrapper = mount(
659
+ DummyComponent,
660
+ {
661
+ data: () => ({
662
+ chart: { versions },
663
+ existing: { spec: { chart: { metadata: { version: '1.0.0' } } } }
664
+ }),
665
+ global: {
666
+ mocks: {
667
+ $store: mockStore,
668
+ $route: { query: { version: '1.0.0' } },
669
+ t: (key: string, args: any) => `${ args?.ver } (Current)`
670
+ }
671
+ }
672
+ });
673
+
674
+ const result = (wrapper.vm as any).mappedVersions;
675
+
676
+ expect(result[0].label).toBe('1.0.0 (Current)');
677
+ });
584
678
  });
585
679
  });