@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.
Files changed (99) hide show
  1. package/assets/translations/en-us.yaml +8 -23
  2. package/assets/translations/zh-hans.yaml +2 -26
  3. package/chart/gatekeeper.vue +2 -11
  4. package/chart/istio.vue +1 -10
  5. package/chart/logging/index.vue +2 -11
  6. package/chart/monitoring/index.vue +1 -9
  7. package/chart/rancher-backup/index.vue +1 -9
  8. package/components/AlertTable.vue +8 -6
  9. package/components/Carousel.vue +2 -1
  10. package/components/EmberPage.vue +2 -2
  11. package/components/EtcdInfoBanner.vue +12 -2
  12. package/components/GlobalRoleBindings.vue +10 -0
  13. package/components/GrafanaDashboard.vue +8 -3
  14. package/components/Wizard.vue +17 -1
  15. package/components/auth/RoleDetailEdit.vue +17 -1
  16. package/components/form/ArrayList.vue +20 -11
  17. package/components/form/__tests__/ArrayList.test.ts +44 -0
  18. package/components/formatter/ClusterProvider.vue +1 -18
  19. package/components/nav/Header.vue +5 -4
  20. package/components/nav/TopLevelMenu.vue +38 -15
  21. package/components/nav/WindowManager/ContainerLogs.vue +22 -19
  22. package/components/nav/__tests__/TopLevelMenu.test.ts +120 -0
  23. package/components/nav/__tests__/Type.test.ts +139 -0
  24. package/config/private-label.js +1 -1
  25. package/config/product/manager.js +0 -13
  26. package/config/settings.ts +0 -2
  27. package/config/types.js +0 -4
  28. package/core/types.ts +11 -4
  29. package/edit/management.cattle.io.project.vue +1 -52
  30. package/edit/management.cattle.io.setting.vue +31 -2
  31. package/edit/provisioning.cattle.io.cluster/Basics.vue +19 -107
  32. package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +1 -1
  33. package/edit/provisioning.cattle.io.cluster/__tests__/Basics.tests.ts +0 -3
  34. package/edit/provisioning.cattle.io.cluster/rke2.vue +3 -128
  35. package/edit/workload/mixins/workload.js +14 -4
  36. package/middleware/authenticated.js +4 -2
  37. package/models/__tests__/management.cattle.io.cluster.test.ts +19 -0
  38. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +90 -0
  39. package/models/cluster.x-k8s.io.machine.js +1 -1
  40. package/models/fleet.cattle.io.cluster.js +11 -1
  41. package/models/management.cattle.io.cluster.js +4 -0
  42. package/models/management.cattle.io.project.js +0 -36
  43. package/models/management.cattle.io.setting.js +11 -7
  44. package/models/provisioning.cattle.io.cluster.js +16 -4
  45. package/package.json +1 -1
  46. package/pages/auth/setup.vue +38 -1
  47. package/pages/c/_cluster/apps/charts/__tests__/install.helper.test.ts +2 -17
  48. package/pages/c/_cluster/apps/charts/index.vue +0 -15
  49. package/pages/c/_cluster/apps/charts/install.helpers.js +2 -13
  50. package/pages/c/_cluster/apps/charts/install.vue +1 -1
  51. package/pages/c/_cluster/auth/roles/index.vue +11 -1
  52. package/pages/c/_cluster/explorer/index.vue +7 -49
  53. package/pages/c/_cluster/manager/pages/_page.vue +4 -5
  54. package/pages/c/_cluster/monitoring/index.vue +26 -39
  55. package/pages/support/index.vue +1 -8
  56. package/promptRemove/management.cattle.io.project.vue +6 -9
  57. package/rancher-components/BadgeState/BadgeState.vue +1 -5
  58. package/rancher-components/Banner/Banner.test.ts +1 -51
  59. package/rancher-components/Banner/Banner.vue +53 -134
  60. package/rancher-components/Card/Card.vue +7 -24
  61. package/rancher-components/Form/Checkbox/Checkbox.test.ts +29 -20
  62. package/rancher-components/Form/Checkbox/Checkbox.vue +20 -45
  63. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +8 -2
  64. package/rancher-components/Form/LabeledInput/LabeledInput.vue +10 -22
  65. package/rancher-components/Form/Radio/RadioButton.vue +13 -30
  66. package/rancher-components/Form/Radio/RadioGroup.vue +7 -26
  67. package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +6 -7
  68. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.test.ts +38 -25
  69. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +11 -23
  70. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +5 -19
  71. package/rancher-components/StringList/StringList.test.ts +49 -453
  72. package/rancher-components/StringList/StringList.vue +58 -92
  73. package/rancher-components/components/Form/Radio/RadioGroup.test.ts +30 -0
  74. package/rancher-components/components/Form/Radio/RadioGroup.vue +4 -0
  75. package/rancher-components/components/StringList/StringList.test.ts +270 -0
  76. package/rancher-components/components/StringList/StringList.vue +57 -18
  77. package/store/features.js +1 -0
  78. package/store/prefs.js +0 -3
  79. package/types/shell/index.d.ts +26 -17
  80. package/utils/__tests__/object.test.ts +67 -1
  81. package/utils/__tests__/version.test.ts +13 -23
  82. package/utils/cluster.js +1 -1
  83. package/utils/custom-validators.js +0 -2
  84. package/utils/error.js +16 -1
  85. package/utils/grafana.js +1 -2
  86. package/utils/monitoring.js +25 -1
  87. package/utils/object.js +4 -3
  88. package/utils/sort.js +1 -1
  89. package/utils/validators/formRules/__tests__/index.test.ts +49 -4
  90. package/utils/validators/formRules/index.ts +13 -10
  91. package/utils/validators/role-template.js +1 -1
  92. package/utils/validators/setting.js +6 -10
  93. package/utils/version.js +0 -13
  94. package/components/ChartPsp.vue +0 -76
  95. package/components/__tests__/ChartPsp.test.ts +0 -75
  96. package/components/formatter/__tests__/ClusterProvider.test.ts +0 -28
  97. package/rancher-components/Card/Card.test.ts +0 -37
  98. package/rancher-components/Form/Radio/RadioButton.test.ts +0 -31
  99. 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
- // There's margin already in the icon component, so take that in to account ($side-menu-logo-margin-left)
729
- margin-left: $side-menu-group-padding-left - $side-menu-logo-margin-left;
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: $side-menu-logo-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.find((s) => s.id === SETTING.BANNERS);
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.map((x) => {
108
- const pCluster = pClusters?.find((c) => c.mgmt.id === x.id);
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.map((p) => {
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="{'marginBottom':
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 line = base64Decode(e.detail.data);
277
+ const data = base64Decode(e.detail.data);
278
278
 
279
- let msg = line;
280
- let time = null;
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
- const idx = line.indexOf(' ');
284
+ const idx = line.indexOf(' ');
283
285
 
284
- if ( idx > 0 ) {
285
- const timeStr = line.substr(0, idx);
286
- const date = new Date(timeStr);
286
+ if ( idx > 0 ) {
287
+ const timeStr = line.substr(0, idx);
288
+ const date = new Date(timeStr);
287
289
 
288
- if ( !isNaN(date.getSeconds()) ) {
289
- time = date.toISOString();
290
- msg = line.substr(idx + 1);
290
+ if ( !isNaN(date.getSeconds()) ) {
291
+ time = date.toISOString();
292
+ msg = line.substr(idx + 1);
293
+ }
291
294
  }
292
- }
293
295
 
294
- const parsedLine = {
295
- id: lastId++,
296
- msg: ansiup.ansi_to_html(msg),
297
- rawMsg: msg,
298
- time,
299
- };
296
+ const parsedLine = {
297
+ id: lastId++,
298
+ msg: ansiup.ansi_to_html(msg),
299
+ rawMsg: msg,
300
+ time,
301
+ };
300
302
 
301
- Object.freeze(parsedLine);
303
+ Object.freeze(parsedLine);
302
304
 
303
- this.backlog.push(parsedLine);
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();
@@ -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/docs/rancher/v2.7/en';
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
 
@@ -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 },