@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
@@ -0,0 +1,440 @@
1
+ import {
2
+ ref, computed, watch, onMounted, onBeforeUnmount
3
+ } from 'vue';
4
+ import { useStore } from 'vuex';
5
+ import { useRouter, type RouteLocationRaw } from 'vue-router';
6
+ import { NAMESPACE } from '@shell/config/types';
7
+ import type { StateColor } from '@shell/utils/style';
8
+ import { useI18n } from '@shell/composables/useI18n';
9
+ import { useStateColor } from '@shell/composables/useStateColor';
10
+ import { stateDisplay } from '@shell/plugins/dashboard-store/resource-class';
11
+ import { ALL_NAMESPACES } from '@shell/store/prefs';
12
+ import {
13
+ NAMESPACE_FILTER_ALL_USER,
14
+ NAMESPACE_FILTER_ALL_SYSTEM,
15
+ NAMESPACE_FILTER_P_FULL_PREFIX,
16
+ NAMESPACE_FILTER_NS_FULL_PREFIX,
17
+ } from '@shell/utils/namespace-filter';
18
+ import stevePaginationUtils from '@shell/plugins/steve/steve-pagination-utils';
19
+ import {
20
+ WORKLOAD_RESOURCE_TYPES, COLOR_ORDER,
21
+ type WorkloadDashboardSummaryEntry,
22
+ type WorkloadDashboardEntry,
23
+ type WorkloadDashboardStateCard,
24
+ type WorkloadDashboardByStateLayout,
25
+ type WorkloadDashboardByTypeCard,
26
+ type WorkloadDashboardByNamespaceCard,
27
+ } from './types';
28
+
29
+ export function useWorkloadDashboard() {
30
+ const store = useStore();
31
+ const router = useRouter();
32
+ const { t } = useI18n(store);
33
+ const { toStateColor, resolveStateColors } = useStateColor();
34
+
35
+ const summaries = ref<WorkloadDashboardSummaryEntry[]>([]);
36
+ const fetchError = ref<string | null>(null);
37
+ const loading = ref(true);
38
+ let pollTimer: ReturnType<typeof setInterval> | null = null;
39
+
40
+ const clusterId = computed<string>(() => store.getters['clusterId']);
41
+
42
+ // ── Namespace filtering ──
43
+
44
+ const isAllNamespaces = computed<boolean>(() => store.getters['isAllNamespaces']);
45
+
46
+ const namespaceFilterParam = ref('');
47
+
48
+ function buildNamespaceFilterParam(): string {
49
+ const selection: string[] = store.getters['namespaceFilters'];
50
+ const { projectsOrNamespaces, filters } = stevePaginationUtils.createParamsFromNsFilter({
51
+ allNamespaces: store.getters['cluster/all'](NAMESPACE),
52
+ selection,
53
+ isAllNamespaces: isAllNamespaces.value,
54
+ isLocalCluster: store.getters['currentCluster']?.isLocal,
55
+ showReservedRancherNamespaces: store.getters['prefs/get'](ALL_NAMESPACES),
56
+ productHidesSystemNamespaces: store.getters['currentProduct']?.hideSystemResources,
57
+ });
58
+
59
+ // Getting the first schema is sufficient since the namespace filter param structure is the same across all resource types
60
+ const schema = WORKLOAD_RESOURCE_TYPES
61
+ .map((type) => store.getters['cluster/schemaFor'](type))
62
+ .find((s) => !!s);
63
+
64
+ // To generate proper params path to be used
65
+ const path = stevePaginationUtils.createParamsForPagination({
66
+ schema,
67
+ opt: {
68
+ pagination: {
69
+ filters,
70
+ projectsOrNamespaces,
71
+ page: 1,
72
+ sort: [],
73
+ }
74
+ }
75
+ }) || '';
76
+
77
+ return path.replace(/page=\d+&?/g, '').replace(/pagesize=\d+&?/g, '').replace(/&$/, '');
78
+ }
79
+
80
+ watch(() => store.getters['namespaceFilters'], () => {
81
+ namespaceFilterParam.value = buildNamespaceFilterParam();
82
+ }, { immediate: true });
83
+
84
+ // ── Subtitle ──
85
+
86
+ const totalWorkloads = computed<number>(() => {
87
+ return workloadData.value.reduce((sum, w) => sum + (w.error ? 0 : w.total), 0);
88
+ });
89
+
90
+ const namespaceSubtitle = computed<string>(() => {
91
+ const count = totalWorkloads.value;
92
+ const countSuffix = t('workloadDashboard.workloadCount', { count });
93
+ const filters: string[] = store.getters['namespaceFilters'];
94
+
95
+ if (isAllNamespaces.value) {
96
+ return `${ t('workloadDashboard.subtitle.allNamespaces') } ${ countSuffix }`;
97
+ }
98
+
99
+ if (filters.length === 1) {
100
+ const filter = filters[0];
101
+
102
+ if (filter === NAMESPACE_FILTER_ALL_USER) {
103
+ return `${ t('workloadDashboard.subtitle.userNamespaces') } ${ countSuffix }`;
104
+ }
105
+
106
+ if (filter === NAMESPACE_FILTER_ALL_SYSTEM) {
107
+ return `${ t('workloadDashboard.subtitle.systemNamespaces') } ${ countSuffix }`;
108
+ }
109
+
110
+ if (filter.startsWith(NAMESPACE_FILTER_P_FULL_PREFIX)) {
111
+ const projectId = filter.replace(NAMESPACE_FILTER_P_FULL_PREFIX, '');
112
+ const projects = store.getters['management/all']('management.cattle.io.project');
113
+ const project = projects.find((p: { id?: string; nameDisplay?: string; metadata?: { name: string } }) => p.id?.endsWith(`/${ projectId }`) || p.metadata?.name === projectId);
114
+
115
+ return `${ t('workloadDashboard.subtitle.project', { name: project?.nameDisplay || projectId }) } ${ countSuffix }`;
116
+ }
117
+
118
+ if (filter.startsWith(NAMESPACE_FILTER_NS_FULL_PREFIX)) {
119
+ const name = filter.replace(NAMESPACE_FILTER_NS_FULL_PREFIX, '');
120
+
121
+ return `${ t('workloadDashboard.subtitle.namespace', { name }) } ${ countSuffix }`;
122
+ }
123
+ }
124
+
125
+ return `${ t('workloadDashboard.subtitle.multipleSelected', { selected: filters.length }) } ${ countSuffix }`;
126
+ });
127
+
128
+ // ── Workload data ──
129
+
130
+ const workloadData = computed<WorkloadDashboardEntry[]>(() => {
131
+ return summaries.value.map((entry) => {
132
+ const label = t(`typeLabel."${ entry.type }"`, { count: 2 })?.trim() || entry.type;
133
+ const stateCounts: Record<string, number> = {};
134
+ let total = 0;
135
+
136
+ if (entry.summary) {
137
+ for (const s of entry.summary) {
138
+ if (s.property === 'metadata.state.name') {
139
+ for (const [state, detail] of Object.entries(s.counts)) {
140
+ stateCounts[state] = detail.total;
141
+ total += detail.total;
142
+ }
143
+ }
144
+ }
145
+ }
146
+
147
+ return {
148
+ type: entry.type,
149
+ label,
150
+ total,
151
+ stateCounts,
152
+ error: entry.error,
153
+ };
154
+ });
155
+ });
156
+
157
+ const hasWorkloads = computed<boolean>(() => {
158
+ return workloadData.value.some((w) => !w.error && w.total > 0);
159
+ });
160
+
161
+ // ── By State cards ──
162
+
163
+ const byStateCards = computed<WorkloadDashboardStateCard[]>(() => {
164
+ const colorGroups: Record<string, Record<string, { count: number; type: string; stateNames: Set<string> }>> = {
165
+ error: {},
166
+ warning: {},
167
+ info: {},
168
+ success: {},
169
+ disabled: {},
170
+ };
171
+
172
+ for (const w of workloadData.value) {
173
+ if (w.error || w.total === 0) {
174
+ continue;
175
+ }
176
+
177
+ for (const [state, count] of Object.entries(w.stateCounts)) {
178
+ const color = toStateColor(state, w.type);
179
+
180
+ if (!colorGroups[color][w.label]) {
181
+ colorGroups[color][w.label] = {
182
+ count: 0, type: w.type, stateNames: new Set()
183
+ };
184
+ }
185
+ colorGroups[color][w.label].count += count;
186
+ colorGroups[color][w.label].stateNames.add(state);
187
+ }
188
+ }
189
+
190
+ return Object.entries(colorGroups)
191
+ .filter(([, typeMap]) => Object.keys(typeMap).length > 0)
192
+ .map(([color, typeMap]) => ({
193
+ color: color as StateColor,
194
+ rows: Object.entries(typeMap).map(([label, { count, type, stateNames }]) => ({
195
+ label,
196
+ color: color as StateColor,
197
+ type,
198
+ stateNames: Array.from(stateNames),
199
+ counts: [{ label: '', count }],
200
+ })),
201
+ }));
202
+ });
203
+
204
+ const byStateLayout = computed<WorkloadDashboardByStateLayout>(() => {
205
+ const cards = byStateCards.value;
206
+ const hero = cards.find((c) => c.color === 'success') ||
207
+ cards.find((c) => c.color === 'info') ||
208
+ cards.find((c) => c.color === 'error') ||
209
+ (cards.length === 1 ? cards[0] : null) || null;
210
+
211
+ const others = cards.filter((c) => c !== hero);
212
+
213
+ const subHero =
214
+ (hero && others.length >= 2) ? others.find((c) => c.color === 'info') ||
215
+ null : null;
216
+
217
+ const regularCards = subHero ? others.filter((c) => c !== subHero) : others;
218
+
219
+ return {
220
+ hero,
221
+ subHero,
222
+ cards: regularCards,
223
+ };
224
+ });
225
+
226
+ // ── By Type cards ──
227
+
228
+ const byTypeCards = computed<WorkloadDashboardByTypeCard[]>(() => {
229
+ return workloadData.value.filter((w) => !w.error && w.total > 0).map((w) => {
230
+ const resources = Object.entries(w.stateCounts)
231
+ .sort(([a], [b]) => (COLOR_ORDER[toStateColor(a, w.type)] ?? 5) - (COLOR_ORDER[toStateColor(b, w.type)] ?? 5))
232
+ .map(([state, count]) => ({
233
+ stateDisplay: stateDisplay(state, true),
234
+ stateId: state,
235
+ stateSimpleColor: toStateColor(state, w.type),
236
+ count,
237
+ }));
238
+
239
+ return {
240
+ title: w.label,
241
+ type: w.type,
242
+ resources,
243
+ };
244
+ });
245
+ });
246
+
247
+ // ── By Namespace cards ──
248
+
249
+ const byNamespaceCards = computed<WorkloadDashboardByNamespaceCard[]>(() => {
250
+ // namespace -> type -> color -> { count, stateNames }
251
+ const nsMap: Record<string, Record<string, Record<string, { count: number; stateNames: Set<string> }>>> = {};
252
+
253
+ for (const entry of summaries.value) {
254
+ if (entry.error || !entry.summary) {
255
+ continue;
256
+ }
257
+
258
+ for (const s of entry.summary) {
259
+ if (s.property !== 'metadata.state.name') {
260
+ continue;
261
+ }
262
+
263
+ for (const [state, detail] of Object.entries(s.counts)) {
264
+ const color = toStateColor(state, entry.type);
265
+
266
+ for (const [ns, count] of Object.entries(detail.namespace)) {
267
+ if (!nsMap[ns]) {
268
+ nsMap[ns] = {};
269
+ }
270
+ if (!nsMap[ns][entry.type]) {
271
+ nsMap[ns][entry.type] = {};
272
+ }
273
+ if (!nsMap[ns][entry.type][color]) {
274
+ nsMap[ns][entry.type][color] = { count: 0, stateNames: new Set() };
275
+ }
276
+ nsMap[ns][entry.type][color].count += count;
277
+ nsMap[ns][entry.type][color].stateNames.add(state);
278
+ }
279
+ }
280
+ }
281
+ }
282
+
283
+ return Object.entries(nsMap)
284
+ .sort(([a], [b]) => a.localeCompare(b))
285
+ .map(([ns, typeMap]) => {
286
+ const rows = WORKLOAD_RESOURCE_TYPES
287
+ .filter((type) => typeMap[type])
288
+ .map((type) => {
289
+ const label = t(`typeLabel."${ type }"`, { count: 2 })?.trim() || type;
290
+ const counts = Object.entries(typeMap[type])
291
+ .sort(([a], [b]) => (COLOR_ORDER[a] ?? 5) - (COLOR_ORDER[b] ?? 5))
292
+ .map(([color, { count, stateNames }]) => ({
293
+ color: color as StateColor,
294
+ count,
295
+ stateNames: Array.from(stateNames),
296
+ }));
297
+
298
+ return {
299
+ label, type, counts
300
+ };
301
+ });
302
+
303
+ return { title: ns, rows };
304
+ });
305
+ });
306
+
307
+ // ── Actions ──
308
+
309
+ function resetNamespaceFilter(): void {
310
+ store.dispatch('switchNamespaces', {
311
+ ids: [],
312
+ key: clusterId.value,
313
+ });
314
+ }
315
+
316
+ function filterByNamespace(namespace: string): void {
317
+ store.dispatch('switchNamespaces', {
318
+ ids: [`${ NAMESPACE_FILTER_NS_FULL_PREFIX }${ namespace }`],
319
+ key: clusterId.value,
320
+ });
321
+ }
322
+
323
+ function resourceRoute(type: string, stateNames?: string[]): RouteLocationRaw {
324
+ const loc: { name: string; params: Record<string, string>; query?: Record<string, string> } = {
325
+ name: 'c-cluster-product-resource',
326
+ params: {
327
+ cluster: clusterId.value,
328
+ product: 'explorer',
329
+ resource: type,
330
+ },
331
+ };
332
+
333
+ if (stateNames?.length) {
334
+ loc.query = { stateFilter: stateNames.join(',') };
335
+ }
336
+
337
+ return loc;
338
+ }
339
+
340
+ function navigateToNamespace(type: string, namespace: string, stateNames?: string[]): void {
341
+ filterByNamespace(namespace);
342
+ router.push(resourceRoute(type, stateNames));
343
+ }
344
+
345
+ // ── Fetching & polling ──
346
+
347
+ async function fetchSummaries(): Promise<void> {
348
+ try {
349
+ const accessibleTypes = WORKLOAD_RESOURCE_TYPES.filter(
350
+ (type) => store.getters['cluster/canList'](type)
351
+ );
352
+
353
+ const workloadPromises = accessibleTypes.map(async(type): Promise<WorkloadDashboardSummaryEntry> => {
354
+ try {
355
+ let url = `${ store.getters['cluster/urlFor'](type) }`;
356
+
357
+ if (namespaceFilterParam.value) {
358
+ url += `&${ namespaceFilterParam.value }`;
359
+ }
360
+ url += `&summary=metadata.state.name&summaryonly&summarynamespaced`;
361
+
362
+ const res = await store.dispatch('cluster/request', { url });
363
+
364
+ return {
365
+ type, summary: res.summary || [], error: null
366
+ };
367
+ } catch (e: unknown) {
368
+ const message = e instanceof Error ? e.message : t('workloadDashboard.errors.fetchType', { type });
369
+
370
+ return {
371
+ type, summary: null, error: message
372
+ };
373
+ }
374
+ });
375
+
376
+ const results = await Promise.all(workloadPromises);
377
+
378
+ await resolveStateColors(results);
379
+ summaries.value = results;
380
+ fetchError.value = null;
381
+ } catch (e: unknown) {
382
+ fetchError.value = e instanceof Error ? e.message : t('workloadDashboard.errors.fetchAll');
383
+ }
384
+ }
385
+
386
+ function stopPollTimer(): void {
387
+ if (pollTimer) {
388
+ clearInterval(pollTimer);
389
+ pollTimer = null;
390
+ }
391
+ }
392
+
393
+ function startPollTimer(): void {
394
+ stopPollTimer();
395
+
396
+ pollTimer = setInterval(() => {
397
+ fetchSummaries();
398
+ }, 5000);
399
+ }
400
+
401
+ function handleVisibilityChange(): void {
402
+ if (document.hidden) {
403
+ stopPollTimer();
404
+ } else {
405
+ fetchSummaries();
406
+ startPollTimer();
407
+ }
408
+ }
409
+
410
+ watch(namespaceFilterParam, () => {
411
+ fetchSummaries();
412
+ startPollTimer();
413
+ });
414
+
415
+ onMounted(async() => {
416
+ await fetchSummaries();
417
+ loading.value = false;
418
+ startPollTimer();
419
+ document.addEventListener('visibilitychange', handleVisibilityChange);
420
+ });
421
+
422
+ onBeforeUnmount(() => {
423
+ stopPollTimer();
424
+ document.removeEventListener('visibilitychange', handleVisibilityChange);
425
+ });
426
+
427
+ return {
428
+ loading,
429
+ fetchError,
430
+ hasWorkloads,
431
+ namespaceSubtitle,
432
+ byStateLayout,
433
+ byTypeCards,
434
+ byNamespaceCards,
435
+ resetNamespaceFilter,
436
+ filterByNamespace,
437
+ resourceRoute,
438
+ navigateToNamespace,
439
+ };
440
+ }
@@ -0,0 +1,187 @@
1
+ <script setup lang="ts">
2
+ import { Banner } from '@components/Banner';
3
+ import Loading from '@shell/components/Loading';
4
+ import Masthead from '@shell/components/ResourceList/Masthead';
5
+ import RichTranslation from '@shell/components/RichTranslation.vue';
6
+ import SubtleLink from '@shell/components/SubtleLink.vue';
7
+ import { DOCS_BASE } from '@shell/config/private-label';
8
+ import { useI18n } from '@shell/composables/useI18n';
9
+ import { useStore } from 'vuex';
10
+ import { useWorkloadDashboard } from './composable';
11
+ import ByStateSection from './ByStateSection.vue';
12
+ import ByTypeSection from './ByTypeSection.vue';
13
+ import ByNamespaceSection from './ByNamespaceSection.vue';
14
+
15
+ const store = useStore();
16
+ const { t } = useI18n(store);
17
+
18
+ const {
19
+ loading,
20
+ fetchError,
21
+ hasWorkloads,
22
+ namespaceSubtitle,
23
+ byStateLayout,
24
+ byTypeCards,
25
+ byNamespaceCards,
26
+ resetNamespaceFilter,
27
+ filterByNamespace,
28
+ resourceRoute,
29
+ navigateToNamespace,
30
+ } = useWorkloadDashboard();
31
+ </script>
32
+
33
+ <template>
34
+ <Loading v-if="loading" />
35
+
36
+ <div
37
+ v-else
38
+ class="workload-dashboard"
39
+ >
40
+ <Banner
41
+ v-if="fetchError"
42
+ color="error"
43
+ >
44
+ {{ fetchError }}
45
+ </Banner>
46
+
47
+ <!-- ━━━ Empty state ━━━ -->
48
+ <div
49
+ v-if="!hasWorkloads"
50
+ class="empty-state"
51
+ data-testid="workload-dashboard-empty"
52
+ >
53
+ <h1 class="m-0">
54
+ {{ t('workloadDashboard.empty.title') }}
55
+ </h1>
56
+ <div class="empty-state-tips">
57
+ <RichTranslation k="workloadDashboard.empty.message">
58
+ <template #resetLink="{ content }">
59
+ <a
60
+ role="button"
61
+ tabindex="0"
62
+ class="link"
63
+ @click="resetNamespaceFilter"
64
+ @keyup.enter="resetNamespaceFilter"
65
+ >{{ content }}</a>
66
+ </template>
67
+ </RichTranslation>
68
+ <RichTranslation
69
+ k="workloadDashboard.empty.docsMessage"
70
+ tag="div"
71
+ >
72
+ <template #docsLink="{ content }">
73
+ <SubtleLink
74
+ :href="`${DOCS_BASE}/how-to-guides/new-user-guides/kubernetes-resources-setup/workloads-and-pods`"
75
+ target="_blank"
76
+ :open-in-new-tab-label="t('generic.opensInNewTab')"
77
+ >
78
+ {{ content }}
79
+ </SubtleLink>
80
+ </template>
81
+ </RichTranslation>
82
+ </div>
83
+ </div>
84
+
85
+ <template v-else>
86
+ <Masthead
87
+ resource="workload"
88
+ :type-display="t('workloadDashboard.title')"
89
+ :is-creatable="false"
90
+ :show-favorite="false"
91
+ component-testid="workload-dashboard"
92
+ >
93
+ <template #subHeader>
94
+ <div
95
+ class="text-muted mmt-1"
96
+ data-testid="workload-dashboard-subtitle"
97
+ >
98
+ {{ namespaceSubtitle }}
99
+ </div>
100
+ </template>
101
+ </Masthead>
102
+ <div class="workload-content">
103
+ <!-- ━━━ By State ━━━ -->
104
+ <div
105
+ class="section"
106
+ data-testid="workload-dashboard-by-state"
107
+ >
108
+ <h4 class="m-0 text-deemphasized">
109
+ {{ t('workloadDashboard.sections.byState') }}
110
+ </h4>
111
+ <ByStateSection
112
+ :layout="byStateLayout"
113
+ :resource-route="resourceRoute"
114
+ />
115
+ </div>
116
+
117
+ <!-- ━━━ By Type ━━━ -->
118
+ <div
119
+ class="section"
120
+ data-testid="workload-dashboard-by-type"
121
+ >
122
+ <h4 class="m-0 text-deemphasized">
123
+ {{ t('workloadDashboard.sections.byType') }}
124
+ </h4>
125
+ <ByTypeSection
126
+ :cards="byTypeCards"
127
+ :resource-route="resourceRoute"
128
+ />
129
+ </div>
130
+
131
+ <!-- ━━━ By Namespace ━━━ -->
132
+ <div
133
+ class="section"
134
+ data-testid="workload-dashboard-by-namespace"
135
+ >
136
+ <h4 class="m-0 text-deemphasized">
137
+ {{ t('workloadDashboard.sections.byNamespace') }}
138
+ </h4>
139
+ <ByNamespaceSection
140
+ :cards="byNamespaceCards"
141
+ :navigate-to-namespace="navigateToNamespace"
142
+ :filter-by-namespace="filterByNamespace"
143
+ />
144
+ </div>
145
+ </div>
146
+ </template>
147
+ </div>
148
+ </template>
149
+
150
+ <style lang="scss" scoped>
151
+ .workload-dashboard {
152
+ display: flex;
153
+ flex-direction: column;
154
+
155
+ .workload-content {
156
+ display: flex;
157
+ flex-direction: column;
158
+ gap: 24px;
159
+ }
160
+
161
+ .section {
162
+ h4 {
163
+ line-height: 21px;
164
+ }
165
+ gap: 16px;
166
+ display: flex;
167
+ flex-direction: column;
168
+ }
169
+
170
+ .empty-state {
171
+ text-align: center;
172
+ padding: 72px;
173
+ display: flex;
174
+ flex-direction: column;
175
+ gap: 16px;
176
+
177
+ h1 {
178
+ line-height: 38px;
179
+ }
180
+ .empty-state-tips {
181
+ font-size: 16px;
182
+ line-height: 29px;
183
+ }
184
+ }
185
+
186
+ }
187
+ </style>
@@ -0,0 +1,80 @@
1
+ import { WORKLOAD_TYPES, POD } from '@shell/config/types';
2
+ import type { StateColor } from '@shell/utils/style';
3
+ import type { RouteLocationRaw } from 'vue-router';
4
+
5
+ export interface WorkloadDashboardSummaryEntry {
6
+ type: string;
7
+ summary: { property: string; counts: Record<string, { total: number; namespace: Record<string, number> }> }[] | null;
8
+ error: string | null;
9
+ }
10
+
11
+ export interface WorkloadDashboardEntry {
12
+ type: string;
13
+ label: string;
14
+ total: number;
15
+ stateCounts: Record<string, number>;
16
+ error: string | null;
17
+ }
18
+
19
+ export interface WorkloadDashboardStateCardRow {
20
+ label: string;
21
+ color: StateColor;
22
+ type: string;
23
+ stateNames: string[];
24
+ counts: { label: string; count: number }[];
25
+ }
26
+
27
+ export interface WorkloadDashboardStateCard {
28
+ color: StateColor;
29
+ rows: WorkloadDashboardStateCardRow[];
30
+ }
31
+
32
+ export interface WorkloadDashboardByStateLayout {
33
+ hero: WorkloadDashboardStateCard | null;
34
+ subHero: WorkloadDashboardStateCard | null;
35
+ cards: WorkloadDashboardStateCard[];
36
+ }
37
+
38
+ export interface WorkloadDashboardByTypeCard {
39
+ title: string;
40
+ type: string;
41
+ resources: {
42
+ stateDisplay: string;
43
+ stateId: string;
44
+ stateSimpleColor: StateColor;
45
+ count: number;
46
+ }[];
47
+ }
48
+
49
+ export type WorkloadDashboardResourceRouteFn = (type: string, stateNames?: string[]) => RouteLocationRaw;
50
+
51
+ export interface WorkloadDashboardByNamespaceCardRow {
52
+ label: string;
53
+ type: string;
54
+ counts: {
55
+ color: StateColor;
56
+ count: number;
57
+ stateNames: string[];
58
+ }[];
59
+ }
60
+
61
+ export interface WorkloadDashboardByNamespaceCard {
62
+ title: string;
63
+ rows: WorkloadDashboardByNamespaceCardRow[];
64
+ }
65
+
66
+ export type WorkloadDashboardNamespaceNavigateFn = (type: string, namespace: string, stateNames?: string[]) => void;
67
+ export type WorkloadDashboardFilterByNamespaceFn = (namespace: string) => void;
68
+
69
+ export const WORKLOAD_RESOURCE_TYPES: string[] = [
70
+ WORKLOAD_TYPES.CRON_JOB,
71
+ WORKLOAD_TYPES.DAEMON_SET,
72
+ WORKLOAD_TYPES.DEPLOYMENT,
73
+ WORKLOAD_TYPES.JOB,
74
+ WORKLOAD_TYPES.STATEFUL_SET,
75
+ POD,
76
+ ];
77
+
78
+ export const COLOR_ORDER: Record<string, number> = {
79
+ error: 0, warning: 1, disabled: 2, info: 3, success: 4
80
+ };