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

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 (315) hide show
  1. package/assets/styles/global/_button.scss +1 -1
  2. package/assets/styles/global/_layout.scss +4 -0
  3. package/assets/translations/en-us.yaml +183 -51
  4. package/assets/translations/zh-hans.yaml +1 -7
  5. package/chart/monitoring/ClusterSelector.vue +0 -21
  6. package/chart/monitoring/prometheus/index.vue +6 -3
  7. package/components/ActionDropdownShell.vue +5 -3
  8. package/components/ButtonGroup.vue +26 -1
  9. package/components/CruResource.vue +212 -16
  10. package/components/ExplorerMembers.vue +8 -4
  11. package/components/ExplorerProjectsNamespaces.vue +10 -6
  12. package/components/GrowlManager.vue +4 -0
  13. package/components/MgmtNodeList.vue +184 -0
  14. package/components/PromptRestore.vue +93 -32
  15. package/components/Questions/index.vue +1 -0
  16. package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +90 -1
  17. package/components/Resource/Detail/Card/StateCard/composables.ts +57 -87
  18. package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +61 -0
  19. package/components/Resource/Detail/Card/StatusCard/index.vue +61 -15
  20. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +2 -0
  21. package/components/Resource/Detail/Metadata/KeyValue.vue +5 -2
  22. package/components/Resource/Detail/Metadata/KeyValueRow.vue +2 -6
  23. package/components/ResourceDetail/index.vue +1 -1
  24. package/components/ResourceList/Masthead.vue +7 -1
  25. package/components/ResourceList/index.vue +82 -1
  26. package/components/ResourceTable.vue +1 -0
  27. package/components/RichTranslation.vue +5 -2
  28. package/components/Setting.vue +1 -0
  29. package/components/SortableTable/index.vue +4 -3
  30. package/components/SubtleLink.vue +31 -6
  31. package/components/Tabbed/Tab.vue +29 -3
  32. package/components/Tabbed/index.vue +25 -3
  33. package/components/TableOfContents/TableOfContents.vue +109 -0
  34. package/components/TableOfContents/composables.ts +258 -0
  35. package/components/Window/ContainerShell.vue +21 -11
  36. package/components/Window/__tests__/ContainerShell.test.ts +107 -37
  37. package/components/Wizard.vue +23 -5
  38. package/components/__tests__/ButtonGroup.test.ts +56 -0
  39. package/components/__tests__/PromptRestore.test.ts +169 -19
  40. package/components/fleet/AppCoChartGrid.vue +401 -0
  41. package/components/fleet/AppCoEmptyState.vue +127 -0
  42. package/components/fleet/AppCoPageHeader.vue +119 -0
  43. package/components/fleet/AppCoVersionSelect.vue +70 -0
  44. package/components/fleet/FleetClusterTargets/ClusterSelectionFields.vue +217 -0
  45. package/components/fleet/FleetClusterTargets/TargetsList.vue +123 -35
  46. package/components/fleet/FleetClusterTargets/index.vue +189 -146
  47. package/components/fleet/FleetIntro.vue +7 -3
  48. package/components/fleet/FleetNoWorkspaces.vue +7 -3
  49. package/components/fleet/FleetSecretSelector.vue +5 -3
  50. package/components/fleet/FleetValuesFrom.vue +8 -2
  51. package/components/fleet/GitRepoAdvancedTab.vue +1 -0
  52. package/components/fleet/GitRepoMetadataTab.vue +5 -0
  53. package/components/fleet/GitRepoTargetTab.vue +0 -2
  54. package/components/fleet/HelmOpAdvancedTab.vue +19 -53
  55. package/components/fleet/HelmOpAppCoConfigTab.vue +597 -0
  56. package/components/fleet/HelmOpAppCoResourcesSection.vue +162 -0
  57. package/components/fleet/HelmOpMetadataTab.vue +5 -0
  58. package/components/fleet/HelmOpResourcesSection.vue +82 -0
  59. package/components/fleet/HelmOpTargetOptionsSection.vue +89 -0
  60. package/components/fleet/HelmOpTargetTab.vue +64 -60
  61. package/components/fleet/HelmOpValuesTab.vue +129 -105
  62. package/components/fleet/__tests__/AppCoEmptyState.test.ts +71 -0
  63. package/components/fleet/__tests__/AppCoVersionSelect.test.ts +36 -0
  64. package/components/fleet/__tests__/ClusterSelectionFields.test.ts +62 -0
  65. package/components/fleet/__tests__/FleetClusterTargets.test.ts +253 -0
  66. package/components/fleet/__tests__/FleetSecretSelector.test.ts +16 -0
  67. package/components/fleet/__tests__/FleetValuesFrom.test.ts +44 -0
  68. package/components/fleet/__tests__/HelmOpAppCoConfigTab.test.ts +59 -0
  69. package/components/fleet/__tests__/HelmOpAppCoResourcesSection.test.ts +62 -0
  70. package/components/fleet/__tests__/HelmOpResourcesSection.test.ts +43 -0
  71. package/components/fleet/__tests__/HelmOpTargetOptionsSection.test.ts +34 -0
  72. package/components/fleet/__tests__/HelmOpValuesTab.test.ts +39 -0
  73. package/components/fleet/__tests__/__snapshots__/AppCoEmptyState.test.ts.snap +97 -0
  74. package/components/fleet/__tests__/__snapshots__/AppCoVersionSelect.test.ts.snap +30 -0
  75. package/components/fleet/__tests__/__snapshots__/ClusterSelectionFields.test.ts.snap +209 -0
  76. package/components/fleet/__tests__/__snapshots__/HelmOpTargetOptionsSection.test.ts.snap +140 -0
  77. package/components/fleet/dashboard/Empty.vue +8 -4
  78. package/components/fleet/dashboard/ResourceCard.vue +28 -0
  79. package/components/fleet/dashboard/ResourceDetails.vue +28 -0
  80. package/components/fleet/dashboard/__tests__/ResourceCard.test.ts +87 -0
  81. package/components/form/ArrayList.vue +61 -4
  82. package/components/form/FileSelector.vue +39 -1
  83. package/components/form/KeyValue.vue +23 -2
  84. package/components/form/LabeledSelect.vue +39 -1
  85. package/components/form/Labels.vue +22 -3
  86. package/components/form/NameNsDescription.vue +13 -5
  87. package/components/form/PrivateRegistry.constants.ts +7 -0
  88. package/components/form/PrivateRegistry.vue +253 -18
  89. package/components/form/ResourceTabs/index.vue +1 -0
  90. package/components/form/SelectOrCreateAuthSecret.vue +140 -17
  91. package/components/form/__tests__/FileSelector.test.ts +23 -0
  92. package/components/form/__tests__/NameNsDescription.test.ts +75 -0
  93. package/components/form/__tests__/PrivateRegistry.test.ts +463 -73
  94. package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +122 -0
  95. package/components/formatter/EtcdSnapshotName.vue +73 -0
  96. package/components/formatter/InternalExternalIP.vue +10 -4
  97. package/components/formatter/ServiceTargets.vue +26 -7
  98. package/components/formatter/__tests__/InternalExternalIP.test.ts +132 -0
  99. package/components/formatter/__tests__/ServiceTargets.test.ts +412 -0
  100. package/components/nav/Header.vue +12 -1
  101. package/components/nav/TopLevelMenu.vue +7 -2
  102. package/components/nav/__tests__/Header.test.ts +15 -0
  103. package/components/nav/__tests__/TopLevelMenu.test.ts +120 -2
  104. package/components/templates/default.vue +16 -4
  105. package/components/templates/home.vue +9 -4
  106. package/components/templates/plain.vue +9 -4
  107. package/composables/useHelmOpResources.test.ts +56 -0
  108. package/composables/useHelmOpResources.ts +32 -0
  109. package/composables/useStateColor.test.ts +325 -0
  110. package/composables/useStateColor.ts +128 -0
  111. package/config/features.js +1 -0
  112. package/config/home-links.js +1 -1
  113. package/config/labels-annotations.js +3 -0
  114. package/config/product/explorer.js +17 -4
  115. package/config/product/manager.js +8 -0
  116. package/config/router/index.js +16 -0
  117. package/config/router/navigation-guards/__tests__/authentication.test.ts +130 -0
  118. package/config/router/navigation-guards/authentication.js +10 -4
  119. package/config/router/routes.js +20 -6
  120. package/config/secret.ts +10 -0
  121. package/config/settings.ts +6 -4
  122. package/config/table-headers.js +3 -4
  123. package/config/types.js +16 -0
  124. package/core/plugin-products-base.ts +3 -3
  125. package/core/plugin-types.ts +83 -30
  126. package/core/plugin.ts +3 -0
  127. package/core/types-provisioning.ts +34 -1
  128. package/core/types.ts +15 -2
  129. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +114 -0
  130. package/detail/__tests__/workload.test.ts +3 -152
  131. package/detail/catalog.cattle.io.clusterrepo.vue +1 -1
  132. package/detail/provisioning.cattle.io.cluster.vue +109 -7
  133. package/detail/workload/index.vue +12 -55
  134. package/dialog/RotateEncryptionKeyDialog.vue +33 -9
  135. package/dialog/__tests__/RotateEncryptionKeyDialog.test.ts +78 -0
  136. package/edit/__tests__/catalog.cattle.io.clusterrepo.test.ts +248 -0
  137. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +92 -0
  138. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +206 -0
  139. package/edit/__tests__/management.cattle.io.setting.test.ts +2 -1
  140. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
  141. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +1 -0
  142. package/edit/auth/__tests__/azuread.test.ts +34 -9
  143. package/edit/auth/__tests__/github.test.ts +234 -0
  144. package/edit/auth/__tests__/oidc.test.ts +26 -6
  145. package/edit/auth/__tests__/saml.test.ts +196 -0
  146. package/edit/auth/azuread.vue +128 -95
  147. package/edit/auth/github.vue +72 -13
  148. package/edit/auth/ldap/__tests__/index.test.ts +206 -0
  149. package/edit/auth/ldap/config.vue +8 -0
  150. package/edit/auth/ldap/index.vue +75 -1
  151. package/edit/auth/oidc.vue +119 -73
  152. package/edit/auth/saml.vue +76 -12
  153. package/edit/catalog.cattle.io.clusterrepo.vue +140 -32
  154. package/edit/compliance.cattle.io.clusterscanprofile.vue +39 -41
  155. package/edit/fleet.cattle.io.gitrepo.vue +70 -16
  156. package/edit/fleet.cattle.io.helmop.vue +542 -141
  157. package/edit/helm.cattle.io.projecthelmchart.vue +1 -0
  158. package/edit/{management.cattle.io.setting.vue → management.cattle.io.setting/index.vue} +32 -9
  159. package/edit/management.cattle.io.setting/system-default-registry-pull-secrets.vue +81 -0
  160. package/edit/management.cattle.io.user.vue +5 -2
  161. package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +3 -12
  162. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +18 -0
  163. package/edit/provisioning.cattle.io.cluster/rke2.vue +89 -11
  164. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
  165. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +0 -1
  166. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +14 -55
  167. package/list/group.principal.vue +5 -4
  168. package/list/harvesterhci.io.management.cluster.vue +8 -9
  169. package/list/management.cattle.io.user.vue +12 -9
  170. package/list/provisioning.cattle.io.cluster.vue +16 -10
  171. package/mixins/__tests__/auth-config.test.ts +90 -0
  172. package/mixins/__tests__/chart.test.ts +94 -0
  173. package/mixins/__tests__/resource-fetch-api-pagination.test.ts +48 -0
  174. package/mixins/auth-config.js +7 -0
  175. package/mixins/chart.js +11 -2
  176. package/mixins/child-hook.js +12 -6
  177. package/mixins/create-edit-view/impl.js +5 -3
  178. package/mixins/resource-fetch-api-pagination.js +21 -1
  179. package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +57 -0
  180. package/models/__tests__/compliance.cattle.io.clusterscan.test.ts +144 -0
  181. package/models/__tests__/fleet-application.test.ts +175 -0
  182. package/models/__tests__/fleet.cattle.io.bundle.test.ts +169 -0
  183. package/models/__tests__/fleet.cattle.io.helmop.test.ts +84 -0
  184. package/models/__tests__/management.cattle.io.node.ts +22 -0
  185. package/models/__tests__/namespace.test.ts +36 -0
  186. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +205 -0
  187. package/models/__tests__/secret.test.ts +68 -1
  188. package/models/__tests__/workload.test.ts +401 -26
  189. package/models/catalog.cattle.io.clusterrepo.js +28 -4
  190. package/models/compliance.cattle.io.clusterscan.js +39 -4
  191. package/models/fleet-application.js +4 -0
  192. package/models/fleet.cattle.io.helmop.js +20 -1
  193. package/models/management.cattle.io.cluster.js +39 -5
  194. package/models/management.cattle.io.node.js +44 -3
  195. package/models/namespace.js +1 -1
  196. package/models/pod.js +46 -3
  197. package/models/provisioning.cattle.io.cluster.js +64 -14
  198. package/models/rke.cattle.io.etcdsnapshot.js +17 -9
  199. package/models/secret.js +19 -0
  200. package/models/workload.js +120 -20
  201. package/models/workload.service.js +5 -0
  202. package/package.json +14 -13
  203. package/pages/about.vue +5 -6
  204. package/pages/auth/login.vue +0 -35
  205. package/pages/auth/setup.vue +11 -0
  206. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +2 -2
  207. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +10 -1
  208. package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +93 -0
  209. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +485 -107
  210. package/pages/c/_cluster/apps/charts/chart.vue +2 -1
  211. package/pages/c/_cluster/apps/charts/index.vue +48 -10
  212. package/pages/c/_cluster/apps/charts/install.vue +236 -144
  213. package/pages/c/_cluster/auth/roles/index.vue +5 -4
  214. package/pages/c/_cluster/explorer/workload-dashboard/ByNamespaceSection.vue +31 -0
  215. package/pages/c/_cluster/explorer/workload-dashboard/ByStateSection.vue +138 -0
  216. package/pages/c/_cluster/explorer/workload-dashboard/ByTypeSection.vue +30 -0
  217. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadCard.vue +155 -0
  218. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadNamespaceCard.vue +142 -0
  219. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadTypeCard.vue +159 -0
  220. package/pages/c/_cluster/explorer/workload-dashboard/__tests__/composable.test.ts +561 -0
  221. package/pages/c/_cluster/explorer/workload-dashboard/composable.ts +440 -0
  222. package/pages/c/_cluster/explorer/workload-dashboard/index.vue +187 -0
  223. package/pages/c/_cluster/explorer/workload-dashboard/types.ts +80 -0
  224. package/pages/c/_cluster/fleet/application/create.vue +187 -136
  225. package/pages/c/_cluster/fleet/application/index.vue +5 -3
  226. package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailBody.vue +338 -0
  227. package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailHeader.vue +121 -0
  228. package/pages/c/_cluster/fleet/application/suse-app-collection/chart.vue +369 -0
  229. package/pages/c/_cluster/fleet/application/suse-app-collection/charts.vue +248 -0
  230. package/pages/c/_cluster/fleet/application/suse-app-collection/credentials.vue +310 -0
  231. package/pages/c/_cluster/fleet/index.vue +2 -2
  232. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +96 -0
  233. package/pages/c/_cluster/uiplugins/index.vue +15 -0
  234. package/pages/fail-whale.vue +16 -11
  235. package/pages/home.vue +16 -46
  236. package/pkg/require-asset.lib.js +25 -0
  237. package/pkg/vue.config.js +7 -0
  238. package/plugins/clean-html.d.ts +9 -0
  239. package/plugins/dashboard-store/__tests__/resource-class.test.ts +177 -0
  240. package/plugins/dashboard-store/getters.js +0 -1
  241. package/plugins/dashboard-store/resource-class.js +114 -19
  242. package/plugins/steve/__tests__/actions.test.ts +212 -0
  243. package/plugins/steve/actions.js +96 -0
  244. package/plugins/steve/steve-pagination-utils.ts +1 -1
  245. package/rancher-components/Accordion/Accordion.vue +53 -9
  246. package/rancher-components/Form/Checkbox/Checkbox.vue +14 -0
  247. package/rancher-components/Form/Radio/RadioButton.vue +17 -1
  248. package/rancher-components/Form/Radio/RadioGroup.vue +10 -0
  249. package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +30 -0
  250. package/rancher-components/Form/TextArea/__tests__/TextAreaAutoGrow.test.ts +95 -0
  251. package/rancher-components/Pill/RcTag/RcTag.vue +3 -2
  252. package/rancher-components/RcButton/RcButton.test.ts +103 -0
  253. package/rancher-components/RcButton/RcButton.vue +94 -15
  254. package/rancher-components/RcButton/index.ts +1 -1
  255. package/rancher-components/RcButton/types.ts +3 -0
  256. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +6 -1
  257. package/rancher-components/RcItemCard/RcItemCard.test.ts +18 -0
  258. package/rancher-components/RcItemCard/RcItemCard.vue +2 -2
  259. package/rancher-components/RcSection/RcSection.vue +28 -3
  260. package/scripts/extension/helm/package/Dockerfile +1 -1
  261. package/scripts/test-plugins-build.sh +2 -1
  262. package/store/__tests__/features.test.ts +131 -0
  263. package/store/__tests__/growl.test.ts +374 -0
  264. package/store/__tests__/modal.test.ts +131 -0
  265. package/store/__tests__/notifications.test.ts +434 -0
  266. package/store/__tests__/slideInPanel.test.ts +88 -0
  267. package/store/__tests__/type-map.utils.test.ts +433 -0
  268. package/store/catalog.js +57 -0
  269. package/store/features.js +4 -0
  270. package/store/plugins.js +7 -4
  271. package/types/components/buttonGroup.ts +5 -0
  272. package/types/shell/index.d.ts +166 -70
  273. package/utils/__tests__/auth.test.ts +273 -0
  274. package/utils/__tests__/computed.test.ts +193 -0
  275. package/utils/__tests__/cspAdaptor.test.ts +163 -0
  276. package/utils/__tests__/dom.test.ts +81 -0
  277. package/utils/__tests__/duration.test.ts +37 -1
  278. package/utils/__tests__/dynamic-importer.test.ts +102 -0
  279. package/utils/__tests__/fleet-appco.test.ts +312 -0
  280. package/utils/__tests__/monitoring.test.ts +130 -0
  281. package/utils/__tests__/object.test.ts +22 -0
  282. package/utils/__tests__/operation-cr.test.ts +34 -0
  283. package/utils/__tests__/platform.test.ts +91 -0
  284. package/utils/__tests__/position.test.ts +237 -0
  285. package/utils/__tests__/provider.test.ts +51 -1
  286. package/utils/__tests__/queue.test.ts +232 -0
  287. package/utils/__tests__/release-notes.test.ts +221 -0
  288. package/utils/__tests__/router.test.js +254 -1
  289. package/utils/__tests__/select.test.ts +208 -0
  290. package/utils/__tests__/time.test.ts +265 -1
  291. package/utils/__tests__/title.test.ts +47 -0
  292. package/utils/__tests__/width.test.ts +53 -0
  293. package/utils/__tests__/window.test.ts +158 -0
  294. package/utils/__tests__/xccdf.test.ts +126 -6
  295. package/utils/crypto/__tests__/browserHashUtils.test.ts +98 -0
  296. package/utils/crypto/__tests__/index.test.ts +144 -0
  297. package/utils/duration.ts +104 -0
  298. package/utils/dynamic-content/__tests__/notification-handler.test.ts +196 -0
  299. package/utils/dynamic-content/info.ts +2 -1
  300. package/utils/error.js +13 -0
  301. package/utils/fleet-appco.ts +323 -0
  302. package/utils/object.js +22 -2
  303. package/utils/operation-cr.js +19 -0
  304. package/utils/provider.ts +12 -0
  305. package/utils/require-asset.ts +7 -0
  306. package/utils/validators/__tests__/container-images.test.ts +104 -0
  307. package/utils/validators/__tests__/flow-output.test.ts +91 -0
  308. package/utils/validators/__tests__/logging-outputs.test.ts +58 -0
  309. package/utils/validators/__tests__/monitoring-route.test.ts +119 -0
  310. package/utils/validators/__tests__/private-registry.test.ts +27 -15
  311. package/utils/validators/private-registry.ts +15 -4
  312. package/utils/xccdf.ts +39 -42
  313. package/vue.config.js +1 -1
  314. package/pages/support/index.vue +0 -264
  315. package/utils/duration.js +0 -43
@@ -22,7 +22,6 @@ import Tabbed from '@shell/components/Tabbed';
22
22
  import UnitInput from '@shell/components/form/UnitInput';
23
23
  import YamlEditor, { EDITOR_MODES } from '@shell/components/YamlEditor';
24
24
  import Wizard from '@shell/components/Wizard';
25
- import TypeDescription from '@shell/components/TypeDescription';
26
25
  import ChartMixin from '@shell/mixins/chart';
27
26
  import ChildHook, { BEFORE_SAVE_HOOKS, AFTER_SAVE_HOOKS } from '@shell/mixins/child-hook';
28
27
  import {
@@ -41,9 +40,12 @@ import {
41
40
  import { ignoreVariables } from './install.helpers';
42
41
  import { findBy, insertAt } from '@shell/utils/array';
43
42
  import { saferDump } from '@shell/utils/create-yaml';
44
- import { WINDOWS, isRancherRepo, getPermittedOSs } from '@shell/store/catalog';
43
+ import { addParam } from '@shell/utils/url';
44
+ import { WINDOWS } from '@shell/store/catalog';
45
45
  import { SETTING } from '@shell/config/settings';
46
46
  import SelectOrCreateAuthSecret from '@shell/components/form/SelectOrCreateAuthSecret.vue';
47
+ import PrivateRegistry from '@shell/components/form/PrivateRegistry.vue';
48
+ import { PRIVATE_REGISTRY_CONTEXT } from '@shell/components/form/PrivateRegistry.constants';
47
49
  import { generateRandomAlphaString } from '@shell/utils/string';
48
50
 
49
51
  const VALUES_STATE = {
@@ -96,8 +98,8 @@ export default {
96
98
  UnitInput,
97
99
  YamlEditor,
98
100
  Wizard,
99
- TypeDescription,
100
- SelectOrCreateAuthSecret
101
+ SelectOrCreateAuthSecret,
102
+ PrivateRegistry
101
103
  },
102
104
 
103
105
  mixins: [
@@ -359,6 +361,15 @@ export default {
359
361
  this.showCustomRegistryInput = !!this.customRegistrySetting;
360
362
  }
361
363
 
364
+ // On upgrade, pre-select a single existing image pull secret in the dropdown
365
+ if (this.existing && this.showRegistryPullSecrets) {
366
+ const existingPullSecrets = this.chartValues?.global?.imagePullSecrets;
367
+
368
+ if (Array.isArray(existingPullSecrets) && existingPullSecrets.length === 1) {
369
+ this.registryPullSecret = existingPullSecrets[0];
370
+ }
371
+ }
372
+
362
373
  /* Serializes an object as a YAML document */
363
374
  this.valuesYaml = saferDump(this.chartValues);
364
375
 
@@ -455,6 +466,9 @@ export default {
455
466
  appCoDataFetched: false,
456
467
  AUTH_TYPE,
457
468
  CLUSTER_REPO_APPCO_AUTH_GENERATE_NAME,
469
+ PRIVATE_REGISTRY_CONTEXT,
470
+ skipPullSecrets: false,
471
+ registryPullSecret: null,
458
472
  stepBasic: {
459
473
  name: 'basics',
460
474
  label: this.t('catalog.install.steps.basics.label'),
@@ -601,6 +615,10 @@ export default {
601
615
  return out;
602
616
  },
603
617
 
618
+ selectedVersionOption() {
619
+ return this.filteredVersions?.find((v) => v.id === this.query.versionName) || this.query.versionName;
620
+ },
621
+
604
622
  showSelectVersionOrChart() {
605
623
  // Allow the user to choose a version if:
606
624
  // - the app exists (editing/upgrading)
@@ -680,7 +698,7 @@ export default {
680
698
  },
681
699
 
682
700
  stepperSubtext() {
683
- return this.existing && this.currentVersion !== this.targetVersion ? `${ this.currentVersion } > ${ this.targetVersion }` : this.targetVersion;
701
+ return this.mappedVersions?.find((v) => v.id === this.targetVersion)?.label || this.targetVersion;
684
702
  },
685
703
 
686
704
  readmeWindowName() {
@@ -765,24 +783,6 @@ export default {
765
783
  return { name: 'c-cluster-legacy-project' };
766
784
  },
767
785
 
768
- windowsIncompatible() {
769
- if (this.versionInfo) {
770
- const isRancher = isRancherRepo(this.repo, this.chart);
771
- const permittedSystems = getPermittedOSs(this.versionInfo?.chart?.annotations, isRancher);
772
- const incompatibleVersion = permittedSystems.length > 0 && !permittedSystems.includes('windows');
773
-
774
- if (incompatibleVersion) {
775
- if (!this.chart?.windowsIncompatible) {
776
- return this.t('catalog.charts.versionWindowsIncompatible');
777
- }
778
-
779
- return this.t('catalog.charts.windowsIncompatible');
780
- }
781
- }
782
-
783
- return null;
784
- },
785
-
786
786
  /**
787
787
  * Check if the chart contains `systemDefaultRegistry` properties.
788
788
  * If not we shouldn't apply the setting, because if the option
@@ -799,6 +799,29 @@ export default {
799
799
  return global.systemDefaultRegistry !== undefined || global.cattle?.systemDefaultRegistry !== undefined;
800
800
  },
801
801
 
802
+ showRegistryPullSecrets() {
803
+ return !!this.repo?.spec?.defaultImagePullSecrets?.length;
804
+ },
805
+
806
+ existingValuesPullSecrets() {
807
+ if (!this.existing) {
808
+ return [];
809
+ }
810
+
811
+ const pullSecrets = this.chartValues?.global?.imagePullSecrets;
812
+
813
+ return Array.isArray(pullSecrets) ? pullSecrets.filter(Boolean) : [];
814
+ },
815
+
816
+ /**
817
+ * if the system-default-pull-image-secrets global setting is set OR the current cluster has system default registry pull secrets configured
818
+ * the Rancher cluster repo will automatically be populated with
819
+ * copies of the secrets referenced in the global setting
820
+ */
821
+ repoDefaultPullSecretNames() {
822
+ return (this.repo?.spec?.defaultImagePullSecrets || []).map((s) => s.name).filter(Boolean);
823
+ },
824
+
802
825
  setImagePullSecretDataTrigger() {
803
826
  return `
804
827
  ${ this.defaultImagePullSecret?.name }
@@ -990,11 +1013,30 @@ export default {
990
1013
  }
991
1014
  }
992
1015
  },
1016
+
993
1017
  async getClusterRegistry() {
1018
+ const mgmCluster = this.$store.getters['currentCluster'];
1019
+
1020
+ // For local, imported, and hosted (AKS, EKS, GKE, ALI) clusters,
1021
+ // the cluster-scoped private registry is on the norman cluster's importedConfig.
1022
+ if (mgmCluster?.isLocal || mgmCluster?.isImported || mgmCluster?.isHostedKubernetesProvider) {
1023
+ try {
1024
+ const normanCluster = await mgmCluster.findNormanCluster();
1025
+ const importedRegistryURL = normanCluster?.importedConfig?.privateRegistryURL;
1026
+
1027
+ if (importedRegistryURL) {
1028
+ return importedRegistryURL;
1029
+ }
1030
+ } catch (e) {
1031
+ console.warn('Unable to fetch norman cluster for registry lookup: ', e); // eslint-disable-line no-console
1032
+ }
1033
+
1034
+ return;
1035
+ }
1036
+
994
1037
  const hasPermissionToSeeProvCluster = this.$store.getters[`management/schemaFor`](CAPI.RANCHER_CLUSTER);
995
1038
 
996
1039
  if (hasPermissionToSeeProvCluster) {
997
- const mgmCluster = this.$store.getters['currentCluster'];
998
1040
  const provClusterId = mgmCluster?.provClusterId;
999
1041
  let provCluster;
1000
1042
 
@@ -1164,14 +1206,22 @@ export default {
1164
1206
  const isUpgrade = !!this.existing;
1165
1207
 
1166
1208
  this.errors = [];
1209
+ // Create namespace if it doesn't exist
1210
+ // this is done before save hooks so that image pull secrets can be created in the target namespace
1211
+ await this.createNamespaceIfNeeded();
1167
1212
 
1168
- // Create namespace if it doesn't exist (before hooks run)
1169
- // And only if it is SUSE APP Collection, overall should just do the same flow
1170
- if (!isUpgrade && this.isNamespaceNew && this.repo?.isSuseAppCollection) {
1171
- await this.createNamespaceIfNeeded();
1172
- }
1213
+ const hookResults = await this.applyHooks(BEFORE_SAVE_HOOKS);
1173
1214
 
1174
- await this.applyHooks(BEFORE_SAVE_HOOKS);
1215
+ // When a new pull secret is created by SelectOrCreateAuthSecret inside
1216
+ // PrivateRegistry, the emit chain does not propagate the secret name
1217
+ // back to registryPullSecret in time. Read it from the hook result.
1218
+ if (this.showRegistryPullSecrets && !this.skipPullSecrets && !this.registryPullSecret) {
1219
+ const createdSecret = hookResults?.registerAuthSecret;
1220
+
1221
+ if (createdSecret?.metadata?.name) {
1222
+ this.registryPullSecret = createdSecret.metadata.name;
1223
+ }
1224
+ }
1175
1225
 
1176
1226
  const { errors, input } = this.actionInput(isUpgrade);
1177
1227
 
@@ -1182,7 +1232,16 @@ export default {
1182
1232
  return;
1183
1233
  }
1184
1234
 
1185
- const res = await this.repo.doAction((isUpgrade ? 'upgrade' : 'install'), input);
1235
+ const actionName = isUpgrade ? 'upgrade' : 'install';
1236
+ const actionOpt = {};
1237
+
1238
+ if (this.skipPullSecrets) {
1239
+ const baseUrl = this.repo.actionLinkFor(actionName);
1240
+
1241
+ actionOpt.url = addParam(baseUrl, 'skipPullSecrets', 'true');
1242
+ }
1243
+
1244
+ const res = await this.repo.doAction(actionName, input, actionOpt);
1186
1245
  const operationId = `${ res.operationNamespace }/${ res.operationName }`;
1187
1246
 
1188
1247
  // Non-admins without a cluster won't be able to fetch operations immediately
@@ -1243,6 +1302,15 @@ export default {
1243
1302
  set(global, 'systemDefaultRegistry', this.customRegistrySetting);
1244
1303
  }
1245
1304
 
1305
+ if (this.showRegistryPullSecrets && this.registryPullSecret) {
1306
+ // User explicitly selected or created a pull secret
1307
+ set(global, 'imagePullSecrets', [this.registryPullSecret]);
1308
+ } else if (this.showRegistryPullSecrets) {
1309
+ // User chose "skip" or "use default" — remove explicit imagePullSecrets
1310
+ // so the backend falls back to the repo/global defaults
1311
+ delete global.imagePullSecrets;
1312
+ }
1313
+
1246
1314
  setIfNotSet(global, 'cattle.systemProjectId', systemProjectId);
1247
1315
  setIfNotSet(cattle, 'url', serverUrl);
1248
1316
  setIfNotSet(cattle, 'rkePathPrefix', pathPrefix);
@@ -1345,7 +1413,6 @@ export default {
1345
1413
  */
1346
1414
 
1347
1415
  this.addGlobalValuesTo(values);
1348
-
1349
1416
  const form = JSON.parse(JSON.stringify(this.value));
1350
1417
 
1351
1418
  /*
@@ -1504,6 +1571,8 @@ export default {
1504
1571
  }
1505
1572
  },
1506
1573
 
1574
+ // not the same as PrivateRegistry pull secrets which are created based off the global/cluster system default registry hostname value not the repo url directly
1575
+ // those secrets will be created in a beforeSaveHook managed by SelectOrCreateAuthSecret
1507
1576
  async createImagePullSecret() {
1508
1577
  if (!this.repo?.isSuseAppCollection) {
1509
1578
  return;
@@ -1554,10 +1623,9 @@ export default {
1554
1623
  <Loading v-if="$fetchState.pending" />
1555
1624
  <div
1556
1625
  v-else-if="!legacyApp && !mcapp"
1557
- class="install-steps pt-20"
1626
+ class="install-steps"
1558
1627
  :class="{ 'isPlainLayout': isPlainLayout}"
1559
1628
  >
1560
- <TypeDescription resource="chart" />
1561
1629
  <Wizard
1562
1630
  v-if="value"
1563
1631
  :steps="steps"
@@ -1567,25 +1635,45 @@ export default {
1567
1635
  :banner-title-subtext="stepperSubtext"
1568
1636
  :finish-mode="action.name"
1569
1637
  :header-mode="action.tKey"
1638
+ :show-step-header="false"
1570
1639
  class="wizard"
1571
- :class="{'windowsIncompatible': windowsIncompatible}"
1572
1640
  @cancel="cancel"
1573
1641
  @finish="finish"
1574
1642
  >
1575
- <template #bannerTitleImage>
1576
- <div>
1577
- <div class="logo-bg">
1578
- <LazyImage
1579
- :src="chart ? chart.icon : ''"
1580
- class="logo"
1581
- />
1643
+ <template #bannerTitle>
1644
+ <div class="chart-title-container">
1645
+ <div class="logo-container">
1646
+ <div class="logo-box">
1647
+ <LazyImage
1648
+ :src="chart ? chart.icon : ''"
1649
+ class="logo"
1650
+ />
1651
+ </div>
1652
+ </div>
1653
+ <div class="chart-title">
1654
+ <h1>
1655
+ <router-link
1656
+ v-if="chart"
1657
+ :to="chartLocation()"
1658
+ data-testid="chart-install-name-link"
1659
+ >
1660
+ {{ stepperName }}
1661
+ </router-link>
1662
+ <span v-else>
1663
+ {{ stepperName }}
1664
+ </span>: {{ t(`wizard.${action.tKey}`) }}
1665
+ </h1>
1666
+ <span
1667
+ v-if="stepperSubtext"
1668
+ class="subtext"
1669
+ >
1670
+ <i
1671
+ v-clean-tooltip="t('tableHeaders.version')"
1672
+ class="icon icon-version-alt"
1673
+ />
1674
+ {{ stepperSubtext }}
1675
+ </span>
1582
1676
  </div>
1583
- <label
1584
- v-if="windowsIncompatible"
1585
- class="os-label"
1586
- >
1587
- {{ windowsIncompatible }}
1588
- </label>
1589
1677
  </div>
1590
1678
  </template>
1591
1679
  <template #basics>
@@ -1629,20 +1717,26 @@ export default {
1629
1717
  v-if="showSelectVersionOrChart"
1630
1718
  class="row mb-20"
1631
1719
  >
1632
- <div class="col span-4">
1633
- <!-- We have a chart for the app, let the user select a new version -->
1720
+ <!-- We have a chart for the app, let the user select a new version -->
1721
+ <div
1722
+ v-if="chart"
1723
+ class="col span-4"
1724
+ >
1634
1725
  <LabeledSelect
1635
- v-if="chart"
1636
1726
  data-testid="chart-version-selector"
1637
1727
  :label="t('catalog.install.version')"
1638
- :value="query.versionName"
1728
+ :value="selectedVersionOption"
1639
1729
  :options="filteredVersions"
1640
1730
  :selectable="version => !version.disabled"
1641
1731
  @update:value="selectVersion"
1642
1732
  />
1643
- <!-- Can't find the chart for the app, let the user try to select one -->
1733
+ </div>
1734
+ <!-- Can't find the chart for the app, let the user try to select one -->
1735
+ <div
1736
+ v-else
1737
+ class="col span-4"
1738
+ >
1644
1739
  <LabeledSelect
1645
- v-else
1646
1740
  :label="t('catalog.install.chart')"
1647
1741
  :value="chart"
1648
1742
  :options="charts"
@@ -1744,26 +1838,27 @@ export default {
1744
1838
  :label="t('catalog.install.steps.helmCli.checkbox', { action: action.name, existing: !!existing })"
1745
1839
  />
1746
1840
 
1747
- <Checkbox
1841
+ <PrivateRegistry
1748
1842
  v-if="showCustomRegistry"
1749
- v-model:value="showCustomRegistryInput"
1750
- class="mb-20"
1751
- data-testid="custom-registry-checkbox"
1752
- :label="t('catalog.chart.registry.custom.checkBoxLabel')"
1753
- :tooltip="t('catalog.chart.registry.tooltip')"
1843
+ :context="PRIVATE_REGISTRY_CONTEXT.CHARTS"
1844
+ :value="customRegistrySetting"
1845
+ :enabled="showCustomRegistryInput"
1846
+ :default-registry="defaultRegistrySetting"
1847
+ :namespace="targetNamespace"
1848
+ in-store="cluster"
1849
+ :register-before-hook="registerBeforeHook"
1850
+ :show-pull-secrets="showRegistryPullSecrets"
1851
+ :repo-default-pull-secrets="repoDefaultPullSecretNames"
1852
+ :existing-values-pull-secrets="existingValuesPullSecrets"
1853
+ :pull-secret="registryPullSecret"
1854
+ :skip-pull-secrets="skipPullSecrets"
1855
+ checkbox-test-id="custom-registry-checkbox"
1856
+ input-test-id="custom-registry-input"
1857
+ @update:value="(val) => customRegistrySetting = val"
1858
+ @update:enabled="(val) => showCustomRegistryInput = val"
1859
+ @update:pull-secret="(val) => registryPullSecret = val"
1860
+ @update:skip-pull-secrets="(val) => skipPullSecrets = val"
1754
1861
  />
1755
- <div class="row">
1756
- <div class="col span-6">
1757
- <LabeledInput
1758
- v-if="showCustomRegistryInput"
1759
- v-model:value="customRegistrySetting"
1760
- data-testid="custom-registry-input"
1761
- label-key="catalog.chart.registry.custom.inputLabel"
1762
- placeholder-key="catalog.chart.registry.custom.placeholder"
1763
- :min-height="30"
1764
- />
1765
- </div>
1766
- </div>
1767
1862
  <div
1768
1863
  class="step__values__controls--spacer"
1769
1864
  style="flex:1"
@@ -1791,7 +1886,7 @@ export default {
1791
1886
  <LabeledSelect
1792
1887
  v-if="chart"
1793
1888
  :label="t('catalog.install.version')"
1794
- :value="query.versionName"
1889
+ :value="selectedVersionOption"
1795
1890
  :options="filteredVersions"
1796
1891
  :selectable="version => !version.disabled"
1797
1892
  @update:value="selectVersion"
@@ -2046,30 +2141,37 @@ export default {
2046
2141
  :class="{ 'isPlainLayout': isPlainLayout}"
2047
2142
  >
2048
2143
  <div class="outer-container">
2049
- <div class="header mb-20">
2050
- <div class="title">
2051
- <div class="top choice-banner">
2052
- <div class="title">
2053
- <!-- Logo -->
2054
- <slot name="bannerTitleImage">
2055
- <div class="round-image">
2056
- <LazyImage
2057
- :src="chart ? chart.icon : ''"
2058
- class="logo"
2059
- />
2060
- </div>
2061
- </slot>
2062
- <!-- Title with subtext -->
2063
- <div class="subtitle">
2064
- <h2 v-if="stepperName">
2065
- {{ stepperName }}
2066
- </h2>
2067
- <span
2068
- v-if="stepperSubtext"
2069
- class="subtext"
2070
- >{{ stepperSubtext }}</span>
2144
+ <div class="header mmt-6 mmb-6">
2145
+ <div class="top choice-banner">
2146
+ <div class="chart-title-container mmb-6">
2147
+ <div class="logo-container">
2148
+ <div class="logo-box">
2149
+ <LazyImage
2150
+ :src="chart ? chart.icon : ''"
2151
+ class="logo"
2152
+ />
2071
2153
  </div>
2072
2154
  </div>
2155
+ <div class="chart-title">
2156
+ <h2 v-if="stepperName">
2157
+ <router-link
2158
+ :to="chartLocation()"
2159
+ data-testid="chart-install-name-link"
2160
+ >
2161
+ {{ stepperName }}
2162
+ </router-link>
2163
+ </h2>
2164
+ <span
2165
+ v-if="stepperSubtext"
2166
+ class="subtext"
2167
+ >
2168
+ <i
2169
+ v-clean-tooltip="t('tableHeaders.version')"
2170
+ class="icon icon-version-alt"
2171
+ />
2172
+ {{ stepperSubtext }}
2173
+ </span>
2174
+ </div>
2073
2175
  </div>
2074
2176
  </div>
2075
2177
  </div>
@@ -2100,12 +2202,16 @@ export default {
2100
2202
  </template>
2101
2203
 
2102
2204
  <style lang="scss" scoped>
2205
+ .chart-version-footnote {
2206
+ margin-top: 8px;
2207
+ color: var(--input-label);
2208
+ }
2209
+
2103
2210
  $title-height: 50px;
2104
2211
  $padding: 5px;
2105
2212
  $slideout-width: 35%;
2106
2213
 
2107
2214
  .install-steps {
2108
- padding-top: 0;
2109
2215
  height: 0;
2110
2216
  position: relative;
2111
2217
  overflow: hidden;
@@ -2121,37 +2227,29 @@ export default {
2121
2227
  }
2122
2228
  }
2123
2229
 
2124
- .wizard {
2125
- .logo-bg {
2126
- margin-right: 10px;
2127
- height: $title-height;
2128
- width: $title-height;
2129
- background-color: white;
2130
- border: $padding solid white;
2131
- border-radius: calc( 3 * var(--border-radius));
2132
- position: relative;
2133
- }
2230
+ .logo-container {
2231
+ display: flex;
2232
+ flex-direction: column;
2233
+ align-items: center;
2134
2234
 
2135
- .logo {
2136
- max-height: $title-height - 2 * $padding;
2137
- max-width: $title-height - 2 * $padding;
2138
- position: absolute;
2139
- width: auto;
2140
- height: auto;
2141
- top: 0;
2142
- right: 0;
2143
- bottom: 0;
2144
- left: 0;
2145
- margin: auto;
2146
- }
2235
+ .logo-box {
2236
+ width: 60px;
2237
+ height: 60px;
2238
+ display: flex;
2239
+ justify-content: center;
2240
+ align-items: center;
2241
+ background: #fff;
2242
+ border-radius: var(--border-radius);
2147
2243
 
2148
- // Hack - We're adding an absolute tag under the logo that we want to consume space without breaking vertical alignment of row.
2149
- // W ith the slots available this isn't possible without adding tag specific styles to the root wizard classes
2150
- &.windowsIncompatible {
2151
- :deep() .header {
2152
- padding-bottom: 15px;
2244
+ .logo {
2245
+ width: 48px;
2246
+ height: 48px;
2247
+ object-fit: contain;
2153
2248
  }
2154
2249
  }
2250
+ }
2251
+
2252
+ .wizard {
2155
2253
 
2156
2254
  .os-label {
2157
2255
  position: absolute;
@@ -2300,38 +2398,43 @@ export default {
2300
2398
 
2301
2399
  border-bottom: var(--header-border-size) solid var(--header-border);
2302
2400
 
2303
- & > .title {
2401
+ & > .chart-title-container {
2304
2402
  flex: 1;
2305
2403
  min-height: 75px;
2306
2404
  }
2307
2405
 
2308
2406
  .choice-banner {
2309
2407
 
2310
- flex-basis: 40%;
2408
+ flex-basis: 100%;
2311
2409
  display: flex;
2312
2410
  align-items: center;
2313
2411
 
2314
2412
  &.top {
2315
2413
 
2316
- H2 {
2414
+ H1, H2 {
2317
2415
  margin: 0px;
2318
2416
  }
2319
2417
 
2320
- .title{
2418
+ .chart-title-container {
2321
2419
  display: flex;
2322
- align-items: center;
2420
+ align-items: flex-start;
2323
2421
  justify-content: space-evenly;
2324
-
2325
- & > .subtitle {
2326
- margin: 0 20px;
2327
- }
2422
+ gap: 24px;
2328
2423
  }
2329
2424
 
2330
- .subtitle{
2425
+ .chart-title {
2331
2426
  display: flex;
2332
2427
  flex-direction: column;
2333
2428
  & .subtext {
2429
+ display: flex;
2430
+ align-items: center;
2431
+ gap: 8px;
2334
2432
  color: var(--input-label);
2433
+ margin-top: 8px;
2434
+
2435
+ .icon-version-alt {
2436
+ font-size: 19px;
2437
+ }
2335
2438
  }
2336
2439
  }
2337
2440
 
@@ -2348,17 +2451,6 @@ export default {
2348
2451
  }
2349
2452
  }
2350
2453
 
2351
- & .round-image {
2352
- min-width: 50px;
2353
- height: 50px;
2354
- margin: 10px 10px 10px 0;
2355
- border-radius: 50%;
2356
- overflow: hidden;
2357
- .logo {
2358
- min-width: 50px;
2359
- height: 50px;
2360
- }
2361
- }
2362
2454
  }
2363
2455
  }
2364
2456
 
@@ -7,6 +7,7 @@ import Loading from '@shell/components/Loading';
7
7
  import { SUBTYPE_MAPPING, CREATE_VERBS } from '@shell/models/management.cattle.io.roletemplate';
8
8
  import { NAME } from '@shell/config/product/auth';
9
9
  import { BLANK_CLUSTER } from '@shell/store/store-types.js';
10
+ import { RcButton } from '@components/RcButton';
10
11
 
11
12
  const GLOBAL = SUBTYPE_MAPPING.GLOBAL.key;
12
13
  const CLUSTER = SUBTYPE_MAPPING.CLUSTER.key;
@@ -31,7 +32,7 @@ export default {
31
32
  name: 'Roles',
32
33
 
33
34
  components: {
34
- Tab, Tabbed, ResourceTable, Loading
35
+ Tab, Tabbed, ResourceTable, Loading, RcButton
35
36
  },
36
37
 
37
38
  async fetch() {
@@ -162,13 +163,13 @@ export default {
162
163
  </div>
163
164
  <div class="actions-container">
164
165
  <div class="actions">
165
- <router-link
166
+ <rc-button
166
167
  v-if="canCreate"
168
+ size="large"
167
169
  :to="createLocation"
168
- class="btn role-primary"
169
170
  >
170
171
  {{ createLabel }}
171
- </router-link>
172
+ </rc-button>
172
173
  </div>
173
174
  </div>
174
175
  </header>
@@ -0,0 +1,31 @@
1
+ <script setup lang="ts">
2
+ import WorkloadNamespaceCard from './WorkloadNamespaceCard.vue';
3
+ import type { WorkloadDashboardByNamespaceCard, WorkloadDashboardNamespaceNavigateFn, WorkloadDashboardFilterByNamespaceFn } from './types';
4
+
5
+ defineProps<{
6
+ cards: WorkloadDashboardByNamespaceCard[];
7
+ navigateToNamespace: WorkloadDashboardNamespaceNavigateFn;
8
+ filterByNamespace: WorkloadDashboardFilterByNamespaceFn;
9
+ }>();
10
+ </script>
11
+
12
+ <template>
13
+ <div class="card-grid">
14
+ <WorkloadNamespaceCard
15
+ v-for="card in cards"
16
+ :key="card.title"
17
+ :title="card.title"
18
+ :rows="card.rows"
19
+ :navigate-to-namespace="navigateToNamespace"
20
+ :filter-by-namespace="filterByNamespace"
21
+ />
22
+ </div>
23
+ </template>
24
+
25
+ <style lang="scss" scoped>
26
+ .card-grid {
27
+ display: grid;
28
+ grid-template-columns: repeat(3, 1fr);
29
+ gap: 15px;
30
+ }
31
+ </style>