@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
@@ -10,6 +10,7 @@ import { NAMESPACE } from '@shell/config/types';
10
10
  import { handleKubeApiHeaderWarnings } from '@shell/plugins/steve/header-warnings';
11
11
  import { steveCleanForDownload } from '@shell/plugins/steve/resource-utils';
12
12
  import paginationUtils from '@shell/utils/pagination-utils';
13
+ import stevePaginationUtils from '@shell/plugins/steve/steve-pagination-utils';
13
14
 
14
15
  export default {
15
16
 
@@ -221,6 +222,101 @@ export default {
221
222
  }
222
223
  },
223
224
 
225
+ /**
226
+ * Fetch aggregated state counts for a resource type via the Steve summary API.
227
+ * Requires VAI (ui-sql-cache) to be enabled; returns undefined otherwise.
228
+ *
229
+ * Uses `summaryonly` by default so no resource data is returned.
230
+ *
231
+ * @param {string} type - Resource type (e.g. 'pod', 'service')
232
+ * @param {object} [opt] - Options object
233
+ * @param {string} opt.summaryField - Field to aggregate counts by.
234
+ * Must be a field indexed by the VAI cache (see StevePaginationUtils.VALID_FIELDS in steve-pagination-utils.ts)
235
+ * @param {string} [opt.namespace] - Namespace to scope the request to (only applies to namespaced resource types)
236
+ * @param {boolean} [opt.summaryOnly=true] - Omit resource data from the response (set to false to include data)
237
+ * @param {boolean} [opt.namespaceCounts] - Include per-namespace breakdowns in counts
238
+ * @param {PaginationParamFilter[]} [opt.filters] - Pre-built filters from PaginationParamFilter.createSingleField()
239
+ * @param {KubeLabelSelector} [opt.labelSelector] - Kube label selector to filter by (converted via convertLabelSelectorPaginationParams)
240
+ * @returns {Promise<{ count: number, summary: { property: string, counts: Record<string, { total: number, namespace?: Record<string, number> }> }[] | null } | undefined>}
241
+ *
242
+ * @example
243
+ * const result = await dispatch('fetchResourceSummary', {
244
+ * type: 'pod',
245
+ * opt: { summaryField: 'metadata.state.name', labelSelector: { matchExpressions: podMatchExpression } }
246
+ * });
247
+ * // result.summary[0].counts => { running: { total: 3 }, error: { total: 1 } }
248
+ *
249
+ * // With namespace breakdowns:
250
+ * const result = await dispatch('fetchResourceSummary', {
251
+ * type: 'pod',
252
+ * opt: { summaryField: 'metadata.state.name', namespaceCounts: true }
253
+ * });
254
+ * // result.summary[0].counts => { running: { total: 3, namespace: { default: 2, 'kube-system': 1 } } }
255
+ */
256
+ async fetchResourceSummary({ getters, dispatch, rootGetters }, { type, opt = {} }) {
257
+ type = getters.normalizeType(type);
258
+ const schema = getters.schemaFor(type);
259
+
260
+ if (!schema) {
261
+ console.warn(`fetchResourceSummary: no schema found for type "${ type }"`); // eslint-disable-line no-console
262
+
263
+ return undefined;
264
+ }
265
+
266
+ if (!paginationUtils.isSteveCacheEnabled({ rootGetters })) {
267
+ console.warn(`fetchResourceSummary: VAI is not enabled, summary API unavailable for type "${ type }"`); // eslint-disable-line no-console
268
+
269
+ return undefined;
270
+ }
271
+
272
+ if (!opt.summaryField) {
273
+ console.warn(`fetchResourceSummary: summaryField is required and must be a string for type "${ type }"`); // eslint-disable-line no-console
274
+
275
+ return undefined;
276
+ }
277
+
278
+ try {
279
+ const url = new URL(schema.links.collection, window.location.origin);
280
+
281
+ if (schema.attributes?.namespaced && opt.namespace) {
282
+ url.pathname += `/${ opt.namespace }`;
283
+ }
284
+
285
+ url.searchParams.set('summary', opt.summaryField);
286
+
287
+ if (opt.summaryOnly !== false) {
288
+ url.searchParams.set('summaryonly', '');
289
+ }
290
+
291
+ if (opt.namespaceCounts) {
292
+ url.searchParams.set('summarynamespaced', '');
293
+ }
294
+
295
+ if (opt.filters?.length) {
296
+ const filterParams = new URLSearchParams(stevePaginationUtils.convertPaginationParams({ schema, filters: opt.filters }));
297
+
298
+ filterParams.forEach((v, k) => url.searchParams.append(k, v));
299
+ }
300
+
301
+ if (opt.labelSelector) {
302
+ const labelParams = new URLSearchParams(stevePaginationUtils.convertLabelSelectorPaginationParams({ labelSelector: opt.labelSelector }));
303
+
304
+ labelParams.forEach((v, k) => url.searchParams.append(k, v));
305
+ }
306
+
307
+ const res = await dispatch('request', { opt: { url: url.pathname + url.search } });
308
+
309
+ return {
310
+ count: res.count ?? 0,
311
+ summary: res.summary || null
312
+ };
313
+ } catch (e) {
314
+ console.warn(`fetchResourceSummary: summary API request failed for type "${ type }"`, e); // eslint-disable-line no-console
315
+
316
+ return undefined;
317
+ }
318
+ },
319
+
224
320
  promptRestore({ commit, state }, resources ) {
225
321
  commit('action-menu/togglePromptRestore', resources, { root: true });
226
322
  },
@@ -656,7 +656,7 @@ class StevePaginationUtils extends NamespaceProjectFilters {
656
656
  * A lot of the requirements and details are taken directly from
657
657
  * https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
658
658
  */
659
- private convertLabelSelectorPaginationParams({ labelSelector }: { labelSelector: KubeLabelSelector}): string {
659
+ convertLabelSelectorPaginationParams({ labelSelector }: { labelSelector: KubeLabelSelector}): string {
660
660
  // Get a list of matchExpressions
661
661
  const expressions: KubeLabelSelectorExpression[] = labelSelector.matchExpressions ? [...labelSelector.matchExpressions] : [];
662
662
 
@@ -1,8 +1,14 @@
1
1
  <script lang="ts">
2
- import { defineComponent } from 'vue';
3
- import { mapGetters } from 'vuex';
2
+ import {
3
+ computed, defineComponent, nextTick, ref, useTemplateRef
4
+ } from 'vue';
5
+ import { mapGetters, useStore } from 'vuex';
6
+ import { useInSummary } from '@shell/components/TableOfContents/composables';
7
+ import { useI18n } from '@shell/composables/useI18n';
4
8
 
5
9
  export default defineComponent({
10
+ name: 'Accordion',
11
+
6
12
  props: {
7
13
  title: {
8
14
  type: String,
@@ -20,22 +26,59 @@ export default defineComponent({
20
26
  }
21
27
  },
22
28
 
29
+ setup(props) {
30
+ const store = useStore();
31
+ const { t } = useI18n(store);
32
+ const label = computed(() => props.titleKey && typeof t === 'function' ? t(props.titleKey) : props.title);
33
+
34
+ const isOpen = ref(props.openInitially);
35
+ const accordionSummarizedContainer = useTemplateRef<HTMLElement>('accordion-summarized-container');
36
+
37
+ const scrollTo = () => {
38
+ isOpen.value = true;
39
+ nextTick(() => {
40
+ accordionSummarizedContainer.value?.scrollIntoView();
41
+ });
42
+ };
43
+
44
+ const { summary } = useInSummary({
45
+ scrollTo,
46
+ label,
47
+ elementRef: accordionSummarizedContainer,
48
+ });
49
+
50
+ return {
51
+ summary,
52
+ isOpen,
53
+ scrollTo,
54
+ };
55
+ },
56
+
23
57
  data() {
24
- return { isOpen: this.openInitially };
58
+ return {};
25
59
  },
26
60
 
27
- computed: { ...mapGetters({ t: 'i18n/t' }) },
61
+ computed: {
62
+ ...mapGetters({ t: 'i18n/t' }),
63
+
64
+ displayTitle() {
65
+ return this.titleKey ? this.t(this.titleKey) : this.title;
66
+ },
67
+ },
28
68
 
29
69
  methods: {
30
70
  toggle() {
31
71
  this.isOpen = !this.isOpen;
32
- }
33
- }
72
+ },
73
+ },
34
74
  });
35
75
  </script>
36
76
 
37
77
  <template>
38
- <div class="accordion-container">
78
+ <div
79
+ ref="accordion-summarized-container"
80
+ class="accordion-container"
81
+ >
39
82
  <div
40
83
  class="accordion-header"
41
84
  data-testid="accordion-header"
@@ -51,7 +94,7 @@ export default defineComponent({
51
94
  data-testid="accordion-title-slot-content"
52
95
  class="mb-0"
53
96
  >
54
- {{ titleKey ? t(titleKey) : title }}
97
+ {{ displayTitle }}
55
98
  </h2>
56
99
  </slot>
57
100
  </div>
@@ -67,7 +110,8 @@ export default defineComponent({
67
110
 
68
111
  <style lang="scss" scoped>
69
112
  .accordion-container {
70
- border: 1px solid var(--border)
113
+ border: 1px solid var(--border);
114
+ border-radius: var(--border-radius);
71
115
  }
72
116
  .accordion-header {
73
117
  padding: 16px 16px 16px 11px;
@@ -125,6 +125,14 @@ export default defineComponent({
125
125
  default: undefined
126
126
  },
127
127
 
128
+ /**
129
+ * Use body text color for the label instead of the default input-label color.
130
+ */
131
+ useBodyTextColor: {
132
+ type: Boolean,
133
+ default: false
134
+ },
135
+
128
136
  /**
129
137
  * Inherited global identifier prefix for tests
130
138
  * Define a term based on the parent component to avoid conflicts on multiple components
@@ -316,6 +324,7 @@ export default defineComponent({
316
324
  <span
317
325
  v-if="labelKey"
318
326
  :id="idForLabel"
327
+ :class="{ 'body-text-color': useBodyTextColor }"
319
328
  >
320
329
  <t
321
330
  :k="labelKey"
@@ -325,6 +334,7 @@ export default defineComponent({
325
334
  <span
326
335
  v-else-if="label"
327
336
  :id="idForLabel"
337
+ :class="{ 'body-text-color': useBodyTextColor }"
328
338
  >{{ label }}</span>
329
339
  <i
330
340
  v-if="tooltipKey"
@@ -403,6 +413,10 @@ $fontColor: var(--input-label);
403
413
  display: inline-flex;
404
414
  margin: 0px 10px 0px 5px;
405
415
 
416
+ .body-text-color {
417
+ color: var(--body-text);
418
+ }
419
+
406
420
  &.checkbox-primary {
407
421
  color: inherit;
408
422
  font-weight: 600;
@@ -80,6 +80,14 @@ export default defineComponent({
80
80
  default: false
81
81
  },
82
82
 
83
+ /**
84
+ * Use body text color for the label instead of the default input-label color.
85
+ */
86
+ useBodyTextColor: {
87
+ type: Boolean,
88
+ default: false
89
+ },
90
+
83
91
  /**
84
92
  * Radio option Id - used to link to aria-activedescendant
85
93
  * when using inside of the context of a Radio Group
@@ -189,7 +197,11 @@ export default defineComponent({
189
197
  />
190
198
  <div class="labeling">
191
199
  <label
192
- :class="[ muteLabel ? 'text-muted' : '', 'radio-label', 'm-0']"
200
+ class="radio-label m-0"
201
+ :class="{
202
+ 'text-muted': muteLabel,
203
+ 'body-text-color': useBodyTextColor
204
+ }"
193
205
  :for="name"
194
206
  >
195
207
  <slot
@@ -314,6 +326,10 @@ $fontColor: var(--input-label);
314
326
  flex-direction: column;
315
327
 
316
328
  margin: 3px 10px 0px 5px;
329
+
330
+ .body-text-color {
331
+ color: var(--body-text);
332
+ }
317
333
  }
318
334
  }
319
335
 
@@ -12,6 +12,7 @@ interface Option {
12
12
  }
13
13
 
14
14
  export default defineComponent({
15
+ name: 'RadioGroup',
15
16
  components: { RadioButton },
16
17
  props: {
17
18
  /**
@@ -102,6 +103,14 @@ export default defineComponent({
102
103
  row: {
103
104
  type: Boolean,
104
105
  default: false
106
+ },
107
+
108
+ /**
109
+ * Use body text color for the label instead of the default input-label color.
110
+ */
111
+ useBodyTextColor: {
112
+ type: Boolean,
113
+ default: false
105
114
  }
106
115
  },
107
116
 
@@ -302,6 +311,7 @@ export default defineComponent({
302
311
  :disabled="isDisabled"
303
312
  :data-testid="`radio-button-${i}`"
304
313
  :mode="mode"
314
+ :use-body-text-color="useBodyTextColor"
305
315
  :prevent-focus-on-radio-groups="true"
306
316
  @update:value="$emit('update:value', $event)"
307
317
  />
@@ -77,6 +77,16 @@ export default defineComponent({
77
77
  disabled: {
78
78
  type: Boolean,
79
79
  default: false
80
+ },
81
+
82
+ /**
83
+ * Recalculate the height when the value is changed programmatically (e.g.
84
+ * populated from a file) and when the window is resized, not just on user
85
+ * input. Opt-in to avoid changing the behaviour of existing usages.
86
+ */
87
+ resizeOnValueChangeAndResizeWindow: {
88
+ type: Boolean,
89
+ default: false
80
90
  }
81
91
  },
82
92
 
@@ -117,6 +127,14 @@ export default defineComponent({
117
127
  },
118
128
 
119
129
  watch: {
130
+ // Recalculate the height when the value is changed programmatically (e.g.
131
+ // populated from a file), not just on user input. Opt-in via resizeOnValueChangeAndResizeWindow.
132
+ value() {
133
+ if (this.resizeOnValueChangeAndResizeWindow) {
134
+ this.queueResize();
135
+ }
136
+ },
137
+
120
138
  $attrs: {
121
139
  deep: true,
122
140
  handler() {
@@ -134,6 +152,18 @@ export default defineComponent({
134
152
  this.$nextTick(() => {
135
153
  this.autoSize();
136
154
  });
155
+
156
+ // Width changes alter text wrapping, so the required height can change when
157
+ // the window is resized. Opt-in via resizeOnValueChangeAndResizeWindow.
158
+ if (this.resizeOnValueChangeAndResizeWindow) {
159
+ window.addEventListener('resize', this.queueResize);
160
+ }
161
+ },
162
+
163
+ beforeUnmount() {
164
+ if (this.resizeOnValueChangeAndResizeWindow) {
165
+ window.removeEventListener('resize', this.queueResize);
166
+ }
137
167
  },
138
168
 
139
169
  methods: {
@@ -0,0 +1,95 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import TextAreaAutoGrow from '@components/Form/TextArea/TextAreaAutoGrow.vue';
3
+
4
+ describe('component: TextAreaAutoGrow', () => {
5
+ it('should recalculate its height when the value changes programmatically and resizeOnValueChangeAndResizeWindow is set', async() => {
6
+ const wrapper = mount(TextAreaAutoGrow, { props: { value: 'initial', resizeOnValueChangeAndResizeWindow: true } });
7
+
8
+ // queueResize is the (debounced) entrypoint that triggers autoSize
9
+ const queueResize = jest.fn();
10
+
11
+ wrapper.vm.queueResize = queueResize;
12
+
13
+ await wrapper.setProps({ value: 'a\nmuch\nlonger\nvalue\nset\nfrom\noutside' });
14
+
15
+ expect(queueResize).toHaveBeenCalledWith();
16
+ });
17
+
18
+ it('should not recalculate its height on programmatic value change by default', async() => {
19
+ const wrapper = mount(TextAreaAutoGrow, { props: { value: 'initial' } });
20
+
21
+ const queueResize = jest.fn();
22
+
23
+ wrapper.vm.queueResize = queueResize;
24
+
25
+ await wrapper.setProps({ value: 'a\nmuch\nlonger\nvalue\nset\nfrom\noutside' });
26
+
27
+ expect(queueResize).not.toHaveBeenCalled();
28
+ });
29
+
30
+ it('should recalculate its height on user input', async() => {
31
+ const wrapper = mount(TextAreaAutoGrow, { props: { value: '' } });
32
+
33
+ const queueResize = jest.fn();
34
+
35
+ wrapper.vm.queueResize = queueResize;
36
+
37
+ await wrapper.find('textarea').setValue('typed value');
38
+
39
+ expect(queueResize).toHaveBeenCalledWith();
40
+ });
41
+
42
+ it('should register a window resize listener when resizeOnValueChangeAndResizeWindow is set', () => {
43
+ const addSpy = jest.spyOn(window, 'addEventListener');
44
+
45
+ const wrapper = mount(TextAreaAutoGrow, { props: { value: 'initial', resizeOnValueChangeAndResizeWindow: true } });
46
+
47
+ const resizeListener = addSpy.mock.calls.find(([event]) => event === 'resize')?.[1];
48
+
49
+ expect(resizeListener).toBe(wrapper.vm.queueResize);
50
+
51
+ addSpy.mockRestore();
52
+ });
53
+
54
+ it('should not register a window resize listener by default', () => {
55
+ const addSpy = jest.spyOn(window, 'addEventListener');
56
+
57
+ mount(TextAreaAutoGrow, { props: { value: 'initial' } });
58
+
59
+ const resizeListener = addSpy.mock.calls.find(([event]) => event === 'resize')?.[1];
60
+
61
+ expect(resizeListener).toBeUndefined();
62
+
63
+ addSpy.mockRestore();
64
+ });
65
+
66
+ it('should recalculate its height when a window resize fires and resizeOnValueChangeAndResizeWindow is set', () => {
67
+ jest.useFakeTimers();
68
+ const component = TextAreaAutoGrow as unknown as { methods: Record<string, () => void> };
69
+ const autoSizeSpy = jest.spyOn(component.methods, 'autoSize');
70
+
71
+ mount(TextAreaAutoGrow, { props: { value: 'initial', resizeOnValueChangeAndResizeWindow: true } });
72
+ autoSizeSpy.mockClear();
73
+
74
+ window.dispatchEvent(new Event('resize'));
75
+ jest.advanceTimersByTime(150); // queueResize is debounced (100ms)
76
+
77
+ expect(autoSizeSpy).toHaveBeenCalledWith(expect.any(Event));
78
+
79
+ autoSizeSpy.mockRestore();
80
+ jest.useRealTimers();
81
+ });
82
+
83
+ it('should remove the window resize listener when unmounted', () => {
84
+ const removeSpy = jest.spyOn(window, 'removeEventListener');
85
+
86
+ const wrapper = mount(TextAreaAutoGrow, { props: { value: 'initial', resizeOnValueChangeAndResizeWindow: true } });
87
+ const { queueResize } = wrapper.vm;
88
+
89
+ wrapper.unmount();
90
+
91
+ expect(removeSpy).toHaveBeenCalledWith('resize', queueResize);
92
+
93
+ removeSpy.mockRestore();
94
+ });
95
+ });
@@ -28,7 +28,8 @@ const emit = defineEmits(['close']);
28
28
  <style lang="scss" scoped>
29
29
  .rc-tag {
30
30
  display: inline-flex;
31
- padding: 1px 8px;
31
+ min-height: 24px;
32
+ padding: 2px 8px;
32
33
  align-items: center;
33
34
  gap: 8px;
34
35
 
@@ -41,7 +42,7 @@ const emit = defineEmits(['close']);
41
42
  font-size: 13px;
42
43
  font-style: normal;
43
44
  font-weight: 400;
44
- line-height: 22px;
45
+ line-height: 20px;
45
46
  color: var(--body-text);
46
47
 
47
48
  button {
@@ -139,6 +139,109 @@ describe('rcButton.vue', () => {
139
139
  });
140
140
  });
141
141
 
142
+ describe('space key navigation', () => {
143
+ it('triggers click when Space is pressed on a link button', async() => {
144
+ const to = { name: 'some-route' };
145
+ const wrapper = mount(RcButton, {
146
+ props: { to },
147
+ global: { stubs: { RouterLink: RouterLinkStub } },
148
+ });
149
+
150
+ const link = wrapper.findComponent(RouterLinkStub);
151
+ const clickSpy = jest.spyOn(link.element, 'click');
152
+
153
+ await link.trigger('keyup', { key: ' ' });
154
+
155
+ expect(clickSpy).toHaveBeenCalledWith();
156
+ });
157
+
158
+ it('does not manually trigger click when Space is pressed on a regular button', async() => {
159
+ const wrapper = mount(RcButton);
160
+ const button = wrapper.find('button');
161
+ const clickSpy = jest.spyOn(button.element, 'click');
162
+
163
+ await button.trigger('keyup', { key: ' ' });
164
+
165
+ expect(clickSpy).not.toHaveBeenCalled();
166
+ });
167
+ });
168
+
169
+ describe('to and href mutual exclusion', () => {
170
+ it('warns when both "to" and "href" props are provided', () => {
171
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
172
+
173
+ mount(RcButton, {
174
+ props: { to: '/foo', href: 'https://example.com' },
175
+ global: { stubs: { RouterLink: RouterLinkStub } },
176
+ });
177
+
178
+ expect(warnSpy).toHaveBeenCalledWith('[RcButton] "to" and "href" are mutually exclusive. Provide only one.');
179
+ warnSpy.mockRestore();
180
+ });
181
+
182
+ it('does not warn when only "to" is provided', () => {
183
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
184
+
185
+ mount(RcButton, {
186
+ props: { to: '/foo' },
187
+ global: { stubs: { RouterLink: RouterLinkStub } },
188
+ });
189
+
190
+ expect(warnSpy).not.toHaveBeenCalledWith('[RcButton] "to" and "href" are mutually exclusive. Provide only one.');
191
+ warnSpy.mockRestore();
192
+ });
193
+
194
+ it('does not warn when only "href" is provided', () => {
195
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
196
+
197
+ mount(RcButton, { props: { href: 'https://example.com' } });
198
+
199
+ expect(warnSpy).not.toHaveBeenCalledWith('[RcButton] "to" and "href" are mutually exclusive. Provide only one.');
200
+ warnSpy.mockRestore();
201
+ });
202
+ });
203
+
204
+ describe('href prop', () => {
205
+ it('renders as an <a> element when "href" prop is provided', () => {
206
+ const wrapper = mount(RcButton, { props: { href: 'https://example.com' } });
207
+
208
+ expect(wrapper.find('a').exists()).toBe(true);
209
+ expect(wrapper.find('button').exists()).toBe(false);
210
+ });
211
+
212
+ it('sets the href attribute on the rendered anchor', () => {
213
+ const href = 'https://example.com';
214
+ const wrapper = mount(RcButton, { props: { href } });
215
+
216
+ expect(wrapper.find('a').attributes('href')).toStrictEqual(href);
217
+ });
218
+
219
+ it('sets role="link" when rendered as an anchor', () => {
220
+ const wrapper = mount(RcButton, { props: { href: 'https://example.com' } });
221
+
222
+ expect(wrapper.find('a').attributes('role')).toStrictEqual('link');
223
+ });
224
+
225
+ it('applies button classes when rendered as an anchor', () => {
226
+ const wrapper = mount(RcButton, { props: { href: 'https://example.com', variant: 'secondary' } });
227
+ const anchor = wrapper.find('a');
228
+
229
+ expect(anchor.classes()).toContain('rc-button');
230
+ expect(anchor.classes()).toContain('btn');
231
+ expect(anchor.classes()).toContain('variant-secondary');
232
+ });
233
+
234
+ it('triggers click when Space is pressed on an anchor', async() => {
235
+ const wrapper = mount(RcButton, { props: { href: 'https://example.com' } });
236
+ const anchor = wrapper.find('a');
237
+ const clickSpy = jest.spyOn(anchor.element, 'click');
238
+
239
+ await anchor.trigger('keyup', { key: ' ' });
240
+
241
+ expect(clickSpy).toHaveBeenCalledWith();
242
+ });
243
+ });
244
+
142
245
  describe('to prop', () => {
143
246
  it('renders as a <button> when no "to" prop is provided', () => {
144
247
  const wrapper = mount(RcButton);