@rancher/shell 3.0.5-rc.3 → 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 (200) hide show
  1. package/assets/images/icons/document.svg +3 -0
  2. package/assets/images/vendor/cognito.svg +1 -0
  3. package/assets/styles/app.scss +1 -0
  4. package/assets/styles/base/_basic.scss +10 -0
  5. package/assets/styles/base/_spacing.scss +29 -0
  6. package/assets/styles/global/_layout.scss +1 -1
  7. package/assets/styles/themes/_dark.scss +25 -0
  8. package/assets/styles/themes/_light.scss +65 -0
  9. package/assets/translations/en-us.yaml +322 -24
  10. package/assets/translations/zh-hans.yaml +8 -5
  11. package/components/Certificates.vue +5 -0
  12. package/components/FilterPanel.vue +156 -0
  13. package/components/{fleet/ForceDirectedTreeChart/index.vue → ForceDirectedTreeChart.vue} +47 -41
  14. package/components/IconOrSvg.vue +14 -35
  15. package/components/PromptRemove.vue +5 -1
  16. package/components/Resource/Detail/Card/PodsCard/Bubble.vue +13 -0
  17. package/components/Resource/Detail/Card/PodsCard/composable.ts +30 -0
  18. package/components/Resource/Detail/Card/PodsCard/index.vue +118 -0
  19. package/components/Resource/Detail/Card/ResourceUsageCard/composable.ts +51 -0
  20. package/components/Resource/Detail/Card/ResourceUsageCard/index.vue +79 -0
  21. package/components/Resource/Detail/Card/Scaler.vue +89 -0
  22. package/components/Resource/Detail/Card/StateCard/composables.ts +112 -0
  23. package/components/Resource/Detail/Card/StateCard/index.vue +39 -0
  24. package/components/Resource/Detail/Card/VerticalGap.vue +11 -0
  25. package/components/Resource/Detail/Card/__tests__/Card.test.ts +36 -0
  26. package/components/Resource/Detail/Card/__tests__/PodsCard.test.ts +84 -0
  27. package/components/Resource/Detail/Card/__tests__/ResourceUsageCard.test.ts +72 -0
  28. package/components/Resource/Detail/Card/__tests__/Scaler.test.ts +87 -0
  29. package/components/Resource/Detail/Card/__tests__/StateCard.test.ts +53 -0
  30. package/components/Resource/Detail/Card/__tests__/VerticalGap.test.ts +14 -0
  31. package/components/Resource/Detail/Card/__tests__/index.test.ts +36 -0
  32. package/components/Resource/Detail/Card/index.vue +56 -0
  33. package/components/Resource/Detail/Metadata/Annotations/__tests__/index.test.ts +19 -0
  34. package/components/Resource/Detail/Metadata/Annotations/composable.ts +12 -0
  35. package/components/Resource/Detail/Metadata/Annotations/index.vue +26 -0
  36. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/index.test.ts +103 -0
  37. package/components/Resource/Detail/Metadata/IdentifyingInformation/composable.ts +281 -0
  38. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +111 -0
  39. package/components/Resource/Detail/Metadata/KeyValue.vue +130 -0
  40. package/components/Resource/Detail/Metadata/Labels/__tests__/index.test.ts +18 -0
  41. package/components/Resource/Detail/Metadata/Labels/composable.ts +12 -0
  42. package/components/Resource/Detail/Metadata/Labels/index.vue +27 -0
  43. package/components/Resource/Detail/Metadata/Rectangle.vue +32 -0
  44. package/components/Resource/Detail/Metadata/__tests__/KeyValue.test.ts +107 -0
  45. package/components/Resource/Detail/Metadata/__tests__/Rectangle.test.ts +24 -0
  46. package/components/Resource/Detail/Metadata/__tests__/index.test.ts +91 -0
  47. package/components/Resource/Detail/Metadata/composables.ts +29 -0
  48. package/components/Resource/Detail/Metadata/index.vue +66 -0
  49. package/components/Resource/Detail/Page.vue +22 -0
  50. package/components/Resource/Detail/PercentageBar.vue +40 -0
  51. package/components/Resource/Detail/ResourceRow.vue +119 -0
  52. package/components/Resource/Detail/SpacedRow.vue +14 -0
  53. package/components/Resource/Detail/StatusBar.vue +59 -0
  54. package/components/Resource/Detail/StatusRow.vue +61 -0
  55. package/components/Resource/Detail/TitleBar/Title.vue +13 -0
  56. package/components/Resource/Detail/TitleBar/Top.vue +14 -0
  57. package/components/Resource/Detail/TitleBar/__tests__/Title.test.ts +17 -0
  58. package/components/Resource/Detail/TitleBar/__tests__/Top.test.ts +17 -0
  59. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +142 -0
  60. package/components/Resource/Detail/TitleBar/composable.ts +31 -0
  61. package/components/Resource/Detail/TitleBar/index.vue +124 -0
  62. package/components/Resource/Detail/Top/index.vue +34 -0
  63. package/components/Resource/Detail/__tests__/Page.test.ts +32 -0
  64. package/components/ResourceDetail/__tests__/index.test.ts +114 -0
  65. package/components/ResourceDetail/index.vue +64 -562
  66. package/components/ResourceDetail/legacy.vue +545 -0
  67. package/components/ResourceTable.vue +41 -7
  68. package/components/SlideInPanelManager.vue +76 -8
  69. package/components/SortableTable/index.vue +13 -2
  70. package/components/SortableTable/selection.js +21 -8
  71. package/components/StatusBadge.vue +6 -4
  72. package/components/SubtleLink.vue +25 -0
  73. package/components/Wizard.vue +12 -1
  74. package/components/YamlEditor.vue +1 -1
  75. package/components/__tests__/FilterPanel.test.ts +81 -0
  76. package/components/auth/AuthBanner.vue +2 -3
  77. package/components/auth/RoleDetailEdit.vue +45 -3
  78. package/components/auth/login/oidc.vue +6 -1
  79. package/components/fleet/FleetApplications.vue +181 -0
  80. package/components/fleet/FleetHelmOps.vue +115 -0
  81. package/components/fleet/FleetIntro.vue +58 -28
  82. package/components/fleet/FleetNoWorkspaces.vue +5 -1
  83. package/components/fleet/FleetOCIStorageSecret.vue +171 -0
  84. package/components/fleet/FleetRepos.vue +38 -76
  85. package/components/fleet/FleetResources.vue +50 -22
  86. package/components/fleet/FleetSummary.vue +26 -51
  87. package/components/fleet/__tests__/FleetOCIStorageSecret.test.ts +213 -0
  88. package/components/fleet/__tests__/FleetSummary.test.ts +39 -39
  89. package/components/fleet/dashboard/Empty.vue +73 -0
  90. package/components/fleet/dashboard/ResourceCard.vue +183 -0
  91. package/components/fleet/dashboard/ResourceCardSummary.vue +199 -0
  92. package/components/fleet/dashboard/ResourceDetails.vue +196 -0
  93. package/components/fleet/dashboard/ResourcePanel.vue +376 -0
  94. package/components/form/ArrayList.vue +6 -0
  95. package/components/form/SimpleSecretSelector.vue +8 -2
  96. package/components/form/ValueFromResource.vue +31 -19
  97. package/components/formatter/FleetApplicationClustersReady.vue +77 -0
  98. package/components/formatter/FleetApplicationSource.vue +71 -0
  99. package/components/formatter/FleetSummaryGraph.vue +7 -0
  100. package/components/nav/Header.vue +8 -7
  101. package/components/nav/TopLevelMenu.helper.ts +55 -34
  102. package/components/nav/TopLevelMenu.vue +11 -0
  103. package/components/nav/Type.vue +4 -1
  104. package/composables/useI18n.ts +12 -11
  105. package/config/labels-annotations.js +14 -11
  106. package/config/product/auth.js +1 -0
  107. package/config/product/fleet.js +70 -17
  108. package/config/query-params.js +3 -1
  109. package/config/roles.ts +1 -0
  110. package/config/router/routes.js +20 -2
  111. package/config/secret.ts +15 -0
  112. package/config/settings.ts +3 -2
  113. package/config/table-headers.js +52 -22
  114. package/config/types.js +2 -0
  115. package/core/plugin-helpers.ts +3 -2
  116. package/detail/fleet.cattle.io.cluster.vue +28 -15
  117. package/detail/fleet.cattle.io.gitrepo.vue +10 -1
  118. package/detail/fleet.cattle.io.helmop.vue +157 -0
  119. package/dialog/HelmOpForceUpdateDialog.vue +132 -0
  120. package/dialog/RedeployWorkloadDialog.vue +164 -0
  121. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +56 -67
  122. package/edit/auth/oidc.vue +159 -93
  123. package/edit/fleet.cattle.io.gitrepo.vue +26 -33
  124. package/edit/fleet.cattle.io.helmop.vue +997 -0
  125. package/edit/management.cattle.io.fleetworkspace.vue +43 -10
  126. package/list/fleet.cattle.io.gitrepo.vue +1 -1
  127. package/list/fleet.cattle.io.helmop.vue +108 -0
  128. package/list/namespace.vue +5 -2
  129. package/mixins/auth-config.js +8 -1
  130. package/mixins/preset.js +100 -0
  131. package/mixins/resource-fetch-api-pagination.js +2 -0
  132. package/mixins/resource-fetch.js +1 -1
  133. package/mixins/resource-table-watch.js +45 -0
  134. package/models/__tests__/chart.test.ts +273 -0
  135. package/models/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -1
  136. package/models/chart.js +144 -2
  137. package/models/fleet-application.js +385 -0
  138. package/models/fleet.cattle.io.bundle.js +9 -8
  139. package/models/fleet.cattle.io.gitrepo.js +41 -365
  140. package/models/fleet.cattle.io.helmop.js +228 -0
  141. package/models/management.cattle.io.authconfig.js +1 -0
  142. package/models/management.cattle.io.fleetworkspace.js +12 -0
  143. package/models/workload.js +14 -18
  144. package/package.json +2 -1
  145. package/pages/auth/verify.vue +13 -1
  146. package/pages/c/_cluster/apps/charts/AddRepoLink.vue +37 -0
  147. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +80 -0
  148. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +54 -0
  149. package/pages/c/_cluster/apps/charts/StatusLabel.vue +33 -0
  150. package/pages/c/_cluster/apps/charts/index.vue +302 -484
  151. package/pages/c/_cluster/explorer/EventsTable.vue +1 -1
  152. package/pages/c/_cluster/fleet/__tests__/index.test.ts +426 -0
  153. package/pages/c/_cluster/fleet/application/_resource/_id.vue +14 -0
  154. package/pages/c/_cluster/fleet/application/_resource/create.vue +14 -0
  155. package/pages/c/_cluster/fleet/application/create.vue +340 -0
  156. package/pages/c/_cluster/fleet/application/index.vue +139 -0
  157. package/pages/c/_cluster/fleet/graph/config.js +277 -0
  158. package/pages/c/_cluster/fleet/index.vue +772 -330
  159. package/pages/explorer/resource/detail/configmap.vue +19 -0
  160. package/plugins/dashboard-store/actions.js +31 -9
  161. package/plugins/dashboard-store/getters.js +34 -21
  162. package/plugins/dashboard-store/mutations.js +51 -7
  163. package/plugins/dashboard-store/resource-class.js +14 -2
  164. package/plugins/steve/__tests__/subscribe.spec.ts +66 -1
  165. package/plugins/steve/actions.js +3 -0
  166. package/plugins/steve/steve-pagination-utils.ts +14 -13
  167. package/plugins/steve/subscribe.js +229 -42
  168. package/rancher-components/BadgeState/BadgeState.vue +3 -1
  169. package/rancher-components/Form/Checkbox/Checkbox.vue +2 -2
  170. package/rancher-components/RcItemCard/RcItemCard.test.ts +189 -0
  171. package/rancher-components/RcItemCard/RcItemCard.vue +425 -0
  172. package/rancher-components/RcItemCard/RcItemCardAction.vue +24 -0
  173. package/rancher-components/RcItemCard/index.ts +2 -0
  174. package/store/auth.js +1 -0
  175. package/store/catalog.js +62 -24
  176. package/store/index.js +33 -14
  177. package/store/slideInPanel.ts +6 -0
  178. package/store/type-map.js +1 -0
  179. package/types/fleet.d.ts +35 -0
  180. package/types/resources/settings.d.ts +19 -1
  181. package/types/shell/index.d.ts +339 -272
  182. package/types/store/dashboard-store.types.ts +17 -3
  183. package/types/store/pagination.types.ts +6 -1
  184. package/types/store/subscribe.types.ts +50 -0
  185. package/utils/auth.js +32 -3
  186. package/utils/fleet-types.ts +0 -0
  187. package/utils/fleet.ts +200 -1
  188. package/utils/pagination-utils.ts +26 -1
  189. package/utils/pagination-wrapper.ts +132 -50
  190. package/utils/settings.ts +4 -1
  191. package/utils/style.ts +39 -0
  192. package/utils/validators/formRules/__tests__/index.test.ts +36 -3
  193. package/utils/validators/formRules/index.ts +10 -3
  194. package/utils/window.js +11 -7
  195. package/components/__tests__/ApplicationCard.test.ts +0 -27
  196. package/components/cards/ApplicationCard.vue +0 -145
  197. package/components/fleet/ForceDirectedTreeChart/chartIcons.js +0 -17
  198. package/config/secret.js +0 -14
  199. package/pages/c/_cluster/fleet/GitRepoGraphConfig.js +0 -249
  200. /package/{components/form/SSHKnownHosts → dialog}/__tests__/KnownHostsEditDialog.test.ts +0 -0
@@ -0,0 +1,130 @@
1
+ <script lang="ts">
2
+ import { computed, toRefs } from 'vue';
3
+ import Rectangle from '@shell/components/Resource/Detail/Metadata/Rectangle.vue';
4
+ import { useStore } from 'vuex';
5
+ import { useI18n } from '@shell/composables/useI18n';
6
+
7
+ export type KeyValueType = {[key: string]: string};
8
+
9
+ export interface Row {
10
+ key: string;
11
+ value: string;
12
+ }
13
+
14
+ export interface KeyValueProps {
15
+ propertyName: string;
16
+ rows: Row[];
17
+ maxRows?: number;
18
+ outline?: boolean;
19
+ }
20
+ </script>
21
+
22
+ <script setup lang="ts">
23
+ const props = withDefaults(
24
+ defineProps<KeyValueProps>(),
25
+ { outline: false, maxRows: 4 }
26
+ );
27
+ const {
28
+ propertyName, rows, maxRows, outline
29
+ } = toRefs(props);
30
+
31
+ const store = useStore();
32
+ const i18n = useI18n(store);
33
+
34
+ // Account for the show all button
35
+ const visibleRowsLength = computed(() => (rows.value.length > maxRows.value ? maxRows.value - 1 : rows.value.length));
36
+ const visibleRows = computed(() => rows.value.slice(0, visibleRowsLength.value));
37
+ const lowercasePropertyName = computed(() => propertyName.value.toLowerCase());
38
+
39
+ const showShowAllButton = computed(() => rows.value.length > maxRows.value);
40
+ const showAllLabel = computed(() => `Show all ${ lowercasePropertyName.value }`);
41
+
42
+ const displayValue = (row: Row) => `${ row.key }: ${ row.value }`;
43
+ </script>
44
+
45
+ <template>
46
+ <div class="key-value">
47
+ <div class="heading">
48
+ <span class="title text-muted">{{ propertyName }}</span>
49
+ <span class="count">{{ rows.length }}</span>
50
+ </div>
51
+ <div
52
+ v-if="visibleRows.length === 0"
53
+ class="empty mmt-2"
54
+ >
55
+ <div class="no-rows">
56
+ {{ i18n.t('component.resource.detail.metadata.keyValue.noRows', {propertyName: lowercasePropertyName}) }}
57
+ </div>
58
+ <div class="show-configuration mmt-1">
59
+ <router-link
60
+ class="secondary"
61
+ to="#"
62
+ >
63
+ {{ i18n.t('component.resource.detail.metadata.keyValue.showConfiguration') }}
64
+ </router-link>
65
+ </div>
66
+ </div>
67
+ <div
68
+ v-for="row in visibleRows"
69
+ :key="displayValue(row)"
70
+ class="row"
71
+ >
72
+ <Rectangle
73
+ v-clean-tooltip="displayValue(row)"
74
+ :outline="outline"
75
+ >
76
+ {{ displayValue(row) }}
77
+ </Rectangle>
78
+ </div>
79
+ <router-link
80
+ v-if="showShowAllButton"
81
+ to="#"
82
+ class="show-all"
83
+ >
84
+ {{ showAllLabel }}
85
+ </router-link>
86
+ </div>
87
+ </template>
88
+
89
+ <style lang="scss" scoped>
90
+ .key-value {
91
+ display: flex;
92
+ flex-direction: column;
93
+ align-items: flex-start;
94
+
95
+ .count {
96
+ margin-left: 24px;
97
+ }
98
+
99
+ .heading {
100
+ margin-bottom: 4px;
101
+ }
102
+
103
+ .row {
104
+ &:not(:first-of-type) {
105
+ margin-top: 4px;
106
+ }
107
+
108
+ & {
109
+ margin-top: 8px;
110
+ }
111
+ }
112
+ .row {
113
+ margin-top: 8px;
114
+
115
+ &:not(:first-of-type) {
116
+ margin-top: 4px;
117
+ }
118
+ }
119
+ .show-all {
120
+ margin-top: 8px;
121
+ }
122
+
123
+ .rectangle {
124
+ max-width: 100%;
125
+ overflow: hidden;
126
+ text-overflow: ellipsis;
127
+ white-space: nowrap;
128
+ }
129
+ }
130
+ </style>
@@ -0,0 +1,18 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import Labels from '@shell/components/Resource/Detail/Metadata/Labels/index.vue';
3
+ import { createStore } from 'vuex';
4
+
5
+ describe('component: Metadata/Labels', () => {
6
+ it('shoulder render KeyValue with the appropriate props', async() => {
7
+ const labels = [{ key: 'key', value: 'value' }];
8
+ const wrapper = mount(Labels, {
9
+ props: { labels },
10
+ global: { provide: { store: createStore({}) }, stubs: { KeyValue: true } }
11
+ });
12
+
13
+ const keyValue = wrapper.getComponent<any>('key-value-stub');
14
+
15
+ expect(keyValue.props('propertyName')).toStrictEqual('component.resource.detail.metadata.labels.title');
16
+ expect(keyValue.props('rows')).toStrictEqual(labels);
17
+ });
18
+ });
@@ -0,0 +1,12 @@
1
+ import { Label } from '@shell/components/Resource/Detail/Metadata/Labels/index.vue';
2
+ import { computed, Ref, toValue } from 'vue';
3
+
4
+ export const useDefaultLabels = (resource: any): Ref<Label[]> => {
5
+ const resourceValue = toValue(resource);
6
+
7
+ return computed(() => {
8
+ const entries = Object.entries<string>(resourceValue.labels || {});
9
+
10
+ return entries.map(([key, value]) => ({ key, value }));
11
+ });
12
+ };
@@ -0,0 +1,27 @@
1
+ <script lang="ts">
2
+ import KeyValue, { Row } from '@shell/components/Resource/Detail/Metadata/KeyValue.vue';
3
+ import { useI18n } from '@shell/composables/useI18n';
4
+ import { toRefs } from 'vue';
5
+ import { useStore } from 'vuex';
6
+
7
+ export type Label = Row;
8
+ export interface LabelsProps {
9
+ labels: Label[];
10
+ }
11
+
12
+ </script>
13
+
14
+ <script setup lang="ts">
15
+ const props = defineProps<LabelsProps>();
16
+ const { labels } = toRefs(props);
17
+
18
+ const store = useStore();
19
+ const i18n = useI18n(store);
20
+ </script>
21
+
22
+ <template>
23
+ <KeyValue
24
+ :propertyName="i18n.t('component.resource.detail.metadata.labels.title')"
25
+ :rows="labels"
26
+ />
27
+ </template>
@@ -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>