@rancher/shell 3.0.5-rc.2 → 3.0.5-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 (289) hide show
  1. package/assets/data/aws-regions.json +2 -0
  2. package/assets/images/icons/document.svg +3 -0
  3. package/assets/images/vendor/cognito.svg +1 -0
  4. package/assets/styles/app.scss +1 -0
  5. package/assets/styles/base/_basic.scss +10 -0
  6. package/assets/styles/base/_spacing.scss +29 -0
  7. package/assets/styles/global/_layout.scss +1 -2
  8. package/assets/styles/themes/_dark.scss +25 -0
  9. package/assets/styles/themes/_light.scss +65 -0
  10. package/assets/translations/en-us.yaml +377 -37
  11. package/assets/translations/zh-hans.yaml +8 -15
  12. package/chart/monitoring/index.vue +1 -1
  13. package/components/AsyncButton.vue +2 -0
  14. package/components/Certificates.vue +5 -0
  15. package/components/CodeMirror.vue +3 -3
  16. package/components/CruResource.vue +103 -15
  17. package/components/ExplorerProjectsNamespaces.vue +7 -2
  18. package/components/FilterPanel.vue +156 -0
  19. package/components/FixedBanner.vue +19 -5
  20. package/components/{fleet/ForceDirectedTreeChart/index.vue → ForceDirectedTreeChart.vue} +47 -41
  21. package/components/IconOrSvg.vue +14 -35
  22. package/components/PaginatedResourceTable.vue +7 -0
  23. package/components/PromptRemove.vue +5 -1
  24. package/components/Resource/Detail/Card/PodsCard/Bubble.vue +13 -0
  25. package/components/Resource/Detail/Card/PodsCard/composable.ts +30 -0
  26. package/components/Resource/Detail/Card/PodsCard/index.vue +118 -0
  27. package/components/Resource/Detail/Card/ResourceUsageCard/composable.ts +51 -0
  28. package/components/Resource/Detail/Card/ResourceUsageCard/index.vue +79 -0
  29. package/components/Resource/Detail/Card/Scaler.vue +89 -0
  30. package/components/Resource/Detail/Card/StateCard/composables.ts +112 -0
  31. package/components/Resource/Detail/Card/StateCard/index.vue +39 -0
  32. package/components/Resource/Detail/Card/VerticalGap.vue +11 -0
  33. package/components/Resource/Detail/Card/__tests__/Card.test.ts +36 -0
  34. package/components/Resource/Detail/Card/__tests__/PodsCard.test.ts +84 -0
  35. package/components/Resource/Detail/Card/__tests__/ResourceUsageCard.test.ts +72 -0
  36. package/components/Resource/Detail/Card/__tests__/Scaler.test.ts +87 -0
  37. package/components/Resource/Detail/Card/__tests__/StateCard.test.ts +53 -0
  38. package/components/Resource/Detail/Card/__tests__/VerticalGap.test.ts +14 -0
  39. package/components/Resource/Detail/Card/__tests__/index.test.ts +36 -0
  40. package/components/Resource/Detail/Card/index.vue +56 -0
  41. package/components/Resource/Detail/Metadata/Annotations/__tests__/index.test.ts +19 -0
  42. package/components/Resource/Detail/Metadata/Annotations/composable.ts +12 -0
  43. package/components/Resource/Detail/Metadata/Annotations/index.vue +26 -0
  44. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/index.test.ts +103 -0
  45. package/components/Resource/Detail/Metadata/IdentifyingInformation/composable.ts +281 -0
  46. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +111 -0
  47. package/components/Resource/Detail/Metadata/KeyValue.vue +130 -0
  48. package/components/Resource/Detail/Metadata/Labels/__tests__/index.test.ts +18 -0
  49. package/components/Resource/Detail/Metadata/Labels/composable.ts +12 -0
  50. package/components/Resource/Detail/Metadata/Labels/index.vue +27 -0
  51. package/components/Resource/Detail/Metadata/Rectangle.vue +32 -0
  52. package/components/Resource/Detail/Metadata/__tests__/KeyValue.test.ts +107 -0
  53. package/components/Resource/Detail/Metadata/__tests__/Rectangle.test.ts +24 -0
  54. package/components/Resource/Detail/Metadata/__tests__/index.test.ts +91 -0
  55. package/components/Resource/Detail/Metadata/composables.ts +29 -0
  56. package/components/Resource/Detail/Metadata/index.vue +66 -0
  57. package/components/Resource/Detail/Page.vue +22 -0
  58. package/components/Resource/Detail/PercentageBar.vue +40 -0
  59. package/components/Resource/Detail/ResourceRow.vue +119 -0
  60. package/components/Resource/Detail/SpacedRow.vue +14 -0
  61. package/components/Resource/Detail/StatusBar.vue +59 -0
  62. package/components/Resource/Detail/StatusRow.vue +61 -0
  63. package/components/Resource/Detail/TitleBar/Title.vue +13 -0
  64. package/components/Resource/Detail/TitleBar/Top.vue +14 -0
  65. package/components/Resource/Detail/TitleBar/__tests__/Title.test.ts +17 -0
  66. package/components/Resource/Detail/TitleBar/__tests__/Top.test.ts +17 -0
  67. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +142 -0
  68. package/components/Resource/Detail/TitleBar/composable.ts +31 -0
  69. package/components/Resource/Detail/TitleBar/index.vue +124 -0
  70. package/components/Resource/Detail/Top/index.vue +34 -0
  71. package/components/Resource/Detail/__tests__/Page.test.ts +32 -0
  72. package/components/ResourceDetail/Masthead.vue +0 -1
  73. package/components/ResourceDetail/__tests__/index.test.ts +114 -0
  74. package/components/ResourceDetail/index.vue +64 -562
  75. package/components/ResourceDetail/legacy.vue +545 -0
  76. package/components/ResourceList/index.vue +2 -1
  77. package/components/ResourceTable.vue +41 -7
  78. package/components/SlideInPanelManager.vue +77 -10
  79. package/components/SortableTable/index.vue +13 -2
  80. package/components/SortableTable/selection.js +22 -9
  81. package/components/StatusBadge.vue +6 -4
  82. package/components/SubtleLink.vue +25 -0
  83. package/components/Tabbed/index.vue +6 -0
  84. package/components/Wizard.vue +12 -1
  85. package/components/YamlEditor.vue +1 -1
  86. package/components/__tests__/AsyncButton.test.ts +39 -0
  87. package/components/__tests__/CruResource.test.ts +63 -0
  88. package/components/__tests__/FilterPanel.test.ts +81 -0
  89. package/components/__tests__/PromptModal.test.ts +0 -2
  90. package/components/auth/AuthBanner.vue +2 -3
  91. package/components/auth/RoleDetailEdit.vue +45 -3
  92. package/components/auth/login/oidc.vue +6 -1
  93. package/components/fleet/FleetApplications.vue +181 -0
  94. package/components/fleet/FleetHelmOps.vue +115 -0
  95. package/components/fleet/FleetIntro.vue +58 -28
  96. package/components/fleet/FleetNoWorkspaces.vue +5 -1
  97. package/components/fleet/FleetOCIStorageSecret.vue +171 -0
  98. package/components/fleet/FleetRepos.vue +38 -76
  99. package/components/fleet/FleetResources.vue +50 -22
  100. package/components/fleet/FleetSummary.vue +26 -51
  101. package/components/fleet/__tests__/FleetOCIStorageSecret.test.ts +213 -0
  102. package/components/fleet/__tests__/FleetSummary.test.ts +39 -39
  103. package/components/fleet/dashboard/Empty.vue +73 -0
  104. package/components/fleet/dashboard/ResourceCard.vue +183 -0
  105. package/components/fleet/dashboard/ResourceCardSummary.vue +199 -0
  106. package/components/fleet/dashboard/ResourceDetails.vue +196 -0
  107. package/components/fleet/dashboard/ResourcePanel.vue +376 -0
  108. package/components/form/ArrayList.vue +139 -117
  109. package/components/form/BannerSettings.vue +145 -96
  110. package/components/form/KeyValue.vue +10 -7
  111. package/components/form/LabeledSelect.vue +9 -2
  112. package/components/form/MatchExpressions.vue +5 -1
  113. package/components/form/NameNsDescription.vue +1 -1
  114. package/components/form/ResourceSelector.vue +26 -23
  115. package/components/form/ResourceTabs/index.vue +2 -1
  116. package/components/form/Select.vue +9 -2
  117. package/components/form/SimpleSecretSelector.vue +8 -2
  118. package/components/form/UnitInput.vue +13 -0
  119. package/components/form/ValueFromResource.vue +31 -19
  120. package/components/form/__tests__/ArrayList.test.ts +32 -0
  121. package/components/form/__tests__/KeyValue.test.ts +36 -0
  122. package/components/form/__tests__/LabeledSelect.test.ts +33 -0
  123. package/components/form/__tests__/Select.test.ts +34 -1
  124. package/components/form/__tests__/UnitInput.test.ts +23 -1
  125. package/components/formatter/ClusterLink.vue +5 -8
  126. package/components/formatter/Description.vue +30 -0
  127. package/components/formatter/FleetApplicationClustersReady.vue +77 -0
  128. package/components/formatter/FleetApplicationSource.vue +71 -0
  129. package/components/formatter/FleetSummaryGraph.vue +7 -0
  130. package/components/formatter/__tests__/ClusterLink.test.ts +2 -32
  131. package/components/nav/Header.vue +8 -7
  132. package/components/nav/NamespaceFilter.vue +1 -1
  133. package/components/nav/TopLevelMenu.helper.ts +55 -34
  134. package/components/nav/TopLevelMenu.vue +11 -0
  135. package/components/nav/Type.vue +4 -1
  136. package/components/nav/WindowManager/index.vue +1 -0
  137. package/composables/useI18n.ts +12 -11
  138. package/config/labels-annotations.js +14 -11
  139. package/config/product/auth.js +1 -0
  140. package/config/product/explorer.js +16 -13
  141. package/config/product/fleet.js +70 -17
  142. package/config/product/manager.js +1 -28
  143. package/config/query-params.js +3 -1
  144. package/config/roles.ts +1 -0
  145. package/config/router/routes.js +20 -2
  146. package/config/secret.ts +15 -0
  147. package/config/settings.ts +14 -15
  148. package/config/table-headers.js +59 -27
  149. package/config/types.js +2 -0
  150. package/core/plugin-helpers.ts +3 -2
  151. package/detail/catalog.cattle.io.app.vue +0 -1
  152. package/detail/fleet.cattle.io.cluster.vue +28 -15
  153. package/detail/fleet.cattle.io.gitrepo.vue +10 -1
  154. package/detail/fleet.cattle.io.helmop.vue +157 -0
  155. package/detail/provisioning.cattle.io.cluster.vue +13 -3
  156. package/detail/service.vue +0 -1
  157. package/detail/workload/index.vue +21 -34
  158. package/dialog/ExtensionCatalogUninstallDialog.vue +14 -8
  159. package/dialog/HelmOpForceUpdateDialog.vue +132 -0
  160. package/dialog/RedeployWorkloadDialog.vue +164 -0
  161. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +56 -67
  162. package/edit/__tests__/service.test.ts +2 -1
  163. package/edit/auth/oidc.vue +159 -93
  164. package/edit/fleet.cattle.io.gitrepo.vue +26 -33
  165. package/edit/fleet.cattle.io.helmop.vue +997 -0
  166. package/edit/management.cattle.io.fleetworkspace.vue +43 -10
  167. package/edit/networking.k8s.io.networkpolicy/PolicyRule.vue +3 -14
  168. package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +57 -62
  169. package/edit/networking.k8s.io.networkpolicy/PolicyRules.vue +3 -14
  170. package/edit/networking.k8s.io.networkpolicy/__tests__/PolicyRuleTarget.test.ts +72 -41
  171. package/edit/networking.k8s.io.networkpolicy/__tests__/utils/mock.json +17 -1
  172. package/edit/networking.k8s.io.networkpolicy/index.vue +18 -30
  173. package/edit/provisioning.cattle.io.cluster/index.vue +21 -73
  174. package/edit/service.vue +13 -28
  175. package/list/fleet.cattle.io.gitrepo.vue +1 -1
  176. package/list/fleet.cattle.io.helmop.vue +108 -0
  177. package/list/namespace.vue +5 -2
  178. package/list/workload.vue +6 -1
  179. package/mixins/auth-config.js +8 -1
  180. package/mixins/preset.js +100 -0
  181. package/mixins/resource-fetch-api-pagination.js +57 -43
  182. package/mixins/resource-fetch.js +15 -6
  183. package/mixins/resource-table-watch.js +45 -0
  184. package/models/__tests__/chart.test.ts +273 -0
  185. package/models/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -1
  186. package/models/__tests__/workload.test.ts +1 -0
  187. package/models/chart.js +144 -2
  188. package/models/cluster/node.js +1 -0
  189. package/models/cluster.js +32 -2
  190. package/models/fleet-application.js +385 -0
  191. package/models/fleet.cattle.io.bundle.js +9 -8
  192. package/models/fleet.cattle.io.gitrepo.js +41 -365
  193. package/models/fleet.cattle.io.helmop.js +228 -0
  194. package/models/management.cattle.io.authconfig.js +1 -0
  195. package/models/management.cattle.io.cluster.js +0 -20
  196. package/models/management.cattle.io.fleetworkspace.js +12 -0
  197. package/models/management.cattle.io.node.js +7 -22
  198. package/models/management.cattle.io.nodepool.js +12 -0
  199. package/models/namespace.js +5 -0
  200. package/models/provisioning.cattle.io.cluster.js +18 -64
  201. package/models/service.js +24 -9
  202. package/models/workload.js +84 -49
  203. package/package.json +2 -1
  204. package/pages/auth/verify.vue +13 -1
  205. package/pages/c/_cluster/apps/charts/AddRepoLink.vue +37 -0
  206. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +80 -0
  207. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +54 -0
  208. package/pages/c/_cluster/apps/charts/StatusLabel.vue +33 -0
  209. package/pages/c/_cluster/apps/charts/index.vue +302 -484
  210. package/pages/c/_cluster/apps/charts/install.vue +0 -1
  211. package/pages/c/_cluster/explorer/EventsTable.vue +1 -1
  212. package/pages/c/_cluster/explorer/index.vue +11 -0
  213. package/pages/c/_cluster/fleet/__tests__/index.test.ts +426 -0
  214. package/pages/c/_cluster/fleet/application/_resource/_id.vue +14 -0
  215. package/pages/c/_cluster/fleet/application/_resource/create.vue +14 -0
  216. package/pages/c/_cluster/fleet/application/create.vue +340 -0
  217. package/pages/c/_cluster/fleet/application/index.vue +139 -0
  218. package/pages/c/_cluster/fleet/graph/config.js +277 -0
  219. package/pages/c/_cluster/fleet/index.vue +772 -330
  220. package/pages/c/_cluster/longhorn/index.vue +2 -2
  221. package/pages/c/_cluster/settings/banners.vue +56 -2
  222. package/pages/c/_cluster/settings/performance.vue +7 -26
  223. package/pages/explorer/resource/detail/configmap.vue +19 -0
  224. package/pages/home.vue +11 -52
  225. package/plugins/clean-html.js +2 -0
  226. package/plugins/dashboard-store/__tests__/actions.test.ts +4 -1
  227. package/plugins/dashboard-store/actions.js +153 -30
  228. package/plugins/dashboard-store/getters.js +108 -24
  229. package/plugins/dashboard-store/mutations.js +61 -12
  230. package/plugins/dashboard-store/resource-class.js +36 -4
  231. package/plugins/steve/__tests__/getters.test.ts +18 -11
  232. package/plugins/steve/__tests__/steve-class.test.ts +1 -0
  233. package/plugins/steve/__tests__/subscribe.spec.ts +66 -1
  234. package/plugins/steve/actions.js +37 -12
  235. package/plugins/steve/getters.js +39 -10
  236. package/plugins/steve/steve-class.js +5 -0
  237. package/plugins/steve/steve-pagination-utils.ts +213 -50
  238. package/plugins/steve/subscribe.js +229 -42
  239. package/plugins/steve/worker/web-worker.advanced.js +3 -1
  240. package/rancher-components/BadgeState/BadgeState.vue +3 -1
  241. package/rancher-components/Banner/Banner.test.ts +51 -3
  242. package/rancher-components/Banner/Banner.vue +28 -6
  243. package/rancher-components/Form/Checkbox/Checkbox.vue +2 -2
  244. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +5 -1
  245. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +21 -1
  246. package/rancher-components/RcItemCard/RcItemCard.test.ts +189 -0
  247. package/rancher-components/RcItemCard/RcItemCard.vue +425 -0
  248. package/rancher-components/RcItemCard/RcItemCardAction.vue +24 -0
  249. package/rancher-components/RcItemCard/index.ts +2 -0
  250. package/store/auth.js +1 -0
  251. package/store/catalog.js +62 -24
  252. package/store/features.js +0 -1
  253. package/store/index.js +33 -14
  254. package/store/slideInPanel.ts +6 -0
  255. package/store/type-map.js +1 -0
  256. package/store/type-map.utils.ts +45 -2
  257. package/types/fleet.d.ts +36 -1
  258. package/types/kube/kube-api.ts +22 -0
  259. package/types/resources/settings.d.ts +19 -5
  260. package/types/shell/index.d.ts +595 -471
  261. package/types/store/dashboard-store.types.ts +41 -4
  262. package/types/store/pagination.types.ts +25 -3
  263. package/types/store/subscribe.types.ts +50 -0
  264. package/utils/auth.js +32 -3
  265. package/utils/cluster.js +24 -20
  266. package/utils/fleet-types.ts +0 -0
  267. package/utils/fleet.ts +200 -1
  268. package/utils/grafana.js +1 -0
  269. package/utils/object.js +0 -12
  270. package/utils/pagination-utils.ts +32 -3
  271. package/utils/pagination-wrapper.ts +132 -50
  272. package/utils/perf-setting.utils.ts +28 -0
  273. package/utils/selector-typed.ts +205 -0
  274. package/utils/selector.js +29 -6
  275. package/utils/settings.ts +4 -1
  276. package/utils/style.ts +39 -0
  277. package/utils/uiplugins.ts +10 -6
  278. package/utils/v-sphere.ts +5 -1
  279. package/utils/validators/formRules/__tests__/index.test.ts +36 -3
  280. package/utils/validators/formRules/index.ts +10 -3
  281. package/utils/window.js +11 -7
  282. package/components/__tests__/ApplicationCard.test.ts +0 -27
  283. package/components/cards/ApplicationCard.vue +0 -145
  284. package/components/fleet/ForceDirectedTreeChart/chartIcons.js +0 -17
  285. package/components/formatter/RKETemplateName.vue +0 -37
  286. package/config/secret.js +0 -14
  287. package/dialog/SaveAsRKETemplateDialog.vue +0 -139
  288. package/pages/c/_cluster/fleet/GitRepoGraphConfig.js +0 -249
  289. /package/{components/form/SSHKnownHosts → dialog}/__tests__/KnownHostsEditDialog.test.ts +0 -0
@@ -0,0 +1,32 @@
1
+ <script setup lang="ts">
2
+ export interface RectangleProps {
3
+ outline?: boolean;
4
+ }
5
+
6
+ const props = withDefaults(
7
+ defineProps<RectangleProps>(),
8
+ { outline: false }
9
+ );
10
+
11
+ </script>
12
+
13
+ <template>
14
+ <div
15
+ class="rectangle"
16
+ :class="{outline: props.outline}"
17
+ >
18
+ <slot />
19
+ </div>
20
+ </template>
21
+
22
+ <style lang="scss" scoped>
23
+ .rectangle {
24
+ border: 1px solid var(--tag-bg);
25
+ border-radius: 4px;
26
+ padding: 4px;
27
+
28
+ &:not(.outline) {
29
+ background-color: var(--tag-bg);
30
+ }
31
+ }
32
+ </style>
@@ -0,0 +1,107 @@
1
+ import { mount, RouterLinkStub } from '@vue/test-utils';
2
+ import KeyValue from '@shell/components/Resource/Detail/Metadata/KeyValue.vue';
3
+ import { createStore } from 'vuex';
4
+ import Rectangle from '@shell/components/Resource/Detail/Metadata/Rectangle.vue';
5
+
6
+ describe('component: Metadata/IdentifyingInformation', () => {
7
+ const propertyName = 'PROPERTY_NAME';
8
+ const store = createStore({});
9
+ const cleanTooltip = jest.fn();
10
+ const directives = { 'clean-tooltip': cleanTooltip };
11
+ const rows = [{ key: 'KEY', value: 'VALUE' }];
12
+
13
+ afterEach(() => {
14
+ cleanTooltip.mockClear();
15
+ });
16
+
17
+ it('should render container with identifying information', async() => {
18
+ const wrapper = mount(KeyValue, {
19
+ props: { propertyName, rows },
20
+ global: {
21
+ stubs: { 'router-link': RouterLinkStub, 'clean-tooltip': true },
22
+ provide: { store },
23
+ directives
24
+ }
25
+ });
26
+
27
+ expect(wrapper.find('.key-value').exists()).toBeTruthy();
28
+ });
29
+
30
+ it('should render property name and count', async() => {
31
+ const wrapper = mount(KeyValue, {
32
+ props: { propertyName, rows },
33
+ global: {
34
+ stubs: { 'router-link': RouterLinkStub, 'clean-tooltip': true },
35
+ provide: { store },
36
+ directives
37
+ }
38
+ });
39
+
40
+ expect(wrapper.find('.heading .title').element.innerHTML.trim()).toStrictEqual(propertyName);
41
+ expect(wrapper.find('.heading .count').element.innerHTML.trim()).toStrictEqual(rows.length.toString());
42
+ });
43
+
44
+ it('should render no rows messaging', async() => {
45
+ const wrapper = mount(KeyValue, {
46
+ props: { propertyName, rows: [] },
47
+ global: {
48
+ stubs: { 'router-link': RouterLinkStub, 'clean-tooltip': true },
49
+ provide: { store },
50
+ directives
51
+ }
52
+ });
53
+
54
+ expect(wrapper.find('.empty .no-rows').element.innerHTML.trim()).toStrictEqual(`component.resource.detail.metadata.keyValue.noRows-{"propertyName":"${ propertyName.toLowerCase() }"}`);
55
+ expect(wrapper.find('.empty .show-configuration').findComponent(RouterLinkStub).element.innerHTML).toStrictEqual('component.resource.detail.metadata.keyValue.showConfiguration');
56
+ });
57
+
58
+ it('should render show all button if rows length exceeds max', async() => {
59
+ const wrapper = mount(KeyValue, {
60
+ props: {
61
+ propertyName, rows: [...rows, ...rows], maxRows: 1
62
+ },
63
+ global: {
64
+ stubs: { 'router-link': RouterLinkStub, 'clean-tooltip': true },
65
+ provide: { store },
66
+ directives
67
+ }
68
+ });
69
+
70
+ expect(wrapper.find('.show-all').exists()).toBeTruthy();
71
+ });
72
+
73
+ it('should pass outline down to rectangle', async() => {
74
+ const wrapper = mount(KeyValue, {
75
+ props: {
76
+ propertyName, rows, outline: false
77
+ },
78
+ global: {
79
+ stubs: { 'router-link': RouterLinkStub, 'clean-tooltip': true },
80
+ provide: { store },
81
+ directives
82
+ }
83
+ });
84
+
85
+ const rectangleComponent = wrapper.find('.row').findComponent(Rectangle);
86
+
87
+ expect(rectangleComponent.props('outline')).toStrictEqual(false);
88
+ });
89
+
90
+ it('should render a concatenated string for the tooltip and default slot of the rectangle', async() => {
91
+ const wrapper = mount(KeyValue, {
92
+ props: { propertyName, rows },
93
+ global: {
94
+ stubs: { 'router-link': RouterLinkStub, 'clean-tooltip': true },
95
+ provide: { store },
96
+ directives
97
+ }
98
+ });
99
+
100
+ const row = rows[0];
101
+ const concatenated = `${ row.key }: ${ row.value }`;
102
+ const rectangleComponent = wrapper.find('.row').findComponent(Rectangle);
103
+
104
+ expect(rectangleComponent.element.innerHTML).toStrictEqual(concatenated);
105
+ expect(cleanTooltip.mock.calls[0][1].value).toStrictEqual(concatenated);
106
+ });
107
+ });
@@ -0,0 +1,24 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import Rectangle from '@shell/components/Resource/Detail/Metadata/Rectangle.vue';
3
+
4
+ describe('component: Rectangle', () => {
5
+ it('shoulder render container with class title and missing outline when passed outline:false', async() => {
6
+ const wrapper = mount(Rectangle, { props: { outline: false } });
7
+
8
+ expect(wrapper.find('.rectangle').exists()).toBeTruthy();
9
+ expect(wrapper.find('.rectangle.outline').exists()).toBeFalsy();
10
+ });
11
+
12
+ it('should render outline class when passed outline:true', async() => {
13
+ const wrapper = mount(Rectangle, { props: { outline: true } });
14
+
15
+ expect(wrapper.find('.rectangle.outline').exists()).toBeTruthy();
16
+ });
17
+
18
+ it('should render default slot', async() => {
19
+ const content = 'CONTENT';
20
+ const wrapper = mount(Rectangle, { slots: { default: content } });
21
+
22
+ expect(wrapper.find('.rectangle').element.innerHTML).toStrictEqual(content);
23
+ });
24
+ });
@@ -0,0 +1,91 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import Metadata from '@shell/components/Resource/Detail/Metadata/index.vue';
3
+ import { createStore } from 'vuex';
4
+
5
+ describe('component: Metadata/index', () => {
6
+ const store = createStore({});
7
+ const stubs = ['IdentifyingInformation', 'KeyValue', 'Labels', 'Annotations'];
8
+
9
+ const identifyingInformation = [{ label: 'zero' }];
10
+ const keyValue = [{ key: 'key', value: 'value' }];
11
+
12
+ it('should render the container with metadata class', async() => {
13
+ const wrapper = mount(Metadata, {
14
+ props: {
15
+ identifyingInformation,
16
+ labels: [],
17
+ annotations: []
18
+ },
19
+ global: { provide: { store }, stubs }
20
+ });
21
+
22
+ expect(wrapper.find('.spaced-row.metadata').exists()).toBeTruthy();
23
+ });
24
+
25
+ it('should render identifying information with the appropriate class and rows', async() => {
26
+ const wrapper = mount(Metadata, {
27
+ props: {
28
+ identifyingInformation,
29
+ labels: [],
30
+ annotations: []
31
+ },
32
+ global: { provide: { store }, stubs }
33
+ });
34
+
35
+ const identingInformationComponent = wrapper.find('.identifying-info').getComponent<any>('identifying-information-stub');
36
+
37
+ expect(identingInformationComponent.props('rows')).toStrictEqual(identifyingInformation);
38
+ });
39
+
40
+ it('should render both empty message if labels and annotations are empty and labels/annotations are hidden', async() => {
41
+ const wrapper = mount(Metadata, {
42
+ props: {
43
+ identifyingInformation,
44
+ labels: [],
45
+ annotations: []
46
+ },
47
+ global: { provide: { store }, stubs }
48
+ });
49
+
50
+ expect(wrapper.find('.labels').exists()).toBeFalsy();
51
+ expect(wrapper.find('.annotations').exists()).toBeFalsy();
52
+ const keyValueComponent = wrapper.find('.labels-and-annotations-empty').getComponent<any>('key-value-stub');
53
+
54
+ expect(keyValueComponent.props('rows')).toStrictEqual([]);
55
+ expect(keyValueComponent.props('propertyName')).toStrictEqual('component.resource.detail.metadata.labelsAndAnnotations');
56
+ });
57
+
58
+ it('should render labels and pass appropriate props and not render the empty message', async() => {
59
+ const wrapper = mount(Metadata, {
60
+ props: {
61
+ identifyingInformation,
62
+ labels: keyValue,
63
+ annotations: []
64
+ },
65
+ global: { provide: { store }, stubs }
66
+ });
67
+
68
+ expect(wrapper.find('.labels-and-annotations-empty').exists()).toBeFalsy();
69
+
70
+ const labelsComponent = wrapper.find('.labels').getComponent<any>('labels-stub');
71
+
72
+ expect(labelsComponent.props('labels')).toStrictEqual(keyValue);
73
+ });
74
+
75
+ it('should render annotations and pass appropriate props and not render the empty message', async() => {
76
+ const wrapper = mount(Metadata, {
77
+ props: {
78
+ identifyingInformation,
79
+ labels: [],
80
+ annotations: keyValue
81
+ },
82
+ global: { provide: { store }, stubs }
83
+ });
84
+
85
+ expect(wrapper.find('.labels-and-annotations-empty').exists()).toBeFalsy();
86
+
87
+ const labelsComponent = wrapper.find('.annotations').getComponent<any>('annotations-stub');
88
+
89
+ expect(labelsComponent.props('annotations')).toStrictEqual(keyValue);
90
+ });
91
+ });
@@ -0,0 +1,29 @@
1
+ import { Row as IdentifyingInformationRow } from '@shell/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue';
2
+ import { useDefaultIdentifyingInformation } from '@shell/components/Resource/Detail/Metadata/IdentifyingInformation/composable';
3
+ import { useDefaultLabels } from '@shell/components/Resource/Detail/Metadata/Labels/composable';
4
+ import { useDefaultAnnotations } from '@shell/components/Resource/Detail/Metadata/Annotations/composable';
5
+ import { computed, toValue, Ref } from 'vue';
6
+
7
+ export const useBasicMetadata = (resource: any) => {
8
+ const labels = useDefaultLabels(resource);
9
+ const annotations = useDefaultAnnotations(resource);
10
+
11
+ return {
12
+ labels,
13
+ annotations
14
+ };
15
+ };
16
+
17
+ export const useDefaultMetadata = (resource: any, additionalIdentifyingInformation?: (IdentifyingInformationRow[] | Ref<IdentifyingInformationRow[]>)) => {
18
+ const defaultIdentifyingInformation = useDefaultIdentifyingInformation(resource);
19
+ const additionalIdentifyingInformationValue = toValue(additionalIdentifyingInformation);
20
+
21
+ const identifyingInformation = computed(() => [...defaultIdentifyingInformation.value, ...(additionalIdentifyingInformationValue || [])]);
22
+ const { labels, annotations } = useBasicMetadata(resource);
23
+
24
+ return {
25
+ identifyingInformation,
26
+ labels,
27
+ annotations
28
+ };
29
+ };
@@ -0,0 +1,66 @@
1
+ <script setup lang="ts">
2
+ import IdentifyingInformation, { Row as IdentifyingInformationRow } from '@shell/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue';
3
+ import Labels, { Label } from '@shell/components/Resource/Detail/Metadata/Labels/index.vue';
4
+ import Annotations, { Annotation } from '@shell/components/Resource/Detail/Metadata/Annotations/index.vue';
5
+ import SpacedRow from '@shell/components/Resource/Detail/SpacedRow.vue';
6
+ import KeyValue from '@shell/components/Resource/Detail/Metadata/KeyValue.vue';
7
+ import { computed } from 'vue';
8
+ import { useI18n } from '@shell/composables/useI18n';
9
+ import { useStore } from 'vuex';
10
+
11
+ export interface MetadataProps {
12
+ identifyingInformation: IdentifyingInformationRow[],
13
+ labels: Label[],
14
+ annotations: Annotation[]
15
+ }
16
+
17
+ const { identifyingInformation, labels, annotations } = defineProps<MetadataProps>();
18
+
19
+ const store = useStore();
20
+ const i18n = useI18n(store);
21
+
22
+ const showBothEmpty = computed(() => labels.length === 0 && annotations.length === 0);
23
+ </script>
24
+
25
+ <template>
26
+ <SpacedRow class="metadata ppb-3">
27
+ <div
28
+ class="identifying-info"
29
+ >
30
+ <IdentifyingInformation :rows="identifyingInformation" />
31
+ </div>
32
+ <!-- In the renders we want the same empty message that the labels/annotations show but we want it to span two columns. This is a cheap way to keep the look consistent without duplicating code. -->
33
+ <div
34
+ v-if="showBothEmpty"
35
+ class="labels-and-annotations-empty"
36
+ >
37
+ <KeyValue
38
+ :rows="[]"
39
+ :propertyName="i18n.t('component.resource.detail.metadata.labelsAndAnnotations')"
40
+ />
41
+ </div>
42
+ <!-- I'm not using v-else here so I can maintain the spacing correctly with the other columns in other rows. -->
43
+ <div
44
+ v-if="!showBothEmpty"
45
+ class="labels"
46
+ >
47
+ <Labels :labels="labels" />
48
+ </div>
49
+ <div
50
+ v-if="!showBothEmpty"
51
+ class="annotations"
52
+ >
53
+ <Annotations :annotations="annotations" />
54
+ </div>
55
+ </SpacedRow>
56
+ </template>
57
+
58
+ <style lang="scss" scoped>
59
+ .metadata {
60
+ .labels-and-annotations-empty {
61
+ grid-column: span 2;
62
+ }
63
+
64
+ border-bottom: 1px solid var(--border);
65
+ }
66
+ </style>
@@ -0,0 +1,22 @@
1
+ <template>
2
+ <div class="resource-detail-page">
3
+ <div
4
+ v-if="$slots['top-area']"
5
+ class="top-area"
6
+ >
7
+ <slot name="top-area" />
8
+ </div>
9
+ <div
10
+ v-if="$slots['middle-area']"
11
+ class="middle-area mmt-6"
12
+ >
13
+ <slot name="middle-area" />
14
+ </div>
15
+ <div
16
+ v-if="$slots['bottom-area']"
17
+ class="bottom-area mmt-6"
18
+ >
19
+ <slot name="bottom-area" />
20
+ </div>
21
+ </div>
22
+ </template>
@@ -0,0 +1,40 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue';
3
+
4
+ export interface Props {
5
+ percent: number;
6
+ percentageColor?: string;
7
+ baseColor?: string;
8
+ }
9
+
10
+ const props = withDefaults(
11
+ defineProps<Props>(),
12
+ {
13
+ baseColor: 'var(--progress-bg)',
14
+ percentageColor: 'var(--primary)'
15
+ }
16
+ );
17
+
18
+ const usedWidth = computed(() => `${ props.percent }%`);
19
+ </script>
20
+
21
+ <template>
22
+ <div class="percentage-bar">
23
+ <div class="used" />
24
+ </div>
25
+ </template>
26
+
27
+ <style lang="scss" scoped>
28
+ .percentage-bar {
29
+ border-radius: var(--border-radius-md);
30
+ height: 16px;
31
+ overflow: hidden;
32
+ background-color: v-bind('props.baseColor');
33
+
34
+ .used {
35
+ height: 100%;
36
+ background-color: v-bind('props.percentageColor');
37
+ width: v-bind('usedWidth')
38
+ }
39
+ }
40
+ </style>
@@ -0,0 +1,119 @@
1
+ <script lang="ts">
2
+ import SubtleLink from '@shell/components/SubtleLink.vue';
3
+ import { StateColor, stateColorCssVar } from '@shell/utils/style';
4
+ import { sortBy } from 'lodash';
5
+ import { RouteLocationRaw } from 'vue-router';
6
+
7
+ export interface Count {
8
+ label: string;
9
+ count: number;
10
+ }
11
+
12
+ export interface Props {
13
+ label: string;
14
+ to?: RouteLocationRaw;
15
+ color?: StateColor;
16
+ counts?: Count[];
17
+ }
18
+
19
+ export function extractCounts(labels: string[]): Count[] {
20
+ const accumulator: { [k: string]: number} = {};
21
+
22
+ labels.forEach((l: string) => {
23
+ accumulator[l] = accumulator[l] || 0;
24
+ accumulator[l]++;
25
+ });
26
+
27
+ const counts: Count[] = Object.entries(accumulator).map(([label, count]) => ({ label, count }));
28
+
29
+ return sortBy(counts, 'label');
30
+ }
31
+
32
+ </script>
33
+
34
+ <script setup lang="ts">
35
+ const {
36
+ label, to, counts, color
37
+ } = defineProps<Props>();
38
+ </script>
39
+
40
+ <template>
41
+ <div class="resource-row">
42
+ <div class="left">
43
+ <SubtleLink
44
+ v-if="to"
45
+ :to="to"
46
+ >
47
+ {{ label }}
48
+ </SubtleLink>
49
+ <span
50
+ v-else
51
+ class="text-muted"
52
+ >
53
+ {{ label }}
54
+ </span>
55
+ </div>
56
+ <div class="right">
57
+ <div
58
+ v-if="!counts || counts.length == 0"
59
+ class="text-muted"
60
+ >
61
+ &mdash;
62
+ </div>
63
+ <div
64
+ v-else
65
+ class="counts"
66
+ >
67
+ <span
68
+ v-if="color"
69
+ class="dot"
70
+ :style="{backgroundColor: stateColorCssVar(color)}"
71
+ >
72
+ &nbsp;
73
+ </span>
74
+ <span
75
+ v-for="count in counts"
76
+ :key="count.label"
77
+ class="count"
78
+ >
79
+ {{ count.count }} {{ count.label }}<span class="and">&nbsp;+&nbsp;</span>
80
+ </span>
81
+ </div>
82
+ </div>
83
+ </div>
84
+ </template>
85
+
86
+ <style lang="scss" scoped>
87
+ .resource-row {
88
+ display: flex;
89
+ flex-direction: row;
90
+ align-items: center;
91
+
92
+ .right {
93
+ flex-grow: 1;
94
+ text-align: right;
95
+ }
96
+
97
+ .counts {
98
+ display: inline-flex;
99
+ flex-direction: row;
100
+ justify-content: flex-end;
101
+ align-items: center;
102
+ }
103
+
104
+ .count:last-of-type .and {
105
+ display: none;
106
+ }
107
+
108
+ .dot {
109
+ display: inline-block;
110
+
111
+ $size: 6px;
112
+ width: $size;
113
+ height: $size;
114
+
115
+ border-radius: 50%;
116
+ margin-right: 10px;
117
+ }
118
+ }
119
+ </style>
@@ -0,0 +1,14 @@
1
+ <template>
2
+ <div class="spaced-row">
3
+ <slot />
4
+ </div>
5
+ </template>
6
+
7
+ <style lang="scss" scoped>
8
+ .spaced-row {
9
+ display: grid;
10
+ grid-template-columns: 1fr 1fr 1fr;
11
+ grid-auto-flow: dense;
12
+ grid-gap: 24px;
13
+ }
14
+ </style>
@@ -0,0 +1,59 @@
1
+ <script setup lang="ts">
2
+ import { stateColorCssVar, StateColor } from '@shell/utils/style';
3
+
4
+ export interface Segment {
5
+ color: StateColor;
6
+ percent: number;
7
+ }
8
+
9
+ export interface Props {
10
+ segments: Segment[]
11
+ }
12
+
13
+ const { segments } = defineProps<Props>();
14
+
15
+ const computeStyle = (segment: Segment) => {
16
+ return {
17
+ backgroundColor: stateColorCssVar(segment.color),
18
+ width: `${ segment.percent }%`
19
+ };
20
+ };
21
+ </script>
22
+
23
+ <template>
24
+ <div class="status-bar">
25
+ <div
26
+ v-for="(segment, i) in segments"
27
+ :key="i"
28
+ class="segment"
29
+ :style="computeStyle(segment)"
30
+ >
31
+ &nbsp;
32
+ </div>
33
+ </div>
34
+ </template>
35
+
36
+ <style lang="scss" scoped>
37
+ .status-bar {
38
+ display: flex;
39
+ flex-direction: row;
40
+ justify-content: center;
41
+
42
+ column-gap: 2px;
43
+ height: 21px;
44
+
45
+ .segment {
46
+ height: 4px;
47
+
48
+ &:first-of-type {
49
+ border-top-left-radius: 4px;
50
+ border-bottom-left-radius: 4px;
51
+ }
52
+
53
+ &:last-of-type {
54
+ border-top-right-radius: 4px;
55
+ border-bottom-right-radius: 4px;
56
+ }
57
+ }
58
+ }
59
+ </style>
@@ -0,0 +1,61 @@
1
+ <script setup lang="ts">
2
+ import Bubble from '@shell/components/Resource/Detail/Card/PodsCard/Bubble.vue';
3
+ import { StateColor, stateColorCssVar } from '@shell/utils/style';
4
+
5
+ export interface Props {
6
+ color: StateColor;
7
+ label: string;
8
+ count: number;
9
+ percent: number;
10
+ }
11
+
12
+ const {
13
+ color, label, count, percent
14
+ } = defineProps<Props>();
15
+ </script>
16
+
17
+ <template>
18
+ <div class="status-row">
19
+ <div
20
+ class="indicator"
21
+ :style="{backgroundColor: stateColorCssVar(color)}"
22
+ />
23
+ <div class="label">
24
+ {{ label }}
25
+ </div>
26
+ <div class="count">
27
+ <Bubble>{{ count }}</Bubble>
28
+ </div>
29
+ <div class="percent text-muted">
30
+ {{ percent.toFixed(1) }}%
31
+ </div>
32
+ </div>
33
+ </template>
34
+
35
+ <style lang="scss" scoped>
36
+ .status-row {
37
+ display: flex;
38
+ flex-direction: row;
39
+ align-items: center;
40
+
41
+ &:not(:first-of-type) {
42
+ margin-top: 8px;
43
+ }
44
+
45
+ .label {
46
+ flex-grow: 1;
47
+ }
48
+
49
+ .indicator {
50
+ height: 4px;
51
+ border-radius: 4px;
52
+ width: 20px;
53
+ margin-right: 10px;
54
+ }
55
+
56
+ .percent {
57
+ width: 60px;
58
+ text-align: right;
59
+ }
60
+ }
61
+ </style>
@@ -0,0 +1,13 @@
1
+ <template>
2
+ <h1 class="title">
3
+ <slot name="default" />
4
+ </h1>
5
+ </template>
6
+
7
+ <style lang="scss" scoped>
8
+ h1.title {
9
+ display: inline-block;
10
+ align-items: center;
11
+ line-height: 18px;
12
+ }
13
+ </style>