@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.
- package/assets/images/icons/document.svg +3 -0
- package/assets/images/vendor/cognito.svg +1 -0
- package/assets/styles/app.scss +1 -0
- package/assets/styles/base/_basic.scss +10 -0
- package/assets/styles/base/_spacing.scss +29 -0
- package/assets/styles/global/_layout.scss +1 -1
- package/assets/styles/themes/_dark.scss +25 -0
- package/assets/styles/themes/_light.scss +65 -0
- package/assets/translations/en-us.yaml +322 -24
- package/assets/translations/zh-hans.yaml +8 -5
- package/components/Certificates.vue +5 -0
- package/components/FilterPanel.vue +156 -0
- package/components/{fleet/ForceDirectedTreeChart/index.vue → ForceDirectedTreeChart.vue} +47 -41
- package/components/IconOrSvg.vue +14 -35
- package/components/PromptRemove.vue +5 -1
- package/components/Resource/Detail/Card/PodsCard/Bubble.vue +13 -0
- package/components/Resource/Detail/Card/PodsCard/composable.ts +30 -0
- package/components/Resource/Detail/Card/PodsCard/index.vue +118 -0
- package/components/Resource/Detail/Card/ResourceUsageCard/composable.ts +51 -0
- package/components/Resource/Detail/Card/ResourceUsageCard/index.vue +79 -0
- package/components/Resource/Detail/Card/Scaler.vue +89 -0
- package/components/Resource/Detail/Card/StateCard/composables.ts +112 -0
- package/components/Resource/Detail/Card/StateCard/index.vue +39 -0
- package/components/Resource/Detail/Card/VerticalGap.vue +11 -0
- package/components/Resource/Detail/Card/__tests__/Card.test.ts +36 -0
- package/components/Resource/Detail/Card/__tests__/PodsCard.test.ts +84 -0
- package/components/Resource/Detail/Card/__tests__/ResourceUsageCard.test.ts +72 -0
- package/components/Resource/Detail/Card/__tests__/Scaler.test.ts +87 -0
- package/components/Resource/Detail/Card/__tests__/StateCard.test.ts +53 -0
- package/components/Resource/Detail/Card/__tests__/VerticalGap.test.ts +14 -0
- package/components/Resource/Detail/Card/__tests__/index.test.ts +36 -0
- package/components/Resource/Detail/Card/index.vue +56 -0
- package/components/Resource/Detail/Metadata/Annotations/__tests__/index.test.ts +19 -0
- package/components/Resource/Detail/Metadata/Annotations/composable.ts +12 -0
- package/components/Resource/Detail/Metadata/Annotations/index.vue +26 -0
- package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/index.test.ts +103 -0
- package/components/Resource/Detail/Metadata/IdentifyingInformation/composable.ts +281 -0
- package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +111 -0
- package/components/Resource/Detail/Metadata/KeyValue.vue +130 -0
- package/components/Resource/Detail/Metadata/Labels/__tests__/index.test.ts +18 -0
- package/components/Resource/Detail/Metadata/Labels/composable.ts +12 -0
- package/components/Resource/Detail/Metadata/Labels/index.vue +27 -0
- package/components/Resource/Detail/Metadata/Rectangle.vue +32 -0
- package/components/Resource/Detail/Metadata/__tests__/KeyValue.test.ts +107 -0
- package/components/Resource/Detail/Metadata/__tests__/Rectangle.test.ts +24 -0
- package/components/Resource/Detail/Metadata/__tests__/index.test.ts +91 -0
- package/components/Resource/Detail/Metadata/composables.ts +29 -0
- package/components/Resource/Detail/Metadata/index.vue +66 -0
- package/components/Resource/Detail/Page.vue +22 -0
- package/components/Resource/Detail/PercentageBar.vue +40 -0
- package/components/Resource/Detail/ResourceRow.vue +119 -0
- package/components/Resource/Detail/SpacedRow.vue +14 -0
- package/components/Resource/Detail/StatusBar.vue +59 -0
- package/components/Resource/Detail/StatusRow.vue +61 -0
- package/components/Resource/Detail/TitleBar/Title.vue +13 -0
- package/components/Resource/Detail/TitleBar/Top.vue +14 -0
- package/components/Resource/Detail/TitleBar/__tests__/Title.test.ts +17 -0
- package/components/Resource/Detail/TitleBar/__tests__/Top.test.ts +17 -0
- package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +142 -0
- package/components/Resource/Detail/TitleBar/composable.ts +31 -0
- package/components/Resource/Detail/TitleBar/index.vue +124 -0
- package/components/Resource/Detail/Top/index.vue +34 -0
- package/components/Resource/Detail/__tests__/Page.test.ts +32 -0
- package/components/ResourceDetail/__tests__/index.test.ts +114 -0
- package/components/ResourceDetail/index.vue +64 -562
- package/components/ResourceDetail/legacy.vue +545 -0
- package/components/ResourceTable.vue +41 -7
- package/components/SlideInPanelManager.vue +76 -8
- package/components/SortableTable/index.vue +13 -2
- package/components/SortableTable/selection.js +21 -8
- package/components/StatusBadge.vue +6 -4
- package/components/SubtleLink.vue +25 -0
- package/components/Wizard.vue +12 -1
- package/components/YamlEditor.vue +1 -1
- package/components/__tests__/FilterPanel.test.ts +81 -0
- package/components/auth/AuthBanner.vue +2 -3
- package/components/auth/RoleDetailEdit.vue +45 -3
- package/components/auth/login/oidc.vue +6 -1
- package/components/fleet/FleetApplications.vue +181 -0
- package/components/fleet/FleetHelmOps.vue +115 -0
- package/components/fleet/FleetIntro.vue +58 -28
- package/components/fleet/FleetNoWorkspaces.vue +5 -1
- package/components/fleet/FleetOCIStorageSecret.vue +171 -0
- package/components/fleet/FleetRepos.vue +38 -76
- package/components/fleet/FleetResources.vue +50 -22
- package/components/fleet/FleetSummary.vue +26 -51
- package/components/fleet/__tests__/FleetOCIStorageSecret.test.ts +213 -0
- package/components/fleet/__tests__/FleetSummary.test.ts +39 -39
- package/components/fleet/dashboard/Empty.vue +73 -0
- package/components/fleet/dashboard/ResourceCard.vue +183 -0
- package/components/fleet/dashboard/ResourceCardSummary.vue +199 -0
- package/components/fleet/dashboard/ResourceDetails.vue +196 -0
- package/components/fleet/dashboard/ResourcePanel.vue +376 -0
- package/components/form/ArrayList.vue +6 -0
- package/components/form/SimpleSecretSelector.vue +8 -2
- package/components/form/ValueFromResource.vue +31 -19
- package/components/formatter/FleetApplicationClustersReady.vue +77 -0
- package/components/formatter/FleetApplicationSource.vue +71 -0
- package/components/formatter/FleetSummaryGraph.vue +7 -0
- package/components/nav/Header.vue +8 -7
- package/components/nav/TopLevelMenu.helper.ts +55 -34
- package/components/nav/TopLevelMenu.vue +11 -0
- package/components/nav/Type.vue +4 -1
- package/composables/useI18n.ts +12 -11
- package/config/labels-annotations.js +14 -11
- package/config/product/auth.js +1 -0
- package/config/product/fleet.js +70 -17
- package/config/query-params.js +3 -1
- package/config/roles.ts +1 -0
- package/config/router/routes.js +20 -2
- package/config/secret.ts +15 -0
- package/config/settings.ts +3 -2
- package/config/table-headers.js +52 -22
- package/config/types.js +2 -0
- package/core/plugin-helpers.ts +3 -2
- package/detail/fleet.cattle.io.cluster.vue +28 -15
- package/detail/fleet.cattle.io.gitrepo.vue +10 -1
- package/detail/fleet.cattle.io.helmop.vue +157 -0
- package/dialog/HelmOpForceUpdateDialog.vue +132 -0
- package/dialog/RedeployWorkloadDialog.vue +164 -0
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +56 -67
- package/edit/auth/oidc.vue +159 -93
- package/edit/fleet.cattle.io.gitrepo.vue +26 -33
- package/edit/fleet.cattle.io.helmop.vue +997 -0
- package/edit/management.cattle.io.fleetworkspace.vue +43 -10
- package/list/fleet.cattle.io.gitrepo.vue +1 -1
- package/list/fleet.cattle.io.helmop.vue +108 -0
- package/list/namespace.vue +5 -2
- package/mixins/auth-config.js +8 -1
- package/mixins/preset.js +100 -0
- package/mixins/resource-fetch-api-pagination.js +2 -0
- package/mixins/resource-fetch.js +1 -1
- package/mixins/resource-table-watch.js +45 -0
- package/models/__tests__/chart.test.ts +273 -0
- package/models/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -1
- package/models/chart.js +144 -2
- package/models/fleet-application.js +385 -0
- package/models/fleet.cattle.io.bundle.js +9 -8
- package/models/fleet.cattle.io.gitrepo.js +41 -365
- package/models/fleet.cattle.io.helmop.js +228 -0
- package/models/management.cattle.io.authconfig.js +1 -0
- package/models/management.cattle.io.fleetworkspace.js +12 -0
- package/models/workload.js +14 -18
- package/package.json +2 -1
- package/pages/auth/verify.vue +13 -1
- package/pages/c/_cluster/apps/charts/AddRepoLink.vue +37 -0
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +80 -0
- package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +54 -0
- package/pages/c/_cluster/apps/charts/StatusLabel.vue +33 -0
- package/pages/c/_cluster/apps/charts/index.vue +302 -484
- package/pages/c/_cluster/explorer/EventsTable.vue +1 -1
- package/pages/c/_cluster/fleet/__tests__/index.test.ts +426 -0
- package/pages/c/_cluster/fleet/application/_resource/_id.vue +14 -0
- package/pages/c/_cluster/fleet/application/_resource/create.vue +14 -0
- package/pages/c/_cluster/fleet/application/create.vue +340 -0
- package/pages/c/_cluster/fleet/application/index.vue +139 -0
- package/pages/c/_cluster/fleet/graph/config.js +277 -0
- package/pages/c/_cluster/fleet/index.vue +772 -330
- package/pages/explorer/resource/detail/configmap.vue +19 -0
- package/plugins/dashboard-store/actions.js +31 -9
- package/plugins/dashboard-store/getters.js +34 -21
- package/plugins/dashboard-store/mutations.js +51 -7
- package/plugins/dashboard-store/resource-class.js +14 -2
- package/plugins/steve/__tests__/subscribe.spec.ts +66 -1
- package/plugins/steve/actions.js +3 -0
- package/plugins/steve/steve-pagination-utils.ts +14 -13
- package/plugins/steve/subscribe.js +229 -42
- package/rancher-components/BadgeState/BadgeState.vue +3 -1
- package/rancher-components/Form/Checkbox/Checkbox.vue +2 -2
- package/rancher-components/RcItemCard/RcItemCard.test.ts +189 -0
- package/rancher-components/RcItemCard/RcItemCard.vue +425 -0
- package/rancher-components/RcItemCard/RcItemCardAction.vue +24 -0
- package/rancher-components/RcItemCard/index.ts +2 -0
- package/store/auth.js +1 -0
- package/store/catalog.js +62 -24
- package/store/index.js +33 -14
- package/store/slideInPanel.ts +6 -0
- package/store/type-map.js +1 -0
- package/types/fleet.d.ts +35 -0
- package/types/resources/settings.d.ts +19 -1
- package/types/shell/index.d.ts +339 -272
- package/types/store/dashboard-store.types.ts +17 -3
- package/types/store/pagination.types.ts +6 -1
- package/types/store/subscribe.types.ts +50 -0
- package/utils/auth.js +32 -3
- package/utils/fleet-types.ts +0 -0
- package/utils/fleet.ts +200 -1
- package/utils/pagination-utils.ts +26 -1
- package/utils/pagination-wrapper.ts +132 -50
- package/utils/settings.ts +4 -1
- package/utils/style.ts +39 -0
- package/utils/validators/formRules/__tests__/index.test.ts +36 -3
- package/utils/validators/formRules/index.ts +10 -3
- package/utils/window.js +11 -7
- package/components/__tests__/ApplicationCard.test.ts +0 -27
- package/components/cards/ApplicationCard.vue +0 -145
- package/components/fleet/ForceDirectedTreeChart/chartIcons.js +0 -17
- package/config/secret.js +0 -14
- package/pages/c/_cluster/fleet/GitRepoGraphConfig.js +0 -249
- /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,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(' ');
|
|
13
|
+
});
|
|
14
|
+
});
|