@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,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>
|