@rancher/shell 3.0.11 → 3.0.12-rc.1
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/styles/base/_mixins.scss +31 -0
- package/assets/styles/base/_variables.scss +2 -0
- package/assets/styles/themes/_modern.scss +6 -5
- package/assets/translations/en-us.yaml +5 -4
- package/assets/translations/zh-hans.yaml +0 -3
- package/components/EmptyProductPage.vue +76 -0
- package/components/Resource/Detail/CopyToClipboard.vue +1 -2
- package/components/Resource/Detail/Metadata/KeyValueRow.vue +9 -3
- package/components/Resource/Detail/TitleBar/__tests__/__snapshots__/index.test.ts.snap +31 -0
- package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +45 -1
- package/components/Resource/Detail/TitleBar/index.vue +1 -1
- package/components/Resource/Detail/ViewOptions/__tests__/__snapshots__/index.test.ts.snap +9 -0
- package/components/Resource/Detail/ViewOptions/__tests__/index.test.ts +62 -0
- package/components/Resource/Detail/ViewOptions/index.vue +2 -1
- package/components/ResourceList/Masthead.vue +25 -2
- package/components/SideNav.vue +13 -0
- package/components/__tests__/PromptModal.test.ts +2 -0
- package/components/fleet/FleetClusters.vue +1 -0
- package/components/fleet/__tests__/FleetClusters.test.ts +71 -0
- package/components/form/NodeScheduling.vue +17 -3
- package/components/form/PrivateRegistry.vue +69 -0
- package/components/form/__tests__/PrivateRegistry.test.ts +133 -0
- package/components/formatter/WorkloadHealthScale.vue +3 -1
- package/components/nav/Group.vue +26 -3
- package/components/nav/Header.vue +32 -7
- package/components/nav/TopLevelMenu.vue +15 -1
- package/config/pagination-table-headers.js +8 -1
- package/config/product/apps.js +2 -1
- package/config/product/auth.js +1 -0
- package/config/product/backup.js +1 -0
- package/config/product/compliance.js +1 -1
- package/config/product/explorer.js +25 -6
- package/config/product/fleet.js +1 -0
- package/config/product/gatekeeper.js +1 -0
- package/config/product/istio.js +1 -0
- package/config/product/logging.js +1 -0
- package/config/product/longhorn.js +2 -1
- package/config/product/manager.js +1 -0
- package/config/product/monitoring.js +1 -0
- package/config/product/navlinks.js +1 -0
- package/config/product/neuvector.js +2 -1
- package/config/product/settings.js +1 -0
- package/config/product/uiplugins.js +1 -0
- package/core/__tests__/plugin-products-helpers.test.ts +454 -0
- package/core/__tests__/plugin-products.test.ts +3219 -0
- package/core/extension-manager-impl.js +30 -1
- package/core/plugin-products-base.ts +375 -0
- package/core/plugin-products-extending.ts +44 -0
- package/core/plugin-products-helpers.ts +262 -0
- package/core/plugin-products-top-level.ts +66 -0
- package/core/plugin-products-type-guards.ts +33 -0
- package/core/plugin-products.ts +50 -0
- package/core/plugin-types.ts +222 -0
- package/core/plugin.ts +45 -10
- package/core/productDebugger.js +48 -0
- package/core/types.ts +95 -11
- package/detail/__tests__/__snapshots__/fleet.cattle.io.bundle.test.ts.snap +52 -0
- package/detail/__tests__/fleet.cattle.io.bundle.test.ts +171 -0
- package/detail/fleet.cattle.io.bundle.vue +21 -34
- package/dialog/ExtensionCatalogInstallDialog.vue +1 -1
- package/dialog/InstallExtensionDialog.vue +6 -27
- package/dialog/UninstallExistingExtensionDialog.vue +141 -0
- package/dialog/UninstallExtensionDialog.vue +4 -26
- package/dialog/__tests__/UninstallExistingExtensionDialog.test.ts +114 -0
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/Ingress.test.ts +176 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +4 -1
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +6 -0
- package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +7 -2
- package/list/provisioning.cattle.io.cluster.vue +0 -1
- package/list/workload.vue +11 -4
- package/mixins/resource-fetch.js +12 -3
- package/models/pod.js +18 -0
- package/models/workload.js +20 -2
- package/package.json +1 -2
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +0 -1
- package/pages/c/_cluster/settings/brand.vue +4 -4
- package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +231 -13
- package/pages/c/_cluster/uiplugins/index.vue +143 -37
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +1 -0
- package/plugins/dashboard-store/actions.js +3 -2
- package/plugins/dashboard-store/resource-class.js +62 -6
- package/plugins/plugin.js +16 -0
- package/plugins/steve/steve-pagination-utils.ts +7 -0
- package/scripts/typegen.sh +13 -1
- package/store/__tests__/type-map.test.ts +84 -24
- package/store/type-map.js +42 -3
- package/tsconfig.paths.json +1 -0
- package/types/resources/pod.ts +18 -0
- package/types/shell/index.d.ts +8506 -2909
- package/types/store/dashboard-store.types.ts +5 -0
- package/types/store/pagination.types.ts +6 -0
- package/utils/axios.js +1 -4
- package/utils/dynamic-importer.js +3 -2
- package/utils/pagination-utils.ts +1 -1
- package/utils/uiplugins.ts +12 -16
- package/utils/validators/__tests__/private-registry.test.ts +76 -0
- package/utils/validators/private-registry.ts +28 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { mapGetters } from 'vuex';
|
|
3
3
|
import { RadioGroup } from '@components/Form/Radio';
|
|
4
4
|
import ResourceLabeledSelect from '@shell/components/form/ResourceLabeledSelect.vue';
|
|
5
|
+
import LabeledSelect from '@shell/components/form/LabeledSelect.vue';
|
|
5
6
|
import NodeAffinity from '@shell/components/form/NodeAffinity.vue';
|
|
6
7
|
import { HARVESTER_NAME as VIRTUAL } from '@shell/config/features';
|
|
7
8
|
import { _VIEW } from '@shell/config/query-params';
|
|
@@ -18,6 +19,7 @@ const parseNode = (node: string | KubeNode) => typeof node === 'string' ? node :
|
|
|
18
19
|
export default {
|
|
19
20
|
components: {
|
|
20
21
|
RadioGroup,
|
|
22
|
+
LabeledSelect,
|
|
21
23
|
ResourceLabeledSelect,
|
|
22
24
|
NodeAffinity,
|
|
23
25
|
},
|
|
@@ -35,7 +37,7 @@ export default {
|
|
|
35
37
|
*/
|
|
36
38
|
nodes: {
|
|
37
39
|
type: Array,
|
|
38
|
-
default: () =>
|
|
40
|
+
default: () => null
|
|
39
41
|
},
|
|
40
42
|
|
|
41
43
|
mode: {
|
|
@@ -218,14 +220,14 @@ export default {
|
|
|
218
220
|
handler(nodeSelector) {
|
|
219
221
|
// Harvester specific code should not live in rancher/dashboard components
|
|
220
222
|
// This was brought into harvester/dashboard via https://github.com/harvester/dashboard/pull/342
|
|
221
|
-
// rancher/dashboard via https://github.com/rancher/dashboard/pull/6310
|
|
223
|
+
// and then rancher/dashboard via https://github.com/rancher/dashboard/pull/6310
|
|
222
224
|
if (this.isHarvester && nodeSelector?.[HOSTNAME]) {
|
|
223
225
|
this.selectNode = 'nodeSelector';
|
|
224
226
|
const nodeName = nodeSelector[HOSTNAME];
|
|
225
227
|
|
|
226
228
|
this.nodeName = nodeName;
|
|
227
229
|
|
|
228
|
-
const array = this.nodes
|
|
230
|
+
const array = this.nodes?.map((n) => n.value) || [];
|
|
229
231
|
|
|
230
232
|
if (nodeName && !array.includes(nodeName)) {
|
|
231
233
|
this.$store.dispatch('growl/error', {
|
|
@@ -259,7 +261,19 @@ export default {
|
|
|
259
261
|
<template v-if="selectNode === 'nodeSelector'">
|
|
260
262
|
<div class="row">
|
|
261
263
|
<div class="col span-6">
|
|
264
|
+
<LabeledSelect
|
|
265
|
+
v-if="nodes"
|
|
266
|
+
v-model:value="nodeName"
|
|
267
|
+
:label="t('workload.scheduling.affinity.nodeName')"
|
|
268
|
+
:options="nodes || []"
|
|
269
|
+
:mode="mode"
|
|
270
|
+
:multiple="false"
|
|
271
|
+
:loading="loading"
|
|
272
|
+
:data-testid="'node-scheduling-nodeSelector'"
|
|
273
|
+
@update:value="update"
|
|
274
|
+
/>
|
|
262
275
|
<ResourceLabeledSelect
|
|
276
|
+
v-else
|
|
263
277
|
v-model:value="nodeName"
|
|
264
278
|
:label="t('workload.scheduling.affinity.nodeName')"
|
|
265
279
|
:resource-type="NODE"
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, watch } from 'vue';
|
|
3
|
+
import Banner from '@components/Banner/Banner.vue';
|
|
4
|
+
import { Checkbox } from '@components/Form/Checkbox';
|
|
5
|
+
import LabeledInput from '@components/Form/LabeledInput/LabeledInput.vue';
|
|
6
|
+
|
|
7
|
+
const props = defineProps<{
|
|
8
|
+
value?: string | null;
|
|
9
|
+
enabled?: boolean;
|
|
10
|
+
mode?: string;
|
|
11
|
+
rules?: Function[];
|
|
12
|
+
checkboxTestId?: string;
|
|
13
|
+
inputTestId?: string;
|
|
14
|
+
}>();
|
|
15
|
+
|
|
16
|
+
const emit = defineEmits<{
|
|
17
|
+
'update:value': [val: string | null];
|
|
18
|
+
'update:enabled': [val: boolean];
|
|
19
|
+
}>();
|
|
20
|
+
|
|
21
|
+
const showInput = ref(!!props.value);
|
|
22
|
+
|
|
23
|
+
watch(() => props.enabled, (neu) => {
|
|
24
|
+
if (typeof neu === 'boolean' && neu !== showInput.value) {
|
|
25
|
+
showInput.value = neu;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
watch(showInput, (neu, old) => {
|
|
30
|
+
if (neu !== props.enabled) {
|
|
31
|
+
emit('update:enabled', neu);
|
|
32
|
+
}
|
|
33
|
+
if (!neu && old && props.value) {
|
|
34
|
+
emit('update:value', null);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
watch(() => props.value, (neu) => {
|
|
39
|
+
if (!!neu && !showInput.value) {
|
|
40
|
+
showInput.value = true;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<template>
|
|
46
|
+
<Banner
|
|
47
|
+
color="info"
|
|
48
|
+
class="mt-0"
|
|
49
|
+
label-key="cluster.privateRegistry.importedDescription"
|
|
50
|
+
/>
|
|
51
|
+
<Checkbox
|
|
52
|
+
v-model:value="showInput"
|
|
53
|
+
class="mb-20"
|
|
54
|
+
:mode="mode"
|
|
55
|
+
:label="t('cluster.privateRegistry.label')"
|
|
56
|
+
:data-testid="checkboxTestId"
|
|
57
|
+
/>
|
|
58
|
+
<LabeledInput
|
|
59
|
+
v-if="showInput"
|
|
60
|
+
:value="value as string"
|
|
61
|
+
:mode="mode"
|
|
62
|
+
:rules="rules"
|
|
63
|
+
:required="true"
|
|
64
|
+
label-key="catalog.chart.registry.custom.inputLabel"
|
|
65
|
+
:data-testid="inputTestId"
|
|
66
|
+
:placeholder="t('catalog.chart.registry.custom.placeholder')"
|
|
67
|
+
@update:value="(val) => emit('update:value', val)"
|
|
68
|
+
/>
|
|
69
|
+
</template>
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
import PrivateRegistry from '@shell/components/form/PrivateRegistry.vue';
|
|
3
|
+
import { Checkbox } from '@components/Form/Checkbox';
|
|
4
|
+
import LabeledInput from '@components/Form/LabeledInput/LabeledInput.vue';
|
|
5
|
+
|
|
6
|
+
const defaultMocks = {
|
|
7
|
+
$store: {
|
|
8
|
+
getters: {
|
|
9
|
+
'i18n/t': (text: string) => text,
|
|
10
|
+
t: (text: string) => text,
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const mountPrivateRegistry = (props = {}) => {
|
|
16
|
+
return shallowMount(PrivateRegistry, {
|
|
17
|
+
props: {
|
|
18
|
+
mode: 'edit',
|
|
19
|
+
...props
|
|
20
|
+
},
|
|
21
|
+
global: { mocks: defaultMocks }
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
describe('privateRegistry', () => {
|
|
26
|
+
it('should render the info banner', () => {
|
|
27
|
+
const wrapper = mountPrivateRegistry();
|
|
28
|
+
const banner = wrapper.find('[color="info"]');
|
|
29
|
+
|
|
30
|
+
expect(banner.exists()).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should render the enable checkbox', () => {
|
|
34
|
+
const wrapper = mountPrivateRegistry();
|
|
35
|
+
const checkbox = wrapper.findComponent(Checkbox);
|
|
36
|
+
|
|
37
|
+
expect(checkbox.exists()).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should not show the URL input when no value is provided', () => {
|
|
41
|
+
const wrapper = mountPrivateRegistry();
|
|
42
|
+
|
|
43
|
+
expect(wrapper.findComponent(LabeledInput).exists()).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should show the URL input when a value is provided', () => {
|
|
47
|
+
const wrapper = mountPrivateRegistry({ value: 'registry.example.com' });
|
|
48
|
+
|
|
49
|
+
expect(wrapper.findComponent(LabeledInput).exists()).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should show the URL input when checkbox is checked', async() => {
|
|
53
|
+
const wrapper = mountPrivateRegistry();
|
|
54
|
+
|
|
55
|
+
expect(wrapper.findComponent(LabeledInput).exists()).toBe(false);
|
|
56
|
+
|
|
57
|
+
const checkbox = wrapper.findComponent(Checkbox);
|
|
58
|
+
|
|
59
|
+
await checkbox.vm.$emit('update:value', true);
|
|
60
|
+
await wrapper.vm.$nextTick();
|
|
61
|
+
|
|
62
|
+
expect(wrapper.findComponent(LabeledInput).exists()).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should emit update:value with null when checkbox is unchecked', async() => {
|
|
66
|
+
const wrapper = mountPrivateRegistry({ value: 'registry.example.com' });
|
|
67
|
+
|
|
68
|
+
const checkbox = wrapper.findComponent(Checkbox);
|
|
69
|
+
|
|
70
|
+
await checkbox.vm.$emit('update:value', false);
|
|
71
|
+
await wrapper.vm.$nextTick();
|
|
72
|
+
|
|
73
|
+
expect(wrapper.emitted('update:value')).toHaveLength(1);
|
|
74
|
+
expect(wrapper.emitted('update:value')![0]).toStrictEqual([null]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should emit update:value when the URL input changes', async() => {
|
|
78
|
+
const wrapper = mountPrivateRegistry({ value: 'registry.example.com' });
|
|
79
|
+
const input = wrapper.findComponent(LabeledInput);
|
|
80
|
+
|
|
81
|
+
await input.vm.$emit('update:value', 'new-registry.example.com');
|
|
82
|
+
|
|
83
|
+
expect(wrapper.emitted('update:value')).toHaveLength(1);
|
|
84
|
+
expect(wrapper.emitted('update:value')![0]).toStrictEqual(['new-registry.example.com']);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should auto-enable the checkbox when value changes from null to a string', async() => {
|
|
88
|
+
const wrapper = mountPrivateRegistry();
|
|
89
|
+
|
|
90
|
+
expect(wrapper.findComponent(LabeledInput).exists()).toBe(false);
|
|
91
|
+
|
|
92
|
+
await wrapper.setProps({ value: 'registry.example.com' });
|
|
93
|
+
|
|
94
|
+
expect(wrapper.findComponent(LabeledInput).exists()).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should pass rules to the URL input', () => {
|
|
98
|
+
const mockRule = jest.fn();
|
|
99
|
+
const wrapper = mountPrivateRegistry({
|
|
100
|
+
value: 'registry.example.com',
|
|
101
|
+
rules: [mockRule]
|
|
102
|
+
});
|
|
103
|
+
const input = wrapper.findComponent(LabeledInput);
|
|
104
|
+
|
|
105
|
+
expect(input.attributes('rules')).toBeDefined();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should apply custom data-testid to checkbox when provided', () => {
|
|
109
|
+
const wrapper = mountPrivateRegistry({ checkboxTestId: 'my-checkbox' });
|
|
110
|
+
const checkbox = wrapper.findComponent(Checkbox);
|
|
111
|
+
|
|
112
|
+
expect(checkbox.attributes('data-testid')).toBe('my-checkbox');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should apply custom data-testid to input when provided', () => {
|
|
116
|
+
const wrapper = mountPrivateRegistry({
|
|
117
|
+
value: 'registry.example.com',
|
|
118
|
+
inputTestId: 'my-input'
|
|
119
|
+
});
|
|
120
|
+
const input = wrapper.findComponent(LabeledInput);
|
|
121
|
+
|
|
122
|
+
expect(input.attributes('data-testid')).toBe('my-input');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should not set data-testid when not provided', () => {
|
|
126
|
+
const wrapper = mountPrivateRegistry({ value: 'registry.example.com' });
|
|
127
|
+
const checkbox = wrapper.findComponent(Checkbox);
|
|
128
|
+
const input = wrapper.findComponent(LabeledInput);
|
|
129
|
+
|
|
130
|
+
expect(checkbox.attributes('data-testid')).toBeUndefined();
|
|
131
|
+
expect(input.attributes('data-testid')).toBeUndefined();
|
|
132
|
+
});
|
|
133
|
+
});
|
package/components/nav/Group.vue
CHANGED
|
@@ -269,8 +269,9 @@ export default {
|
|
|
269
269
|
@keyup.space="groupSelected()"
|
|
270
270
|
>
|
|
271
271
|
<slot name="header">
|
|
272
|
+
<!-- Group overview with link -->
|
|
272
273
|
<router-link
|
|
273
|
-
v-if="hasOverview"
|
|
274
|
+
v-if="hasOverview && hasChildren"
|
|
274
275
|
:to="headerRoute"
|
|
275
276
|
:exact="group.children[0].exact"
|
|
276
277
|
:tabindex="-1"
|
|
@@ -279,15 +280,32 @@ export default {
|
|
|
279
280
|
<span v-clean-html="group.labelDisplay || group.label" />
|
|
280
281
|
</h6>
|
|
281
282
|
</router-link>
|
|
283
|
+
<!-- Non-linked group header -->
|
|
282
284
|
<h6
|
|
283
|
-
v-else
|
|
285
|
+
v-else-if="hasChildren"
|
|
284
286
|
>
|
|
285
287
|
<span v-clean-html="group.labelDisplay || group.label" />
|
|
286
288
|
</h6>
|
|
289
|
+
<!-- Simple child (nav item) -->
|
|
290
|
+
<ul
|
|
291
|
+
v-else
|
|
292
|
+
class="list-unstyled body root-depth"
|
|
293
|
+
v-bind="$attrs"
|
|
294
|
+
>
|
|
295
|
+
<Type
|
|
296
|
+
|
|
297
|
+
:key="id+'_' + group.name + '_type'"
|
|
298
|
+
:is-root="depth == 0 && !showHeader"
|
|
299
|
+
:type="group"
|
|
300
|
+
:depth="depth"
|
|
301
|
+
:highlight-route="highlightRoute"
|
|
302
|
+
@selected="selectType($event)"
|
|
303
|
+
/>
|
|
304
|
+
</ul>
|
|
287
305
|
</slot>
|
|
288
306
|
</div>
|
|
289
307
|
<i
|
|
290
|
-
v-if="!onlyHasOverview && canCollapse"
|
|
308
|
+
v-if="!onlyHasOverview && canCollapse && hasChildren"
|
|
291
309
|
class="icon toggle toggle-accordion"
|
|
292
310
|
:class="{'icon-chevron-right': !isExpanded, 'icon-chevron-down': isExpanded}"
|
|
293
311
|
role="button"
|
|
@@ -377,6 +395,7 @@ export default {
|
|
|
377
395
|
display: block;
|
|
378
396
|
box-sizing:border-box;
|
|
379
397
|
height: 100%;
|
|
398
|
+
|
|
380
399
|
&:hover{
|
|
381
400
|
text-decoration: none;
|
|
382
401
|
}
|
|
@@ -484,6 +503,10 @@ export default {
|
|
|
484
503
|
}
|
|
485
504
|
}
|
|
486
505
|
}
|
|
506
|
+
|
|
507
|
+
.root-depth :deep() > .child.nav-type a {
|
|
508
|
+
padding-left: 14px;
|
|
509
|
+
}
|
|
487
510
|
}
|
|
488
511
|
|
|
489
512
|
&.depth-1 {
|
|
@@ -203,12 +203,6 @@ export default {
|
|
|
203
203
|
return !!this.currentCluster?.actions?.apply;
|
|
204
204
|
},
|
|
205
205
|
|
|
206
|
-
prod() {
|
|
207
|
-
const name = this.rootProduct.name;
|
|
208
|
-
|
|
209
|
-
return this.$store.getters['i18n/withFallback'](`product."${ name }"`, null, ucFirst(name));
|
|
210
|
-
},
|
|
211
|
-
|
|
212
206
|
showSearch() {
|
|
213
207
|
return this.rootProduct?.inStore === 'cluster';
|
|
214
208
|
},
|
|
@@ -239,6 +233,30 @@ export default {
|
|
|
239
233
|
isHarvester() {
|
|
240
234
|
return this.$store.getters['currentProduct'].inStore === HARVESTER;
|
|
241
235
|
},
|
|
236
|
+
|
|
237
|
+
productLabel() {
|
|
238
|
+
const name = this.rootProduct.name;
|
|
239
|
+
|
|
240
|
+
// single products do their own thing, which is the previous default behavior as per next line
|
|
241
|
+
if (this.isSingleProduct) {
|
|
242
|
+
return this.$store.getters['i18n/withFallback'](`product."${ name }"`, null, ucFirst(name));
|
|
243
|
+
} else {
|
|
244
|
+
if (this.rootProduct?.label) {
|
|
245
|
+
return this.rootProduct.label;
|
|
246
|
+
}
|
|
247
|
+
if (this.rootProduct?.labelKey) {
|
|
248
|
+
return this.$store.getters['i18n/t'](this.rootProduct.labelKey);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return this.$store.getters['i18n/withFallback'](`product."${ name }"`, null, ucFirst(name));
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
|
|
255
|
+
// Determine if we are on a route that shows the logo instead of the product label
|
|
256
|
+
// This is to enforce the logo display on certain routes like home, about, prefs, account, etc
|
|
257
|
+
isLogoRoute() {
|
|
258
|
+
return !this.$route.name.includes('c-cluster');
|
|
259
|
+
}
|
|
242
260
|
},
|
|
243
261
|
|
|
244
262
|
watch: {
|
|
@@ -518,7 +536,7 @@ export default {
|
|
|
518
536
|
:alt="t('branding.logos.label')"
|
|
519
537
|
>
|
|
520
538
|
<div class="product-name">
|
|
521
|
-
{{
|
|
539
|
+
{{ productLabel }}
|
|
522
540
|
</div>
|
|
523
541
|
</div>
|
|
524
542
|
</div>
|
|
@@ -534,6 +552,13 @@ export default {
|
|
|
534
552
|
{{ t(isSingleProduct.productNameKey) }}
|
|
535
553
|
</div>
|
|
536
554
|
|
|
555
|
+
<div
|
|
556
|
+
v-else-if="productLabel && !isLogoRoute"
|
|
557
|
+
class="product-name"
|
|
558
|
+
>
|
|
559
|
+
{{ productLabel }}
|
|
560
|
+
</div>
|
|
561
|
+
|
|
537
562
|
<div
|
|
538
563
|
v-else
|
|
539
564
|
class="side-menu-logo"
|
|
@@ -186,8 +186,22 @@ export default {
|
|
|
186
186
|
to.params.product = p.name;
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
let label;
|
|
190
|
+
|
|
191
|
+
// Allow product to specify its label (old DSL product() did not have "label" or "labelKey")
|
|
192
|
+
// new extensions product registration supports both "label" and "labelKey" (with "labelKey" taking precedence if both are provided)
|
|
193
|
+
if (p.labelKey) {
|
|
194
|
+
label = this.$store.getters['i18n/t'](p.labelKey);
|
|
195
|
+
} else if (p.label) {
|
|
196
|
+
label = p.label;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!label) {
|
|
200
|
+
label = this.$store.getters['i18n/withFallback'](`product.${ p.name }`, null, ucFirst(p.name));
|
|
201
|
+
}
|
|
202
|
+
|
|
189
203
|
return {
|
|
190
|
-
label
|
|
204
|
+
label,
|
|
191
205
|
icon: `icon-${ p.icon || 'copy' }`,
|
|
192
206
|
svg: p.svg,
|
|
193
207
|
value: p.name,
|
|
@@ -4,7 +4,8 @@ import {
|
|
|
4
4
|
EVENT_LAST_SEEN_TIME,
|
|
5
5
|
EVENT_TYPE,
|
|
6
6
|
SECRET_ORIGIN,
|
|
7
|
-
EVENT_FIRST_SEEN_TIME
|
|
7
|
+
EVENT_FIRST_SEEN_TIME,
|
|
8
|
+
WORKLOAD_HEALTH_SCALE
|
|
8
9
|
} from '@shell/config/table-headers';
|
|
9
10
|
|
|
10
11
|
// This file contains table headers
|
|
@@ -95,3 +96,9 @@ export const STEVE_SECRET_ORIGIN = {
|
|
|
95
96
|
// So we sort by the 'UI_PROJECT_SECRET_COPY' annotation (management.cattle.io/project-scoped-secret-copy) which at least groups the copies.
|
|
96
97
|
sort: `metadata.annotations[${ UI_PROJECT_SECRET_COPY }]:desc`,
|
|
97
98
|
};
|
|
99
|
+
|
|
100
|
+
export const STEVE_WORKLOAD_HEALTH_SCALE = {
|
|
101
|
+
...WORKLOAD_HEALTH_SCALE,
|
|
102
|
+
sort: false,
|
|
103
|
+
search: false,
|
|
104
|
+
};
|
package/config/product/apps.js
CHANGED
package/config/product/auth.js
CHANGED
package/config/product/backup.js
CHANGED
|
@@ -14,7 +14,7 @@ export function init(store) {
|
|
|
14
14
|
headers
|
|
15
15
|
} = DSL(store, NAME);
|
|
16
16
|
|
|
17
|
-
product({ ifHaveGroup: /^(.*\.)*compliance\.cattle\.io
|
|
17
|
+
product({ ifHaveGroup: /^(.*\.)*compliance\.cattle\.io$/, extendable: true });
|
|
18
18
|
|
|
19
19
|
weightType(COMPLIANCE.CLUSTER_SCAN, 3, true);
|
|
20
20
|
weightType(COMPLIANCE.CLUSTER_SCAN_PROFILE, 2, true);
|
|
@@ -27,13 +27,15 @@ import {
|
|
|
27
27
|
|
|
28
28
|
import { DSL } from '@shell/store/type-map';
|
|
29
29
|
import {
|
|
30
|
-
STEVE_AGE_COL, STEVE_EVENT_FIRST_SEEN, STEVE_EVENT_LAST_SEEN, STEVE_EVENT_OBJECT, STEVE_EVENT_TYPE, STEVE_LIST_GROUPS, STEVE_NAMESPACE_COL, STEVE_NAME_COL, STEVE_STATE_COL
|
|
30
|
+
STEVE_AGE_COL, STEVE_EVENT_FIRST_SEEN, STEVE_EVENT_LAST_SEEN, STEVE_EVENT_OBJECT, STEVE_EVENT_TYPE, STEVE_LIST_GROUPS, STEVE_NAMESPACE_COL, STEVE_NAME_COL, STEVE_STATE_COL,
|
|
31
|
+
STEVE_WORKLOAD_HEALTH_SCALE
|
|
31
32
|
} from '@shell/config/pagination-table-headers';
|
|
32
33
|
|
|
33
34
|
import { COLUMN_BREAKPOINTS } from '@shell/types/store/type-map';
|
|
34
35
|
import { STEVE_CACHE } from '@shell/store/features';
|
|
35
36
|
import { configureConditionalDepaginate } from '@shell/store/type-map.utils';
|
|
36
37
|
import { CATTLE_PUBLIC_ENDPOINTS, STORAGE } from '@shell/config/labels-annotations';
|
|
38
|
+
import { POD_LAST_RESTART_FIELD as POD_RESTARTS_LAST_FIELD, POD_RESTART_FIELD as POD_RESTARTS_COUNT_FIELD } from '@shell/types/resources/pod';
|
|
37
39
|
|
|
38
40
|
export const NAME = 'explorer';
|
|
39
41
|
|
|
@@ -58,6 +60,7 @@ export function init(store) {
|
|
|
58
60
|
weight: 3,
|
|
59
61
|
showNamespaceFilter: true,
|
|
60
62
|
icon: 'compass',
|
|
63
|
+
extendable: true,
|
|
61
64
|
typeStoreMap: {
|
|
62
65
|
[MANAGEMENT.PROJECT]: 'management',
|
|
63
66
|
[MANAGEMENT.CLUSTER_ROLE_TEMPLATE_BINDING]: 'management',
|
|
@@ -386,11 +389,11 @@ export function init(store) {
|
|
|
386
389
|
headers(WORKLOAD, [STATE, NAME_COL, NAMESPACE_COL, TYPE, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, POD_RESTARTS, AGE, WORKLOAD_HEALTH_SCALE]);
|
|
387
390
|
headers(WORKLOAD_TYPES.DEPLOYMENT,
|
|
388
391
|
[STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Ready', 'Up-to-date', 'Available', POD_RESTARTS, AGE, WORKLOAD_HEALTH_SCALE],
|
|
389
|
-
[STEVE_STATE_COL, STEVE_NAME_COL, STEVE_NAMESPACE_COL, createSteveWorkloadImageCol(6), STEVE_WORKLOAD_ENDPOINTS, 'Ready', 'Up-to-date', 'Available', STEVE_AGE_COL],
|
|
392
|
+
[STEVE_STATE_COL, STEVE_NAME_COL, STEVE_NAMESPACE_COL, createSteveWorkloadImageCol(6), STEVE_WORKLOAD_ENDPOINTS, 'Ready', 'Up-to-date', 'Available', STEVE_AGE_COL, STEVE_WORKLOAD_HEALTH_SCALE],
|
|
390
393
|
);
|
|
391
394
|
headers(WORKLOAD_TYPES.DAEMON_SET,
|
|
392
395
|
[STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Ready', 'Current', 'Desired', POD_RESTARTS, AGE, WORKLOAD_HEALTH_SCALE],
|
|
393
|
-
[STEVE_STATE_COL, STEVE_NAME_COL, STEVE_NAMESPACE_COL, createSteveWorkloadImageCol(9), STEVE_WORKLOAD_ENDPOINTS, 'Ready', 'Current', 'Desired', STEVE_AGE_COL]
|
|
396
|
+
[STEVE_STATE_COL, STEVE_NAME_COL, STEVE_NAMESPACE_COL, createSteveWorkloadImageCol(9), STEVE_WORKLOAD_ENDPOINTS, 'Ready', 'Current', 'Desired', STEVE_AGE_COL, STEVE_WORKLOAD_HEALTH_SCALE]
|
|
394
397
|
);
|
|
395
398
|
headers(WORKLOAD_TYPES.REPLICA_SET,
|
|
396
399
|
[STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Ready', 'Current', 'Desired', POD_RESTARTS, AGE, WORKLOAD_HEALTH_SCALE],
|
|
@@ -398,7 +401,7 @@ export function init(store) {
|
|
|
398
401
|
);
|
|
399
402
|
headers(WORKLOAD_TYPES.STATEFUL_SET,
|
|
400
403
|
[STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Ready', POD_RESTARTS, AGE, WORKLOAD_HEALTH_SCALE],
|
|
401
|
-
[STEVE_STATE_COL, STEVE_NAME_COL, STEVE_NAMESPACE_COL, createSteveWorkloadImageCol(4), STEVE_WORKLOAD_ENDPOINTS, 'Ready', STEVE_AGE_COL],
|
|
404
|
+
[STEVE_STATE_COL, STEVE_NAME_COL, STEVE_NAMESPACE_COL, createSteveWorkloadImageCol(4), STEVE_WORKLOAD_ENDPOINTS, 'Ready', STEVE_AGE_COL, STEVE_WORKLOAD_HEALTH_SCALE],
|
|
402
405
|
);
|
|
403
406
|
headers(WORKLOAD_TYPES.JOB,
|
|
404
407
|
[STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Completions', DURATION, POD_RESTARTS, AGE, WORKLOAD_HEALTH_SCALE],
|
|
@@ -408,7 +411,7 @@ export function init(store) {
|
|
|
408
411
|
sort: 'metadata.fields.3',
|
|
409
412
|
search: 'metadata.fields.3',
|
|
410
413
|
formatter: undefined, // Now that sort/search is remote we're not doing weird things with start time (see `duration` in model)
|
|
411
|
-
}, STEVE_AGE_COL],
|
|
414
|
+
}, STEVE_AGE_COL, STEVE_WORKLOAD_HEALTH_SCALE],
|
|
412
415
|
);
|
|
413
416
|
headers(WORKLOAD_TYPES.CRON_JOB,
|
|
414
417
|
[STATE, NAME_COL, NAMESPACE_COL, WORKLOAD_IMAGES, WORKLOAD_ENDPOINTS, 'Schedule', 'Last Schedule', POD_RESTARTS, AGE, WORKLOAD_HEALTH_SCALE],
|
|
@@ -428,7 +431,23 @@ export function init(store) {
|
|
|
428
431
|
...POD_IMAGES,
|
|
429
432
|
sort: false,
|
|
430
433
|
search: 'spec.containers.image'
|
|
431
|
-
},
|
|
434
|
+
},
|
|
435
|
+
'Ready',
|
|
436
|
+
{
|
|
437
|
+
name: 'pod-restart',
|
|
438
|
+
labelKey: 'tableHeaders.podRestarts',
|
|
439
|
+
search: false,
|
|
440
|
+
sort: [POD_RESTARTS_COUNT_FIELD, POD_RESTARTS_LAST_FIELD, 'metadata.name'],
|
|
441
|
+
value: 'restartsCount',
|
|
442
|
+
}, {
|
|
443
|
+
name: 'pod-last-restart',
|
|
444
|
+
labelKey: 'tableHeaders.podLastRestart',
|
|
445
|
+
value: 'restartsLaster',
|
|
446
|
+
search: false,
|
|
447
|
+
sort: [POD_RESTARTS_LAST_FIELD, POD_RESTARTS_COUNT_FIELD, 'metadata.name'],
|
|
448
|
+
},
|
|
449
|
+
'IP',
|
|
450
|
+
{
|
|
432
451
|
...NODE_COL,
|
|
433
452
|
search: 'spec.nodeName'
|
|
434
453
|
},
|
package/config/product/fleet.js
CHANGED
package/config/product/istio.js
CHANGED