@rancher/shell 3.0.1-rc.3 → 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.
- package/assets/data/aws-regions.json +1 -0
- package/assets/styles/base/_basic.scss +5 -0
- package/assets/styles/base/_mixins.scss +8 -0
- package/assets/styles/global/_button.scss +5 -0
- package/assets/styles/themes/_dark.scss +2 -0
- package/assets/styles/themes/_light.scss +2 -0
- package/assets/translations/en-us.yaml +40 -22
- package/assets/translations/zh-hans.yaml +1 -7
- package/chart/monitoring/StorageClassSelector.vue +1 -1
- package/components/AssignTo.vue +1 -0
- package/components/AsyncButton.vue +1 -0
- package/components/BackLink.vue +8 -2
- package/components/PaginatedResourceTable.vue +135 -0
- package/components/ResourceDetail/Masthead.vue +1 -1
- package/components/ResourceDetail/index.vue +66 -11
- package/components/ResourceList/index.vue +0 -1
- package/components/ResourceTable.vue +6 -1
- package/components/ResourceYaml.vue +0 -53
- package/components/SortableTable/index.vue +8 -6
- package/components/Tabbed/index.vue +35 -2
- package/components/form/ResourceLabeledSelect.vue +2 -2
- package/components/form/ResourceTabs/index.vue +0 -23
- package/components/form/Taints.vue +1 -1
- package/components/form/UnitInput.vue +1 -1
- package/components/form/__tests__/UnitInput.test.ts +1 -1
- package/components/nav/TopLevelMenu.helper.ts +546 -0
- package/components/nav/TopLevelMenu.vue +125 -160
- package/components/nav/WindowManager/ContainerShell.vue +13 -4
- package/components/nav/WindowManager/__tests__/ContainerShell.test.ts +20 -18
- package/components/nav/__tests__/TopLevelMenu.test.ts +338 -326
- package/composables/useLabeledFormElement.ts +6 -2
- package/config/pagination-table-headers.js +4 -4
- package/config/product/explorer.js +2 -0
- package/config/router/navigation-guards/index.js +1 -2
- package/config/router/routes.js +1 -1
- package/config/settings.ts +15 -8
- package/core/plugin.ts +8 -1
- package/core/types-provisioning.ts +5 -0
- package/core/types.ts +26 -1
- package/dialog/DrainNode.vue +6 -6
- package/edit/catalog.cattle.io.clusterrepo.vue +95 -52
- package/edit/provisioning.cattle.io.cluster/index.vue +8 -3
- package/edit/workload/index.vue +1 -1
- package/edit/workload/storage/csi/index.vue +29 -1
- package/edit/workload/storage/index.vue +1 -0
- package/initialize/App.vue +3 -10
- package/initialize/install-plugins.js +1 -2
- package/list/management.cattle.io.podsecurityadmissionconfigurationtemplate.vue +6 -2
- package/list/node.vue +8 -5
- package/mixins/resource-fetch-api-pagination.js +40 -5
- package/mixins/resource-fetch.js +48 -5
- package/models/management.cattle.io.nodepool.js +5 -4
- package/models/nodedriver.js +2 -2
- package/models/provisioning.cattle.io.cluster.js +3 -11
- package/package.json +7 -8
- package/pages/about.vue +22 -0
- package/pages/auth/setup.vue +7 -28
- package/pages/c/_cluster/explorer/__tests__/index.test.ts +36 -24
- package/pages/c/_cluster/explorer/index.vue +100 -59
- package/pages/home.vue +308 -123
- package/plugins/dashboard-store/__tests__/mutations.test.ts +2 -0
- package/plugins/dashboard-store/actions.js +29 -19
- package/plugins/dashboard-store/getters.js +5 -2
- package/plugins/dashboard-store/mutations.js +4 -2
- package/plugins/steve/__tests__/mutations.test.ts +2 -1
- package/plugins/steve/steve-pagination-utils.ts +25 -2
- package/plugins/steve/subscribe.js +22 -8
- package/rancher-components/Banner/Banner.vue +1 -0
- package/rancher-components/Form/Checkbox/Checkbox.vue +2 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +2 -0
- package/rancher-components/Form/Radio/RadioButton.vue +2 -0
- package/rancher-components/Form/Radio/RadioGroup.vue +2 -0
- package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +2 -0
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +3 -0
- package/rancher-components/StringList/StringList.test.ts +15 -15
- package/rancher-components/StringList/StringList.vue +3 -0
- package/scripts/extension/helm/charts/ui-plugin-server/Chart.yaml +0 -2
- package/scripts/extension/parse-tag-name +2 -0
- package/scripts/test-plugins-build.sh +4 -2
- package/store/index.js +31 -9
- package/tsconfig.json +7 -1
- package/types/resources/settings.d.ts +1 -1
- package/types/shell/index.d.ts +1107 -1276
- package/types/store/dashboard-store.types.ts +4 -0
- package/types/store/pagination.types.ts +13 -0
- package/types/store/vuex.d.ts +8 -0
- package/types/vue-shim.d.ts +6 -31
- package/utils/cluster.js +92 -1
- package/utils/pagination-utils.ts +17 -8
- package/utils/pagination-wrapper.ts +70 -0
- package/utils/uiplugins.ts +307 -0
- package/components/templates/error.vue +0 -131
- package/config/router/navigation-guards/history.js +0 -13
- package/plugins/back-button.js +0 -3
|
@@ -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
|
-
|
|
47
|
-
|
|
48
|
-
this
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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.
|
|
83
|
+
return this.allClustersCount > this.maxClustersToShow;
|
|
72
84
|
},
|
|
73
85
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
98
|
-
|
|
90
|
+
return count?.summary.count;
|
|
91
|
+
},
|
|
99
92
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (!provClustersByMgmtId[mgmtCluster.id]) {
|
|
105
|
-
return res;
|
|
106
|
-
}
|
|
93
|
+
// New
|
|
94
|
+
search() {
|
|
95
|
+
return (this.clusterFilter || '').toLowerCase();
|
|
96
|
+
},
|
|
107
97
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
127
|
-
|
|
103
|
+
// New
|
|
104
|
+
searchActive() {
|
|
105
|
+
return !!this.search;
|
|
128
106
|
},
|
|
129
107
|
|
|
130
108
|
/**
|
|
131
|
-
*
|
|
132
|
-
* 1. Not pinned
|
|
133
|
-
* 2. Includes search term
|
|
134
|
-
*
|
|
135
|
-
* Sort remaining clusters
|
|
109
|
+
* Only Clusters that are pinned
|
|
136
110
|
*
|
|
137
|
-
*
|
|
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
|
-
|
|
142
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
201
|
-
|
|
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.
|
|
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="
|
|
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="
|
|
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="
|
|
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;
|
|
@@ -1119,7 +1084,7 @@ export default {
|
|
|
1119
1084
|
display: block;
|
|
1120
1085
|
font-size: $icon-size;
|
|
1121
1086
|
margin-right: 14px;
|
|
1122
|
-
|
|
1087
|
+
&:not(.pin){
|
|
1123
1088
|
width: 42px;
|
|
1124
1089
|
}
|
|
1125
1090
|
}
|
|
@@ -14,6 +14,7 @@ import Socket, {
|
|
|
14
14
|
EVENT_CONNECT_ERROR,
|
|
15
15
|
} from '@shell/utils/socket';
|
|
16
16
|
import Window from './Window';
|
|
17
|
+
import dayjs from 'dayjs';
|
|
17
18
|
|
|
18
19
|
const commands = {
|
|
19
20
|
linux: [
|
|
@@ -287,7 +288,7 @@ export default {
|
|
|
287
288
|
|
|
288
289
|
// If we had an error message, try connecting with the next command
|
|
289
290
|
if (this.errorMsg) {
|
|
290
|
-
this.terminal.
|
|
291
|
+
this.terminal.writeln(this.errorMsg);
|
|
291
292
|
if (this.backupShells.length && this.retries < 2) {
|
|
292
293
|
this.retries++;
|
|
293
294
|
// we're not really counting on this being a reactive change so there's no need to fire the whole action
|
|
@@ -299,7 +300,9 @@ export default {
|
|
|
299
300
|
this.connect();
|
|
300
301
|
} else {
|
|
301
302
|
// Output an message to let he user know none of the shell commands worked
|
|
302
|
-
|
|
303
|
+
const timestamp = dayjs().format('YYYY-MM-DD HH:mm:ss');
|
|
304
|
+
|
|
305
|
+
this.terminal.writeln(`[${ timestamp }] ${ this.t('wm.containerShell.logLevel.info') }: ${ this.t('wm.containerShell.failed') }`);
|
|
303
306
|
}
|
|
304
307
|
}
|
|
305
308
|
});
|
|
@@ -317,10 +320,16 @@ export default {
|
|
|
317
320
|
}
|
|
318
321
|
this.terminal.write(msg);
|
|
319
322
|
} else {
|
|
320
|
-
|
|
323
|
+
const timestamp = dayjs().format('YYYY-MM-DD HH:mm:ss');
|
|
324
|
+
let customError = `[${ timestamp }] ${ this.t('wm.containerShell.logLevel.error') }: ${ this.container }: ${ msg }`;
|
|
325
|
+
|
|
326
|
+
if (msg.includes('stat /bin/sh: no such file or directory')) {
|
|
327
|
+
customError = `[${ timestamp }] ${ this.t('wm.containerShell.logMessage.containerError', { logLevel: this.t('wm.containerShell.logLevel.error') }) }: ${ msg }`;
|
|
328
|
+
}
|
|
329
|
+
console.error(customError); // eslint-disable-line no-console
|
|
321
330
|
|
|
322
331
|
if (`${ type }` === '3') {
|
|
323
|
-
this.errorMsg =
|
|
332
|
+
this.errorMsg = customError;
|
|
324
333
|
}
|
|
325
334
|
}
|
|
326
335
|
});
|
|
@@ -29,6 +29,7 @@ describe('component: ContainerShell', () => {
|
|
|
29
29
|
return { rows: 1 };
|
|
30
30
|
});
|
|
31
31
|
const write = jest.fn();
|
|
32
|
+
const writeln = jest.fn();
|
|
32
33
|
const reset = jest.fn();
|
|
33
34
|
|
|
34
35
|
jest.mock(/* webpackChunkName: "xterm" */ 'xterm', () => {
|
|
@@ -39,6 +40,7 @@ describe('component: ContainerShell', () => {
|
|
|
39
40
|
open = open;
|
|
40
41
|
focus = focus;
|
|
41
42
|
write = write;
|
|
43
|
+
writeln = writeln;
|
|
42
44
|
reset = reset
|
|
43
45
|
}
|
|
44
46
|
};
|
|
@@ -277,10 +279,10 @@ describe('component: ContainerShell', () => {
|
|
|
277
279
|
eventConnected();
|
|
278
280
|
eventMessage({ detail: { data: `3${ errorMessage }` } });
|
|
279
281
|
|
|
280
|
-
expect(consoleError.mock.calls[0][0]).
|
|
282
|
+
expect(consoleError.mock.calls[0][0]).toContain(errorMessage);
|
|
281
283
|
expect(wrapper.vm.isOpen).toBe(true);
|
|
282
284
|
expect(wrapper.vm.isOpening).toBe(false);
|
|
283
|
-
expect(wrapper.vm.errorMsg).
|
|
285
|
+
expect(wrapper.vm.errorMsg).toContain(errorMessage);
|
|
284
286
|
expect(wrapper.vm.os).toBe('linux');
|
|
285
287
|
});
|
|
286
288
|
|
|
@@ -325,10 +327,10 @@ describe('component: ContainerShell', () => {
|
|
|
325
327
|
|
|
326
328
|
eventDisconnected();
|
|
327
329
|
|
|
328
|
-
expect(consoleError.mock.calls[0][0]).
|
|
330
|
+
expect(consoleError.mock.calls[0][0]).toContain(errorMessage);
|
|
329
331
|
expect(wrapper.vm.isOpen).toBe(false);
|
|
330
332
|
expect(wrapper.vm.isOpening).toBe(false);
|
|
331
|
-
expect(wrapper.vm.errorMsg).
|
|
333
|
+
expect(wrapper.vm.errorMsg).toContain('eventMessageError');
|
|
332
334
|
// the backup shell that was leftover was windows so it became the new os in dataprops
|
|
333
335
|
expect(wrapper.vm.os).toBeUndefined();
|
|
334
336
|
// but we still didn't write it to the pod itself since we don't know if it worked
|
|
@@ -370,11 +372,11 @@ describe('component: ContainerShell', () => {
|
|
|
370
372
|
eventMessage({ detail: { data: `3${ windowsErrorMessage }` } });
|
|
371
373
|
eventDisconnected();
|
|
372
374
|
|
|
373
|
-
expect(consoleError.mock.calls[0][0]).
|
|
374
|
-
expect(consoleError.mock.calls[1][0]).
|
|
375
|
+
expect(consoleError.mock.calls[0][0]).toContain(linuxErrorMessage);
|
|
376
|
+
expect(consoleError.mock.calls[1][0]).toContain(windowsErrorMessage);
|
|
375
377
|
expect(wrapper.vm.isOpen).toBe(false);
|
|
376
378
|
expect(wrapper.vm.isOpening).toBe(false);
|
|
377
|
-
expect(wrapper.vm.errorMsg).
|
|
379
|
+
expect(wrapper.vm.errorMsg).toContain(windowsErrorMessage);
|
|
378
380
|
expect(wrapper.vm.os).toBeUndefined();
|
|
379
381
|
// we never found a shell that worked so we're going to leave the pod os as undefined
|
|
380
382
|
expect(defaultContainerShellParams.propsData.pod.os).toBeUndefined();
|
|
@@ -426,11 +428,11 @@ describe('component: ContainerShell', () => {
|
|
|
426
428
|
eventMessage({ detail: { data: `3${ windowsErrorMessage }` } });
|
|
427
429
|
eventDisconnected();
|
|
428
430
|
|
|
429
|
-
expect(consoleError.mock.calls[0][0]).
|
|
430
|
-
expect(consoleError.mock.calls[1][0]).
|
|
431
|
+
expect(consoleError.mock.calls[0][0]).toContain(linuxErrorMessage);
|
|
432
|
+
expect(consoleError.mock.calls[1][0]).toContain(windowsErrorMessage);
|
|
431
433
|
expect(wrapper.vm.isOpen).toBe(false);
|
|
432
434
|
expect(wrapper.vm.isOpening).toBe(false);
|
|
433
|
-
expect(wrapper.vm.errorMsg).
|
|
435
|
+
expect(wrapper.vm.errorMsg).toContain(windowsErrorMessage);
|
|
434
436
|
expect(wrapper.vm.os).toBeUndefined();
|
|
435
437
|
expect(testUndefinedOsParams.propsData.pod.os).toBeUndefined();
|
|
436
438
|
expect(connect.mock.calls).toHaveLength(3);
|
|
@@ -472,13 +474,13 @@ describe('component: ContainerShell', () => {
|
|
|
472
474
|
expect(wrapper.vm.backupShells).toHaveLength(1);
|
|
473
475
|
expect(wrapper.vm.os).toBeUndefined();
|
|
474
476
|
expect(testUndefinedOsParams.propsData.pod.os).toBeUndefined();
|
|
475
|
-
expect(wrapper.vm.errorMsg).
|
|
477
|
+
expect(wrapper.vm.errorMsg).toContain(linuxErrorMessage);
|
|
476
478
|
|
|
477
479
|
eventConnecting();
|
|
478
480
|
eventConnected();
|
|
479
481
|
eventMessage({ detail: { data: `1${ windowsShellMessage }` } });
|
|
480
482
|
|
|
481
|
-
expect(consoleError.mock.calls[0][0]).
|
|
483
|
+
expect(consoleError.mock.calls[0][0]).toContain(linuxErrorMessage);
|
|
482
484
|
expect(consoleError.mock.calls[1]).toBeUndefined();
|
|
483
485
|
expect(wrapper.vm.isOpen).toBe(true);
|
|
484
486
|
expect(wrapper.vm.isOpening).toBe(false);
|
|
@@ -532,7 +534,7 @@ describe('component: ContainerShell', () => {
|
|
|
532
534
|
expect(wrapper.vm.backupShells).toHaveLength(1);
|
|
533
535
|
expect(wrapper.vm.os).toBe('linux');
|
|
534
536
|
expect(testNodeDefinedOsParams.propsData.pod.os).toBe('linux');
|
|
535
|
-
expect(wrapper.vm.errorMsg).
|
|
537
|
+
expect(wrapper.vm.errorMsg).toContain(linuxErrorMessage);
|
|
536
538
|
|
|
537
539
|
eventConnecting();
|
|
538
540
|
eventConnected();
|
|
@@ -542,7 +544,7 @@ describe('component: ContainerShell', () => {
|
|
|
542
544
|
expect(wrapper.vm.backupShells).toHaveLength(1);
|
|
543
545
|
expect(wrapper.vm.isOpen).toBe(false);
|
|
544
546
|
expect(wrapper.vm.isOpening).toBe(false);
|
|
545
|
-
expect(wrapper.vm.errorMsg).
|
|
547
|
+
expect(wrapper.vm.errorMsg).toContain(linuxErrorMessage);
|
|
546
548
|
expect(wrapper.vm.os).toBe('linux');
|
|
547
549
|
expect(testNodeDefinedOsParams.propsData.pod.os).toBe('linux');
|
|
548
550
|
expect(connect.mock.calls).toHaveLength(3);
|
|
@@ -552,13 +554,13 @@ describe('component: ContainerShell', () => {
|
|
|
552
554
|
eventMessage({ detail: { data: `3${ linuxErrorMessage }` } });
|
|
553
555
|
eventDisconnected();
|
|
554
556
|
|
|
555
|
-
expect(consoleError.mock.calls[0][0]).
|
|
556
|
-
expect(consoleError.mock.calls[1][0]).
|
|
557
|
-
expect(consoleError.mock.calls[2][0]).
|
|
557
|
+
expect(consoleError.mock.calls[0][0]).toContain(linuxErrorMessage);
|
|
558
|
+
expect(consoleError.mock.calls[1][0]).toContain(linuxErrorMessage);
|
|
559
|
+
expect(consoleError.mock.calls[2][0]).toContain(linuxErrorMessage);
|
|
558
560
|
expect(wrapper.vm.backupShells).toHaveLength(1);
|
|
559
561
|
expect(wrapper.vm.isOpen).toBe(false);
|
|
560
562
|
expect(wrapper.vm.isOpening).toBe(false);
|
|
561
|
-
expect(wrapper.vm.errorMsg).
|
|
563
|
+
expect(wrapper.vm.errorMsg).toContain(linuxErrorMessage);
|
|
562
564
|
expect(wrapper.vm.os).toBe('linux');
|
|
563
565
|
expect(testNodeDefinedOsParams.propsData.pod.os).toBe('linux');
|
|
564
566
|
// at some point we have to stop retying and if we're not burning through backup shells, there's a retry limit of 2 for a total of 3 attempts
|