@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
@@ -24,6 +24,8 @@ import BrowserTabVisibility from '@shell/mixins/browser-tab-visibility';
24
24
  import { getClusterFromRoute, getProductFromRoute } from '@shell/utils/router';
25
25
  import SideNav from '@shell/components/SideNav';
26
26
  import { Layout } from '@shell/types/window-manager';
27
+ import { RcButton } from '@components/RcButton';
28
+ import { CLUSTER_SHELL } from '@shell/store/features';
27
29
 
28
30
  const SET_LOGIN_ACTION = 'set-as-login';
29
31
 
@@ -42,6 +44,7 @@ export default {
42
44
  AwsComplianceBanner,
43
45
  Inactivity,
44
46
  SideNav,
47
+ RcButton,
45
48
  },
46
49
 
47
50
  mixins: [PageHeaderActions, Brand, BrowserTabVisibility],
@@ -138,6 +141,7 @@ export default {
138
141
  debugger;
139
142
  },
140
143
 
144
+ // Open the shell for the current cluster if the user has permissions and the feature is enabled (invoked via keyboard shortcut)
141
145
  async toggleShell() {
142
146
  const clusterId = this.$route.params.cluster;
143
147
 
@@ -145,6 +149,11 @@ export default {
145
149
  return;
146
150
  }
147
151
 
152
+ // Cluster shell is disabled via feature flag
153
+ if (!this.$store.getters['features/get'](CLUSTER_SHELL)) {
154
+ return;
155
+ }
156
+
148
157
  const cluster = await this.$store.dispatch('management/find', {
149
158
  type: MANAGEMENT.CLUSTER,
150
159
  id: clusterId,
@@ -162,10 +171,13 @@ export default {
162
171
 
163
172
  <template>
164
173
  <div class="dashboard-root">
165
- <a
166
- href="#main-content"
167
- class="skip-to-content btn role-primary"
168
- >{{ t('nav.skipToContent') }}</a>
174
+ <rc-button
175
+ size="large"
176
+ class="skip-to-content"
177
+ :to="{ hash: '#main-content' }"
178
+ >
179
+ {{ t('nav.skipToContent') }}
180
+ </rc-button>
169
181
  <FixedBanner :header="true" />
170
182
  <AwsComplianceBanner v-if="managementReady" />
171
183
  <div
@@ -12,6 +12,7 @@ import Inactivity from '@shell/components/Inactivity';
12
12
  import { mapState, mapGetters } from 'vuex';
13
13
  import PromptModal from '@shell/components/PromptModal';
14
14
  import { Layout } from '@shell/types/window-manager';
15
+ import { RcButton } from '@components/RcButton';
15
16
 
16
17
  export default {
17
18
 
@@ -24,6 +25,7 @@ export default {
24
25
  AwsComplianceBanner,
25
26
  Inactivity,
26
27
  PromptModal,
28
+ RcButton
27
29
  },
28
30
 
29
31
  mixins: [Brand, BrowserTabVisibility],
@@ -62,10 +64,13 @@ export default {
62
64
 
63
65
  <template>
64
66
  <div class="dashboard-root">
65
- <a
66
- href="#main-content"
67
- class="skip-to-content btn role-primary"
68
- >{{ t('nav.skipToContent') }}</a>
67
+ <rc-button
68
+ size="large"
69
+ class="skip-to-content"
70
+ :to="{ hash: '#main-content' }"
71
+ >
72
+ {{ t('nav.skipToContent') }}
73
+ </rc-button>
69
74
  <FixedBanner :header="true" />
70
75
  <Inactivity />
71
76
  <AwsComplianceBanner />
@@ -15,6 +15,7 @@ import Inactivity from '@shell/components/Inactivity';
15
15
  import { mapGetters } from 'vuex';
16
16
  import PromptModal from '@shell/components/PromptModal';
17
17
  import { Layout } from '@shell/types/window-manager';
18
+ import { RcButton } from '@components/RcButton';
18
19
 
19
20
  export default {
20
21
 
@@ -30,6 +31,7 @@ export default {
30
31
  SlideInPanelManager,
31
32
  AwsComplianceBanner,
32
33
  Inactivity,
34
+ RcButton
33
35
  },
34
36
 
35
37
  mixins: [Brand, BrowserTabVisibility],
@@ -66,10 +68,13 @@ export default {
66
68
 
67
69
  <template>
68
70
  <div class="dashboard-root">
69
- <a
70
- href="#main-content"
71
- class="skip-to-content btn role-primary"
72
- >{{ t('nav.skipToContent') }}</a>
71
+ <rc-button
72
+ size="large"
73
+ class="skip-to-content"
74
+ :to="{ hash: '#main-content' }"
75
+ >
76
+ {{ t('nav.skipToContent') }}
77
+ </rc-button>
73
78
  <FixedBanner :header="true" />
74
79
  <AwsComplianceBanner />
75
80
 
@@ -0,0 +1,56 @@
1
+ import { ref } from 'vue';
2
+ import { useHelmOpResources } from '@shell/composables/useHelmOpResources';
3
+
4
+ describe('useHelmOpResources', () => {
5
+ let emit: jest.Mock;
6
+
7
+ beforeEach(() => {
8
+ emit = jest.fn();
9
+ });
10
+
11
+ describe('updateCorrectDrift', () => {
12
+ it('should emit update:correct-drift with the given value', () => {
13
+ const { updateCorrectDrift } = useHelmOpResources(emit, ref([]));
14
+
15
+ updateCorrectDrift(true);
16
+
17
+ expect(emit).toHaveBeenCalledWith('update:correct-drift', true);
18
+ });
19
+ });
20
+
21
+ describe('updateSecrets', () => {
22
+ it('should emit update:downstream-resources with kind Secret', () => {
23
+ const { updateSecrets } = useHelmOpResources(emit, ref([]));
24
+
25
+ updateSecrets(['secret-a', 'secret-b']);
26
+
27
+ expect(emit).toHaveBeenCalledWith('update:downstream-resources', { kind: 'Secret', list: ['secret-a', 'secret-b'] });
28
+ });
29
+
30
+ it('should append locked secrets that are not already in the list', () => {
31
+ const { updateSecrets } = useHelmOpResources(emit, ref(['locked-secret']));
32
+
33
+ updateSecrets(['secret-a']);
34
+
35
+ expect(emit).toHaveBeenCalledWith('update:downstream-resources', { kind: 'Secret', list: ['secret-a', 'locked-secret'] });
36
+ });
37
+
38
+ it('should not duplicate locked secrets already in the list', () => {
39
+ const { updateSecrets } = useHelmOpResources(emit, ref(['secret-a']));
40
+
41
+ updateSecrets(['secret-a', 'secret-b']);
42
+
43
+ expect(emit).toHaveBeenCalledWith('update:downstream-resources', { kind: 'Secret', list: ['secret-a', 'secret-b'] });
44
+ });
45
+ });
46
+
47
+ describe('updateDownstreamResources', () => {
48
+ it('should emit update:downstream-resources with the given kind and list', () => {
49
+ const { updateDownstreamResources } = useHelmOpResources(emit, ref([]));
50
+
51
+ updateDownstreamResources('ConfigMap', ['cm-1']);
52
+
53
+ expect(emit).toHaveBeenCalledWith('update:downstream-resources', { kind: 'ConfigMap', list: ['cm-1'] });
54
+ });
55
+ });
56
+ });
@@ -0,0 +1,32 @@
1
+ import { type Ref, toValue } from 'vue';
2
+
3
+ type Emit = {
4
+ (e: 'update:correct-drift', value: boolean): void;
5
+ (e: 'update:downstream-resources', value: { kind: string; list: string[] }): void;
6
+ };
7
+
8
+ export function useHelmOpResources(emit: Emit, lockedSecrets: Ref<string[]> | (() => string[])) {
9
+ const updateCorrectDrift = (value: boolean) => {
10
+ emit('update:correct-drift', value);
11
+ };
12
+
13
+ const updateSecrets = (list: string[]) => {
14
+ const newList = [...list];
15
+
16
+ for (const locked of toValue(lockedSecrets)) {
17
+ if (!newList.includes(locked)) {
18
+ newList.push(locked);
19
+ }
20
+ }
21
+
22
+ emit('update:downstream-resources', { kind: 'Secret', list: newList });
23
+ };
24
+
25
+ const updateDownstreamResources = (kind: string, list: string[]) => {
26
+ emit('update:downstream-resources', { kind, list });
27
+ };
28
+
29
+ return {
30
+ updateCorrectDrift, updateSecrets, updateDownstreamResources
31
+ };
32
+ }
@@ -0,0 +1,325 @@
1
+ import { useStateColor } from '@shell/composables/useStateColor';
2
+ import type { StateSummaryEntry } from '@shell/composables/useStateColor';
3
+ import { flushPromises } from '@vue/test-utils';
4
+
5
+ const mockGetters: Record<string, any> = {};
6
+ const mockDispatch = jest.fn();
7
+
8
+ jest.mock('vuex', () => ({
9
+ useStore: () => ({
10
+ getters: new Proxy(mockGetters, {
11
+ get(target, prop: string) {
12
+ return target[prop];
13
+ },
14
+ }),
15
+ dispatch: mockDispatch,
16
+ }),
17
+ }));
18
+
19
+ jest.mock('@shell/plugins/steve/steve-pagination-utils', () => ({
20
+ __esModule: true,
21
+ default: { createParamsForPagination: jest.fn(() => 'page=1&pagesize=1') },
22
+ }));
23
+
24
+ jest.mock('@shell/plugins/steve/projectAndNamespaceFiltering.utils', () => ({
25
+ __esModule: true,
26
+ default: { createParam: jest.fn(() => '') },
27
+ }));
28
+
29
+ describe('composable: useStateColor', () => {
30
+ beforeEach(() => {
31
+ jest.clearAllMocks();
32
+ Object.keys(mockGetters).forEach((key) => delete mockGetters[key]);
33
+ });
34
+
35
+ describe('toStateColor', () => {
36
+ const testType = 'apps.deployment';
37
+
38
+ it.each([
39
+ ['running', 'success'],
40
+ ['active', 'success'],
41
+ ['completed', 'success'],
42
+ ['error', 'error'],
43
+ ['failed', 'error'],
44
+ ['stopped', 'error'],
45
+ ['warning', 'warning'],
46
+ ['initializing', 'warning'],
47
+ ['pending', 'info'],
48
+ ['waiting', 'info'],
49
+ ['creating', 'info'],
50
+ ])('should return %s color for known state "%s"', (state, expectedColor) => {
51
+ const { toStateColor } = useStateColor();
52
+
53
+ expect(toStateColor(state, testType)).toStrictEqual(expectedColor);
54
+ });
55
+
56
+ it('should return "disabled" for state with "darker" color', () => {
57
+ const { toStateColor } = useStateColor();
58
+
59
+ expect(toStateColor('off', testType)).toStrictEqual('disabled');
60
+ });
61
+
62
+ it('should return "warning" for unknown states', () => {
63
+ const { toStateColor } = useStateColor();
64
+
65
+ expect(toStateColor('unknown-state', testType)).toStrictEqual('warning');
66
+ });
67
+
68
+ it('should be case-insensitive', () => {
69
+ const { toStateColor } = useStateColor();
70
+
71
+ expect(toStateColor('Running', testType)).toStrictEqual(toStateColor('running', testType));
72
+ expect(toStateColor('ERROR', testType)).toStrictEqual(toStateColor('error', testType));
73
+ });
74
+
75
+ it('should handle empty string as active state', () => {
76
+ const { toStateColor } = useStateColor();
77
+
78
+ expect(toStateColor('', testType)).toStrictEqual('success');
79
+ });
80
+
81
+ it('should cache results across calls', () => {
82
+ const { toStateColor } = useStateColor();
83
+
84
+ const first = toStateColor('running', testType);
85
+ const second = toStateColor('running', testType);
86
+
87
+ expect(first).toStrictEqual(second);
88
+ expect(first).toStrictEqual('success');
89
+ });
90
+ });
91
+
92
+ describe('resolveStateColors', () => {
93
+ const schema = { links: { collection: '/k8s/clusters/local/v1/pods' } };
94
+
95
+ beforeEach(() => {
96
+ mockGetters['cluster/schemaFor'] = () => schema;
97
+ mockGetters['cluster/urlFor'] = () => schema.links.collection;
98
+ });
99
+
100
+ it('should not make requests when all states are already cached', async() => {
101
+ const { toStateColor, resolveStateColors } = useStateColor();
102
+
103
+ mockDispatch.mockResolvedValueOnce({
104
+ data: [{
105
+ metadata: {
106
+ state: {
107
+ name: 'running', error: false, transitioning: false
108
+ }
109
+ }
110
+ }]
111
+ });
112
+ toStateColor('running', 'pod');
113
+ await flushPromises();
114
+
115
+ mockDispatch.mockClear();
116
+
117
+ const entries: StateSummaryEntry[] = [{
118
+ type: 'pod',
119
+ summary: [{ property: 'metadata.state.name', counts: { running: { total: 5, namespace: { default: 5 } } } }],
120
+ }];
121
+
122
+ await resolveStateColors(entries);
123
+
124
+ expect(mockDispatch).not.toHaveBeenCalled();
125
+ });
126
+
127
+ it('should fetch a resource to resolve unknown state colors', async() => {
128
+ const { resolveStateColors } = useStateColor();
129
+
130
+ mockDispatch.mockResolvedValueOnce({
131
+ data: [{
132
+ metadata: {
133
+ state: {
134
+ name: 'customState', error: true, transitioning: false
135
+ }
136
+ }
137
+ }],
138
+ });
139
+
140
+ const entries: StateSummaryEntry[] = [{
141
+ type: 'pod',
142
+ summary: [{ property: 'metadata.state.name', counts: { customState: { total: 3, namespace: { default: 3 } } } }],
143
+ }];
144
+
145
+ await resolveStateColors(entries);
146
+
147
+ expect(mockDispatch).toHaveBeenCalledWith('cluster/request', { url: `${ schema.links.collection }?page=1&pagesize=1` });
148
+ });
149
+
150
+ it('should resolve error states from resource metadata', async() => {
151
+ const { toStateColor, resolveStateColors } = useStateColor();
152
+
153
+ mockDispatch.mockResolvedValueOnce({
154
+ data: [{
155
+ metadata: {
156
+ state: {
157
+ name: 'init:Error', error: true, transitioning: false
158
+ }
159
+ }
160
+ }],
161
+ });
162
+
163
+ const entries: StateSummaryEntry[] = [{
164
+ type: 'pod',
165
+ summary: [{ property: 'metadata.state.name', counts: { 'init:Error': { total: 1, namespace: { default: 1 } } } }],
166
+ }];
167
+
168
+ await resolveStateColors(entries);
169
+
170
+ expect(toStateColor('init:Error', 'pod')).toStrictEqual('error');
171
+ });
172
+
173
+ it('should resolve transitioning states as info', async() => {
174
+ const { toStateColor, resolveStateColors } = useStateColor();
175
+
176
+ mockDispatch.mockResolvedValueOnce({
177
+ data: [{
178
+ metadata: {
179
+ state: {
180
+ name: 'init:0/1', error: false, transitioning: true
181
+ }
182
+ }
183
+ }],
184
+ });
185
+
186
+ const entries: StateSummaryEntry[] = [{
187
+ type: 'pod',
188
+ summary: [{ property: 'metadata.state.name', counts: { 'init:0/1': { total: 2, namespace: { default: 2 } } } }],
189
+ }];
190
+
191
+ await resolveStateColors(entries);
192
+
193
+ expect(toStateColor('init:0/1', 'pod')).toStrictEqual('info');
194
+ });
195
+
196
+ it('should skip entries with null summary', async() => {
197
+ const { resolveStateColors } = useStateColor();
198
+
199
+ const entries: StateSummaryEntry[] = [{
200
+ type: 'pod',
201
+ summary: null,
202
+ }];
203
+
204
+ await resolveStateColors(entries);
205
+
206
+ expect(mockDispatch).not.toHaveBeenCalled();
207
+ });
208
+
209
+ it('should skip summary properties that are not metadata.state.name', async() => {
210
+ const { resolveStateColors } = useStateColor();
211
+
212
+ const entries: StateSummaryEntry[] = [{
213
+ type: 'pod',
214
+ summary: [{ property: 'metadata.namespace', counts: { default: { total: 5, namespace: { default: 5 } } } }],
215
+ }];
216
+
217
+ await resolveStateColors(entries);
218
+
219
+ expect(mockDispatch).not.toHaveBeenCalled();
220
+ });
221
+
222
+ it('should handle API errors gracefully and fall back to default color', async() => {
223
+ const { toStateColor, resolveStateColors } = useStateColor();
224
+
225
+ mockDispatch.mockRejectedValueOnce(new Error('network error'));
226
+
227
+ const entries: StateSummaryEntry[] = [{
228
+ type: 'pod',
229
+ summary: [{ property: 'metadata.state.name', counts: { networkFailState: { total: 1, namespace: { default: 1 } } } }],
230
+ }];
231
+
232
+ await resolveStateColors(entries);
233
+
234
+ expect(toStateColor('networkFailState', 'pod')).toStrictEqual('warning');
235
+ });
236
+
237
+ it('should handle response with no resource data', async() => {
238
+ const { toStateColor, resolveStateColors } = useStateColor();
239
+
240
+ mockDispatch.mockResolvedValueOnce({ data: [] });
241
+
242
+ const entries: StateSummaryEntry[] = [{
243
+ type: 'pod',
244
+ summary: [{ property: 'metadata.state.name', counts: { emptyResult: { total: 1, namespace: { default: 1 } } } }],
245
+ }];
246
+
247
+ await resolveStateColors(entries);
248
+
249
+ expect(toStateColor('emptyResult', 'pod')).toStrictEqual('warning');
250
+ });
251
+
252
+ it('should handle resource without metadata.state', async() => {
253
+ const { toStateColor, resolveStateColors } = useStateColor();
254
+
255
+ mockDispatch.mockResolvedValueOnce({ data: [{ metadata: {} }] });
256
+
257
+ const entries: StateSummaryEntry[] = [{
258
+ type: 'pod',
259
+ summary: [{ property: 'metadata.state.name', counts: { noStateField: { total: 1, namespace: { default: 1 } } } }],
260
+ }];
261
+
262
+ await resolveStateColors(entries);
263
+
264
+ expect(toStateColor('noStateField', 'pod')).toStrictEqual('warning');
265
+ });
266
+
267
+ it('should use schema.links.collection for the request URL', async() => {
268
+ const { resolveStateColors } = useStateColor();
269
+ const customSchema = { links: { collection: '/k8s/clusters/c-m-abc123/v1/apps.deployments' } };
270
+
271
+ mockGetters['cluster/schemaFor'] = () => customSchema;
272
+ mockGetters['cluster/urlFor'] = () => customSchema.links.collection;
273
+ mockDispatch.mockResolvedValueOnce({ data: [] });
274
+
275
+ const entries: StateSummaryEntry[] = [{
276
+ type: 'apps.deployments',
277
+ summary: [{ property: 'metadata.state.name', counts: { urlTestState: { total: 1, namespace: { default: 1 } } } }],
278
+ }];
279
+
280
+ await resolveStateColors(entries);
281
+
282
+ expect(mockDispatch).toHaveBeenCalledWith('cluster/request', { url: `${ customSchema.links.collection }?page=1&pagesize=1` });
283
+ });
284
+
285
+ it('should resolve multiple unknown states across entries', async() => {
286
+ const { toStateColor, resolveStateColors } = useStateColor();
287
+
288
+ mockDispatch
289
+ .mockResolvedValueOnce({
290
+ data: [{
291
+ metadata: {
292
+ state: {
293
+ name: 'stateA', error: true, transitioning: false
294
+ }
295
+ }
296
+ }],
297
+ })
298
+ .mockResolvedValueOnce({
299
+ data: [{
300
+ metadata: {
301
+ state: {
302
+ name: 'stateB', error: false, transitioning: false
303
+ }
304
+ }
305
+ }],
306
+ });
307
+
308
+ const entries: StateSummaryEntry[] = [
309
+ {
310
+ type: 'pod',
311
+ summary: [{ property: 'metadata.state.name', counts: { stateA: { total: 1, namespace: { default: 1 } } } }],
312
+ },
313
+ {
314
+ type: 'apps.deployments',
315
+ summary: [{ property: 'metadata.state.name', counts: { stateB: { total: 2, namespace: { default: 2 } } } }],
316
+ },
317
+ ];
318
+
319
+ await resolveStateColors(entries);
320
+
321
+ expect(toStateColor('stateA', 'pod')).toStrictEqual('error');
322
+ expect(toStateColor('stateB', 'apps.deployments')).toStrictEqual('warning');
323
+ });
324
+ });
325
+ });
@@ -0,0 +1,128 @@
1
+ import { reactive } from 'vue';
2
+ import { useStore } from 'vuex';
3
+ import { colorForState } from '@shell/plugins/dashboard-store/resource-class';
4
+ import type { StateColor } from '@shell/utils/style';
5
+ import stevePaginationUtils from '@shell/plugins/steve/steve-pagination-utils';
6
+ import { PaginationParamFilter } from '@shell/types/store/pagination.types';
7
+
8
+ export interface StateSummaryEntry {
9
+ type: string;
10
+ summary: { property: string; counts: Record<string, { total: number; namespace: Record<string, number> }> }[] | null;
11
+ }
12
+
13
+ const stateColorCache = reactive<Record<string, StateColor>>({});
14
+ const pendingResolves = new Set<string>();
15
+
16
+ /**
17
+ * Composable that maps Kubernetes resource state names to dashboard color tokens.
18
+ *
19
+ * This scenario is to forcefully resolve state colors based on properties like error and transitioning.
20
+ * It is to be used when you only have the state name and don't have the actual state object
21
+ *
22
+ * Using a request of only one resource per unknown state, it will fetch the data to retrieve the state object.
23
+ * For normal cases where the object is already available, this composable is not needed.
24
+ *
25
+ * @returns `toStateColor` — synchronous lookup that triggers background API
26
+ * resolution for unknown states when a resource type is provided.
27
+ */
28
+ export function useStateColor() {
29
+ const store = useStore();
30
+
31
+ /**
32
+ * Returns the cached {@link StateColor} for a given state name.
33
+ * Falls back to `colorForState` as a temporary color and triggers an async
34
+ * resolve via the Steve API when `type` is provided. Once resolved, the
35
+ * reactive cache updates and the UI re-renders with the correct color.
36
+ *
37
+ * @param state - The resource state name (e.g. `'active'`, `'error'`). Case-insensitive.
38
+ * @param type - The Steve resource type (e.g. `'apps.deployment'`). Used to
39
+ * fetch a resource via the API when the state is not cached.
40
+ * @returns The resolved color token.
41
+ */
42
+ function toStateColor(state: string, type: string): StateColor {
43
+ const stateLower = (state || '').toLowerCase();
44
+ const key = `${ type }:${ stateLower }`;
45
+
46
+ if (stateColorCache[key]) {
47
+ return stateColorCache[key];
48
+ }
49
+
50
+ if (!pendingResolves.has(key)) {
51
+ pendingResolves.add(key);
52
+ resolveStateColor(key, state, type);
53
+ }
54
+
55
+ const rawColor = colorForState(stateLower, false, false).replace('text-', '');
56
+
57
+ return (rawColor === 'darker' ? 'disabled' : rawColor) as StateColor;
58
+ }
59
+
60
+ async function resolveStateColor(key: string, originalName: string, type: string): Promise<void> {
61
+ try {
62
+ const schema = store.getters['cluster/schemaFor'](type);
63
+ const params = stevePaginationUtils.createParamsForPagination({
64
+ schema,
65
+ opt: {
66
+ pagination: {
67
+ page: 1,
68
+ pageSize: 1,
69
+ sort: [],
70
+ filters: [PaginationParamFilter.createSingleField({ field: 'metadata.state.name', value: originalName })],
71
+ projectsOrNamespaces: [],
72
+ }
73
+ }
74
+ });
75
+ const url = `${ store.getters['cluster/urlFor'](type) }?${ params }`;
76
+ const res = await store.dispatch('cluster/request', { url });
77
+ const resource = res?.data?.[0];
78
+
79
+ if (resource?.metadata?.state) {
80
+ const { error: isError, transitioning, name } = resource.metadata.state;
81
+ const rawColor = colorForState(name, isError, transitioning).replace('text-', '');
82
+
83
+ stateColorCache[key] = (rawColor === 'darker' ? 'disabled' : rawColor) as StateColor;
84
+ }
85
+ } catch {
86
+ // Keep the colorForState fallback
87
+ } finally {
88
+ pendingResolves.delete(key);
89
+ }
90
+ }
91
+
92
+ async function resolveStateColors(entries: StateSummaryEntry[]): Promise<void> {
93
+ const toResolve: Array<{ key: string; originalName: string; type: string }> = [];
94
+
95
+ for (const entry of entries) {
96
+ if (!entry.summary) {
97
+ continue;
98
+ }
99
+
100
+ for (const s of entry.summary) {
101
+ if (s.property === 'metadata.state.name') {
102
+ for (const stateName of Object.keys(s.counts)) {
103
+ const key = stateName.toLowerCase();
104
+
105
+ const cacheKey = `${ entry.type }:${ key }`;
106
+
107
+ if (!stateColorCache[cacheKey] && !pendingResolves.has(cacheKey)) {
108
+ pendingResolves.add(cacheKey);
109
+ toResolve.push({
110
+ key: cacheKey, originalName: stateName, type: entry.type
111
+ });
112
+ }
113
+ }
114
+ }
115
+ }
116
+ }
117
+
118
+ const concurrency = 10;
119
+
120
+ for (let i = 0; i < toResolve.length; i += concurrency) {
121
+ await Promise.all(
122
+ toResolve.slice(i, i + concurrency).map((r) => resolveStateColor(r.key, r.originalName, r.type))
123
+ );
124
+ }
125
+ }
126
+
127
+ return { toStateColor, resolveStateColors };
128
+ }
@@ -4,3 +4,4 @@ export const ONE_WAY = [
4
4
 
5
5
  export const HARVESTER_NAME = 'harvester';
6
6
  export const SCHEDULING_CUSTOMIZATION = 'cluster-agent-scheduling-customization';
7
+ export const IMPORTED_DAY_2_OPS = 'imported-day-2-ops';
@@ -44,7 +44,7 @@ const APP_COLLECTION_LINK = {
44
44
 
45
45
  const SUPPORT_LINK = {
46
46
  key: 'commercialSupport',
47
- value: '/support',
47
+ value: 'https://www.suse.com/products/rancher/',
48
48
  enabled: true,
49
49
  readonly: true
50
50
  };