@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,79 @@
1
+ <script lang="ts">
2
+ import Card from '@shell/components/Resource/Detail/Card/index.vue';
3
+ import PercentageBar from '@shell/components/Resource/Detail/PercentageBar.vue';
4
+ import { useI18n } from '@shell/composables/useI18n';
5
+ import { computed } from 'vue';
6
+ import { useStore } from 'vuex';
7
+
8
+ export interface Props {
9
+ title: string;
10
+ unit?: string;
11
+ used: number;
12
+ usedFormatter?: (n: number) => string;
13
+ available: number;
14
+ availableFormatter?: (n: number) => string
15
+ }
16
+ </script>
17
+ <script setup lang="ts">
18
+ const store = useStore();
19
+ const i18n = useI18n(store);
20
+
21
+ const props = withDefaults(
22
+ defineProps<Props>(),
23
+ {
24
+ usedFormatter: (n: number) => n.toString(),
25
+ availableFormatter: (n: number) => n.toString(),
26
+ unit: undefined
27
+ }
28
+ );
29
+
30
+ const percentage = computed(() => {
31
+ const numerator = props.used || 0;
32
+ const denominator = props.available || 0;
33
+
34
+ return denominator === 0 ? 0 : Math.floor((numerator / denominator) * 100);
35
+ });
36
+
37
+ const used = computed(() => props.usedFormatter(props.used));
38
+ const available = computed(() => props.availableFormatter(props.available));
39
+ </script>
40
+
41
+ <template>
42
+ <Card :title="props.title">
43
+ <div class="numerical">
44
+ <div class="used">
45
+ {{ i18n.t('component.resource.detail.card.resourceUsage.used') }}
46
+ </div>
47
+ <div class="data">
48
+ <span class="amount">{{ i18n.t('component.resource.detail.card.resourceUsage.amount', {used, available}) }}</span>
49
+ <span
50
+ v-if="props.unit"
51
+ class="unit"
52
+ >
53
+ {{ props.unit }}
54
+ </span>
55
+ <span class="spacer">/</span>
56
+ <span class="percentage">{{ percentage }}%</span>
57
+ </div>
58
+ </div>
59
+ <PercentageBar :percent="percentage" />
60
+ </Card>
61
+ </template>
62
+
63
+ <style lang="scss" scoped>
64
+ .numerical {
65
+ display: flex;
66
+ flex-direction: row;
67
+ justify-content: space-between;
68
+
69
+ margin-bottom: 8px;
70
+
71
+ .spacer {
72
+ margin: 0 8px;
73
+ }
74
+
75
+ .percentage {
76
+ font-weight: bold;
77
+ }
78
+ }
79
+ </style>
@@ -0,0 +1,89 @@
1
+ <script setup lang="ts">
2
+ import { useI18n } from '@shell/composables/useI18n';
3
+ import { useStore } from 'vuex';
4
+
5
+ export interface Props {
6
+ ariaResourceName: string;
7
+ value?: number;
8
+ min?: number;
9
+ max?: number;
10
+ }
11
+
12
+ const props = withDefaults(defineProps<Props>(), {
13
+ value: 0, min: undefined, max: undefined
14
+ });
15
+ const emit = defineEmits(['decrease', 'increase']);
16
+
17
+ const store = useStore();
18
+ const i18n = useI18n(store);
19
+ </script>
20
+
21
+ <template>
22
+ <div class="scaler">
23
+ <button
24
+ class="decrease"
25
+ :aria-label="i18n.t('component.resource.detail.card.scaler.ariaLabel.decrease', {resourceName: props.ariaResourceName})"
26
+ :disabled="!!props.min && (props.value <= props.min)"
27
+ @click="() => emit('decrease', props.value - 1)"
28
+ >
29
+ <i class="icon icon-sm icon-minus" />
30
+ </button>
31
+ <div class="value">
32
+ {{ props.value }}
33
+ </div>
34
+ <button
35
+ class="increase"
36
+ :aria-label="i18n.t('component.resource.detail.card.scaler.ariaLabel.increase', {resourceName: props.ariaResourceName})"
37
+ :disabled="!!props.max && (props.value >= props.max)"
38
+ @click="() => emit('increase', props.value + 1)"
39
+ >
40
+ <i class="icon icon-sm icon-plus" />
41
+ </button>
42
+ </div>
43
+ </template>
44
+
45
+ <style lang="scss" scoped>
46
+ .scaler {
47
+ display: inline-flex;
48
+ align-items: center;
49
+ background-color: hsl(from var(--primary) h s calc(l + 30));
50
+ border-radius: var(--border-radius-md);
51
+ border: 1px solid var(--primary);
52
+ overflow: hidden;
53
+
54
+ button {
55
+ all: initial;
56
+ cursor: pointer;
57
+ background: none;
58
+ height: 100%;
59
+ width: 32px;
60
+ height: 32px;
61
+
62
+ text-align: center;
63
+ font-size: 20px;
64
+ font-weight: bold;
65
+ color: var(--primary);
66
+
67
+ i.icon {
68
+ font-size: 0.6em;
69
+ }
70
+
71
+ &:hover {
72
+ background-color: hsl(from var(--primary) h s calc(l + 20));
73
+ }
74
+
75
+ &[disabled] {
76
+ cursor: not-allowed;
77
+ background: var(--disabled-bg);
78
+ color: var(--disabled-text);
79
+ }
80
+ }
81
+
82
+ .value {
83
+ color: initial;
84
+ cursor: default;
85
+ padding: 4px;
86
+ padding-top: 5px;
87
+ }
88
+ }
89
+ </style>
@@ -0,0 +1,112 @@
1
+ import { extractCounts, Props as ResourceRowProps } from '@shell/components/Resource/Detail/ResourceRow.vue';
2
+ import { useI18n } from '@shell/composables/useI18n';
3
+ import { INGRESS, SERVICE } from '@shell/config/types';
4
+ import { getHighestAlertColor } from '@shell/utils/style';
5
+ import { computed, Ref, toValue } from 'vue';
6
+ import { useStore } from 'vuex';
7
+ import { Props as StateCardProps } from '@shell/components/Resource/Detail/Card/StateCard/index.vue';
8
+ import { RouteLocationRaw } from 'vue-router';
9
+
10
+ export function useResourceCardRow(label: string, resources: any[], to?: RouteLocationRaw): ResourceRowProps {
11
+ const colors = resources.map((r: any) => r.stateSimpleColor);
12
+ const states = resources.map((r: any) => r.stateDisplay.toLowerCase());
13
+
14
+ return {
15
+ label,
16
+ color: resources.length ? getHighestAlertColor(colors) : undefined,
17
+ counts: resources.length ? extractCounts(states) : undefined,
18
+ to
19
+ };
20
+ }
21
+
22
+ export interface Pairs {
23
+ label: string;
24
+ to?: RouteLocationRaw;
25
+ resources: any[];
26
+ }
27
+
28
+ export function useDefaultResources(pairs: Ref<Pairs[]>) {
29
+ const pairsValue = toValue(pairs);
30
+ const rows = computed(() => pairsValue.map(({ label, resources, to }) => useResourceCardRow(label, resources, to)));
31
+
32
+ return rows;
33
+ }
34
+
35
+ export function useResourcePair(label: string, type: string, resources?: any[]) {
36
+ const store = useStore();
37
+
38
+ return computed(() => {
39
+ if (!resources) {
40
+ return undefined;
41
+ }
42
+
43
+ const resourcesValue = toValue(resources);
44
+
45
+ return {
46
+ label,
47
+ resources: resourcesValue,
48
+ to: (resourcesValue?.length > 0) ? {
49
+ name: 'c-cluster-product-resource',
50
+ params: {
51
+ cluster: store.getters.currentCluster.id,
52
+ product: store.getters.currentProduct.name,
53
+ resource: type
54
+ }
55
+ } : undefined,
56
+ };
57
+ });
58
+ }
59
+
60
+ export function useDefaultWorkloadResources(services?: any[], ingresses?: any[], referredToBy?: any[], refersTo?: any[]): StateCardProps {
61
+ const store = useStore();
62
+ const i18n = useI18n(store);
63
+
64
+ const pairs: Ref<(Pairs | undefined)[]> = computed(() => [
65
+ useResourcePair(i18n.t('component.resource.detail.card.resourcesCard.rows.services'), SERVICE, services).value,
66
+ useResourcePair(i18n.t('component.resource.detail.card.resourcesCard.rows.ingresses'), INGRESS, ingresses).value,
67
+ referredToBy ? {
68
+ label: i18n.t('component.resource.detail.card.resourcesCard.rows.referredToBy'),
69
+ resources: toValue(referredToBy || []),
70
+ to: { hash: '#related' }
71
+ } : undefined,
72
+ refersTo ? {
73
+ label: i18n.t('component.resource.detail.card.resourcesCard.rows.refersTo'),
74
+ resources: toValue(refersTo || []),
75
+ to: { hash: '#related' }
76
+ } : undefined
77
+ ]);
78
+
79
+ const remainingPairs = computed(() => pairs.value.filter((p: (Pairs | undefined)): p is Pairs => p !== undefined));
80
+
81
+ const rows = useDefaultResources(remainingPairs);
82
+
83
+ return {
84
+ title: 'Resources',
85
+ rows: rows.value
86
+ };
87
+ }
88
+
89
+ export function useDefaultWorkloadInsightsCardProps(): StateCardProps {
90
+ const store = useStore();
91
+ const i18n = useI18n(store);
92
+
93
+ const rows: ResourceRowProps[] = [
94
+ {
95
+ label: i18n.t('component.resource.detail.card.insightsCard.rows.conditions'),
96
+ to: '#',
97
+ color: 'disabled',
98
+ counts: [{ label: 'Available', count: 1 }, { label: 'Progressing', count: 1 }]
99
+ },
100
+ {
101
+ label: i18n.t('component.resource.detail.card.insightsCard.rows.events'),
102
+ to: '#',
103
+ color: 'disabled',
104
+ counts: [{ label: 'Normal', count: 2 }]
105
+ }
106
+ ];
107
+
108
+ return {
109
+ title: i18n.t('component.resource.detail.card.insightsCard.title'),
110
+ rows
111
+ };
112
+ }
@@ -0,0 +1,39 @@
1
+ <script lang="ts">
2
+ import Card from '@shell/components/Resource/Detail/Card/index.vue';
3
+ import ResourceRow, { Props as ResourceRowProps } from '@shell/components/Resource/Detail/ResourceRow.vue';
4
+
5
+ export interface Props {
6
+ title: string;
7
+ rows: ResourceRowProps[];
8
+ }
9
+ </script>
10
+
11
+ <script setup lang="ts">
12
+ const { title, rows } = defineProps<Props>();
13
+ </script>
14
+
15
+ <template>
16
+ <Card :title="title">
17
+ <div class="resource-rows">
18
+ <ResourceRow
19
+ v-for="(row, i) in rows"
20
+ :key="i"
21
+ :label="row.label"
22
+ :color="row.color"
23
+ :to="row.to"
24
+ :counts="row.counts"
25
+ />
26
+ </div>
27
+ </Card>
28
+ </template>
29
+
30
+ <style lang="scss" scoped>
31
+ .resource-rows {
32
+ display: flex;
33
+ flex-direction: column;
34
+
35
+ & > *:not(:first-of-type) {
36
+ margin-top: 4px;
37
+ }
38
+ }
39
+ </style>
@@ -0,0 +1,11 @@
1
+ <template>
2
+ <div class="vertical-gap">
3
+ &nbsp;
4
+ </div>
5
+ </template>
6
+
7
+ <style lang="scss" scoped>
8
+ .vertical-gap {
9
+ height: 12px;
10
+ }
11
+ </style>
@@ -0,0 +1,36 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import Card from '@shell/components/Resource/Detail/Card/index.vue';
3
+
4
+ describe('component: Card/index', () => {
5
+ it('should render title and default slot', async() => {
6
+ const title = 'title';
7
+ const content = 'content';
8
+ const wrapper = mount(Card, { props: { title }, slots: { default: content } });
9
+
10
+ expect(wrapper.find('.title').element.innerHTML).toStrictEqual(title);
11
+ });
12
+
13
+ it('should allow you to override the heading with slot', async() => {
14
+ const title = 'title';
15
+ const content = 'content';
16
+ const wrapper = mount(Card, { props: { title }, slots: { heading: content } });
17
+
18
+ expect(wrapper.find('.title').exists()).toBeFalsy();
19
+ expect(wrapper.find('.heading').element.innerHTML).toStrictEqual(content);
20
+ });
21
+
22
+ it('should allow you to override the title with slot', async() => {
23
+ const title = 'title';
24
+ const content = 'content';
25
+ const wrapper = mount(Card, { props: { title }, slots: { title: content } });
26
+
27
+ expect(wrapper.find('.title').element.innerHTML).toStrictEqual(content);
28
+ });
29
+
30
+ it('should allow you to insert heading-action with slot', async() => {
31
+ const content = '<div id="test">content</div>';
32
+ const wrapper = mount(Card, { slots: { 'heading-action': content } });
33
+
34
+ expect(wrapper.find('.heading #test').exists()).toBeTruthy();
35
+ });
36
+ });
@@ -0,0 +1,84 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import Bubble from '@shell/components/Resource/Detail/Card/PodsCard/Bubble.vue';
3
+ import PodsCard from '@shell/components/Resource/Detail/Card/PodsCard/index.vue';
4
+ import Card from '@shell/components/Resource/Detail/Card/index.vue';
5
+ import Scaler from '@shell/components/Resource/Detail/Card/Scaler.vue';
6
+ import StatusBar from '@shell/components/Resource/Detail/StatusBar.vue';
7
+ import StatusRow from '@shell/components/Resource/Detail/StatusRow.vue';
8
+ import { createStore } from 'vuex';
9
+
10
+ describe('component: Bubble', () => {
11
+ it('should render element with bubble class for styling', async() => {
12
+ const content = 'content';
13
+ const wrapper = mount(Bubble, { slots: { default: content } });
14
+
15
+ expect(wrapper.element.className).toStrictEqual('bubble');
16
+ });
17
+
18
+ it('should render default slot content', async() => {
19
+ const content = 'content';
20
+ const wrapper = mount(Bubble, { slots: { default: content } });
21
+
22
+ expect(wrapper.find('.bubble').element.innerHTML).toStrictEqual(content);
23
+ });
24
+ });
25
+
26
+ describe('component: PodsCard', () => {
27
+ const store = createStore({});
28
+
29
+ const podSuccess = { stateSimpleColor: 'success', stateDisplay: 'Completed' };
30
+ const podFail = { stateSimpleColor: 'error', stateDisplay: 'Error' };
31
+
32
+ it('should pass title to Card prop correctly', async() => {
33
+ const wrapper = mount(PodsCard, { props: { showScaling: true }, global: { provide: { store } } });
34
+
35
+ const card = wrapper.findComponent(Card);
36
+
37
+ expect(card.props('title')).toStrictEqual('component.resource.detail.card.podsCard.title');
38
+ });
39
+
40
+ it('should show Scaler when showScaling is true', async() => {
41
+ const wrapper = mount(PodsCard, { props: { showScaling: true }, global: { provide: { store } } });
42
+
43
+ expect(wrapper.find('.scaler').exists()).toBeTruthy();
44
+ });
45
+
46
+ it('should hide scaler when showScaling is false', async() => {
47
+ const wrapper = mount(PodsCard, { props: { showScaling: false }, global: { provide: { store } } });
48
+
49
+ expect(wrapper.find('.scaler').exists()).toBeFalsy();
50
+ });
51
+
52
+ it('should pass the appropriate props to the Scaler component', async() => {
53
+ const wrapper = mount(PodsCard, { props: { showScaling: true, pods: [podSuccess] }, global: { provide: { store } } });
54
+ const scaler = wrapper.findComponent(Scaler);
55
+
56
+ expect(scaler.props('value')).toStrictEqual(1);
57
+ expect(scaler.props('min')).toStrictEqual(0);
58
+ });
59
+
60
+ it('should pass the appropriate props to the StatusBar component based on the pods input', async() => {
61
+ const wrapper = mount(PodsCard, { props: { showScaling: true, pods: [podSuccess, podFail] }, global: { provide: { store } } });
62
+ const statusBar = wrapper.findComponent(StatusBar);
63
+
64
+ const segments = statusBar.props('segments');
65
+
66
+ expect(segments[0].color).toStrictEqual('success');
67
+ expect(segments[0].percent).toStrictEqual(50);
68
+
69
+ expect(segments[1].color).toStrictEqual('error');
70
+ expect(segments[1].percent).toStrictEqual(50);
71
+ });
72
+
73
+ it('should pass the appropriate props to the StatusRow component based on the pods input', async() => {
74
+ const wrapper = mount(PodsCard, { props: { showScaling: true, pods: [podSuccess, podFail] }, global: { provide: { store } } });
75
+ const rows = wrapper.findComponent(StatusRow);
76
+
77
+ expect(rows.props()).toStrictEqual({
78
+ color: 'success',
79
+ count: 1,
80
+ label: 'Completed',
81
+ percent: 50
82
+ });
83
+ });
84
+ });
@@ -0,0 +1,72 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import ResourceUsageCard from '@shell/components/Resource/Detail/Card/ResourceUsageCard/index.vue';
3
+ import Card from '@shell/components/Resource/Detail/Card/index.vue';
4
+ import { createStore } from 'vuex';
5
+
6
+ describe('component: ResourceUsageCard', () => {
7
+ const title = 'TITLE';
8
+ const used = 20;
9
+ const available = 100;
10
+ const store = createStore({});
11
+ const unit = 'UNIT';
12
+
13
+ it('should pass the title passed in to the card title prop', async() => {
14
+ const wrapper = mount(ResourceUsageCard, {
15
+ props: {
16
+ title, used, available
17
+ },
18
+ global: { provide: { store } }
19
+ });
20
+
21
+ const card = wrapper.findComponent(Card);
22
+
23
+ expect(card.props('title')).toStrictEqual(title);
24
+ });
25
+
26
+ it('should render information in the appropriate places', async() => {
27
+ const wrapper = mount(ResourceUsageCard, {
28
+ props: {
29
+ title, used, available
30
+ },
31
+ global: { provide: { store } }
32
+ });
33
+
34
+ const numerical = wrapper.find('.numerical');
35
+ const usedDiv = numerical.find('.used');
36
+ const data = numerical.find('.data');
37
+
38
+ expect(usedDiv.element.innerHTML).toStrictEqual('component.resource.detail.card.resourceUsage.used');
39
+ expect(data.find('.amount').element.innerHTML).toStrictEqual(`component.resource.detail.card.resourceUsage.amount-{"used":"${ used }","available":"${ available }"}`);
40
+ expect(data.find('.unit').exists()).toBeFalsy();
41
+ expect(data.find('.spacer').exists()).toBeTruthy();
42
+ expect(data.find('.percentage').element.innerHTML).toStrictEqual('20%');
43
+ });
44
+
45
+ it('should render units when passed', async() => {
46
+ const wrapper = mount(ResourceUsageCard, {
47
+ props: {
48
+ title, used, available, unit
49
+ },
50
+ global: { provide: { store } }
51
+ });
52
+
53
+ const numerical = wrapper.find('.numerical');
54
+ const data = numerical.find('.data');
55
+
56
+ expect(data.find('.unit').element.innerHTML).toStrictEqual(unit);
57
+ });
58
+
59
+ it('should handle formatters properly', async() => {
60
+ const wrapper = mount(ResourceUsageCard, {
61
+ props: {
62
+ title, used, available, usedFormatter: (n: number) => `used: ${ n }`, availableFormatter: (n: number) => `available: ${ n }`
63
+ },
64
+ global: { provide: { store } }
65
+ });
66
+
67
+ const numerical = wrapper.find('.numerical');
68
+ const data = numerical.find('.data');
69
+
70
+ expect(data.find('.amount').element.innerHTML).toStrictEqual(`component.resource.detail.card.resourceUsage.amount-{"used":"used: ${ used }","available":"available: ${ available }"}`);
71
+ });
72
+ });
@@ -0,0 +1,87 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import Scaler from '@shell/components/Resource/Detail/Card/Scaler.vue';
3
+ import { useStore } from 'vuex';
4
+
5
+ describe('component: Scaler', () => {
6
+ const ariaResourceName = 'pods';
7
+ const global = { provide: { store: useStore() } };
8
+
9
+ it('should have two buttons and a value', async() => {
10
+ const wrapper = mount(Scaler, {
11
+ props: { ariaResourceName, value: 2 },
12
+ global
13
+ });
14
+
15
+ expect(wrapper.find('.decrease').exists()).toBeTruthy();
16
+ expect(wrapper.find('.value').exists()).toBeTruthy();
17
+ expect(wrapper.find('.increase').exists()).toBeTruthy();
18
+ });
19
+
20
+ it('should render value in the correct location', async() => {
21
+ const value = 2;
22
+ const wrapper = mount(Scaler, {
23
+ props: { ariaResourceName, value },
24
+ global
25
+ });
26
+
27
+ expect(wrapper.find('.value').element.innerHTML).toStrictEqual(`${ value }`);
28
+ });
29
+
30
+ it('should render buttons without disabled class when within bounds', async() => {
31
+ const wrapper = mount(Scaler, {
32
+ props: {
33
+ ariaResourceName, value: 2, min: 1, max: 3
34
+ },
35
+ global
36
+ });
37
+
38
+ expect(wrapper.find('.decrease').element.attributes.getNamedItem('disabled')).toBeNull();
39
+ expect(wrapper.find('.increase').element.attributes.getNamedItem('disabled')).toBeNull();
40
+ });
41
+
42
+ it('should render buttons with disabled class when out of bounds', async() => {
43
+ const wrapper = mount(Scaler, {
44
+ props: {
45
+ ariaResourceName, value: 2, min: 2, max: 2
46
+ },
47
+ global
48
+ });
49
+
50
+ expect(wrapper.find('.decrease').element.attributes.getNamedItem('disabled')).toBeTruthy();
51
+ expect(wrapper.find('.increase').element.attributes.getNamedItem('disabled')).toBeTruthy();
52
+ });
53
+
54
+ it('should render aria labels', async() => {
55
+ const wrapper = mount(Scaler, {
56
+ props: {
57
+ ariaResourceName, value: 2, min: 2, max: 2
58
+ },
59
+ global
60
+ });
61
+
62
+ expect(wrapper.find('.decrease').element.attributes.getNamedItem('aria-label')?.value).toStrictEqual(`component.resource.detail.card.scaler.ariaLabel.decrease-{"resourceName":"${ ariaResourceName }"}`);
63
+ expect(wrapper.find('.increase').element.attributes.getNamedItem('aria-label')?.value).toStrictEqual(`component.resource.detail.card.scaler.ariaLabel.increase-{"resourceName":"${ ariaResourceName }"}`);
64
+ });
65
+
66
+ it('should emit @increase event when increase button clicked', async() => {
67
+ const wrapper = mount(Scaler, {
68
+ props: { ariaResourceName, value: 2 },
69
+ global
70
+ });
71
+
72
+ wrapper.find('.increase').trigger('click');
73
+
74
+ expect(wrapper.emitted()).toHaveProperty('increase');
75
+ });
76
+
77
+ it('should emit @decrease event when decrease button clicked', async() => {
78
+ const wrapper = mount(Scaler, {
79
+ props: { ariaResourceName, value: 2 },
80
+ global
81
+ });
82
+
83
+ wrapper.find('.decrease').trigger('click');
84
+
85
+ expect(wrapper.emitted()).toHaveProperty('decrease');
86
+ });
87
+ });
@@ -0,0 +1,53 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import StateCard from '@shell/components/Resource/Detail/Card/StateCard/index.vue';
3
+ import Card from '@shell/components/Resource/Detail/Card/index.vue';
4
+ import ResourceRow from '@shell/components/Resource/Detail/ResourceRow.vue';
5
+
6
+ describe('component: StateCard', () => {
7
+ const title = 'TITLE';
8
+ const counts = [{ label: 'label2', count: 3 }];
9
+ const row: any = {
10
+ label: 'label',
11
+ to: 'to',
12
+ color: 'success',
13
+ counts,
14
+ };
15
+
16
+ it('should pass the title passed in to the card title prop', async() => {
17
+ const wrapper = mount(StateCard, {
18
+ props: {
19
+ title,
20
+ rows: []
21
+ },
22
+ });
23
+
24
+ const card = wrapper.findComponent(Card);
25
+
26
+ expect(card.props('title')).toStrictEqual(title);
27
+ });
28
+
29
+ it('should have resource-rows class', async() => {
30
+ const wrapper = mount(StateCard, {
31
+ props: {
32
+ title,
33
+ rows: []
34
+ },
35
+ });
36
+
37
+ expect(wrapper.find('.resource-rows').exists()).toBeTruthy();
38
+ });
39
+
40
+ it('should pass props from rows to ResourceRows correctly', async() => {
41
+ const wrapper = mount(StateCard, {
42
+ props: {
43
+ title,
44
+ rows: [row]
45
+ },
46
+ global: { stubs: ['router-link'] }
47
+ });
48
+
49
+ const resourceRows = wrapper.findComponent(ResourceRow);
50
+
51
+ expect(resourceRows.props()).toStrictEqual(row);
52
+ });
53
+ });
@@ -0,0 +1,14 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import VerticalGap from '@shell/components/Resource/Detail/Card/VerticalGap.vue';
3
+
4
+ describe('component: VerticalGap', () => {
5
+ const wrapper = mount(VerticalGap);
6
+
7
+ it('should have appropriate class', async() => {
8
+ expect(wrapper.element.className).toBe('vertical-gap');
9
+ });
10
+
11
+ it('should have blank content so the element can have height styled by the class', async() => {
12
+ expect(wrapper.element.innerHTML.trim()).toBe('&nbsp;');
13
+ });
14
+ });