@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
@@ -3,6 +3,7 @@ import StatusCard from '@shell/components/Resource/Detail/Card/StatusCard/index.
3
3
  import StatusBar from '@shell/components/Resource/Detail/StatusBar.vue';
4
4
  import StatusRow from '@shell/components/Resource/Detail/StatusRow.vue';
5
5
  import Scaler from '@shell/components/Resource/Detail/Card/Scaler.vue';
6
+ import type { SummaryResult } from '@shell/components/Resource/Detail/Card/StateCard/composables';
6
7
 
7
8
  describe('component: StatusCard', () => {
8
9
  const mockResource = (stateDisplay: string, stateSimpleColor: string) => ({
@@ -106,4 +107,64 @@ describe('component: StatusCard', () => {
106
107
  expect(wrapper.findComponent(Scaler).exists()).toBe(false);
107
108
  });
108
109
  });
110
+
111
+ describe('with summaryData', () => {
112
+ it('should render StatusBar and StatusRows from summary counts', () => {
113
+ const summaryData: SummaryResult = {
114
+ count: 5,
115
+ summary: [{ property: 'metadata.state.name', counts: { running: { total: 3 }, error: { total: 2 } } }]
116
+ };
117
+
118
+ const wrapper = mountCard({ summaryData });
119
+
120
+ expect(wrapper.findComponent(StatusBar).exists()).toBe(true);
121
+ expect(wrapper.findAllComponents(StatusRow)).toHaveLength(2);
122
+ });
123
+
124
+ it('should use summaryData over resources when both are provided', () => {
125
+ const summaryData: SummaryResult = {
126
+ count: 4,
127
+ summary: [{ property: 'metadata.state.name', counts: { running: { total: 3 }, completed: { total: 1 } } }]
128
+ };
129
+ const resources = [
130
+ mockResource('Running', 'text-success'),
131
+ ];
132
+
133
+ const wrapper = mountCard({ summaryData, resources });
134
+
135
+ expect(wrapper.findAllComponents(StatusRow)).toHaveLength(2);
136
+ });
137
+
138
+ it('should fall back to resources when summaryData has no summary', () => {
139
+ const summaryData: SummaryResult = { count: 0, summary: null };
140
+ const resources = [
141
+ mockResource('Running', 'text-success'),
142
+ mockResource('Error', 'text-error'),
143
+ ];
144
+
145
+ const wrapper = mountCard({ summaryData, resources });
146
+
147
+ expect(wrapper.findAllComponents(StatusRow)).toHaveLength(2);
148
+ });
149
+
150
+ it('should show noResourcesMessage when summaryData has no counts', () => {
151
+ const summaryData: SummaryResult = { count: 0, summary: [] };
152
+
153
+ const wrapper = mountCard({ summaryData, noResourcesMessage: 'No pods' });
154
+
155
+ expect(wrapper.findComponent(StatusBar).exists()).toBe(false);
156
+ expect(wrapper.find('.text-deemphasized').text()).toBe('No pods');
157
+ });
158
+
159
+ it('should render a single state from summary', () => {
160
+ const summaryData: SummaryResult = {
161
+ count: 2,
162
+ summary: [{ property: 'metadata.state.name', counts: { active: { total: 2 } } }]
163
+ };
164
+
165
+ const wrapper = mountCard({ summaryData });
166
+
167
+ expect(wrapper.findAllComponents(StatusRow)).toHaveLength(1);
168
+ });
169
+ });
109
170
  });
@@ -8,10 +8,13 @@ import { useI18n } from '@shell/composables/useI18n';
8
8
  import { StateColor } from '@shell/utils/style';
9
9
  import { computed } from 'vue';
10
10
  import { useStore } from 'vuex';
11
+ import { colorForState as colorForStateFn, stateDisplay as stateDisplayFn } from '@shell/plugins/dashboard-store/resource-class';
12
+ import type { SummaryResult } from '@shell/components/Resource/Detail/Card/StateCard/composables';
11
13
 
12
14
  export interface Props {
13
15
  title: string;
14
16
  resources?: any[];
17
+ summaryData?: SummaryResult | null;
15
18
  showScaling?: boolean;
16
19
  noResourcesMessage?: string;
17
20
  }
@@ -23,23 +26,47 @@ const i18n = useI18n(store);
23
26
 
24
27
  const props = withDefaults(defineProps<Props>(), {
25
28
  resources: undefined,
29
+ summaryData: undefined,
26
30
  showScaling: false,
27
31
  noResourcesMessage: undefined
28
32
  });
29
33
  const emit = defineEmits(['decrease', 'increase']);
30
34
 
35
+ const summaryStateCounts = computed(() => {
36
+ const summary = props.summaryData?.summary;
37
+
38
+ if (!summary?.length) {
39
+ return null;
40
+ }
41
+
42
+ const entry = summary.find((s) => s.property === 'metadata.state.name');
43
+
44
+ return entry?.counts || null;
45
+ });
46
+
31
47
  const segmentAccumulator = computed(() => {
32
48
  interface Value {
33
49
  count: number;
34
50
  }
35
51
  const accumulator: {[key in StateColor]?: Value} = {};
36
-
37
- props.resources?.forEach((resource: any) => {
38
- const color: StateColor = resource.stateSimpleColor;
39
-
40
- accumulator[color] = accumulator[color] || { count: 0 };
41
- accumulator[color].count++;
42
- });
52
+ const stateCounts = summaryStateCounts.value;
53
+
54
+ if (stateCounts) {
55
+ for (const [state, stateCount] of Object.entries(stateCounts)) {
56
+ const colorRaw = colorForStateFn(state) as string;
57
+ const color = (colorRaw?.replace('text-', '') || 'disabled') as StateColor;
58
+
59
+ accumulator[color] = accumulator[color] || { count: 0 };
60
+ accumulator[color].count += stateCount.total;
61
+ }
62
+ } else {
63
+ props.resources?.forEach((resource: any) => {
64
+ const color: StateColor = resource.stateSimpleColor || 'disabled';
65
+
66
+ accumulator[color] = accumulator[color] || { count: 0 };
67
+ accumulator[color].count++;
68
+ });
69
+ }
43
70
 
44
71
  return accumulator;
45
72
  });
@@ -50,21 +77,40 @@ const rowAccumulator = computed(() => {
50
77
  color: StateColor;
51
78
  }
52
79
  const accumulator: {[key in string]: Value} = {};
53
-
54
- props.resources?.forEach((resource: any) => {
55
- accumulator[resource.stateDisplay] = accumulator[resource.stateDisplay] || { count: 0 };
56
- accumulator[resource.stateDisplay].count++;
57
- accumulator[resource.stateDisplay].color = resource.stateSimpleColor.replace('text-', '') as StateColor;
58
- });
80
+ const stateCounts = summaryStateCounts.value;
81
+
82
+ if (stateCounts) {
83
+ for (const [state, stateCount] of Object.entries(stateCounts)) {
84
+ const display = stateDisplayFn(state) as string;
85
+ const colorRaw = colorForStateFn(state) as string;
86
+ const color = (colorRaw?.replace('text-', '') || 'disabled') as StateColor;
87
+
88
+ accumulator[display] = accumulator[display] || { count: 0, color };
89
+ accumulator[display].count += stateCount.total;
90
+ }
91
+ } else {
92
+ props.resources?.forEach((resource: any) => {
93
+ const color = (resource.stateSimpleColor?.replace('text-', '') || 'disabled') as StateColor;
94
+
95
+ accumulator[resource.stateDisplay] = accumulator[resource.stateDisplay] || { count: 0, color };
96
+ accumulator[resource.stateDisplay].count++;
97
+ });
98
+ }
59
99
 
60
100
  return accumulator;
61
101
  });
62
102
 
63
103
  const percent = (count: number, total: number) => {
64
- return count / total * 100;
104
+ return total > 0 ? count / total * 100 : 0;
65
105
  };
66
106
 
67
- const count = computed(() => props.resources?.length || 0);
107
+ const count = computed(() => {
108
+ if (summaryStateCounts.value) {
109
+ return props.summaryData?.count ?? 0;
110
+ }
111
+
112
+ return props.resources?.length ?? 0;
113
+ });
68
114
 
69
115
  const segmentColors = computed(() => Object.keys(segmentAccumulator.value) as StateColor[]);
70
116
  const segments = computed(() => segmentColors.value.map((color: StateColor) => ({
@@ -103,6 +103,8 @@ const getRowValueId = (row:Row): string => `value-${ row.label }:${ row.value }`
103
103
  flex-direction: column;
104
104
 
105
105
  .row {
106
+ display: flex;
107
+ align-items: center;
106
108
  gap: 8px;
107
109
 
108
110
  // Hide clearfix pseudo-elements inherited from the global .row class
@@ -110,9 +110,12 @@ const showConfigurationMoreFocusSelector = computed(() => `[data-testid="${ show
110
110
  }
111
111
 
112
112
  .row {
113
- display: block;
114
- width: 100%;
115
113
  display: inline-block;
114
+ width: 100%;
115
+
116
+ &::before, &::after {
117
+ display: none;
118
+ }
116
119
 
117
120
  &:not(:nth-child(2)) {
118
121
  margin-top: 4px;
@@ -92,18 +92,14 @@ const previewId = randomStr();
92
92
  max-width: calc(100%);
93
93
  }
94
94
 
95
- .rc-tag {
96
- display: inline-block;
97
- line-height: normal;
98
- }
99
-
100
95
  .tag-data {
101
96
  display: inline-block;
102
97
  overflow: hidden;
103
98
  text-overflow: ellipsis;
104
99
  white-space: nowrap;
100
+ min-width: 0;
105
101
  max-width: calc(100%);
106
- line-height: normal;
102
+ line-height: 1;
107
103
  }
108
104
 
109
105
  & .btn.btn-medium.rc-button.variant-ghost {
@@ -169,7 +169,7 @@ export default {
169
169
  } else {
170
170
  let fqid = id;
171
171
 
172
- if ( schema.attributes?.namespaced && namespace ) {
172
+ if ( schema?.attributes?.namespaced && namespace ) {
173
173
  fqid = `${ namespace }/${ fqid }`;
174
174
  }
175
175
 
@@ -74,6 +74,11 @@ export default {
74
74
  default: false
75
75
  },
76
76
 
77
+ showFavorite: {
78
+ type: Boolean,
79
+ default: true
80
+ },
81
+
77
82
  /**
78
83
  * Inherited global identifier prefix for tests
79
84
  * Define a term based on the parent component to avoid conflicts on multiple components
@@ -202,7 +207,7 @@ export default {
202
207
  <div class="title">
203
208
  <h1 class="m-0">
204
209
  <TabTitle>{{ _typeDisplay }}</TabTitle> <Favorite
205
- v-if="isExplorer"
210
+ v-if="isExplorer && showFavorite"
206
211
  :resource="favoriteResource || resource"
207
212
  />
208
213
  </h1>
@@ -267,6 +272,7 @@ export default {
267
272
  'title actions'
268
273
  'sub-header sub-header'
269
274
  'state-banner state-banner';
275
+ margin-bottom: 24px;
270
276
  }
271
277
 
272
278
  .sub-header {
@@ -10,6 +10,9 @@ import { PanelLocation, ExtensionPoint } from '@shell/core/types';
10
10
  import ExtensionPanel from '@shell/components/ExtensionPanel';
11
11
  import { sameContents } from '@shell/utils/array';
12
12
  import perfSettingsUtils from '@shell/utils/perf-setting.utils';
13
+ import { BadgeState } from '@components/BadgeState';
14
+ import { stateDisplay } from '@shell/plugins/dashboard-store/resource-class';
15
+ import { useStateColor } from '@shell/composables/useStateColor';
13
16
 
14
17
  export default {
15
18
  name: ResourceListComponentName,
@@ -20,10 +23,17 @@ export default {
20
23
  Masthead,
21
24
  ResourceLoadingIndicator,
22
25
  IconMessage,
23
- ExtensionPanel
26
+ ExtensionPanel,
27
+ BadgeState,
24
28
  },
25
29
  mixins: [ResourceFetch],
26
30
 
31
+ setup() {
32
+ const { toStateColor } = useStateColor();
33
+
34
+ return { toStateColor };
35
+ },
36
+
27
37
  props: {
28
38
  hasAdvancedFiltering: {
29
39
  type: Boolean,
@@ -155,6 +165,41 @@ export default {
155
165
  return perfSettingsUtils.incrementalLoadingUtils.isEnabled(this.calcCanPaginate(), this.perfConfig);
156
166
  },
157
167
 
168
+ activeStateFilters() {
169
+ const raw = this.$route?.query?.stateFilter;
170
+
171
+ if (!raw) {
172
+ return [];
173
+ }
174
+
175
+ const states = raw.split(',').filter(Boolean);
176
+
177
+ return states.map((s) => {
178
+ const match = this.rows.find((row) => row.metadata?.state?.name === s);
179
+
180
+ if (match) {
181
+ return {
182
+ label: match.stateDisplay,
183
+ color: match.stateBackground,
184
+ };
185
+ }
186
+
187
+ return {
188
+ label: stateDisplay(s, true),
189
+ color: `bg-${ this.toStateColor(s, this.resource) }`,
190
+ };
191
+ });
192
+ },
193
+
194
+ },
195
+
196
+ methods: {
197
+ clearStateFilter() {
198
+ const query = { ...this.$route.query };
199
+
200
+ delete query.stateFilter;
201
+ this.$router.push({ ...this.$route, query });
202
+ },
158
203
  },
159
204
 
160
205
  watch: {
@@ -260,6 +305,28 @@ export default {
260
305
  :load-resources="loadResources"
261
306
  :load-indeterminate="loadIndeterminate"
262
307
  >
308
+ <template
309
+ v-if="activeStateFilters.length"
310
+ #subHeader
311
+ >
312
+ <div class="state-filter-bar text-muted">
313
+ {{ t('resourceList.stateFilterApplied') }}
314
+ <BadgeState
315
+ v-for="state in activeStateFilters"
316
+ :key="state.label"
317
+ :color="state.color"
318
+ :label="state.label"
319
+ class="badge-state-font-size"
320
+ />
321
+ <span>.</span>
322
+ <a
323
+ role="button"
324
+ @click="clearStateFilter"
325
+ >
326
+ {{ t('resourceList.clearStateFilter') }}
327
+ </a>
328
+ </div>
329
+ </template>
263
330
  <template #extraActions>
264
331
  <slot name="extraActions" />
265
332
  </template>
@@ -317,4 +384,18 @@ export default {
317
384
  top: 10px;
318
385
  right: 10px;
319
386
  }
387
+
388
+ .state-filter-bar {
389
+ display: flex;
390
+ align-items: center;
391
+ gap: 4px;
392
+
393
+ a {
394
+ cursor: pointer;
395
+ }
396
+ }
397
+
398
+ .badge-state-font-size {
399
+ font-size: .85em;
400
+ }
320
401
  </style>
@@ -746,6 +746,7 @@ export default {
746
746
  <ButtonGroup
747
747
  v-model:value="group"
748
748
  :options="_groupOptions"
749
+ size="medium"
749
750
  />
750
751
  </template>
751
752
 
@@ -74,8 +74,11 @@ export default defineComponent({
74
74
  const content = enclosingTagName ? match[2] : '';
75
75
 
76
76
  if (slots[tagName]) {
77
- // If a slot is provided for this tag, render the slot with the content.
78
- children.push(slots[tagName]({ content: purifyHTML(content) }));
77
+ const slotContent = slots[tagName]({ content: purifyHTML(content) });
78
+
79
+ if (slotContent) {
80
+ children.push(...slotContent);
81
+ }
79
82
  } else if (ALLOWED_TAGS.includes(tagName.toLowerCase())) {
80
83
  // If it's an allowed HTML tag, render it directly.
81
84
  if (content) {
@@ -29,6 +29,7 @@ export default {
29
29
  <span
30
30
  v-if="value.fromEnv"
31
31
  class="modified"
32
+ :data-testid="`advanced-setting-env-label-${value.id}`"
32
33
  >{{ t('advancedSettings.setEnv') }}</span>
33
34
  <span
34
35
  v-else-if="value.customized"
@@ -1125,7 +1125,6 @@ export default {
1125
1125
  v-clean-tooltip="actionTooltip"
1126
1126
  type="button"
1127
1127
  variant="primary"
1128
- size="large"
1129
1128
  :class="{[bulkActionClass]:true}"
1130
1129
  :disabled="!act.enabled"
1131
1130
  :data-testid="componentTestid + '-' + act.action"
@@ -1146,6 +1145,7 @@ export default {
1146
1145
  :disabled="!selectedRows.length"
1147
1146
  :hidden-actions="hiddenActions"
1148
1147
  :action-tooltip="actionTooltip"
1148
+ size="medium"
1149
1149
  @click="applyTableAction"
1150
1150
  @mouseover="setBulkActionOfInterest"
1151
1151
  @mouseleave="setBulkActionOfInterest"
@@ -1158,10 +1158,11 @@ export default {
1158
1158
  :disable-button="!selectedRows.length"
1159
1159
  size="sm"
1160
1160
  >
1161
- <template #button-content>
1161
+ <template #button-content="{ buttonSize }">
1162
1162
  <button
1163
1163
  ref="actionDropDown"
1164
1164
  class="btn bg-primary mr-0"
1165
+ :class="buttonSize"
1165
1166
  :disabled="!selectedRows.length"
1166
1167
  >
1167
1168
  <i class="icon icon-gear" />
@@ -1874,7 +1875,7 @@ export default {
1874
1875
  }
1875
1876
 
1876
1877
  .search-box {
1877
- height: 40px;
1878
+ height: 32px;
1878
1879
  margin-left: 10px;
1879
1880
  min-width: 180px;
1880
1881
  }
@@ -1,25 +1,50 @@
1
1
  <script setup lang="ts">
2
+ import { computed } from 'vue';
2
3
  import { RouterLink, RouteLocationRaw } from 'vue-router';
3
4
 
4
5
  export interface Props {
5
- to: RouteLocationRaw;
6
+ to?: RouteLocationRaw;
7
+ href?: string;
8
+ target?: string;
9
+ openInNewTabLabel?: string;
6
10
  }
7
11
 
8
- const { to } = defineProps<Props>();
12
+ const props = defineProps<Props>();
13
+
14
+ const isExternal = computed(() => !!props.href);
9
15
  </script>
10
16
 
11
17
  <template>
12
- <RouterLink
18
+ <component
19
+ :is="isExternal ? 'a' : RouterLink"
13
20
  class="subtle-link"
14
- :to="to"
21
+ v-bind="isExternal
22
+ ? { href, target, rel: target === '_blank' ? 'noopener noreferrer nofollow' : undefined }
23
+ : { to }"
15
24
  >
16
- <slot name="default" />
17
- </RouterLink>
25
+ <span
26
+ v-if="openInNewTabLabel"
27
+ class="sr-only"
28
+ >{{ openInNewTabLabel }}</span>
29
+ <slot name="default" /><span v-if="openInNewTabLabel">&nbsp;</span><i
30
+ v-if="openInNewTabLabel"
31
+ class="link-icon icon icon-external-link"
32
+ />
33
+ </component>
18
34
  </template>
19
35
 
20
36
  <style lang="scss" scoped>
21
37
  .subtle-link {
22
38
  text-decoration: underline;
23
39
  color: var(--body-text);
40
+
41
+ &:hover,
42
+ &:active {
43
+ text-decoration: none;
44
+ }
45
+ }
46
+
47
+ .link-icon {
48
+ display: inline;
24
49
  }
25
50
  </style>
@@ -1,8 +1,14 @@
1
1
  <script>
2
2
  import { useTabCountWatcher } from '@shell/components/form/ResourceTabs/composable';
3
+ import { useInSummary } from '@shell/components/TableOfContents/composables';
4
+ import { computed, inject, useTemplateRef } from 'vue';
5
+ import { useStore } from 'vuex';
6
+ import { useI18n } from '@shell/composables/useI18n';
3
7
 
4
8
  export default {
5
- inject: ['addTab', 'removeTab', 'sideTabs'],
9
+ name: 'Tab',
10
+
11
+ inject: ['addTab', 'removeTab', 'sideTabs', 'select'],
6
12
 
7
13
  emits: ['active'],
8
14
 
@@ -64,9 +70,28 @@ export default {
64
70
  },
65
71
 
66
72
  setup(props) {
67
- const { count, isCountVisible } = useTabCountWatcher();
73
+ const select = inject('select');
74
+ const store = useStore();
75
+ const { t } = useI18n(store);
76
+ const label = computed(() => {
77
+ if (props.labelKey && typeof t === 'function') {
78
+ return t(props.labelKey);
79
+ }
68
80
 
69
- return { inferredCount: count, isInferredCountVisible: isCountVisible };
81
+ return props.label ?? props.name;
82
+ });
83
+ const { count, isCountVisible } = useTabCountWatcher();
84
+ const summarizedContainerRef = useTemplateRef('tab-summarized-container');
85
+ // when a Tab is scrolled to, call its Tabbed's 'select' method to ensure the Tab is active
86
+ const { summary } = useInSummary({
87
+ scrollTo: () => select(props.name),
88
+ label,
89
+ elementRef: summarizedContainerRef,
90
+ });
91
+
92
+ return {
93
+ inferredCount: count, isInferredCountVisible: isCountVisible, summary
94
+ };
70
95
  },
71
96
 
72
97
  data() {
@@ -143,6 +168,7 @@ export default {
143
168
  <section
144
169
  v-show="active"
145
170
  :id="name"
171
+ ref="tab-summarized-container"
146
172
  :aria-hidden="!active"
147
173
  role="tabpanel"
148
174
  :aria-labelledby="`tab-${name}`"
@@ -7,10 +7,11 @@ import findIndex from 'lodash/findIndex';
7
7
  import { ExtensionPoint, TabLocation } from '@shell/core/types';
8
8
  import { getApplicableExtensionEnhancements } from '@shell/core/plugin-helpers';
9
9
  import Tab from '@shell/components/Tabbed/Tab';
10
- import { ref } from 'vue';
10
+ import { computed, ref, useTemplateRef } from 'vue';
11
11
  import { useIsInResourceDetailDrawer } from '@shell/components/Drawer/ResourceDetailDrawer/composables';
12
12
  import { useIsInResourceDetailPage } from '@shell/composables/resourceDetail';
13
13
  import { useIsInResourceCreatePage, useIsInResourceEditPage } from '@shell/composables/cruResource';
14
+ import { useInSummary } from '@shell/components/TableOfContents/composables';
14
15
 
15
16
  export default {
16
17
  name: 'Tabbed',
@@ -94,6 +95,17 @@ export default {
94
95
  removeBorders: {
95
96
  type: Boolean,
96
97
  default: false,
98
+ },
99
+
100
+ /**
101
+ * title is NOT displayed within the Tabbed component itself
102
+ * this prop is used to determine a label to use for the set of tabs in the table of contents component
103
+ * if a title is not provided a random string will be used
104
+ * components using the table of contents may exclude tabbed and only show tab components, too
105
+ */
106
+ title: {
107
+ type: String,
108
+ default: null,
97
109
  }
98
110
  },
99
111
 
@@ -101,6 +113,8 @@ export default {
101
113
  const tabs = this.tabs;
102
114
 
103
115
  return {
116
+ select: this.select,
117
+
104
118
  sideTabs: this.sideTabs,
105
119
 
106
120
  addTab(tab) {
@@ -156,14 +170,20 @@ export default {
156
170
  },
157
171
  },
158
172
 
159
- setup() {
173
+ setup(props) {
160
174
  const isInResourceDetailDrawer = ref(useIsInResourceDetailDrawer());
161
175
  const isInResourceDetailPage = ref(useIsInResourceDetailPage());
162
176
  const isInResourceEditPage = ref(useIsInResourceEditPage());
163
177
  const isInResourceCreatePage = ref(useIsInResourceCreatePage());
178
+ const tabbedSummarizedContainer = useTemplateRef('tabbed-summarized-container');
179
+ const { summary } = useInSummary({
180
+ label: computed(() => props.title ?? ''),
181
+ scrollTo: () => tabbedSummarizedContainer.value?.scrollIntoView(true),
182
+ elementRef: tabbedSummarizedContainer,
183
+ });
164
184
 
165
185
  return {
166
- isInResourceDetailDrawer, isInResourceDetailPage, isInResourceEditPage, isInResourceCreatePage
186
+ isInResourceDetailDrawer, isInResourceDetailPage, isInResourceEditPage, isInResourceCreatePage, summary
167
187
  };
168
188
  },
169
189
 
@@ -311,6 +331,7 @@ export default {
311
331
 
312
332
  <template>
313
333
  <div
334
+ ref="tabbed-summarized-container"
314
335
  class="tabbed-container"
315
336
  :class="{
316
337
  'side-tabs': !!sideTabs,
@@ -435,6 +456,7 @@ export default {
435
456
  <component
436
457
  :is="tab.component"
437
458
  :resource="resource"
459
+ @select="select(tab.name)"
438
460
  />
439
461
  </Tab>
440
462
  </div>