@rancher/shell 0.3.23 → 0.3.25

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 (149) hide show
  1. package/assets/styles/base/_variables.scss +1 -0
  2. package/assets/styles/themes/_dark.scss +1 -0
  3. package/assets/styles/themes/_light.scss +6 -5
  4. package/assets/translations/en-us.yaml +44 -17
  5. package/assets/translations/zh-hans.yaml +2 -2
  6. package/components/ClusterIconMenu.vue +143 -0
  7. package/components/CruResource.vue +7 -1
  8. package/components/ExplorerProjectsNamespaces.vue +11 -1
  9. package/components/FixedBanner.vue +17 -1
  10. package/components/Loading.vue +1 -1
  11. package/components/Markdown.vue +1 -1
  12. package/components/Questions/__tests__/Yaml.test.ts +3 -2
  13. package/components/SideNav.vue +1 -1
  14. package/components/SortableTable/index.vue +3 -2
  15. package/components/auth/RoleDetailEdit.vue +15 -2
  16. package/components/auth/login/saml.vue +12 -1
  17. package/components/form/LabeledSelect.vue +12 -5
  18. package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
  19. package/components/form/Members/MembershipEditor.vue +6 -1
  20. package/components/form/SelectOrCreateAuthSecret.vue +7 -0
  21. package/components/form/__tests__/KeyValue.test.ts +6 -3
  22. package/components/form/__tests__/LabeledSelect.test.ts +18 -0
  23. package/components/formatter/PodsUsage.vue +11 -36
  24. package/components/formatter/PrincipalGroupBindings.vue +8 -5
  25. package/components/formatter/__tests__/PodsUsage.test.ts +36 -19
  26. package/components/nav/Group.vue +62 -34
  27. package/components/nav/Header.vue +13 -6
  28. package/components/nav/Pinned.vue +47 -0
  29. package/components/nav/TopLevelMenu.vue +673 -325
  30. package/components/nav/Type.vue +88 -8
  31. package/config/home-links.js +1 -1
  32. package/config/product/istio.js +15 -5
  33. package/config/router.js +3 -9
  34. package/config/table-headers.js +5 -6
  35. package/config/uiplugins.js +1 -0
  36. package/core/plugin-helpers.js +3 -0
  37. package/core/types.ts +6 -1
  38. package/creators/app/files/.vscode/settings.json +0 -1
  39. package/creators/pkg/init +2 -2
  40. package/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts +118 -0
  41. package/detail/autoscaling.horizontalpodautoscaler/index.vue +4 -4
  42. package/detail/provisioning.cattle.io.cluster.vue +7 -5
  43. package/edit/__tests__/management.cattle.io.clusterroletemplatebinding.test.ts +58 -0
  44. package/edit/__tests__/namespace.test.ts +5 -3
  45. package/edit/fleet.cattle.io.gitrepo.vue +43 -15
  46. package/edit/logging.banzaicloud.io.output/index.vue +7 -0
  47. package/edit/management.cattle.io.clusterroletemplatebinding.vue +3 -11
  48. package/edit/namespace.vue +8 -4
  49. package/edit/provisioning.cattle.io.cluster/Basics.vue +662 -0
  50. package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +9 -8
  51. package/edit/provisioning.cattle.io.cluster/DrainOptions.vue +13 -8
  52. package/edit/provisioning.cattle.io.cluster/MachinePool.vue +11 -2
  53. package/edit/provisioning.cattle.io.cluster/MemberRoles.vue +40 -0
  54. package/edit/provisioning.cattle.io.cluster/__tests__/Basics.tests.ts +237 -0
  55. package/edit/provisioning.cattle.io.cluster/__tests__/CustomCommand.tests.ts +71 -23
  56. package/edit/provisioning.cattle.io.cluster/__tests__/DrainOptions.test.ts +52 -0
  57. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +65 -142
  58. package/edit/provisioning.cattle.io.cluster/rke2.vue +253 -582
  59. package/edit/workload/storage/ContainerMountPaths.vue +7 -5
  60. package/edit/workload/storage/__tests__/Storage.test.ts +2 -2
  61. package/edit/workload/storage/persistentVolumeClaim/__tests__/persistentvolumeclaim.test.ts +36 -0
  62. package/edit/workload/storage/persistentVolumeClaim/persistentvolumeclaim.vue +15 -7
  63. package/initialize/App.js +2 -0
  64. package/initialize/client.js +63 -51
  65. package/initialize/index.js +7 -5
  66. package/layouts/default.vue +10 -2
  67. package/layouts/home.vue +6 -2
  68. package/layouts/plain.vue +9 -2
  69. package/list/fleet.cattle.io.cluster.vue +2 -2
  70. package/list/management.cattle.io.feature.vue +1 -1
  71. package/machine-config/amazonec2.vue +1 -0
  72. package/machine-config/vmwarevsphere.vue +48 -7
  73. package/mixins/brand.js +0 -8
  74. package/mixins/child-hook.js +2 -2
  75. package/mixins/create-edit-view/impl.js +3 -3
  76. package/mixins/fetch.client.js +3 -3
  77. package/models/__tests__/management.cattle.io.node.ts +96 -0
  78. package/models/__tests__/node.ts +74 -0
  79. package/models/cluster/node.js +6 -5
  80. package/models/cluster.x-k8s.io.machinedeployment.js +2 -2
  81. package/models/management.cattle.io.cluster.js +22 -1
  82. package/models/management.cattle.io.clusterroletemplatebinding.js +3 -3
  83. package/models/management.cattle.io.globalrole.js +17 -2
  84. package/models/management.cattle.io.node.js +6 -4
  85. package/models/management.cattle.io.projectroletemplatebinding.js +3 -3
  86. package/models/management.cattle.io.roletemplate.js +17 -2
  87. package/package.json +2 -6
  88. package/pages/__tests__/prefs.test.ts +1 -1
  89. package/pages/about.vue +2 -0
  90. package/pages/auth/setup.vue +5 -4
  91. package/pages/c/_cluster/explorer/ConfigBadge.vue +1 -0
  92. package/pages/c/_cluster/monitoring/index.vue +8 -3
  93. package/pages/c/_cluster/uiplugins/CatalogList/CatalogLoadDialog.vue +9 -66
  94. package/pages/c/_cluster/uiplugins/CatalogList/CatalogUninstallDialog.vue +182 -0
  95. package/pages/c/_cluster/uiplugins/CatalogList/index.vue +15 -32
  96. package/pages/c/_cluster/uiplugins/UninstallDialog.vue +8 -46
  97. package/pages/c/_cluster/uiplugins/index.vue +64 -64
  98. package/pages/diagnostic.vue +0 -39
  99. package/pages/home.vue +1 -1
  100. package/pages/prefs.vue +3 -13
  101. package/plugins/dashboard-store/normalize.js +4 -4
  102. package/plugins/dashboard-store/resource-class.js +1 -1
  103. package/plugins/int-number.js +5 -2
  104. package/plugins/positive-int-number.js +19 -0
  105. package/plugins/steve/__tests__/getters.spec.ts +15 -0
  106. package/plugins/steve/getters.js +22 -10
  107. package/public/index.html +4 -2
  108. package/rancher-components/BadgeState/BadgeState.vue +5 -1
  109. package/rancher-components/Banner/Banner.test.ts +51 -1
  110. package/rancher-components/Banner/Banner.vue +134 -53
  111. package/rancher-components/Card/Card.test.ts +37 -0
  112. package/rancher-components/Card/Card.vue +24 -7
  113. package/rancher-components/Form/Checkbox/Checkbox.test.ts +20 -29
  114. package/rancher-components/Form/Checkbox/Checkbox.vue +45 -20
  115. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +2 -8
  116. package/rancher-components/Form/LabeledInput/LabeledInput.vue +22 -10
  117. package/rancher-components/Form/Radio/RadioButton.test.ts +31 -0
  118. package/rancher-components/Form/Radio/RadioButton.vue +30 -13
  119. package/rancher-components/Form/Radio/RadioGroup.vue +26 -7
  120. package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +7 -6
  121. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.test.ts +25 -38
  122. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +23 -11
  123. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +19 -5
  124. package/rancher-components/StringList/StringList.test.ts +453 -49
  125. package/rancher-components/StringList/StringList.vue +92 -58
  126. package/scripts/extension/parse-tag-name +0 -0
  127. package/store/index.js +4 -0
  128. package/store/prefs.js +4 -4
  129. package/store/type-map.js +2 -16
  130. package/types/shell/index.d.ts +26 -14
  131. package/utils/__tests__/cluster.test.ts +55 -0
  132. package/utils/__tests__/object.test.ts +21 -2
  133. package/utils/__tests__/sort.test.ts +61 -0
  134. package/utils/cluster.js +47 -1
  135. package/utils/object.js +12 -5
  136. package/utils/string.js +12 -0
  137. package/utils/validators/formRules/__tests__/index.test.ts +13 -1
  138. package/utils/validators/formRules/index.ts +4 -0
  139. package/utils/validators/role-template.js +9 -1
  140. package/utils/version.js +1 -1
  141. package/vue.config.js +1 -4
  142. package/yarn-error.log +200 -0
  143. package/content/docs/en-us/getting-started.md +0 -224
  144. package/content/docs/en-us/whats-new.md +0 -29
  145. package/content/docs/zh-hans/getting-started.md +0 -224
  146. package/content/docs/zh-hans/whats-new.md +0 -28
  147. package/pages/docs/_doc.vue +0 -345
  148. package/pages/docs/toc.js +0 -27
  149. package/plugins/console.js +0 -34
@@ -1,10 +1,11 @@
1
1
  <script>
2
2
  import BrandImage from '@shell/components/BrandImage';
3
- import ClusterProviderIcon from '@shell/components/ClusterProviderIcon';
3
+ import ClusterIconMenu from '@shell/components/ClusterIconMenu';
4
4
  import IconOrSvg from '../IconOrSvg';
5
+ import { BLANK_CLUSTER } from '@shell/store/store-types.js';
5
6
  import { mapGetters } from 'vuex';
6
7
  import { CAPI, MANAGEMENT } from '@shell/config/types';
7
- import { mapPref, MENU_MAX_CLUSTERS } from '@shell/store/prefs';
8
+ import { MENU_MAX_CLUSTERS } from '@shell/store/prefs';
8
9
  import { sortBy } from '@shell/utils/sort';
9
10
  import { ucFirst } from '@shell/utils/string';
10
11
  import { KEY } from '@shell/utils/platform';
@@ -13,13 +14,14 @@ import { LEGACY } from '@shell/store/features';
13
14
  import { SETTING } from '@shell/config/settings';
14
15
  import { filterOnlyKubernetesClusters, filterHiddenLocalCluster } from '@shell/utils/cluster';
15
16
  import { isRancherPrime } from '@shell/config/version';
17
+ import Pinned from '@shell/components/nav/Pinned';
16
18
 
17
19
  export default {
18
-
19
20
  components: {
20
21
  BrandImage,
21
- ClusterProviderIcon,
22
- IconOrSvg
22
+ ClusterIconMenu,
23
+ IconOrSvg,
24
+ Pinned
23
25
  },
24
26
 
25
27
  data() {
@@ -27,11 +29,15 @@ export default {
27
29
  const hasProvCluster = this.$store.getters[`management/schemaFor`](CAPI.RANCHER_CLUSTER);
28
30
 
29
31
  return {
30
- shown: false,
32
+ shown: false,
31
33
  displayVersion,
32
34
  fullVersion,
33
- clusterFilter: '',
35
+ clusterFilter: '',
34
36
  hasProvCluster,
37
+ maxClustersToShow: MENU_MAX_CLUSTERS,
38
+ emptyCluster: BLANK_CLUSTER,
39
+ showPinClusters: false,
40
+ searchActive: false,
35
41
  };
36
42
  },
37
43
 
@@ -53,6 +59,24 @@ export default {
53
59
  },
54
60
  },
55
61
 
62
+ globalBannerSettings() {
63
+ const settings = this.$store.getters['management/all'](MANAGEMENT.SETTING);
64
+ const bannerSettings = settings.find((s) => s.id === SETTING.BANNERS);
65
+
66
+ if (bannerSettings) {
67
+ const parsed = JSON.parse(bannerSettings.value);
68
+ const {
69
+ showFooter, showHeader, bannerFooter, bannerHeader
70
+ } = parsed;
71
+
72
+ return {
73
+ headerFont: showHeader === 'true' ? this.pxToEm(bannerHeader.fontSize) : '0px',
74
+ footerFont: showFooter === 'true' ? this.pxToEm(bannerFooter.fontSize) : '0px'
75
+ };
76
+ }
77
+
78
+ return undefined;
79
+ },
56
80
  legacyEnabled() {
57
81
  return this.features(LEGACY);
58
82
  },
@@ -74,9 +98,9 @@ export default {
74
98
  return p;
75
99
  }, {});
76
100
 
77
- // Filter to only show mgmt clusters that exist for the available provisionning clusters
101
+ // Filter to only show mgmt clusters that exist for the available provisioning clusters
78
102
  // Addresses issue where a mgmt cluster can take some time to get cleaned up after the corresponding
79
- // provisionning cluster has been deleted
103
+ // provisioning cluster has been deleted
80
104
  kubeClusters = kubeClusters.filter((c) => !!available[c]);
81
105
  }
82
106
 
@@ -91,7 +115,10 @@ export default {
91
115
  providerNavLogo: x.providerMenuLogo,
92
116
  badge: x.badge,
93
117
  isLocal: x.isLocal,
94
- isHarvester: x.isHarvester
118
+ isHarvester: x.isHarvester,
119
+ pinned: x.pinned,
120
+ pin: () => x.pin(),
121
+ unpin: () => x.unpin()
95
122
  };
96
123
  });
97
124
  },
@@ -101,12 +128,43 @@ export default {
101
128
 
102
129
  const out = search ? this.clusters.filter((item) => item.label.toLowerCase().includes(search)) : this.clusters;
103
130
 
104
- const sorted = sortBy(out, ['name:desc', 'label']);
131
+ const sorted = sortBy(out, ['ready:desc', 'label']);
132
+
133
+ if (search) {
134
+ this.showPinClusters = false;
135
+ this.searchActive = !sorted.length > 0;
136
+
137
+ return sorted;
138
+ }
139
+ this.showPinClusters = true;
140
+ this.searchActive = false;
141
+
142
+ if (sorted.length >= this.maxClustersToShow) {
143
+ const sortedPinOut = sorted.filter((item) => !item.pinned).slice(0, this.maxClustersToShow);
144
+
145
+ return sortedPinOut;
146
+ } else {
147
+ return sorted.filter((item) => !item.pinned);
148
+ }
149
+ },
150
+
151
+ pinFiltered() {
152
+ const out = this.clusters.filter((item) => item.pinned);
153
+ const sorted = sortBy(out, ['ready:desc', 'label']);
105
154
 
106
155
  return sorted;
107
156
  },
108
157
 
109
- maxClustersToShow: mapPref(MENU_MAX_CLUSTERS),
158
+ pinnedClustersHeight() {
159
+ const pinCount = this.clusters.filter((item) => item.pinned).length;
160
+ const height = pinCount > 2 ? (pinCount * 43) : 90;
161
+
162
+ return `min-height: ${ height }px`;
163
+ },
164
+
165
+ clusterFilterCount() {
166
+ return this.clusterFilter ? this.clustersFiltered.length : this.clusters.length;
167
+ },
110
168
 
111
169
  multiClusterApps() {
112
170
  const options = this.options;
@@ -198,18 +256,19 @@ export default {
198
256
  },
199
257
 
200
258
  methods: {
201
- // Cluster list number of items shown is configurable via user preference
202
- setClusterListHeight(maxToShow) {
203
- const el = this.$refs.clusterList;
204
- const max = Math.min(maxToShow, this.clusters.length);
205
-
206
- if (el) {
207
- const h = 33 * max;
208
-
209
- el.style.minHeight = `${ h }px`;
210
- el.style.maxHeight = `${ h }px`;
211
- }
259
+ /**
260
+ * Converts a pixel value to an em value based on the default font size.
261
+ * @param {number} elementFontSize - The font size of the element in pixels.
262
+ * @param {number} [defaultFontSize=14] - The default font size in pixels.
263
+ * @returns {string} The converted value in em units.
264
+ */
265
+ pxToEm(elementFontSize, defaultFontSize = 14) {
266
+ const lineHeightInPx = 2 * parseInt(elementFontSize);
267
+ const lineHeightInEm = lineHeightInPx / defaultFontSize;
268
+
269
+ return `${ lineHeightInEm }em`;
212
270
  },
271
+
213
272
  handler(e) {
214
273
  if (e.keyCode === KEY.ESCAPE ) {
215
274
  this.hide();
@@ -218,13 +277,13 @@ export default {
218
277
 
219
278
  hide() {
220
279
  this.shown = false;
280
+ if (this.clustersFiltered === 0) {
281
+ this.clusterFilter = '';
282
+ }
221
283
  },
222
284
 
223
285
  toggle() {
224
286
  this.shown = !this.shown;
225
- this.$nextTick(() => {
226
- this.setClusterListHeight(this.maxClustersToShow);
227
- });
228
287
  },
229
288
 
230
289
  async goToHarvesterCluster() {
@@ -234,29 +293,27 @@ export default {
234
293
  await localCluster.goToHarvesterCluster();
235
294
  } catch {
236
295
  }
237
- }
296
+ },
297
+ getTooltipConfig(item) {
298
+ if (!this.shown && !item) {
299
+ return;
300
+ }
301
+
302
+ if (!this.shown) {
303
+ return {
304
+ content: this.shown ? null : item,
305
+ placement: 'right',
306
+ popperOptions: { modifiers: { preventOverflow: { enabled: false }, hide: { enabled: false } } }
307
+ };
308
+ } else {
309
+ return { content: undefined };
310
+ }
311
+ },
238
312
  }
239
313
  };
240
314
  </script>
241
315
  <template>
242
316
  <div>
243
- <div
244
- data-testid="top-level-menu"
245
- class="menu"
246
- :class="{'raised': shown, 'unraised':!shown}"
247
- @click="toggle()"
248
- >
249
- <svg
250
- class="menu-icon"
251
- xmlns="http://www.w3.org/2000/svg"
252
- height="24"
253
- viewBox="0 0 24 24"
254
- width="24"
255
- ><path
256
- d="M0 0h24v24H0z"
257
- fill="none"
258
- /><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z" /></svg>
259
- </div>
260
317
  <div
261
318
  v-if="shown"
262
319
  class="side-menu-glass"
@@ -264,24 +321,44 @@ export default {
264
321
  />
265
322
  <transition name="fade">
266
323
  <div
267
- v-if="shown"
268
324
  data-testid="side-menu"
269
325
  class="side-menu"
326
+ :class="{'menu-open': shown, 'menu-close':!shown}"
327
+ :style="{'marginBottom':
328
+ globalBannerSettings?.footerFont,
329
+ 'marginTop':
330
+ globalBannerSettings?.headerFont}"
270
331
  tabindex="-1"
271
332
  >
272
333
  <div class="title">
273
- <div class="menu-spacer" />
334
+ <div
335
+ data-testid="top-level-menu"
336
+ class="menu"
337
+ @click="toggle()"
338
+ >
339
+ <svg
340
+ class="menu-icon"
341
+ xmlns="http://www.w3.org/2000/svg"
342
+ height="24"
343
+ viewBox="0 0 24 24"
344
+ width="24"
345
+ ><path
346
+ d="M0 0h24v24H0z"
347
+ fill="none"
348
+ /><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z" /></svg>
349
+ </div>
274
350
  <div class="side-menu-logo">
275
351
  <BrandImage file-name="rancher-logo.svg" />
276
352
  </div>
277
353
  </div>
278
354
  <div class="body">
279
- <div @click="hide()">
355
+ <div>
280
356
  <nuxt-link
281
357
  class="option cluster selector home"
282
358
  :to="{ name: 'home' }"
283
359
  >
284
360
  <svg
361
+ v-tooltip="getTooltipConfig(t('nav.home'))"
285
362
  xmlns="http://www.w3.org/2000/svg"
286
363
  height="24"
287
364
  viewBox="0 0 24 24"
@@ -290,16 +367,41 @@ export default {
290
367
  d="M0 0h24v24H0z"
291
368
  fill="none"
292
369
  /><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" /></svg>
293
- <div>
370
+ <div class="home-text">
294
371
  {{ t('nav.home') }}
295
372
  </div>
296
373
  </nuxt-link>
297
- </div>
374
+ <div
375
+ v-if="showClusterSearch"
376
+ class="clusters-search"
377
+ >
378
+ <div class="clusters-search-count">
379
+ <span>{{ clusterFilterCount }}</span>
380
+ {{ t('nav.search.clusters') }}
381
+ <i
382
+ v-if="clusterFilter"
383
+ class="icon icon-filter_alt"
384
+ />
385
+ </div>
298
386
 
299
- <template v-if="hciApps.length">
300
- <div class="category">
301
- {{ t('nav.categories.hci') }}
387
+ <div
388
+ class="search"
389
+ >
390
+ <input
391
+ ref="clusterFilter"
392
+ v-model="clusterFilter"
393
+ :placeholder="t('nav.search.placeholder')"
394
+ >
395
+ <i
396
+ v-if="clusterFilter"
397
+ class="icon icon-close"
398
+ @click="clusterFilter=''"
399
+ />
400
+ </div>
302
401
  </div>
402
+ </div>
403
+ <template v-if="hciApps.length">
404
+ <div class="category" />
303
405
  <div>
304
406
  <a
305
407
  v-if="isRancherInHarvester"
@@ -334,147 +436,230 @@ export default {
334
436
  </template>
335
437
 
336
438
  <template v-if="clusters && !!clusters.length">
337
- <div class="category">
338
- {{ t('nav.categories.explore') }}
339
- </div>
340
-
341
- <div
342
- v-if="showClusterSearch"
343
- class="search"
344
- >
345
- <input
346
- ref="clusterFilter"
347
- v-model="clusterFilter"
348
- :placeholder="t('nav.search.placeholder')"
349
- >
350
- <i
351
- v-if="clusterFilter"
352
- class="icon icon-close"
353
- @click="clusterFilter=''"
354
- />
355
- </div>
356
439
  <div
357
440
  ref="clusterList"
358
441
  class="clusters"
442
+ :style="pinnedClustersHeight"
359
443
  >
360
444
  <div
361
- v-for="c in clustersFiltered"
362
- :key="c.id"
363
- @click="hide()"
445
+ v-if="showPinClusters && pinFiltered.length"
446
+ class="clustersPinned"
364
447
  >
365
- <nuxt-link
366
- v-if="c.ready"
367
- class="cluster selector option"
368
- :to="{ name: 'c-cluster-explorer', params: { cluster: c.id } }"
448
+ <div
449
+ v-for="c in pinFiltered"
450
+ :key="c.id"
451
+ @click="hide()"
369
452
  >
370
- <ClusterProviderIcon
371
- :small="true"
372
- :cluster="c"
373
- class="rancher-provider-icon mr-10"
374
- />
375
- <div class="cluster-name">
376
- {{ c.label }}
377
- </div>
378
- </nuxt-link>
379
- <span
380
- v-else
381
- class="option-disabled cluster selector disabled"
453
+ <nuxt-link
454
+ v-if="c.ready"
455
+ :data-testid="`menu-cluster-${ c.id }`"
456
+ class="cluster selector option"
457
+ :to="{ name: 'c-cluster-explorer', params: { cluster: c.id } }"
458
+ >
459
+ <ClusterIconMenu
460
+ v-tooltip="getTooltipConfig(c.label)"
461
+ :cluster="c"
462
+ class="rancher-provider-icon"
463
+ />
464
+ <div class="cluster-name">
465
+ {{ c.label }}
466
+ </div>
467
+ <Pinned
468
+ :cluster="c"
469
+ />
470
+ </nuxt-link>
471
+ <span
472
+ v-else
473
+ class="option cluster selector disabled"
474
+ >
475
+ <ClusterIconMenu
476
+ v-tooltip="getTooltipConfig(c.label)"
477
+ :cluster="c"
478
+ class="rancher-provider-icon"
479
+ />
480
+ <div class="cluster-name">{{ c.label }}</div>
481
+ <Pinned
482
+ :cluster="c"
483
+ />
484
+ </span>
485
+ </div>
486
+ <div
487
+ v-if="clustersFiltered.length > 0"
488
+ class="category-title"
382
489
  >
383
- <ClusterProviderIcon
384
- :small="true"
385
- :cluster="c"
386
- class="rancher-provider-icon mr-10"
387
- />
388
- <div class="cluster-name">{{ c.label }}</div>
389
- </span>
490
+ <hr>
491
+ </div>
492
+ </div>
493
+ <div class="clustersList">
494
+ <div
495
+ v-for="c in clustersFiltered"
496
+ :key="c.id"
497
+ @click="hide()"
498
+ >
499
+ <nuxt-link
500
+ v-if="c.ready"
501
+ :data-testid="`menu-cluster-${ c.id }`"
502
+ class="cluster selector option"
503
+ :to="{ name: 'c-cluster-explorer', params: { cluster: c.id } }"
504
+ >
505
+ <ClusterIconMenu
506
+ v-tooltip="getTooltipConfig(c.label)"
507
+ :cluster="c"
508
+ class="rancher-provider-icon"
509
+ />
510
+ <div class="cluster-name">
511
+ {{ c.label }}
512
+ </div>
513
+ <Pinned
514
+ :class="{'showPin': c.pinned}"
515
+ :cluster="c"
516
+ />
517
+ </nuxt-link>
518
+ <span
519
+ v-else
520
+ class="option cluster selector disabled"
521
+ >
522
+ <ClusterIconMenu
523
+ v-tooltip="getTooltipConfig(c.label)"
524
+ :cluster="c"
525
+ class="rancher-provider-icon"
526
+ />
527
+ <div class="cluster-name">{{ c.label }}</div>
528
+ <Pinned
529
+ :class="{'showPin': c.pinned}"
530
+ :cluster="c"
531
+ />
532
+ </span>
533
+ </div>
390
534
  </div>
391
535
  <div
392
- v-if="clustersFiltered.length === 0"
536
+ v-if="(clustersFiltered.length === 0 || pinFiltered.length === 0) && searchActive"
393
537
  class="none-matching"
394
538
  >
395
539
  {{ t('nav.search.noResults') }}
396
540
  </div>
397
541
  </div>
398
- </template>
399
542
 
400
- <template v-if="multiClusterApps.length">
401
- <div class="category">
402
- {{ t('nav.categories.multiCluster') }}
403
- </div>
404
- <div
405
- v-for="a in multiClusterApps"
406
- :key="a.label"
407
- @click="hide()"
543
+ <nuxt-link
544
+ v-if="clusters.length > maxClustersToShow"
545
+ class="clusters-all"
546
+ :to="{name: 'c-cluster-product-resource', params: {
547
+ cluster: emptyCluster,
548
+ product: 'manager',
549
+ resource: 'provisioning.cattle.io.cluster'
550
+ } }"
408
551
  >
409
- <nuxt-link
410
- class="option"
411
- :to="a.to"
412
- >
413
- <IconOrSvg
414
- :icon="a.icon"
415
- :src="a.svg"
416
- />
417
- <div>{{ a.label }}</div>
418
- </nuxt-link>
419
- </div>
552
+ <span>
553
+ {{ shown ? t('nav.seeAllClusters') : t('nav.seeAllClustersCollapsed') }}
554
+ <i class="icon icon-chevron-right" />
555
+ </span>
556
+ </nuxt-link>
420
557
  </template>
421
- <template v-if="legacyEnabled">
422
- <div class="category">
423
- {{ t('nav.categories.legacy') }}
424
- </div>
425
- <div
426
- v-for="a in legacyApps"
427
- :key="a.label"
428
- @click="hide()"
429
- >
430
- <nuxt-link
431
- class="option"
432
- :to="a.to"
558
+
559
+ <div class="category">
560
+ <template v-if="multiClusterApps.length">
561
+ <div
562
+ class="category-title"
433
563
  >
434
- <IconOrSvg
435
- :icon="a.icon"
436
- :src="a.svg"
437
- />
438
- <div>{{ a.label }}</div>
439
- </nuxt-link>
440
- </div>
441
- </template>
442
- <template v-if="configurationApps.length">
443
- <div class="category">
444
- {{ t('nav.categories.configuration') }}
445
- </div>
446
- <div
447
- v-for="a in configurationApps"
448
- :key="a.label"
449
- @click="hide()"
450
- >
451
- <nuxt-link
452
- class="option"
453
- :to="a.to"
564
+ <hr>
565
+ <span>
566
+ {{ t('nav.categories.multiCluster') }}
567
+ </span>
568
+ </div>
569
+ <div
570
+ v-for="a in multiClusterApps"
571
+ :key="a.label"
572
+ @click="hide()"
454
573
  >
455
- <IconOrSvg
456
- :icon="a.icon"
457
- :src="a.svg"
458
- />
459
- <div>{{ a.label }}</div>
460
- </nuxt-link>
461
- </div>
462
- </template>
463
- <div class="pad" />
574
+ <nuxt-link
575
+ class="option"
576
+ :to="a.to"
577
+ >
578
+ <IconOrSvg
579
+ v-tooltip="getTooltipConfig(a.label)"
580
+ :icon="a.icon"
581
+ :src="a.svg"
582
+ />
583
+ <span class="option-link">{{ a.label }}</span>
584
+ </nuxt-link>
585
+ </div>
586
+ </template>
587
+ <template v-if="legacyEnabled">
588
+ <div
589
+ class="category-title"
590
+ >
591
+ <hr>
592
+ <span>
593
+ {{ t('nav.categories.legacy') }}
594
+ </span>
595
+ </div>
596
+ <div
597
+ v-for="a in legacyApps"
598
+ :key="a.label"
599
+ @click="hide()"
600
+ >
601
+ <nuxt-link
602
+ class="option"
603
+ :to="a.to"
604
+ >
605
+ <IconOrSvg
606
+ v-tooltip="getTooltipConfig(a.label)"
607
+ :icon="a.icon"
608
+ :src="a.svg"
609
+ />
610
+ <div>{{ a.label }}</div>
611
+ </nuxt-link>
612
+ </div>
613
+ </template>
614
+ <template v-if="configurationApps.length">
615
+ <div
616
+ class="category-title"
617
+ >
618
+ <hr>
619
+ <span>
620
+ {{ t('nav.categories.configuration') }}
621
+ </span>
622
+ </div>
623
+ <div
624
+ v-for="a in configurationApps"
625
+ :key="a.label"
626
+ @click="hide()"
627
+ >
628
+ <nuxt-link
629
+ class="option"
630
+ :to="a.to"
631
+ >
632
+ <IconOrSvg
633
+ v-tooltip="getTooltipConfig(a.label)"
634
+ :icon="a.icon"
635
+ :src="a.svg"
636
+ />
637
+ <div>{{ a.label }}</div>
638
+ </nuxt-link>
639
+ </div>
640
+ </template>
641
+ </div>
464
642
  </div>
465
- <div class="footer">
643
+ <div
644
+ class="footer"
645
+ >
466
646
  <div
467
647
  v-if="canEditSettings"
648
+ class="support"
468
649
  @click="hide()"
469
650
  >
470
- <nuxt-link :to="{name: 'support'}">
651
+ <nuxt-link
652
+ :to="{name: 'support'}"
653
+ >
471
654
  {{ t('nav.support', {hasSupport}) }}
472
655
  </nuxt-link>
473
656
  </div>
474
- <div @click="hide()">
657
+ <div
658
+ class="version"
659
+ @click="hide()"
660
+ >
475
661
  <nuxt-link
476
662
  :to="{ name: 'about' }"
477
- class="version"
478
663
  >
479
664
  {{ t('about.title') }}
480
665
  </nuxt-link>
@@ -485,41 +670,11 @@ export default {
485
670
  </div>
486
671
  </template>
487
672
 
488
- <style scoped>
489
- .cluster.disabled > * {
490
- cursor: not-allowed;
491
- filter: grayscale(1);
492
- color: var(--muted);
493
- }
494
- </style>
495
-
496
673
  <style lang="scss">
497
674
  .localeSelector, .footer-tooltip {
498
675
  z-index: 1000;
499
676
  }
500
677
 
501
- .cluster {
502
- &.selector:not(.disabled):hover {
503
- color: var(--primary-hover-text);
504
- background: var(--primary-hover-bg);
505
- border-radius: 5px;
506
- text-decoration: none;
507
-
508
- .rancher-provider-icon {
509
- .rancher-icon-fill {
510
- fill: var(--primary-hover-text);;
511
- }
512
- }
513
- }
514
-
515
- .rancher-provider-icon {
516
- .rancher-icon-fill {
517
- // Should match .option color
518
- fill: var(--link);
519
- }
520
- }
521
- }
522
-
523
678
  .localeSelector {
524
679
  .popover-inner {
525
680
  padding: 10px 0;
@@ -533,111 +688,56 @@ export default {
533
688
  outline: 0;
534
689
  }
535
690
  }
536
-
537
691
  </style>
538
692
 
539
693
  <style lang="scss" scoped>
540
694
  $clear-search-size: 20px;
541
695
  $icon-size: 25px;
542
- $option-padding: 4px;
696
+ $option-padding: 9px;
697
+ $option-padding-left: 14px;
543
698
  $option-height: $icon-size + $option-padding + $option-padding;
544
699
 
545
- .option {
546
- align-items: center;
547
- cursor: pointer;
548
- display: flex;
549
- color: var(--link);
550
-
551
- &:hover {
552
- text-decoration: none;
553
- }
554
-
555
- &:focus {
556
- outline: 0;
557
- > div {
558
- text-decoration: underline;
559
- }
560
- }
561
-
562
- > i {
563
- width: $icon-size;
564
- font-size: $icon-size;
565
- margin-right: 8px;
566
- }
567
- svg {
568
- margin-right: 8px;
569
- fill: var(--link);
570
- }
571
- img {
572
- margin-right: 8px;
573
- }
574
-
575
- > div {
576
- color: var(--link);
577
- }
700
+ .side-menu {
701
+ .menu {
702
+ position: absolute;
703
+ width: $app-bar-collapsed-width;
704
+ height: 54px;
705
+ top: 0;
706
+ grid-area: menu;
707
+ cursor: pointer;
708
+ display: flex;
709
+ align-items: center;
710
+ justify-content: center;
578
711
 
579
- &:hover {
580
- color: var(--primary-hover-text);
581
- background: var(--primary-hover-bg);
582
- border-radius: 5px;
583
- > div {
584
- color: var(--primary-hover-text);
585
- }
586
- svg {
587
- fill: var(--primary-hover-text);
588
- }
589
- div {
590
- color: var(--primary-hover-text);
712
+ .menu-icon {
713
+ width: 25px;
714
+ height: 25px;
715
+ fill: var(--header-btn-text);
591
716
  }
592
717
  }
593
- }
594
-
595
- .option, .option-disabled {
596
- padding: $option-padding 0 $option-padding 10px;
597
- }
598
-
599
- .menu {
600
- position: absolute;
601
- left: 0;
602
- width: 55px;
603
- height: 54px;
604
- top: 0;
605
- grid-area: menu;
606
- cursor: pointer;
607
- display: flex;
608
- align-items: center;
609
- justify-content: center;
610
- &:hover {
611
- background-color: var(--topmost-light-hover);
612
- }
613
- .menu-icon {
614
- width: 24px;
615
- height: 24px;
616
- fill: var(--header-btn-text);
617
- }
618
- &.raised {
619
- z-index: 200;
620
- }
621
- }
622
718
 
623
- .side-menu {
624
- position: absolute;
719
+ position: fixed;
625
720
  top: 0;
626
721
  left: 0px;
627
722
  bottom: 0;
628
- width: 280px;
723
+ width: $app-bar-collapsed-width;
629
724
  background-color: var(--topmenu-bg);
630
725
  z-index: 100;
631
726
  border-right: 1px solid var(--topmost-border);
632
- box-shadow: 0 0 15px 4px var(--topmost-shadow);
633
727
  display: flex;
634
728
  flex-direction: column;
635
729
  padding: 0;
730
+ overflow: hidden;
731
+ transition: width 500ms;
636
732
 
637
733
  &:focus {
638
734
  outline: 0;
639
735
  }
640
736
 
737
+ &.menu-open {
738
+ width: 300px;
739
+ }
740
+
641
741
  .title {
642
742
  display: flex;
643
743
  height: 55px;
@@ -646,64 +746,150 @@ export default {
646
746
  border-bottom: 1px solid var(--nav-border);
647
747
  justify-content: flex-start;
648
748
  align-items: center;
749
+
649
750
  .menu {
650
751
  display: flex;
651
752
  justify-content: center;
652
- width: 55px;
653
- margin-right: 10px;
654
753
  }
655
754
  .menu-icon {
656
- width: 24px;
657
- height: 24px;
755
+ width: 25px;
756
+ height: 25px;
658
757
  }
659
- .menu-spacer {
660
- width: 55px;
758
+ }
759
+ .home {
760
+ svg {
761
+ width: 25px;
762
+ height: 25px;
763
+ margin-left: 9px;
661
764
  }
662
765
  }
766
+ .home-text {
767
+ margin-left: $option-padding-left - 7;
768
+ }
663
769
  .body {
664
770
  flex: 1;
665
771
  display: flex;
666
772
  flex-direction: column;
667
- margin: 10px 20px;
668
- overflow-y: auto;
669
-
670
- .category {
671
- padding: 10px 0;
672
- text-transform: uppercase;
673
- opacity: 0.8;
674
- margin-top: 10px;
675
- }
676
-
677
- .home {
678
- color: var(--link);
679
- }
773
+ margin: 10px 0;
774
+ width: 300px;
775
+ overflow: auto;
680
776
 
681
- .home:focus {
682
- outline: 0;
683
- }
684
-
685
- .cluster {
777
+ .option {
686
778
  align-items: center;
779
+ cursor: pointer;
687
780
  display: flex;
781
+ color: var(--link);
782
+ font-size: 14px;
688
783
  height: $option-height;
689
-
690
784
  white-space: nowrap;
785
+
786
+ .cluster-badge-logo-text {
787
+ color: var(--default-active-text);
788
+ font-weight: 500;
789
+ }
790
+
791
+ .pin {
792
+ font-size: 16px;
793
+ margin-left: auto;
794
+ display: none;
795
+ color: var(--body-text);
796
+ &.showPin {
797
+ display: block;
798
+ }
799
+ }
800
+
801
+ &:hover {
802
+ text-decoration: none;
803
+
804
+ .pin {
805
+ display: block;
806
+ color: var(--darker-text);
807
+ }
808
+ }
809
+ &.disabled {
810
+ background: transparent;
811
+ cursor: not-allowed;
812
+
813
+ .rancher-provider-icon,
814
+ .cluster-name {
815
+ filter: grayscale(1);
816
+ color: var(--muted);
817
+ }
818
+
819
+ .pin {
820
+ cursor: pointer;
821
+ }
822
+ }
823
+
691
824
  &:focus {
692
825
  outline: 0;
826
+ > div {
827
+ text-decoration: underline;
828
+ }
693
829
  }
830
+
831
+ > i {
832
+ display: block;
833
+ width: 42px;
834
+ font-size: $icon-size;
835
+ margin-right: 14px;
836
+ }
837
+
838
+ .rancher-provider-icon,
839
+ svg {
840
+ margin-right: 16px;
841
+ fill: var(--link);
842
+ }
843
+ img {
844
+ margin-right: 16px;
845
+ }
846
+
847
+ &.nuxt-link-active {
848
+ background: var(--primary-hover-bg);
849
+ color: var(--primary-hover-text);
850
+
851
+ svg {
852
+ fill: var(--primary-hover-text);
853
+ }
854
+
855
+ i {
856
+ color: var(--primary-hover-text);
857
+ }
858
+ }
859
+
860
+ &:hover {
861
+ color: var(--primary-hover-text);
862
+ background: var(--primary-hover-bg);
863
+ > div {
864
+ color: var(--primary-hover-text);
865
+ }
866
+ svg {
867
+ fill: var(--primary-hover-text);
868
+ }
869
+ div {
870
+ color: var(--primary-hover-text);
871
+ }
872
+ &.disabled {
873
+ background: transparent;
874
+ color: var(--muted);
875
+
876
+ > .pin {
877
+ color:var(--default-text);
878
+ display: block;
879
+ }
880
+ }
881
+ }
882
+
694
883
  .cluster-name {
695
- text-overflow: ellipsis;
884
+ max-width: 220px;
885
+ white-space: nowrap;
696
886
  overflow: hidden;
697
- }
698
- > img {
699
- max-height: $icon-size;
700
- max-width: $icon-size;
701
- margin-right: 8px;
887
+ text-overflow: ellipsis;
702
888
  }
703
889
  }
704
890
 
705
- .pad {
706
- flex: 1;
891
+ .option, .option-disabled {
892
+ padding: $option-padding 0 $option-padding $option-padding-left;
707
893
  }
708
894
 
709
895
  .search {
@@ -711,12 +897,12 @@ export default {
711
897
  > input {
712
898
  background-color: transparent;
713
899
  margin-bottom: 8px;
714
- padding-right: 34px;
900
+ padding-right: 35px;
715
901
  }
716
902
  > i {
717
903
  position: absolute;
718
904
  font-size: $clear-search-size;
719
- top: 9px;
905
+ top: 11px;
720
906
  right: 8px;
721
907
  opacity: 0.7;
722
908
  cursor: pointer;
@@ -726,18 +912,190 @@ export default {
726
912
  }
727
913
  }
728
914
 
915
+ .clusters-all {
916
+ display: flex;
917
+ flex-direction: row-reverse;
918
+ margin-right: 16px;
919
+ margin-top: 10px;
920
+
921
+ span {
922
+ display: flex;
923
+ align-items: center;
924
+ }
925
+ }
926
+
729
927
  .clusters {
730
928
  overflow-y: auto;
731
- overflow-x: hidden;
929
+
930
+ a, span {
931
+ margin: 0;
932
+ }
933
+
934
+ &-search {
935
+ display: flex;
936
+ align-items: center;
937
+ gap: 14px;
938
+ margin: 16px 0;
939
+ height: 42px;
940
+
941
+ .search {
942
+ transition: all 0.5s ease-in-out;
943
+ transition-delay: 2s;
944
+ width: 72%;
945
+ height: 42px;
946
+
947
+ input {
948
+ height: 100%;
949
+ }
950
+ }
951
+
952
+ &-count {
953
+ position: relative;
954
+ display: flex;
955
+ flex-direction: column;
956
+ width: 42px;
957
+ height: 42px;
958
+ display: flex;
959
+ align-items: center;
960
+ justify-content: center;
961
+ color: var(--default-active-text);
962
+ margin-left: $option-padding-left;
963
+ border-radius: 5px;
964
+ font-size: 10px;
965
+ font-weight: bold;
966
+
967
+ span {
968
+ font-size: 14px;
969
+ }
970
+
971
+ .nuxt-link-active {
972
+ &:hover {
973
+ text-decoration: none;
974
+ }
975
+ }
976
+
977
+ i {
978
+ font-size: 12px;
979
+ position: absolute;
980
+ right: -3px;
981
+ top: 2px;
982
+ }
983
+ }
984
+ }
732
985
  }
733
986
 
734
987
  .none-matching {
988
+ width: 100%;
989
+ text-align: center;
735
990
  padding: 8px
736
991
  }
992
+
993
+ .clustersPinned {
994
+ .category {
995
+ &-title {
996
+ margin: 8px 0;
997
+ margin-left: 16px;
998
+ hr {
999
+ margin: 0;
1000
+ width: 94%;
1001
+ transition: all 0.5s ease-in-out;
1002
+ max-width: 100%;
1003
+ }
1004
+ }
1005
+ }
1006
+ .pin {
1007
+ display: block;
1008
+ }
1009
+ }
1010
+
1011
+ .category {
1012
+ display: flex;
1013
+ flex-direction: column;
1014
+ place-content: flex-end;
1015
+ flex: 1;
1016
+
1017
+ &-title {
1018
+ display: flex;
1019
+ flex-direction: row;
1020
+ align-items: flex-start;
1021
+ align-items: center;
1022
+ margin: 15px 0;
1023
+ margin-left: 16px;
1024
+ font-size: 14px;
1025
+ text-transform: uppercase;
1026
+
1027
+ span {
1028
+ transition: all 0.5s ease-in-out;
1029
+ display: flex;
1030
+ max-height: 16px;
1031
+ }
1032
+
1033
+ hr {
1034
+ margin: 0;
1035
+ max-width: 50px;
1036
+ width: 0;
1037
+ transition: all 0.5s ease-in-out;
1038
+ }
1039
+ }
1040
+
1041
+ i {
1042
+ padding-left: $option-padding-left - 5;
1043
+ }
1044
+ }
737
1045
  }
1046
+
1047
+ &.menu-close {
1048
+ .side-menu-logo {
1049
+ opacity: 0;
1050
+ }
1051
+ .category {
1052
+ &-title {
1053
+ span {
1054
+ opacity: 0;
1055
+ }
1056
+
1057
+ hr {
1058
+ width: 40px;
1059
+ }
1060
+ }
1061
+ }
1062
+ .clusters-all {
1063
+ flex-direction: row;
1064
+ margin-left: $option-padding-left + 2;
1065
+
1066
+ span {
1067
+ i {
1068
+ display: none;
1069
+ }
1070
+ }
1071
+ }
1072
+
1073
+ .clustersPinned {
1074
+ .category {
1075
+ &-title {
1076
+ hr {
1077
+ width: 40px;
1078
+ }
1079
+ }
1080
+ }
1081
+ }
1082
+
1083
+ .footer {
1084
+ margin: 20px 15px;
1085
+
1086
+ .support {
1087
+ display: none;
1088
+ }
1089
+
1090
+ .version{
1091
+ text-align: left;
1092
+ }
1093
+ }
1094
+ }
1095
+
738
1096
  .footer {
739
1097
  margin: 20px;
740
-
1098
+ width: 240px;
741
1099
  display: flex;
742
1100
  flex: 0;
743
1101
  flex-direction: row;
@@ -761,7 +1119,6 @@ export default {
761
1119
  }
762
1120
 
763
1121
  .side-menu-glass {
764
- background-color: transparent;
765
1122
  position: absolute;
766
1123
  top: 0;
767
1124
  left: 0px;
@@ -774,12 +1131,13 @@ export default {
774
1131
  .side-menu-logo {
775
1132
  align-items: center;
776
1133
  display: flex;
777
- margin-left: 10px;
1134
+ transform: translateX($app-bar-collapsed-width);
778
1135
  opacity: 1;
779
- transition: opacity 1.2s;
780
- transition-delay: 0s;
781
- height: 55px;
782
1136
  max-width: 200px;
1137
+ width: 100%;
1138
+ justify-content: center;
1139
+ transition: all 0.5s;
1140
+ overflow: hidden;
783
1141
  & IMG {
784
1142
  object-fit: contain;
785
1143
  height: 21px;
@@ -787,12 +1145,6 @@ export default {
787
1145
  }
788
1146
  }
789
1147
 
790
- .fade-enter-active {
791
- .side-menu-logo {
792
- opacity: 0;
793
- }
794
- }
795
-
796
1148
  .fade-enter-active, .fade-leave-active {
797
1149
  transition: all 0.2s;
798
1150
  transition-timing-function: ease;
@@ -808,10 +1160,6 @@ export default {
808
1160
 
809
1161
  .fade-enter {
810
1162
  left: -300px;
811
-
812
- .side-menu-logo {
813
- opacity: 0;
814
- }
815
1163
  }
816
1164
 
817
1165
  .locale-chooser {