@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,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,56 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import VerticalGap from '@shell/components/Resource/Detail/Card/VerticalGap.vue';
|
|
3
|
+
|
|
4
|
+
export interface CardProps {
|
|
5
|
+
title?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const { title } = defineProps<CardProps>();
|
|
9
|
+
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<div class="detail-card">
|
|
14
|
+
<div class="heading">
|
|
15
|
+
<slot name="heading">
|
|
16
|
+
<div class="title">
|
|
17
|
+
<slot name="title">
|
|
18
|
+
{{ title }}
|
|
19
|
+
</slot>
|
|
20
|
+
</div>
|
|
21
|
+
</slot>
|
|
22
|
+
<slot name="heading-action" />
|
|
23
|
+
</div>
|
|
24
|
+
<VerticalGap />
|
|
25
|
+
<div class="body">
|
|
26
|
+
<slot name="default" />
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
<style lang="scss" scoped>
|
|
32
|
+
.detail-card {
|
|
33
|
+
padding: 16px;
|
|
34
|
+
border-radius: var(--border-radius-md);
|
|
35
|
+
border: 1px solid var(--border);
|
|
36
|
+
|
|
37
|
+
.heading {
|
|
38
|
+
display: flex;
|
|
39
|
+
justify-content: space-between;
|
|
40
|
+
|
|
41
|
+
height: 32px;
|
|
42
|
+
|
|
43
|
+
.title {
|
|
44
|
+
font-size: 18px;
|
|
45
|
+
font-weight: 600;
|
|
46
|
+
line-height: 21px;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.body {
|
|
51
|
+
display: flex;
|
|
52
|
+
flex-direction: column;
|
|
53
|
+
justify-content: flex-start;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
</style>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import Annotations from '@shell/components/Resource/Detail/Metadata/Annotations/index.vue';
|
|
3
|
+
import { createStore } from 'vuex';
|
|
4
|
+
|
|
5
|
+
describe('component: Metadata/Annotations', () => {
|
|
6
|
+
it('shoulder render KeyValue with the appropriate props', async() => {
|
|
7
|
+
const annotations = [{ key: 'key', value: 'value' }];
|
|
8
|
+
const wrapper = mount(Annotations, {
|
|
9
|
+
props: { annotations },
|
|
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.annotations.title');
|
|
16
|
+
expect(keyValue.props('rows')).toStrictEqual(annotations);
|
|
17
|
+
expect(keyValue.props('outline')).toStrictEqual(true);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Annotation } from '@shell/components/Resource/Detail/Metadata/Annotations/index.vue';
|
|
2
|
+
import { computed, Ref, toValue } from 'vue';
|
|
3
|
+
|
|
4
|
+
export const useDefaultAnnotations = (resource: any): Ref<Annotation[]> => {
|
|
5
|
+
const resourceValue = toValue(resource);
|
|
6
|
+
|
|
7
|
+
return computed(() => {
|
|
8
|
+
const keyValuePairs = Object.entries<string>(resourceValue.annotations || {});
|
|
9
|
+
|
|
10
|
+
return keyValuePairs.map(([key, value]) => ({ key, value }));
|
|
11
|
+
});
|
|
12
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
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 { useStore } from 'vuex';
|
|
5
|
+
|
|
6
|
+
export type Annotation = Row;
|
|
7
|
+
|
|
8
|
+
export interface AnnotationsProps {
|
|
9
|
+
annotations: Annotation[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<script setup lang="ts">
|
|
15
|
+
const { annotations } = defineProps<AnnotationsProps>();
|
|
16
|
+
const store = useStore();
|
|
17
|
+
const i18n = useI18n(store);
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<KeyValue
|
|
22
|
+
:propertyName="i18n.t('component.resource.detail.metadata.annotations.title')"
|
|
23
|
+
:rows="annotations"
|
|
24
|
+
:outline="true"
|
|
25
|
+
/>
|
|
26
|
+
</template>
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { mount, RouterLinkStub } from '@vue/test-utils';
|
|
2
|
+
import IdentifyingInformation from '@shell/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue';
|
|
3
|
+
import Rectangle from '@shell/components/Resource/Detail/Metadata/Rectangle.vue';
|
|
4
|
+
import { markRaw } from 'vue';
|
|
5
|
+
|
|
6
|
+
describe('component: Metadata/IdentifyingInformation', () => {
|
|
7
|
+
const label = 'LABEL';
|
|
8
|
+
const value = 'VALUE';
|
|
9
|
+
const status = 'error';
|
|
10
|
+
const to = 'http://google.com';
|
|
11
|
+
|
|
12
|
+
it('should render container with identifying information', async() => {
|
|
13
|
+
const wrapper = mount(IdentifyingInformation, {
|
|
14
|
+
props: { rows: [] },
|
|
15
|
+
global: { stubs: { 'router-link': RouterLinkStub } }
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
expect(wrapper.find('.identifying-information').exists()).toBeTruthy();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should render basic label and value', async() => {
|
|
22
|
+
const wrapper = mount(IdentifyingInformation, {
|
|
23
|
+
props: {
|
|
24
|
+
rows: [
|
|
25
|
+
{
|
|
26
|
+
label,
|
|
27
|
+
value
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
global: { stubs: { 'router-link': RouterLinkStub } }
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
expect(wrapper.find('.label').element.innerHTML.trim()).toStrictEqual(label);
|
|
35
|
+
expect(wrapper.find('.value span').element.innerHTML.trim()).toStrictEqual(value);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should render a link value', async() => {
|
|
39
|
+
const wrapper = mount(IdentifyingInformation, {
|
|
40
|
+
props: {
|
|
41
|
+
rows: [
|
|
42
|
+
{
|
|
43
|
+
label,
|
|
44
|
+
value,
|
|
45
|
+
to
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
global: { stubs: { 'router-link': RouterLinkStub } }
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
expect(wrapper.find('.label').element.innerHTML.trim()).toStrictEqual(label);
|
|
53
|
+
|
|
54
|
+
const routerLinkComponent = wrapper.find('.value').getComponent(RouterLinkStub);
|
|
55
|
+
|
|
56
|
+
expect(routerLinkComponent.props('to')).toStrictEqual(to);
|
|
57
|
+
expect(routerLinkComponent.element.innerHTML).toStrictEqual(value);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should render a status value', async() => {
|
|
61
|
+
const wrapper = mount(IdentifyingInformation, {
|
|
62
|
+
props: {
|
|
63
|
+
rows: [
|
|
64
|
+
{
|
|
65
|
+
label,
|
|
66
|
+
value,
|
|
67
|
+
status
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
global: { stubs: { 'router-link': RouterLinkStub } }
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(wrapper.find('.label').element.innerHTML.trim()).toStrictEqual(label);
|
|
75
|
+
expect(wrapper.find('.value span').element.innerHTML.trim()).toStrictEqual(value);
|
|
76
|
+
expect(wrapper.find(`.value .status.${ status }`).exists()).toBeTruthy();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should render a valueOverride', async() => {
|
|
80
|
+
const valueOverride = {
|
|
81
|
+
component: markRaw(Rectangle),
|
|
82
|
+
props: { outline: false }
|
|
83
|
+
};
|
|
84
|
+
const wrapper = mount(IdentifyingInformation, {
|
|
85
|
+
props: {
|
|
86
|
+
rows: [
|
|
87
|
+
{
|
|
88
|
+
label,
|
|
89
|
+
value,
|
|
90
|
+
valueOverride
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
},
|
|
94
|
+
global: { stubs: { 'router-link': RouterLinkStub } }
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
expect(wrapper.find('.label').element.innerHTML.trim()).toStrictEqual(label);
|
|
98
|
+
|
|
99
|
+
const testComponent = wrapper.find('.value').getComponent(Rectangle);
|
|
100
|
+
|
|
101
|
+
expect(testComponent.props('outline')).toStrictEqual(valueOverride.props.outline);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { useI18n } from '@shell/composables/useI18n';
|
|
2
|
+
import { computed, ComputedRef, markRaw, toValue } from 'vue';
|
|
3
|
+
import LiveDate from '@shell/components/formatter/LiveDate.vue';
|
|
4
|
+
import LinkName from '@shell/components/formatter/LinkName.vue';
|
|
5
|
+
import Additional from '@shell/components/Resource/Detail/Additional.vue';
|
|
6
|
+
import { useStore } from 'vuex';
|
|
7
|
+
import CopyToClipboardText from '@shell/components/CopyToClipboardText.vue';
|
|
8
|
+
import IconText from '@shell/components/formatter/IconText.vue';
|
|
9
|
+
import { NODE } from '@shell/config/types';
|
|
10
|
+
import day from 'dayjs';
|
|
11
|
+
import { Row } from '@shell/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue';
|
|
12
|
+
|
|
13
|
+
export const useContainerRuntime = (resource: any): ComputedRef<Row> => {
|
|
14
|
+
const store = useStore();
|
|
15
|
+
const i18n = useI18n(store);
|
|
16
|
+
const resourceValue = toValue(resource);
|
|
17
|
+
|
|
18
|
+
return computed(() => ({
|
|
19
|
+
label: i18n.t('node.detail.detailTop.containerRuntime'),
|
|
20
|
+
valueOverride: {
|
|
21
|
+
component: markRaw(IconText),
|
|
22
|
+
props: { value: resourceValue.containerRuntimeVersion, iconClass: resourceValue.containerRuntimeIcon }
|
|
23
|
+
},
|
|
24
|
+
value: resourceValue.containerRuntimeVersion
|
|
25
|
+
}));
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const useExternalIp = (resource: any): ComputedRef<Row> => {
|
|
29
|
+
const store = useStore();
|
|
30
|
+
const i18n = useI18n(store);
|
|
31
|
+
const resourceValue = toValue(resource);
|
|
32
|
+
|
|
33
|
+
return computed(() => ({
|
|
34
|
+
label: i18n.t('node.detail.detailTop.externalIP'),
|
|
35
|
+
valueOverride: {
|
|
36
|
+
component: markRaw(CopyToClipboardText),
|
|
37
|
+
props: { text: resourceValue.externalIp }
|
|
38
|
+
},
|
|
39
|
+
value: resourceValue.externalIp
|
|
40
|
+
}));
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const usePodIp = (resource: any): ComputedRef<Row> => {
|
|
44
|
+
const store = useStore();
|
|
45
|
+
const i18n = useI18n(store);
|
|
46
|
+
const resourceValue = toValue(resource);
|
|
47
|
+
|
|
48
|
+
return computed(() => ({
|
|
49
|
+
label: i18n.t('workload.detailTop.podIP'),
|
|
50
|
+
value: resourceValue.status.podIP
|
|
51
|
+
}));
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const useWorkload = (resource: any): ComputedRef<Row> => {
|
|
55
|
+
const store = useStore();
|
|
56
|
+
const i18n = useI18n(store);
|
|
57
|
+
const resourceValue = toValue(resource);
|
|
58
|
+
|
|
59
|
+
return computed(() => ({
|
|
60
|
+
label: i18n.t('workload.detailTop.workload'),
|
|
61
|
+
valueOverride: {
|
|
62
|
+
component: markRaw(LinkName),
|
|
63
|
+
props: {
|
|
64
|
+
type: resourceValue.workloadRef.type,
|
|
65
|
+
value: resourceValue.workloadRef.name,
|
|
66
|
+
namespace: resourceValue.workloadRef.namespace
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
value: resourceValue.workloadRef.name
|
|
70
|
+
}));
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const useNode = (resource: any): ComputedRef<Row> => {
|
|
74
|
+
const store = useStore();
|
|
75
|
+
const i18n = useI18n(store);
|
|
76
|
+
const resourceValue = toValue(resource);
|
|
77
|
+
|
|
78
|
+
return computed(() => ({
|
|
79
|
+
label: i18n.t('workload.detailTop.node'),
|
|
80
|
+
valueOverride: {
|
|
81
|
+
component: markRaw(LinkName),
|
|
82
|
+
props: { type: NODE, value: resourceValue.spec.nodeName }
|
|
83
|
+
},
|
|
84
|
+
value: resourceValue.spec.nodeName
|
|
85
|
+
}));
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const useStarted = (resource: any): ComputedRef<Row> => {
|
|
89
|
+
const store = useStore();
|
|
90
|
+
const i18n = useI18n(store);
|
|
91
|
+
const resourceValue = toValue(resource);
|
|
92
|
+
|
|
93
|
+
return computed(() => ({
|
|
94
|
+
label: i18n.t('workload.detailTop.started'),
|
|
95
|
+
valueOverride: {
|
|
96
|
+
component: markRaw(LiveDate),
|
|
97
|
+
props: { addSuffix: true, value: resourceValue.status.startTime }
|
|
98
|
+
},
|
|
99
|
+
value: resourceValue.spec.nodeName
|
|
100
|
+
}));
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const useDuration = (resource: any): ComputedRef<Row> => {
|
|
104
|
+
const store = useStore();
|
|
105
|
+
const i18n = useI18n(store);
|
|
106
|
+
const resourceValue = toValue(resource);
|
|
107
|
+
|
|
108
|
+
const FACTORS = [60, 60, 24];
|
|
109
|
+
const LABELS = ['sec', 'min', 'hour', 'day'];
|
|
110
|
+
const value = computed(() => {
|
|
111
|
+
const { completionTime, startTime } = resourceValue.status;
|
|
112
|
+
const end = day(completionTime);
|
|
113
|
+
const start = day(startTime);
|
|
114
|
+
let diff = end.diff(start) / 1000;
|
|
115
|
+
|
|
116
|
+
let label: any;
|
|
117
|
+
|
|
118
|
+
let i = 0;
|
|
119
|
+
|
|
120
|
+
while (diff >= FACTORS[i] && i < FACTORS.length) {
|
|
121
|
+
diff /= FACTORS[i];
|
|
122
|
+
i++;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (diff < 5) {
|
|
126
|
+
label = Math.floor(diff * 10) / 10;
|
|
127
|
+
} else {
|
|
128
|
+
label = Math.floor(diff);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
label += ` ${ i18n.t(`unit.${ LABELS[i] }`, { count: label }) } `;
|
|
132
|
+
label = label.trim();
|
|
133
|
+
|
|
134
|
+
return label;
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return computed(() => ({
|
|
138
|
+
label: i18n.t('workload.detailTop.duration'),
|
|
139
|
+
valueOverride: {
|
|
140
|
+
component: markRaw(LiveDate),
|
|
141
|
+
props: { value: value.value }
|
|
142
|
+
},
|
|
143
|
+
value: value.value
|
|
144
|
+
}));
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export const useImage = (resource: any): ComputedRef<Row> => {
|
|
148
|
+
const store = useStore();
|
|
149
|
+
const i18n = useI18n(store);
|
|
150
|
+
const resourceValue = toValue(resource);
|
|
151
|
+
|
|
152
|
+
return computed(() => ({
|
|
153
|
+
label: i18n.t('component.resource.detail.metadata.identifyingInformation.image'),
|
|
154
|
+
value: resourceValue.imageNames,
|
|
155
|
+
valueOverride: {
|
|
156
|
+
component: markRaw(Additional),
|
|
157
|
+
props: { items: resourceValue.imageNames }
|
|
158
|
+
},
|
|
159
|
+
}));
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export const useReady = (resource: any): ComputedRef<Row> => {
|
|
163
|
+
const store = useStore();
|
|
164
|
+
const i18n = useI18n(store);
|
|
165
|
+
const resourceValue = toValue(resource);
|
|
166
|
+
|
|
167
|
+
return computed(() => ({
|
|
168
|
+
label: i18n.t('component.resource.detail.metadata.identifyingInformation.ready'),
|
|
169
|
+
value: resourceValue.ready,
|
|
170
|
+
}));
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export const useRestarts = (resource: any): ComputedRef<Row> => {
|
|
174
|
+
const store = useStore();
|
|
175
|
+
const i18n = useI18n(store);
|
|
176
|
+
const resourceValue = toValue(resource);
|
|
177
|
+
|
|
178
|
+
return computed(() => ({
|
|
179
|
+
label: i18n.t('workload.detailTop.podRestarts'),
|
|
180
|
+
value: resourceValue.restartCount
|
|
181
|
+
}));
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
export const useInternalIp = (resource: any): ComputedRef<Row> => {
|
|
185
|
+
const store = useStore();
|
|
186
|
+
const i18n = useI18n(store);
|
|
187
|
+
const resourceValue = toValue(resource);
|
|
188
|
+
|
|
189
|
+
return computed(() => ({
|
|
190
|
+
label: i18n.t('node.detail.detailTop.internalIP'),
|
|
191
|
+
valueOverride: {
|
|
192
|
+
component: markRaw(CopyToClipboardText),
|
|
193
|
+
props: { text: resourceValue.internalIp }
|
|
194
|
+
},
|
|
195
|
+
value: resourceValue.internalIp
|
|
196
|
+
}));
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export const useVersion = (resource: any): ComputedRef<Row> => {
|
|
200
|
+
const store = useStore();
|
|
201
|
+
const i18n = useI18n(store);
|
|
202
|
+
const resourceValue = toValue(resource);
|
|
203
|
+
|
|
204
|
+
return computed(() => ({
|
|
205
|
+
label: i18n.t('node.detail.detailTop.version'),
|
|
206
|
+
value: resourceValue.version
|
|
207
|
+
}));
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
export const useOs = (resource: any): ComputedRef<Row> => {
|
|
211
|
+
const store = useStore();
|
|
212
|
+
const i18n = useI18n(store);
|
|
213
|
+
const resourceValue = toValue(resource);
|
|
214
|
+
|
|
215
|
+
return computed(() => ({
|
|
216
|
+
label: i18n.t('node.detail.detailTop.os'),
|
|
217
|
+
value: resourceValue.status.nodeInfo.osImage
|
|
218
|
+
}));
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
export const useLiveDate = (resource: any): ComputedRef<Row> => {
|
|
222
|
+
const store = useStore();
|
|
223
|
+
const i18n = useI18n(store);
|
|
224
|
+
const resourceValue = toValue(resource);
|
|
225
|
+
|
|
226
|
+
return computed(() => ({
|
|
227
|
+
label: i18n.t('component.resource.detail.metadata.identifyingInformation.age'),
|
|
228
|
+
valueOverride: {
|
|
229
|
+
component: markRaw(LiveDate),
|
|
230
|
+
props: { value: resourceValue.creationTimestamp }
|
|
231
|
+
},
|
|
232
|
+
value: resourceValue.age,
|
|
233
|
+
}));
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
export const useProject = (resource: any): ComputedRef<Row> => {
|
|
237
|
+
const store = useStore();
|
|
238
|
+
const i18n = useI18n(store);
|
|
239
|
+
const resourceValue = toValue(resource);
|
|
240
|
+
|
|
241
|
+
return computed(() => ({
|
|
242
|
+
label: i18n.t('component.resource.detail.metadata.identifyingInformation.project'),
|
|
243
|
+
value: resourceValue.project.nameDisplay,
|
|
244
|
+
to: '#'
|
|
245
|
+
}));
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
export const useDefaultIdentifyingInformation = (resource: any): ComputedRef<Row[]> => {
|
|
249
|
+
const store = useStore();
|
|
250
|
+
const i18n = useI18n(store);
|
|
251
|
+
const resourceValue = toValue(resource);
|
|
252
|
+
const liveDate = useLiveDate(resource);
|
|
253
|
+
|
|
254
|
+
return computed(() => [
|
|
255
|
+
{
|
|
256
|
+
label: i18n.t('component.resource.detail.metadata.identifyingInformation.namespace'),
|
|
257
|
+
value: resourceValue.namespace,
|
|
258
|
+
},
|
|
259
|
+
liveDate.value
|
|
260
|
+
]);
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
export const useDefaultWorkloadIdentifyingInformation = (resource: any): ComputedRef<Row[]> => {
|
|
264
|
+
const store = useStore();
|
|
265
|
+
const i18n = useI18n(store);
|
|
266
|
+
|
|
267
|
+
const resourceValue = toValue(resource);
|
|
268
|
+
|
|
269
|
+
return computed(() => [
|
|
270
|
+
useImage(resource).value,
|
|
271
|
+
useReady(resource).value,
|
|
272
|
+
{
|
|
273
|
+
label: i18n.t('component.resource.detail.metadata.identifyingInformation.up-to-date'),
|
|
274
|
+
value: resourceValue.upToDate,
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
label: i18n.t('component.resource.detail.metadata.identifyingInformation.available'),
|
|
278
|
+
value: resourceValue.available,
|
|
279
|
+
},
|
|
280
|
+
]);
|
|
281
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { RouteLocationRaw } from 'vue-router';
|
|
3
|
+
|
|
4
|
+
export interface Row {
|
|
5
|
+
label: string;
|
|
6
|
+
value?: string;
|
|
7
|
+
valueOverride?: {
|
|
8
|
+
component: any,
|
|
9
|
+
props?: Object
|
|
10
|
+
},
|
|
11
|
+
to?: RouteLocationRaw;
|
|
12
|
+
status?: 'success' | 'warning' | 'info' | 'error',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface MetadataProps {
|
|
16
|
+
rows: Row[];
|
|
17
|
+
}
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<script setup lang="ts">
|
|
21
|
+
const { rows } = defineProps<MetadataProps>();
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<template>
|
|
25
|
+
<div class="identifying-information">
|
|
26
|
+
<div
|
|
27
|
+
v-for="row in rows"
|
|
28
|
+
:key="`${row.label}:${row.value}`"
|
|
29
|
+
class="row"
|
|
30
|
+
>
|
|
31
|
+
<div class="label text-muted">
|
|
32
|
+
{{ row.label }}
|
|
33
|
+
</div>
|
|
34
|
+
<div
|
|
35
|
+
v-if="row.valueOverride?.component"
|
|
36
|
+
class="value"
|
|
37
|
+
>
|
|
38
|
+
<component
|
|
39
|
+
:is="row.valueOverride?.component"
|
|
40
|
+
v-if="row.valueOverride?.component"
|
|
41
|
+
v-bind="row.valueOverride?.props"
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
<div
|
|
45
|
+
v-else
|
|
46
|
+
class="value"
|
|
47
|
+
>
|
|
48
|
+
<div
|
|
49
|
+
v-if="row.status"
|
|
50
|
+
:class="['status', row.status]"
|
|
51
|
+
/>
|
|
52
|
+
<router-link
|
|
53
|
+
v-if="row.to"
|
|
54
|
+
:to="row.to"
|
|
55
|
+
>
|
|
56
|
+
{{ row.value }}
|
|
57
|
+
</router-link>
|
|
58
|
+
<span v-else>{{ row.value }}</span>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</template>
|
|
63
|
+
|
|
64
|
+
<style lang="scss" scoped>
|
|
65
|
+
.identifying-information {
|
|
66
|
+
display: flex;
|
|
67
|
+
flex-direction: column;
|
|
68
|
+
|
|
69
|
+
.row {
|
|
70
|
+
margin-bottom: 8px;
|
|
71
|
+
|
|
72
|
+
.value {
|
|
73
|
+
display: flex;
|
|
74
|
+
flex-direction: row;
|
|
75
|
+
align-items: center;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.label {
|
|
79
|
+
width: 30%;
|
|
80
|
+
min-width: 120px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.status {
|
|
84
|
+
display: inline-block;
|
|
85
|
+
$size: 8px;
|
|
86
|
+
border-radius: 50%;
|
|
87
|
+
width: $size;
|
|
88
|
+
height: $size;
|
|
89
|
+
margin-right: 12px;
|
|
90
|
+
|
|
91
|
+
&.success {
|
|
92
|
+
background-color: var(--success);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
&.warning {
|
|
96
|
+
background-color: var(--warning);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
&.error {
|
|
100
|
+
background-color: var(--error);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
&.info {
|
|
104
|
+
background-color: var(--info);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
}
|
|
111
|
+
</style>
|