@rancher/shell 3.0.1-rc.4 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/assets/data/aws-regions.json +1 -0
  2. package/assets/styles/base/_basic.scss +5 -0
  3. package/assets/styles/base/_mixins.scss +8 -0
  4. package/assets/styles/global/_button.scss +5 -0
  5. package/assets/styles/themes/_dark.scss +2 -0
  6. package/assets/styles/themes/_light.scss +2 -0
  7. package/assets/translations/en-us.yaml +27 -11
  8. package/assets/translations/zh-hans.yaml +1 -1
  9. package/chart/monitoring/StorageClassSelector.vue +1 -1
  10. package/components/AssignTo.vue +1 -0
  11. package/components/AsyncButton.vue +1 -0
  12. package/components/BackLink.vue +8 -2
  13. package/components/PaginatedResourceTable.vue +135 -0
  14. package/components/ResourceList/index.vue +0 -1
  15. package/components/ResourceTable.vue +6 -1
  16. package/components/SortableTable/index.vue +8 -6
  17. package/components/Tabbed/index.vue +35 -2
  18. package/components/form/ResourceLabeledSelect.vue +2 -2
  19. package/components/form/ResourceTabs/index.vue +0 -23
  20. package/components/form/Taints.vue +1 -1
  21. package/components/nav/TopLevelMenu.helper.ts +546 -0
  22. package/components/nav/TopLevelMenu.vue +124 -159
  23. package/components/nav/__tests__/TopLevelMenu.test.ts +338 -326
  24. package/config/pagination-table-headers.js +4 -4
  25. package/config/product/explorer.js +2 -0
  26. package/config/router/routes.js +1 -1
  27. package/config/settings.ts +13 -1
  28. package/core/plugin.ts +8 -1
  29. package/core/types-provisioning.ts +5 -0
  30. package/core/types.ts +26 -1
  31. package/dialog/DrainNode.vue +6 -6
  32. package/edit/catalog.cattle.io.clusterrepo.vue +95 -52
  33. package/edit/provisioning.cattle.io.cluster/index.vue +8 -3
  34. package/list/node.vue +8 -5
  35. package/mixins/resource-fetch-api-pagination.js +40 -5
  36. package/mixins/resource-fetch.js +48 -5
  37. package/models/management.cattle.io.nodepool.js +5 -4
  38. package/models/provisioning.cattle.io.cluster.js +2 -10
  39. package/package.json +6 -6
  40. package/pages/about.vue +22 -0
  41. package/pages/c/_cluster/explorer/__tests__/index.test.ts +36 -24
  42. package/pages/c/_cluster/explorer/index.vue +100 -59
  43. package/pages/home.vue +308 -123
  44. package/plugins/dashboard-store/__tests__/mutations.test.ts +2 -0
  45. package/plugins/dashboard-store/actions.js +29 -19
  46. package/plugins/dashboard-store/getters.js +5 -2
  47. package/plugins/dashboard-store/mutations.js +4 -2
  48. package/plugins/steve/__tests__/mutations.test.ts +2 -1
  49. package/plugins/steve/steve-pagination-utils.ts +25 -2
  50. package/plugins/steve/subscribe.js +22 -8
  51. package/scripts/extension/parse-tag-name +2 -0
  52. package/scripts/test-plugins-build.sh +1 -0
  53. package/store/index.js +31 -9
  54. package/tsconfig.json +7 -1
  55. package/types/resources/settings.d.ts +1 -1
  56. package/types/shell/index.d.ts +1107 -1276
  57. package/types/store/dashboard-store.types.ts +4 -0
  58. package/types/store/pagination.types.ts +13 -0
  59. package/types/store/vuex.d.ts +8 -0
  60. package/types/vue-shim.d.ts +6 -31
  61. package/utils/cluster.js +92 -1
  62. package/utils/pagination-utils.ts +17 -8
  63. package/utils/pagination-wrapper.ts +70 -0
  64. package/utils/uiplugins.ts +18 -4
@@ -4,18 +4,20 @@ import ClusterIconMenu from '@shell/components/ClusterIconMenu';
4
4
  import IconOrSvg from '../IconOrSvg';
5
5
  import { BLANK_CLUSTER } from '@shell/store/store-types.js';
6
6
  import { mapGetters } from 'vuex';
7
- import { CAPI, MANAGEMENT } from '@shell/config/types';
8
- import { MENU_MAX_CLUSTERS } from '@shell/store/prefs';
7
+ import { CAPI, COUNT, MANAGEMENT } from '@shell/config/types';
8
+ import { MENU_MAX_CLUSTERS, PINNED_CLUSTERS } from '@shell/store/prefs';
9
9
  import { sortBy } from '@shell/utils/sort';
10
10
  import { ucFirst } from '@shell/utils/string';
11
11
  import { KEY } from '@shell/utils/platform';
12
12
  import { getVersionInfo } from '@shell/utils/version';
13
13
  import { SETTING } from '@shell/config/settings';
14
- import { filterOnlyKubernetesClusters, filterHiddenLocalCluster } from '@shell/utils/cluster';
15
14
  import { getProductFromRoute } from '@shell/utils/router';
16
15
  import { isRancherPrime } from '@shell/config/version';
17
16
  import Pinned from '@shell/components/nav/Pinned';
18
17
  import { getGlobalBannerFontSizes } from '@shell/utils/banners';
18
+ import { TopLevelMenuHelperPagination, TopLevelMenuHelperLegacy } from '@shell/components/nav/TopLevelMenu.helper';
19
+ import { debounce } from 'lodash';
20
+ import { sameContents } from '@shell/utils/array';
19
21
 
20
22
  export default {
21
23
  components: {
@@ -29,6 +31,17 @@ export default {
29
31
  const { displayVersion, fullVersion } = getVersionInfo(this.$store);
30
32
  const hasProvCluster = this.$store.getters[`management/schemaFor`](CAPI.RANCHER_CLUSTER);
31
33
 
34
+ const canPagination = this.$store.getters[`management/paginationEnabled`]({
35
+ id: MANAGEMENT.CLUSTER,
36
+ context: 'side-bar',
37
+ }) && this.$store.getters[`management/paginationEnabled`]({
38
+ id: CAPI.RANCHER_CLUSTER,
39
+ context: 'side-bar',
40
+ });
41
+ const helper = canPagination ? new TopLevelMenuHelperPagination({ $store: this.$store }) : new TopLevelMenuHelperLegacy({ $store: this.$store });
42
+ const provClusters = !canPagination && hasProvCluster ? this.$store.getters[`management/all`](CAPI.RANCHER_CLUSTER) : [];
43
+ const mgmtClusters = !canPagination ? this.$store.getters[`management/all`](MANAGEMENT.CLUSTER) : [];
44
+
32
45
  return {
33
46
  shown: false,
34
47
  displayVersion,
@@ -37,27 +50,26 @@ export default {
37
50
  hasProvCluster,
38
51
  maxClustersToShow: MENU_MAX_CLUSTERS,
39
52
  emptyCluster: BLANK_CLUSTER,
40
- showPinClusters: false,
41
- searchActive: false,
42
53
  routeCombo: false,
43
- };
44
- },
45
54
 
46
- fetch() {
47
- if (this.hasProvCluster) {
48
- this.$store.dispatch('management/findAll', { type: CAPI.RANCHER_CLUSTER });
49
- }
55
+ canPagination,
56
+ helper,
57
+ debouncedHelperUpdateSlow: debounce((...args) => this.helper.update(...args), 750),
58
+ debouncedHelperUpdateQuick: debounce((...args) => this.helper.update(...args), 200),
59
+ provClusters,
60
+ mgmtClusters,
61
+ };
50
62
  },
51
63
 
52
64
  computed: {
53
65
  ...mapGetters(['clusterId']),
54
66
  ...mapGetters(['clusterReady', 'isRancher', 'currentCluster', 'currentProduct', 'isRancherInHarvester']),
55
67
  ...mapGetters({ features: 'features/get' }),
56
- value: {
57
- get() {
58
- return this.$store.getters['productId'];
59
- },
68
+
69
+ pinnedIds() {
70
+ return this.$store.getters['prefs/get'](PINNED_CLUSTERS);
60
71
  },
72
+
61
73
  sideMenuStyle() {
62
74
  const globalBannerSettings = getGlobalBannerFontSizes(this.$store);
63
75
 
@@ -68,161 +80,47 @@ export default {
68
80
  },
69
81
 
70
82
  showClusterSearch() {
71
- return this.clusters.length > this.maxClustersToShow;
83
+ return this.allClustersCount > this.maxClustersToShow;
72
84
  },
73
85
 
74
- /**
75
- * Filter mgmt clusters by
76
- * 1. Harvester type 1 (filterOnlyKubernetesClusters)
77
- * 2. Harvester type 2 (filterHiddenLocalCluster)
78
- * 3. There's a matching prov cluster
79
- *
80
- * Convert remaining clusters to special format
81
- */
82
- clusters() {
83
- if (!this.hasProvCluster) {
84
- // We're filtering out mgmt clusters without prov clusters, so if the user can't see any prov clusters at all
85
- // exit early
86
- return [];
87
- }
88
-
89
- const all = this.$store.getters['management/all'](MANAGEMENT.CLUSTER);
90
- const mgmtClusters = filterHiddenLocalCluster(filterOnlyKubernetesClusters(all, this.$store), this.$store);
91
- const provClusters = this.$store.getters['management/all'](CAPI.RANCHER_CLUSTER);
92
- const provClustersByMgmtId = provClusters.reduce((res, provCluster) => {
93
- if (provCluster.mgmt?.id) {
94
- res[provCluster.mgmt.id] = provCluster;
95
- }
86
+ allClustersCount() {
87
+ const counts = this.$store.getters[`management/all`](COUNT)?.[0]?.counts || {};
88
+ const count = counts[MANAGEMENT.CLUSTER] || {};
96
89
 
97
- return res;
98
- }, {});
90
+ return count?.summary.count;
91
+ },
99
92
 
100
- return (mgmtClusters || []).reduce((res, mgmtCluster) => {
101
- // Filter to only show mgmt clusters that exist for the available provisioning clusters
102
- // Addresses issue where a mgmt cluster can take some time to get cleaned up after the corresponding
103
- // provisioning cluster has been deleted
104
- if (!provClustersByMgmtId[mgmtCluster.id]) {
105
- return res;
106
- }
93
+ // New
94
+ search() {
95
+ return (this.clusterFilter || '').toLowerCase();
96
+ },
107
97
 
108
- const pCluster = provClustersByMgmtId[mgmtCluster.id];
109
-
110
- res.push({
111
- id: mgmtCluster.id,
112
- label: mgmtCluster.nameDisplay,
113
- ready: mgmtCluster.isReady && !pCluster?.hasError,
114
- osLogo: mgmtCluster.providerOsLogo,
115
- providerNavLogo: mgmtCluster.providerMenuLogo,
116
- badge: mgmtCluster.badge,
117
- isLocal: mgmtCluster.isLocal,
118
- isHarvester: mgmtCluster.isHarvester,
119
- pinned: mgmtCluster.pinned,
120
- description: pCluster?.description || mgmtCluster.description,
121
- pin: () => mgmtCluster.pin(),
122
- unpin: () => mgmtCluster.unpin(),
123
- clusterRoute: { name: 'c-cluster-explorer', params: { cluster: mgmtCluster.id } }
124
- });
98
+ // New
99
+ showPinClusters() {
100
+ return !this.clusterFilter;
101
+ },
125
102
 
126
- return res;
127
- }, []);
103
+ // New
104
+ searchActive() {
105
+ return !!this.search;
128
106
  },
129
107
 
130
108
  /**
131
- * Filter clusters by
132
- * 1. Not pinned
133
- * 2. Includes search term
134
- *
135
- * Sort remaining clusters
109
+ * Only Clusters that are pinned
136
110
  *
137
- * Reduce number of clusters if too many too show
138
- *
139
- * Important! This is used to show unpinned clusters OR results of search
111
+ * (see description of helper.clustersPinned for more details)
140
112
  */
141
- clustersFiltered() {
142
- const search = (this.clusterFilter || '').toLowerCase();
143
- let localCluster = null;
144
-
145
- const filtered = this.clusters.filter((c) => {
146
- // If we're searching we don't care if pinned or not
147
- if (search) {
148
- if (!c.label?.toLowerCase().includes(search)) {
149
- return false;
150
- }
151
- } else if (c.pinned) {
152
- // Not searching, not pinned, don't care
153
- return false;
154
- }
155
-
156
- if (!localCluster && c.id === 'local') {
157
- // Local cluster is a special case, we're inserting it at top so don't include in the middle
158
- localCluster = c;
159
-
160
- return false;
161
- }
162
-
163
- return true;
164
- });
165
-
166
- const sorted = sortBy(filtered, ['ready:desc', 'label']);
167
-
168
- // put local cluster on top of list always - https://github.com/rancher/dashboard/issues/10975
169
- if (localCluster) {
170
- sorted.unshift(localCluster);
171
- }
172
-
173
- if (search) {
174
- this.showPinClusters = false;
175
- this.searchActive = !sorted.length > 0;
176
-
177
- return sorted;
178
- }
179
- this.showPinClusters = true;
180
- this.searchActive = false;
181
-
182
- if (sorted.length >= this.maxClustersToShow) {
183
- return sorted.slice(0, this.maxClustersToShow);
184
- }
185
-
186
- return sorted;
113
+ pinFiltered() {
114
+ return this.hasProvCluster ? this.helper.clustersPinned : [];
187
115
  },
188
116
 
189
117
  /**
190
- * Filter clusters by
191
- * 1. Not pinned
192
- * 2. Includes search term
193
- *
194
- * Sort remaining clusters
118
+ * Used to shown unpinned clusters OR results of text search
195
119
  *
196
- * Reduce number of clusters if too many too show
197
- *
198
- * Important! This is hidden if there's a filter (user searching)
120
+ * (see description of helper.clustersOthers for more details)
199
121
  */
200
- pinFiltered() {
201
- let localCluster = null;
202
- const filtered = this.clusters.filter((c) => {
203
- if (!c.pinned) {
204
- // We only care about pinned clusters
205
- return false;
206
- }
207
-
208
- if (c.id === 'local') {
209
- // Special case, we're going to add this at the start so filter out
210
- localCluster = c;
211
-
212
- return false;
213
- }
214
-
215
- return true;
216
- });
217
-
218
- const sorted = sortBy(filtered, ['ready:desc', 'label']);
219
-
220
- // put local cluster on top of list always - https://github.com/rancher/dashboard/issues/10975
221
- if (localCluster) {
222
- sorted.unshift(localCluster);
223
- }
224
-
225
- return sorted;
122
+ clustersFiltered() {
123
+ return this.hasProvCluster ? this.helper.clustersOthers : [];
226
124
  },
227
125
 
228
126
  pinnedClustersHeight() {
@@ -232,7 +130,7 @@ export default {
232
130
  return `min-height: ${ height }px`;
233
131
  },
234
132
  clusterFilterCount() {
235
- return this.clusterFilter ? this.clustersFiltered.length : this.clusters.length;
133
+ return this.clusterFilter ? this.clustersFiltered.length : this.allClustersCount;
236
134
  },
237
135
 
238
136
  multiClusterApps() {
@@ -360,10 +258,45 @@ export default {
360
258
  }
361
259
  },
362
260
 
261
+ // See https://github.com/rancher/dashboard/issues/12831 for outstanding performance related work
363
262
  watch: {
364
263
  $route() {
365
264
  this.shown = false;
366
- }
265
+ },
266
+
267
+ pinnedIds: {
268
+ immediate: true,
269
+ handler(neu, old) {
270
+ if (sameContents(neu, old)) {
271
+ return;
272
+ }
273
+
274
+ this.updateClusters(neu, 'quick');
275
+ }
276
+ },
277
+
278
+ search() {
279
+ this.updateClusters(this.pinnedIds, 'slow');
280
+ },
281
+
282
+ provClusters: {
283
+ handler() {
284
+ // Shouldn't get here if SSP
285
+ this.updateClusters(this.pinnedIds, 'slow');
286
+ },
287
+ deep: true,
288
+ immediate: true,
289
+ },
290
+
291
+ mgmtClusters: {
292
+ handler() {
293
+ // Shouldn't get here if SSP
294
+ this.updateClusters(this.pinnedIds, 'slow');
295
+ },
296
+ deep: true,
297
+ immediate: true,
298
+ },
299
+
367
300
  },
368
301
 
369
302
  mounted() {
@@ -490,9 +423,27 @@ export default {
490
423
  popperClass
491
424
  };
492
425
  },
426
+
427
+ updateClusters(pinnedIds, speed = 'slow' | 'quick') {
428
+ const args = {
429
+ pinnedIds,
430
+ searchTerm: this.search,
431
+ unPinnedMax: this.maxClustersToShow
432
+ };
433
+
434
+ switch (speed) {
435
+ case 'slow':
436
+ this.debouncedHelperUpdateSlow(args);
437
+ break;
438
+ case 'quick':
439
+ this.debouncedHelperUpdateQuick(args);
440
+ break;
441
+ }
442
+ }
493
443
  }
494
444
  };
495
445
  </script>
446
+
496
447
  <template>
497
448
  <div>
498
449
  <!-- Overlay -->
@@ -514,7 +465,12 @@ export default {
514
465
  <div class="title">
515
466
  <div
516
467
  data-testid="top-level-menu"
468
+ :aria-label="t('nav.expandCollapseAppBar')"
469
+ role="button"
470
+ tabindex="0"
517
471
  class="menu"
472
+ @keyup.enter="toggle()"
473
+ @keyup.space="toggle()"
518
474
  @click="toggle()"
519
475
  >
520
476
  <svg
@@ -632,7 +588,7 @@ export default {
632
588
  </template>
633
589
 
634
590
  <!-- Cluster menu -->
635
- <template v-if="clusters && !!clusters.length">
591
+ <template v-if="!!allClustersCount">
636
592
  <div
637
593
  ref="clusterList"
638
594
  class="clusters"
@@ -790,7 +746,7 @@ export default {
790
746
 
791
747
  <!-- No clusters message -->
792
748
  <div
793
- v-if="(clustersFiltered.length === 0 || pinFiltered.length === 0) && searchActive"
749
+ v-if="clustersFiltered.length === 0 && searchActive"
794
750
  data-testid="top-level-menu-no-results"
795
751
  class="none-matching"
796
752
  >
@@ -800,7 +756,7 @@ export default {
800
756
 
801
757
  <!-- See all clusters -->
802
758
  <router-link
803
- v-if="clusters.length > maxClustersToShow"
759
+ v-if="allClustersCount > maxClustersToShow"
804
760
  class="clusters-all"
805
761
  :to="{name: 'c-cluster-product-resource', params: {
806
762
  cluster: emptyCluster,
@@ -972,6 +928,15 @@ export default {
972
928
  align-items: center;
973
929
  justify-content: center;
974
930
 
931
+ &:focus-visible {
932
+ outline: none;
933
+
934
+ .menu-icon {
935
+ @include focus-outline;
936
+ outline-offset: 4px; // Ensure there is space around the menu icon for the focus indication
937
+ }
938
+ }
939
+
975
940
  .menu-icon {
976
941
  width: 25px;
977
942
  height: 25px;