@rancher/shell 0.3.26 → 0.3.28
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/translations/en-us.yaml +8 -23
- package/assets/translations/zh-hans.yaml +2 -26
- package/chart/gatekeeper.vue +2 -11
- package/chart/istio.vue +1 -10
- package/chart/logging/index.vue +2 -11
- package/chart/monitoring/index.vue +1 -9
- package/chart/rancher-backup/index.vue +1 -9
- package/components/AlertTable.vue +8 -6
- package/components/Carousel.vue +2 -1
- package/components/EmberPage.vue +2 -2
- package/components/EtcdInfoBanner.vue +12 -2
- package/components/GlobalRoleBindings.vue +10 -0
- package/components/GrafanaDashboard.vue +8 -3
- package/components/Wizard.vue +17 -1
- package/components/auth/RoleDetailEdit.vue +17 -1
- package/components/form/ArrayList.vue +20 -11
- package/components/form/__tests__/ArrayList.test.ts +44 -0
- package/components/formatter/ClusterProvider.vue +1 -18
- package/components/nav/Header.vue +5 -4
- package/components/nav/TopLevelMenu.vue +38 -15
- package/components/nav/WindowManager/ContainerLogs.vue +22 -19
- package/components/nav/__tests__/TopLevelMenu.test.ts +120 -0
- package/components/nav/__tests__/Type.test.ts +139 -0
- package/config/private-label.js +1 -1
- package/config/product/manager.js +0 -13
- package/config/settings.ts +0 -2
- package/config/types.js +0 -4
- package/core/types.ts +11 -4
- package/edit/management.cattle.io.project.vue +1 -52
- package/edit/management.cattle.io.setting.vue +31 -2
- package/edit/provisioning.cattle.io.cluster/Basics.vue +19 -107
- package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/__tests__/Basics.tests.ts +0 -3
- package/edit/provisioning.cattle.io.cluster/rke2.vue +3 -128
- package/edit/workload/mixins/workload.js +14 -4
- package/middleware/authenticated.js +4 -2
- package/models/__tests__/management.cattle.io.cluster.test.ts +19 -0
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +90 -0
- package/models/cluster.x-k8s.io.machine.js +1 -1
- package/models/fleet.cattle.io.cluster.js +11 -1
- package/models/management.cattle.io.cluster.js +4 -0
- package/models/management.cattle.io.project.js +0 -36
- package/models/management.cattle.io.setting.js +11 -7
- package/models/provisioning.cattle.io.cluster.js +16 -4
- package/package.json +1 -1
- package/pages/auth/setup.vue +38 -1
- package/pages/c/_cluster/apps/charts/__tests__/install.helper.test.ts +2 -17
- package/pages/c/_cluster/apps/charts/index.vue +0 -15
- package/pages/c/_cluster/apps/charts/install.helpers.js +2 -13
- package/pages/c/_cluster/apps/charts/install.vue +1 -1
- package/pages/c/_cluster/auth/roles/index.vue +11 -1
- package/pages/c/_cluster/explorer/index.vue +7 -49
- package/pages/c/_cluster/manager/pages/_page.vue +4 -5
- package/pages/c/_cluster/monitoring/index.vue +26 -39
- package/pages/support/index.vue +1 -8
- package/promptRemove/management.cattle.io.project.vue +6 -9
- package/rancher-components/BadgeState/BadgeState.vue +1 -5
- package/rancher-components/Banner/Banner.test.ts +1 -51
- package/rancher-components/Banner/Banner.vue +53 -134
- package/rancher-components/Card/Card.vue +7 -24
- package/rancher-components/Form/Checkbox/Checkbox.test.ts +29 -20
- package/rancher-components/Form/Checkbox/Checkbox.vue +20 -45
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +8 -2
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +10 -22
- package/rancher-components/Form/Radio/RadioButton.vue +13 -30
- package/rancher-components/Form/Radio/RadioGroup.vue +7 -26
- package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +6 -7
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.test.ts +38 -25
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +11 -23
- package/rancher-components/LabeledTooltip/LabeledTooltip.vue +5 -19
- package/rancher-components/StringList/StringList.test.ts +49 -453
- package/rancher-components/StringList/StringList.vue +58 -92
- package/rancher-components/components/Form/Radio/RadioGroup.test.ts +30 -0
- package/rancher-components/components/Form/Radio/RadioGroup.vue +4 -0
- package/rancher-components/components/StringList/StringList.test.ts +270 -0
- package/rancher-components/components/StringList/StringList.vue +57 -18
- package/store/features.js +1 -0
- package/store/prefs.js +0 -3
- package/types/shell/index.d.ts +26 -17
- package/utils/__tests__/object.test.ts +67 -1
- package/utils/__tests__/version.test.ts +13 -23
- package/utils/cluster.js +1 -1
- package/utils/custom-validators.js +0 -2
- package/utils/error.js +16 -1
- package/utils/grafana.js +1 -2
- package/utils/monitoring.js +25 -1
- package/utils/object.js +4 -3
- package/utils/sort.js +1 -1
- package/utils/validators/formRules/__tests__/index.test.ts +49 -4
- package/utils/validators/formRules/index.ts +13 -10
- package/utils/validators/role-template.js +1 -1
- package/utils/validators/setting.js +6 -10
- package/utils/version.js +0 -13
- package/components/ChartPsp.vue +0 -76
- package/components/__tests__/ChartPsp.test.ts +0 -75
- package/components/formatter/__tests__/ClusterProvider.test.ts +0 -28
- package/rancher-components/Card/Card.test.ts +0 -37
- package/rancher-components/Form/Radio/RadioButton.test.ts +0 -31
- package/yarn-error.log +0 -200
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { mount } from '@vue/test-utils';
|
|
2
2
|
import ArrayList from '@shell/components/form/ArrayList.vue';
|
|
3
3
|
import { _EDIT, _VIEW } from '@shell/config/query-params';
|
|
4
|
+
import { ExtendedVue, Vue } from 'vue/types/vue';
|
|
5
|
+
import { DefaultProps } from 'vue/types/options';
|
|
4
6
|
|
|
5
7
|
describe('the ArrayList', () => {
|
|
6
8
|
it('is empty', () => {
|
|
@@ -71,4 +73,46 @@ describe('the ArrayList', () => {
|
|
|
71
73
|
|
|
72
74
|
expect(arrayListButtons).toHaveLength(0);
|
|
73
75
|
});
|
|
76
|
+
|
|
77
|
+
describe('onPaste', () => {
|
|
78
|
+
it('should emit value with updated row text', () => {
|
|
79
|
+
const text = 'test';
|
|
80
|
+
const expectation = [text];
|
|
81
|
+
const wrapper = mount(ArrayList as unknown as ExtendedVue<Vue, {}, {}, {}, DefaultProps>, { propsData: { value: [''] } });
|
|
82
|
+
const event = { preventDefault: jest.fn(), clipboardData: { getData: jest.fn().mockReturnValue(text) } } as any;
|
|
83
|
+
|
|
84
|
+
wrapper.vm.onPaste(0, event);
|
|
85
|
+
|
|
86
|
+
expect(wrapper.emitted().input?.[0][0]).toStrictEqual(expectation);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should emit value with multiple rows', () => {
|
|
90
|
+
const wrapper = mount(ArrayList as unknown as ExtendedVue<Vue, {}, {}, {}, DefaultProps>, { propsData: { value: [''] } });
|
|
91
|
+
const text = `multiline
|
|
92
|
+
rows`;
|
|
93
|
+
const expectation = ['multiline', 'rows'];
|
|
94
|
+
const event = { preventDefault: jest.fn(), clipboardData: { getData: jest.fn().mockReturnValue(text) } } as any;
|
|
95
|
+
|
|
96
|
+
wrapper.vm.onPaste(0, event);
|
|
97
|
+
|
|
98
|
+
expect(wrapper.emitted().input?.[0][0]).toStrictEqual(expectation);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should allow emit multiline pasted values if enabled', () => {
|
|
102
|
+
const wrapper = mount(ArrayList as unknown as ExtendedVue<Vue, {}, {}, {}, DefaultProps>, {
|
|
103
|
+
propsData: {
|
|
104
|
+
value: [''],
|
|
105
|
+
valueMultiline: true,
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
const text = `multiline
|
|
109
|
+
text`;
|
|
110
|
+
const expectation = [text];
|
|
111
|
+
const event = { preventDefault: jest.fn(), clipboardData: { getData: jest.fn().mockReturnValue(text) } } as any;
|
|
112
|
+
|
|
113
|
+
wrapper.vm.onPaste(0, event);
|
|
114
|
+
|
|
115
|
+
expect(wrapper.emitted().input?.[0][0]).toStrictEqual(expectation);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
74
118
|
});
|
|
@@ -6,23 +6,6 @@ export default {
|
|
|
6
6
|
required: true
|
|
7
7
|
},
|
|
8
8
|
},
|
|
9
|
-
data(props) {
|
|
10
|
-
const mgmt = props.row?.mgmt;
|
|
11
|
-
|
|
12
|
-
return {
|
|
13
|
-
// The isImported getter on the provisioning cluster
|
|
14
|
-
// model doesn't work for imported K3s clusters, in
|
|
15
|
-
// which case it returns 'k3s' instead of 'imported.'
|
|
16
|
-
// This is the workaround.
|
|
17
|
-
isImported: mgmt?.providerForEmberParam === 'import' ||
|
|
18
|
-
// when imported cluster is GKE
|
|
19
|
-
!!mgmt?.spec?.gkeConfig?.imported ||
|
|
20
|
-
// or AKS
|
|
21
|
-
!!mgmt?.spec?.aksConfig?.imported ||
|
|
22
|
-
// or EKS
|
|
23
|
-
!!mgmt?.spec?.eksConfig?.imported
|
|
24
|
-
};
|
|
25
|
-
},
|
|
26
9
|
};
|
|
27
10
|
</script>
|
|
28
11
|
|
|
@@ -45,7 +28,7 @@ export default {
|
|
|
45
28
|
<template v-else-if="row.isCustom">
|
|
46
29
|
{{ t('cluster.provider.custom') }}
|
|
47
30
|
</template>
|
|
48
|
-
<template v-else-if="isImported">
|
|
31
|
+
<template v-else-if="row.isImported">
|
|
49
32
|
{{ t('cluster.provider.imported') }}
|
|
50
33
|
</template>
|
|
51
34
|
<div class="text-muted">
|
|
@@ -706,7 +706,6 @@ export default {
|
|
|
706
706
|
</template>
|
|
707
707
|
|
|
708
708
|
<style lang="scss" scoped>
|
|
709
|
-
$side-menu-logo-margin-left: 5px;
|
|
710
709
|
// It would be nice to grab this from `Group.vue`, but there's margin, padding and border, which is overkill to var
|
|
711
710
|
$side-menu-group-padding-left: 16px;
|
|
712
711
|
|
|
@@ -724,9 +723,11 @@ export default {
|
|
|
724
723
|
&.isSingleProduct {
|
|
725
724
|
display: flex;
|
|
726
725
|
justify-content: center;
|
|
726
|
+
|
|
727
727
|
// Align the icon with the side nav menu items ($side-menu-group-padding-left)
|
|
728
|
-
|
|
729
|
-
|
|
728
|
+
.side-menu-logo {
|
|
729
|
+
margin-left: $side-menu-group-padding-left;
|
|
730
|
+
}
|
|
730
731
|
}
|
|
731
732
|
}
|
|
732
733
|
|
|
@@ -815,7 +816,7 @@ export default {
|
|
|
815
816
|
display: flex;
|
|
816
817
|
margin-right: 8px;
|
|
817
818
|
height: 55px;
|
|
818
|
-
margin-left:
|
|
819
|
+
margin-left: 5px;
|
|
819
820
|
max-width: 200px;
|
|
820
821
|
padding: 12px 0;
|
|
821
822
|
}
|
|
@@ -50,7 +50,6 @@ export default {
|
|
|
50
50
|
computed: {
|
|
51
51
|
...mapGetters(['clusterId']),
|
|
52
52
|
...mapGetters(['clusterReady', 'isRancher', 'currentCluster', 'currentProduct', 'isRancherInHarvester']),
|
|
53
|
-
...mapGetters('type-map', ['activeProducts']),
|
|
54
53
|
...mapGetters({ features: 'features/get' }),
|
|
55
54
|
|
|
56
55
|
value: {
|
|
@@ -59,9 +58,16 @@ export default {
|
|
|
59
58
|
},
|
|
60
59
|
},
|
|
61
60
|
|
|
61
|
+
sideMenuStyle() {
|
|
62
|
+
return {
|
|
63
|
+
marginBottom: this.globalBannerSettings?.footerFont,
|
|
64
|
+
marginTop: this.globalBannerSettings?.headerFont
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
|
|
62
68
|
globalBannerSettings() {
|
|
63
69
|
const settings = this.$store.getters['management/all'](MANAGEMENT.SETTING);
|
|
64
|
-
const bannerSettings = settings
|
|
70
|
+
const bannerSettings = settings?.find((s) => s.id === SETTING.BANNERS);
|
|
65
71
|
|
|
66
72
|
if (bannerSettings) {
|
|
67
73
|
const parsed = JSON.parse(bannerSettings.value);
|
|
@@ -104,8 +110,8 @@ export default {
|
|
|
104
110
|
kubeClusters = kubeClusters.filter((c) => !!available[c]);
|
|
105
111
|
}
|
|
106
112
|
|
|
107
|
-
return kubeClusters
|
|
108
|
-
const pCluster = pClusters?.find((c) => c.mgmt
|
|
113
|
+
return kubeClusters?.map((x) => {
|
|
114
|
+
const pCluster = pClusters?.find((c) => c.mgmt?.id === x.id);
|
|
109
115
|
|
|
110
116
|
return {
|
|
111
117
|
id: x.id,
|
|
@@ -120,14 +126,12 @@ export default {
|
|
|
120
126
|
pin: () => x.pin(),
|
|
121
127
|
unpin: () => x.unpin()
|
|
122
128
|
};
|
|
123
|
-
});
|
|
129
|
+
}) || [];
|
|
124
130
|
},
|
|
125
131
|
|
|
126
132
|
clustersFiltered() {
|
|
127
133
|
const search = (this.clusterFilter || '').toLowerCase();
|
|
128
|
-
|
|
129
|
-
const out = search ? this.clusters.filter((item) => item.label.toLowerCase().includes(search)) : this.clusters;
|
|
130
|
-
|
|
134
|
+
const out = search ? this.clusters.filter((item) => item.label?.toLowerCase().includes(search)) : this.clusters;
|
|
131
135
|
const sorted = sortBy(out, ['ready:desc', 'label']);
|
|
132
136
|
|
|
133
137
|
if (search) {
|
|
@@ -203,7 +207,7 @@ export default {
|
|
|
203
207
|
const cluster = this.clusterId || this.$store.getters['defaultClusterId'];
|
|
204
208
|
|
|
205
209
|
// TODO plugin routes
|
|
206
|
-
const entries = this.activeProducts
|
|
210
|
+
const entries = this.$store.getters['type-map/activeProducts']?.map((p) => {
|
|
207
211
|
// Try product-specific index first
|
|
208
212
|
const to = p.to || {
|
|
209
213
|
name: `c-cluster-${ p.name }`,
|
|
@@ -314,22 +318,22 @@ export default {
|
|
|
314
318
|
</script>
|
|
315
319
|
<template>
|
|
316
320
|
<div>
|
|
321
|
+
<!-- Overlay -->
|
|
317
322
|
<div
|
|
318
323
|
v-if="shown"
|
|
319
324
|
class="side-menu-glass"
|
|
320
325
|
@click="hide()"
|
|
321
326
|
/>
|
|
322
327
|
<transition name="fade">
|
|
328
|
+
<!-- Side menu -->
|
|
323
329
|
<div
|
|
324
330
|
data-testid="side-menu"
|
|
325
331
|
class="side-menu"
|
|
326
332
|
:class="{'menu-open': shown, 'menu-close':!shown}"
|
|
327
|
-
:style="
|
|
328
|
-
globalBannerSettings?.footerFont,
|
|
329
|
-
'marginTop':
|
|
330
|
-
globalBannerSettings?.headerFont}"
|
|
333
|
+
:style="sideMenuStyle"
|
|
331
334
|
tabindex="-1"
|
|
332
335
|
>
|
|
336
|
+
<!-- Logo and name -->
|
|
333
337
|
<div class="title">
|
|
334
338
|
<div
|
|
335
339
|
data-testid="top-level-menu"
|
|
@@ -351,8 +355,11 @@ export default {
|
|
|
351
355
|
<BrandImage file-name="rancher-logo.svg" />
|
|
352
356
|
</div>
|
|
353
357
|
</div>
|
|
358
|
+
|
|
359
|
+
<!-- Menu body -->
|
|
354
360
|
<div class="body">
|
|
355
361
|
<div>
|
|
362
|
+
<!-- Home button -->
|
|
356
363
|
<nuxt-link
|
|
357
364
|
class="option cluster selector home"
|
|
358
365
|
:to="{ name: 'home' }"
|
|
@@ -371,6 +378,8 @@ export default {
|
|
|
371
378
|
{{ t('nav.home') }}
|
|
372
379
|
</div>
|
|
373
380
|
</nuxt-link>
|
|
381
|
+
|
|
382
|
+
<!-- Search bar -->
|
|
374
383
|
<div
|
|
375
384
|
v-if="showClusterSearch"
|
|
376
385
|
class="clusters-search"
|
|
@@ -400,6 +409,8 @@ export default {
|
|
|
400
409
|
</div>
|
|
401
410
|
</div>
|
|
402
411
|
</div>
|
|
412
|
+
|
|
413
|
+
<!-- Harvester extras -->
|
|
403
414
|
<template v-if="hciApps.length">
|
|
404
415
|
<div class="category" />
|
|
405
416
|
<div>
|
|
@@ -416,7 +427,6 @@ export default {
|
|
|
416
427
|
</div>
|
|
417
428
|
</a>
|
|
418
429
|
</div>
|
|
419
|
-
|
|
420
430
|
<div
|
|
421
431
|
v-for="a in hciApps"
|
|
422
432
|
:key="a.label"
|
|
@@ -435,12 +445,14 @@ export default {
|
|
|
435
445
|
</div>
|
|
436
446
|
</template>
|
|
437
447
|
|
|
448
|
+
<!-- Cluster menu -->
|
|
438
449
|
<template v-if="clusters && !!clusters.length">
|
|
439
450
|
<div
|
|
440
451
|
ref="clusterList"
|
|
441
452
|
class="clusters"
|
|
442
453
|
:style="pinnedClustersHeight"
|
|
443
454
|
>
|
|
455
|
+
<!-- Pinned Clusters -->
|
|
444
456
|
<div
|
|
445
457
|
v-if="showPinClusters && pinFiltered.length"
|
|
446
458
|
class="clustersPinned"
|
|
@@ -490,10 +502,13 @@ export default {
|
|
|
490
502
|
<hr>
|
|
491
503
|
</div>
|
|
492
504
|
</div>
|
|
505
|
+
|
|
506
|
+
<!-- Clusters Search result -->
|
|
493
507
|
<div class="clustersList">
|
|
494
508
|
<div
|
|
495
|
-
v-for="c in clustersFiltered"
|
|
509
|
+
v-for="(c, index) in clustersFiltered"
|
|
496
510
|
:key="c.id"
|
|
511
|
+
:data-testid="`top-level-menu-cluster-${index}`"
|
|
497
512
|
@click="hide()"
|
|
498
513
|
>
|
|
499
514
|
<nuxt-link
|
|
@@ -532,14 +547,18 @@ export default {
|
|
|
532
547
|
</span>
|
|
533
548
|
</div>
|
|
534
549
|
</div>
|
|
550
|
+
|
|
551
|
+
<!-- No clusters message -->
|
|
535
552
|
<div
|
|
536
553
|
v-if="(clustersFiltered.length === 0 || pinFiltered.length === 0) && searchActive"
|
|
554
|
+
data-testid="top-level-menu-no-results"
|
|
537
555
|
class="none-matching"
|
|
538
556
|
>
|
|
539
557
|
{{ t('nav.search.noResults') }}
|
|
540
558
|
</div>
|
|
541
559
|
</div>
|
|
542
560
|
|
|
561
|
+
<!-- See all clusters -->
|
|
543
562
|
<nuxt-link
|
|
544
563
|
v-if="clusters.length > maxClustersToShow"
|
|
545
564
|
class="clusters-all"
|
|
@@ -611,6 +630,8 @@ export default {
|
|
|
611
630
|
</nuxt-link>
|
|
612
631
|
</div>
|
|
613
632
|
</template>
|
|
633
|
+
|
|
634
|
+
<!-- App menu -->
|
|
614
635
|
<template v-if="configurationApps.length">
|
|
615
636
|
<div
|
|
616
637
|
class="category-title"
|
|
@@ -640,6 +661,8 @@ export default {
|
|
|
640
661
|
</template>
|
|
641
662
|
</div>
|
|
642
663
|
</div>
|
|
664
|
+
|
|
665
|
+
<!-- Footer -->
|
|
643
666
|
<div
|
|
644
667
|
class="footer"
|
|
645
668
|
>
|
|
@@ -274,33 +274,36 @@ export default {
|
|
|
274
274
|
});
|
|
275
275
|
|
|
276
276
|
this.socket.addEventListener(EVENT_MESSAGE, (e) => {
|
|
277
|
-
const
|
|
277
|
+
const data = base64Decode(e.detail.data);
|
|
278
278
|
|
|
279
|
-
|
|
280
|
-
|
|
279
|
+
// Websocket message may contain multiple lines - loop through each line, one by one
|
|
280
|
+
data.split('\n').forEach((line) => {
|
|
281
|
+
let msg = line;
|
|
282
|
+
let time = null;
|
|
281
283
|
|
|
282
|
-
|
|
284
|
+
const idx = line.indexOf(' ');
|
|
283
285
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
286
|
+
if ( idx > 0 ) {
|
|
287
|
+
const timeStr = line.substr(0, idx);
|
|
288
|
+
const date = new Date(timeStr);
|
|
287
289
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
290
|
+
if ( !isNaN(date.getSeconds()) ) {
|
|
291
|
+
time = date.toISOString();
|
|
292
|
+
msg = line.substr(idx + 1);
|
|
293
|
+
}
|
|
291
294
|
}
|
|
292
|
-
}
|
|
293
295
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
296
|
+
const parsedLine = {
|
|
297
|
+
id: lastId++,
|
|
298
|
+
msg: ansiup.ansi_to_html(msg),
|
|
299
|
+
rawMsg: msg,
|
|
300
|
+
time,
|
|
301
|
+
};
|
|
300
302
|
|
|
301
|
-
|
|
303
|
+
Object.freeze(parsedLine);
|
|
302
304
|
|
|
303
|
-
|
|
305
|
+
this.backlog.push(parsedLine);
|
|
306
|
+
});
|
|
304
307
|
});
|
|
305
308
|
|
|
306
309
|
this.socket.connect();
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { mount, Wrapper } from '@vue/test-utils';
|
|
2
|
+
import TopLevelMenu from '@shell/components/nav/TopLevelMenu';
|
|
3
|
+
|
|
4
|
+
// DISCLAIMER: This should not be added here, although we have several store requests which are irrelevant
|
|
5
|
+
const defaultStore = {
|
|
6
|
+
'management/byId': jest.fn(),
|
|
7
|
+
'management/schemaFor': jest.fn(),
|
|
8
|
+
'i18n/t': jest.fn(),
|
|
9
|
+
'features/get': jest.fn(),
|
|
10
|
+
'prefs/theme': jest.fn(),
|
|
11
|
+
defaultClusterId: jest.fn(),
|
|
12
|
+
clusterId: jest.fn(),
|
|
13
|
+
'type-map/activeProducts': [],
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
describe('topLevelMenu', () => {
|
|
17
|
+
it('should display clusters', () => {
|
|
18
|
+
const wrapper: Wrapper<InstanceType<typeof TopLevelMenu>> = mount(TopLevelMenu, {
|
|
19
|
+
mocks: {
|
|
20
|
+
$store: {
|
|
21
|
+
getters: {
|
|
22
|
+
'management/all': () => [{ name: 'whatever' }],
|
|
23
|
+
...defaultStore
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
stubs: ['BrandImage', 'nuxt-link']
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const cluster = wrapper.find('[data-testid="top-level-menu-cluster-0"]');
|
|
31
|
+
|
|
32
|
+
expect(cluster.exists()).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('searching a term', () => {
|
|
36
|
+
describe('should displays a no results message if have clusters but', () => {
|
|
37
|
+
it('given no matching clusters', () => {
|
|
38
|
+
const wrapper: Wrapper<InstanceType<typeof TopLevelMenu>> = mount(TopLevelMenu, {
|
|
39
|
+
data: () => ({ clusterFilter: 'whatever' }),
|
|
40
|
+
mocks: {
|
|
41
|
+
$store: {
|
|
42
|
+
getters: {
|
|
43
|
+
'management/all': () => [{ nameDisplay: 'something else' }],
|
|
44
|
+
...defaultStore
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
stubs: ['BrandImage', 'nuxt-link']
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const noResults = wrapper.find('[data-testid="top-level-menu-no-results"]');
|
|
52
|
+
|
|
53
|
+
expect(noResults.exists()).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('given no matched pinned clusters', () => {
|
|
57
|
+
const wrapper: Wrapper<InstanceType<typeof TopLevelMenu>> = mount(TopLevelMenu, {
|
|
58
|
+
data: () => ({ clusterFilter: 'whatever' }),
|
|
59
|
+
mocks: {
|
|
60
|
+
$store: {
|
|
61
|
+
getters: {
|
|
62
|
+
'management/all': () => [{ nameDisplay: 'something else', pinned: true }],
|
|
63
|
+
...defaultStore
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
stubs: ['BrandImage', 'nuxt-link']
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const noResults = wrapper.find('[data-testid="top-level-menu-no-results"]');
|
|
71
|
+
|
|
72
|
+
expect(noResults.exists()).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('should not displays a no results message', () => {
|
|
77
|
+
it('given matching clusters', () => {
|
|
78
|
+
const search = 'you found me';
|
|
79
|
+
const wrapper: Wrapper<InstanceType<typeof TopLevelMenu>> = mount(TopLevelMenu, {
|
|
80
|
+
data: () => ({ clusterFilter: search }),
|
|
81
|
+
mocks: {
|
|
82
|
+
$store: {
|
|
83
|
+
getters: {
|
|
84
|
+
'management/all': () => [{ nameDisplay: search }],
|
|
85
|
+
...defaultStore
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
stubs: ['BrandImage', 'nuxt-link']
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const noResults = wrapper.find('[data-testid="top-level-menu-no-results"]');
|
|
93
|
+
|
|
94
|
+
expect(wrapper.vm.clustersFiltered).toHaveLength(1);
|
|
95
|
+
expect(noResults.exists()).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('given clusters with status pinned', () => {
|
|
99
|
+
const search = 'you found me';
|
|
100
|
+
const wrapper: Wrapper<InstanceType<typeof TopLevelMenu>> = mount(TopLevelMenu, {
|
|
101
|
+
data: () => ({ clusterFilter: search }),
|
|
102
|
+
mocks: {
|
|
103
|
+
$store: {
|
|
104
|
+
getters: {
|
|
105
|
+
'management/all': () => [{ nameDisplay: search, pinned: true }],
|
|
106
|
+
...defaultStore
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
stubs: ['BrandImage', 'nuxt-link']
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const noResults = wrapper.find('[data-testid="top-level-menu-no-results"]');
|
|
114
|
+
|
|
115
|
+
expect(wrapper.vm.pinFiltered).toHaveLength(1);
|
|
116
|
+
expect(noResults.exists()).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { mount, RouterLinkStub } from '@vue/test-utils';
|
|
2
|
+
import Type from '@shell/components/nav/Type.vue';
|
|
3
|
+
|
|
4
|
+
// Mandatory to mock vue-router in this test
|
|
5
|
+
jest.mock('vue-router');
|
|
6
|
+
|
|
7
|
+
// Configuration text
|
|
8
|
+
const className = 'nuxt-link-active';
|
|
9
|
+
|
|
10
|
+
describe('component: Type', () => {
|
|
11
|
+
describe('should not use highlight class', () => {
|
|
12
|
+
it('given no hash', () => {
|
|
13
|
+
const wrapper = mount(Type, {
|
|
14
|
+
propsData: { type: { route: 'something else' } },
|
|
15
|
+
stubs: { nLink: RouterLinkStub },
|
|
16
|
+
mocks: {
|
|
17
|
+
$route: { path: 'whatever' },
|
|
18
|
+
$router: { resolve: () => ({ route: { path: 'whatever' } }) },
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const highlight = wrapper.find(`.${ className }`);
|
|
23
|
+
|
|
24
|
+
expect(highlight.exists()).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('given no path', () => {
|
|
28
|
+
const wrapper = mount(Type, {
|
|
29
|
+
propsData: { type: { route: 'something else' } },
|
|
30
|
+
stubs: { nLink: RouterLinkStub },
|
|
31
|
+
mocks: {
|
|
32
|
+
$route: { hash: 'whatever' },
|
|
33
|
+
$router: { resolve: () => ({ route: { path: 'whatever' } }) },
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const highlight = wrapper.find(`.${ className }`);
|
|
38
|
+
|
|
39
|
+
expect(highlight.exists()).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('given no matching values', () => {
|
|
43
|
+
const wrapper = mount(Type, {
|
|
44
|
+
propsData: { type: {} },
|
|
45
|
+
stubs: { nLink: RouterLinkStub },
|
|
46
|
+
mocks: {
|
|
47
|
+
$route: {
|
|
48
|
+
hash: 'hash',
|
|
49
|
+
path: 'path',
|
|
50
|
+
},
|
|
51
|
+
$router: { resolve: () => ({ route: { path: 'whatever' } }) },
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const highlight = wrapper.find(`.${ className }`);
|
|
56
|
+
|
|
57
|
+
expect(highlight.exists()).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('given navigation path is bigger than current page route path', () => {
|
|
61
|
+
const wrapper = mount(Type, {
|
|
62
|
+
propsData: { type: { route: 'not empty' } },
|
|
63
|
+
stubs: { nLink: RouterLinkStub },
|
|
64
|
+
mocks: {
|
|
65
|
+
$route: {
|
|
66
|
+
hash: 'not empty',
|
|
67
|
+
path: 'whatever',
|
|
68
|
+
},
|
|
69
|
+
$router: { resolve: () => ({ route: { path: 'many/parts' } }) },
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const highlight = wrapper.find(`.${ className }`);
|
|
74
|
+
|
|
75
|
+
expect(highlight.exists()).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it.each([
|
|
79
|
+
// URL with fragments like anchors
|
|
80
|
+
[
|
|
81
|
+
'/c/c-m-hzqf4tqt/explorer/members#project-membership',
|
|
82
|
+
'/c/c-m-hzqf4tqt/explorer/members'
|
|
83
|
+
],
|
|
84
|
+
// Similar paths
|
|
85
|
+
[
|
|
86
|
+
'/c/c-m-hzqf4tqt/fleet/fleet.cattle.io.bundlenamespacemapping',
|
|
87
|
+
'/c/c-m-hzqf4tqt/fleet/fleet.cattle.io.bundle'
|
|
88
|
+
],
|
|
89
|
+
// paths with same parts, e.g. parents
|
|
90
|
+
[
|
|
91
|
+
'/c/c-m-hzqf4tqt/fleet',
|
|
92
|
+
'/c/c-m-hzqf4tqt/fleet/management.cattle.io.fleetworkspace'
|
|
93
|
+
],
|
|
94
|
+
])('given different current path %p and menu path %p', (currentPath, menuPath) => {
|
|
95
|
+
const wrapper = mount(Type, {
|
|
96
|
+
propsData: { type: { route: 'not empty' } },
|
|
97
|
+
stubs: { nLink: RouterLinkStub },
|
|
98
|
+
mocks: {
|
|
99
|
+
$route: {
|
|
100
|
+
hash: 'not empty',
|
|
101
|
+
path: currentPath,
|
|
102
|
+
},
|
|
103
|
+
$router: { resolve: () => ({ route: { path: menuPath } }) },
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const highlight = wrapper.find(`.${ className }`);
|
|
108
|
+
|
|
109
|
+
expect(highlight.exists()).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('should use highlight class', () => {
|
|
114
|
+
it.each([
|
|
115
|
+
[
|
|
116
|
+
'same',
|
|
117
|
+
'same'
|
|
118
|
+
],
|
|
119
|
+
])('given same current path %p and menu path %p (on first load)', (currentPath, menuPath) => {
|
|
120
|
+
const wrapper = mount(Type, {
|
|
121
|
+
propsData: { type: { route: 'not empty' } },
|
|
122
|
+
stubs: { nLink: RouterLinkStub },
|
|
123
|
+
mocks: {
|
|
124
|
+
$route: {
|
|
125
|
+
hash: 'not empty',
|
|
126
|
+
path: currentPath,
|
|
127
|
+
},
|
|
128
|
+
$router: { resolve: () => ({ route: { path: menuPath } }) },
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const highlight = wrapper.find(`.${ className }`);
|
|
133
|
+
|
|
134
|
+
expect(highlight.exists()).toBe(true);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
jest.restoreAllMocks();
|
package/config/private-label.js
CHANGED
|
@@ -3,7 +3,7 @@ import { SETTING } from './settings';
|
|
|
3
3
|
export const ANY = 0;
|
|
4
4
|
export const STANDARD = 1;
|
|
5
5
|
export const CUSTOM = 2;
|
|
6
|
-
export const DOCS_BASE = 'https://rancher.com/
|
|
6
|
+
export const DOCS_BASE = 'https://ranchermanager.docs.rancher.com/v2.8';
|
|
7
7
|
|
|
8
8
|
const STANDARD_VENDOR = 'Rancher';
|
|
9
9
|
const STANDARD_PRODUCT = 'Explorer';
|
|
@@ -54,22 +54,10 @@ export function init(store) {
|
|
|
54
54
|
route: { name: 'c-cluster-manager-cloudCredential' },
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
-
virtualType({
|
|
58
|
-
labelKey: 'legacy.psps',
|
|
59
|
-
name: 'pod-security-policies',
|
|
60
|
-
group: 'Root',
|
|
61
|
-
namespaced: false,
|
|
62
|
-
weight: 5,
|
|
63
|
-
icon: 'folder',
|
|
64
|
-
route: { name: 'c-cluster-manager-pages-page', params: { cluster: 'local', page: 'pod-security-policies' } },
|
|
65
|
-
exact: true
|
|
66
|
-
});
|
|
67
|
-
|
|
68
57
|
basicType([
|
|
69
58
|
CAPI.RANCHER_CLUSTER,
|
|
70
59
|
'cloud-credentials',
|
|
71
60
|
'drivers',
|
|
72
|
-
'pod-security-policies',
|
|
73
61
|
]);
|
|
74
62
|
|
|
75
63
|
configureType(CAPI.RANCHER_CLUSTER, {
|
|
@@ -131,7 +119,6 @@ export function init(store) {
|
|
|
131
119
|
CAPI.MACHINE_SET,
|
|
132
120
|
CAPI.MACHINE,
|
|
133
121
|
CATALOG.CLUSTER_REPO,
|
|
134
|
-
'pod-security-policies',
|
|
135
122
|
MANAGEMENT.PSA
|
|
136
123
|
], 'advanced');
|
|
137
124
|
|
package/config/settings.ts
CHANGED
|
@@ -42,7 +42,6 @@ export const SETTING = {
|
|
|
42
42
|
HIDE_LOCAL_CLUSTER: 'hide-local-cluster',
|
|
43
43
|
AUTH_TOKEN_MAX_TTL_MINUTES: 'auth-token-max-ttl-minutes',
|
|
44
44
|
KUBECONFIG_GENERATE_TOKEN: 'kubeconfig-generate-token',
|
|
45
|
-
KUBECONFIG_TOKEN_TTL_MINUTES: 'kubeconfig-token-ttl-minutes',
|
|
46
45
|
KUBECONFIG_DEFAULT_TOKEN_TTL_MINUTES: 'kubeconfig-default-token-ttl-minutes',
|
|
47
46
|
ENGINE_URL: 'engine-install-url',
|
|
48
47
|
ENGINE_ISO_URL: 'engine-iso-url',
|
|
@@ -127,7 +126,6 @@ export const ALLOWED_SETTINGS: GlobalSetting = {
|
|
|
127
126
|
[SETTING.AUTH_USER_SESSION_TTL_MINUTES]: {},
|
|
128
127
|
[SETTING.AUTH_TOKEN_MAX_TTL_MINUTES]: {},
|
|
129
128
|
[SETTING.KUBECONFIG_GENERATE_TOKEN]: { kind: 'boolean' },
|
|
130
|
-
[SETTING.KUBECONFIG_TOKEN_TTL_MINUTES]: {},
|
|
131
129
|
[SETTING.KUBECONFIG_DEFAULT_TOKEN_TTL_MINUTES]: { kind: 'integer' },
|
|
132
130
|
[SETTING.AUTH_USER_INFO_RESYNC_CRON]: {},
|
|
133
131
|
[SETTING.SERVER_URL]: { kind: 'url', canReset: true },
|