@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.
- 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 +27 -11
- package/assets/translations/zh-hans.yaml +1 -1
- 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/ResourceList/index.vue +0 -1
- package/components/ResourceTable.vue +6 -1
- 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/nav/TopLevelMenu.helper.ts +546 -0
- package/components/nav/TopLevelMenu.vue +124 -159
- package/components/nav/__tests__/TopLevelMenu.test.ts +338 -326
- package/config/pagination-table-headers.js +4 -4
- package/config/product/explorer.js +2 -0
- package/config/router/routes.js +1 -1
- package/config/settings.ts +13 -1
- 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/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/provisioning.cattle.io.cluster.js +2 -10
- package/package.json +6 -6
- package/pages/about.vue +22 -0
- 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/scripts/extension/parse-tag-name +2 -0
- package/scripts/test-plugins-build.sh +1 -0
- 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 +18 -4
package/mixins/resource-fetch.js
CHANGED
|
@@ -31,11 +31,18 @@ export default {
|
|
|
31
31
|
perfConfig = DEFAULT_PERF_SETTING;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// Normally owner components supply `resource` and `inStore` as part of their data, however these are needed here before parent data runs
|
|
35
|
+
// So set up both here
|
|
36
|
+
const params = { ...this.$route.params };
|
|
37
|
+
const resource = params.resource || this.schema?.id; // Resource can either be on a page showing single list, or a page of a resource showing a list of another resource
|
|
38
|
+
const inStore = this.$store.getters['currentStore'](resource);
|
|
39
|
+
|
|
34
40
|
return {
|
|
41
|
+
inStore,
|
|
35
42
|
perfConfig,
|
|
36
43
|
init: false,
|
|
37
44
|
multipleResources: [],
|
|
38
|
-
loadResources: [
|
|
45
|
+
loadResources: [resource],
|
|
39
46
|
// manual refresh vars
|
|
40
47
|
hasManualRefresh: false,
|
|
41
48
|
watch: true,
|
|
@@ -60,17 +67,47 @@ export default {
|
|
|
60
67
|
}
|
|
61
68
|
},
|
|
62
69
|
|
|
70
|
+
props: {
|
|
71
|
+
/**
|
|
72
|
+
* Add additional filtering to the rows
|
|
73
|
+
*
|
|
74
|
+
* Should only be used when we have all results, otherwise we're filtering a page which already has been filtered...
|
|
75
|
+
*/
|
|
76
|
+
localFilter: {
|
|
77
|
+
type: Function,
|
|
78
|
+
default: null,
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Add additional filtering to the pagination api request
|
|
83
|
+
*/
|
|
84
|
+
apiFilter: {
|
|
85
|
+
type: Function,
|
|
86
|
+
default: null,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
|
|
63
90
|
computed: {
|
|
64
91
|
...mapGetters({ refreshFlag: 'resource-fetch/refreshFlag' }),
|
|
92
|
+
|
|
65
93
|
rows() {
|
|
66
94
|
const currResource = this.fetchedResourceType.find((item) => item.type === this.resource);
|
|
67
95
|
|
|
68
96
|
if (currResource) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
97
|
+
const rows = this.$store.getters[`${ currResource.currStore }/all`](this.resource);
|
|
98
|
+
|
|
99
|
+
if (this.canPaginate) {
|
|
100
|
+
if (this.havePaginated) {
|
|
101
|
+
return rows;
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
return this.localFilter ? this.localFilter(rows) : rows;
|
|
105
|
+
}
|
|
72
106
|
}
|
|
107
|
+
|
|
108
|
+
return [];
|
|
73
109
|
},
|
|
110
|
+
|
|
74
111
|
loading() {
|
|
75
112
|
if (this.canPaginate) {
|
|
76
113
|
return this.paginating;
|
|
@@ -86,7 +123,9 @@ export default {
|
|
|
86
123
|
if (this.init && neu) {
|
|
87
124
|
await this.$fetch();
|
|
88
125
|
if (this.canPaginate && this.fetchPageSecondaryResources) {
|
|
89
|
-
this.fetchPageSecondaryResources(
|
|
126
|
+
this.fetchPageSecondaryResources({
|
|
127
|
+
canPaginate: this.canPaginate, force: true, page: this.rows, pagResult: this.paginationResult
|
|
128
|
+
});
|
|
90
129
|
}
|
|
91
130
|
}
|
|
92
131
|
}
|
|
@@ -140,6 +179,10 @@ export default {
|
|
|
140
179
|
force: this.paginating !== null // Fix for manual refresh (before ripped out).
|
|
141
180
|
};
|
|
142
181
|
|
|
182
|
+
if (this.apiFilter) {
|
|
183
|
+
opt.paginating = this.apiFilter(opt.pagination);
|
|
184
|
+
}
|
|
185
|
+
|
|
143
186
|
this['paginating'] = true;
|
|
144
187
|
|
|
145
188
|
const that = this;
|
|
@@ -4,11 +4,12 @@ import HybridModel from '@shell/plugins/steve/hybrid-class';
|
|
|
4
4
|
import { notOnlyOfRole } from '@shell/models/cluster.x-k8s.io.machine';
|
|
5
5
|
|
|
6
6
|
export default class MgmtNodePool extends HybridModel {
|
|
7
|
-
get
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
get nodeTemplateId() {
|
|
8
|
+
return (this.spec?.nodeTemplateName || '').replace(/:/, '/');
|
|
9
|
+
}
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
get nodeTemplate() {
|
|
12
|
+
return this.$getters['byId'](MANAGEMENT.NODE_TEMPLATE, this.nodeTemplateId);
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
get provider() {
|
|
@@ -349,19 +349,11 @@ export default class ProvCluster extends SteveModel {
|
|
|
349
349
|
}
|
|
350
350
|
|
|
351
351
|
get mgmtClusterId() {
|
|
352
|
-
return this.
|
|
352
|
+
return this.status?.clusterName;
|
|
353
353
|
}
|
|
354
354
|
|
|
355
355
|
get mgmt() {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
if ( !name ) {
|
|
359
|
-
return null;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const out = this.$rootGetters['management/byId'](MANAGEMENT.CLUSTER, name);
|
|
363
|
-
|
|
364
|
-
return out;
|
|
356
|
+
return this.$rootGetters['management/byId'](MANAGEMENT.CLUSTER, this.mgmtClusterId);
|
|
365
357
|
}
|
|
366
358
|
|
|
367
359
|
get isReady() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rancher/shell",
|
|
3
|
-
"version": "3.0.1
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Rancher Dashboard Shell",
|
|
5
5
|
"repository": "https://github.com/rancherlabs/dashboard",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@popperjs/core": "2.4.4",
|
|
40
40
|
"@rancher/icons": "2.0.29",
|
|
41
41
|
"@types/is-url": "1.2.30",
|
|
42
|
-
"@types/node": "
|
|
42
|
+
"@types/node": "20.10.8",
|
|
43
43
|
"@types/semver": "^7.5.8",
|
|
44
44
|
"@typescript-eslint/eslint-plugin": "~5.4.0",
|
|
45
45
|
"@typescript-eslint/parser": "~5.4.0",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"color": "4.2.3",
|
|
61
61
|
"codemirror": ">=5.64.0 <6",
|
|
62
62
|
"codemirror-editor-vue3": "2.7.1",
|
|
63
|
-
"cookie": "0.
|
|
63
|
+
"cookie": "0.7.0",
|
|
64
64
|
"cookie-universal": "2.2.2",
|
|
65
65
|
"core-js": "3.25.3",
|
|
66
66
|
"cron-validator": "1.3.1",
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
"js-yaml": "4.1.0",
|
|
105
105
|
"js-yaml-loader": "1.2.2",
|
|
106
106
|
"jsdiff": "1.1.1",
|
|
107
|
-
"jsonpath-plus": "10.0.
|
|
107
|
+
"jsonpath-plus": "10.0.7",
|
|
108
108
|
"jsrsasign": "10.5.25",
|
|
109
109
|
"jszip": "3.8.0",
|
|
110
110
|
"lodash": "4.17.21",
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
"start-server-and-test": "1.13.1",
|
|
124
124
|
"style-loader": "1.2.1",
|
|
125
125
|
"ts-node": "8.10.2",
|
|
126
|
-
"typescript": "
|
|
126
|
+
"typescript": "5.6.3",
|
|
127
127
|
"ufo": "0.7.11",
|
|
128
128
|
"unfetch": "4.2.0",
|
|
129
129
|
"url-parse": "1.5.10",
|
|
@@ -160,7 +160,7 @@
|
|
|
160
160
|
"roarr": "7.0.4",
|
|
161
161
|
"semver": "7.5.4",
|
|
162
162
|
"@types/lodash": "4.17.5",
|
|
163
|
-
"@types/node": "
|
|
163
|
+
"@types/node": "20.10.8",
|
|
164
164
|
"@vue/cli-service/html-webpack-plugin": "^5.0.0"
|
|
165
165
|
},
|
|
166
166
|
"nyc": {
|
package/pages/about.vue
CHANGED
|
@@ -108,6 +108,9 @@ export default {
|
|
|
108
108
|
:to="{ name: 'diagnostic' }"
|
|
109
109
|
class="btn role-primary"
|
|
110
110
|
data-testid="about__diagnostics_button"
|
|
111
|
+
role="button"
|
|
112
|
+
:aria-label="t('about.diagnostic.title')"
|
|
113
|
+
@keyup.space="$router.push({ name: 'diagnostic' })"
|
|
111
114
|
>
|
|
112
115
|
{{ t('about.diagnostic.title') }}
|
|
113
116
|
</router-link>
|
|
@@ -126,6 +129,8 @@ export default {
|
|
|
126
129
|
href="https://github.com/rancher/rancher"
|
|
127
130
|
target="_blank"
|
|
128
131
|
rel="nofollow noopener noreferrer"
|
|
132
|
+
role="link"
|
|
133
|
+
:aria-label="t('about.versions.githubRepo', {name: t(`about.versions.rancher`) })"
|
|
129
134
|
>
|
|
130
135
|
{{ t("about.versions.rancher") }}
|
|
131
136
|
</a>
|
|
@@ -137,6 +142,8 @@ export default {
|
|
|
137
142
|
href="https://github.com/rancher/dashboard"
|
|
138
143
|
target="_blank"
|
|
139
144
|
rel="nofollow noopener noreferrer"
|
|
145
|
+
role="link"
|
|
146
|
+
:aria-label="t('about.versions.githubRepo', {name: t(`generic.dashboard`)})"
|
|
140
147
|
>
|
|
141
148
|
{{ t("generic.dashboard") }}
|
|
142
149
|
</a>
|
|
@@ -148,6 +155,8 @@ export default {
|
|
|
148
155
|
href="https://github.com/rancher/cli"
|
|
149
156
|
target="_blank"
|
|
150
157
|
rel="nofollow noopener noreferrer"
|
|
158
|
+
role="link"
|
|
159
|
+
:aria-label="t('about.versions.githubRepo', {name: t(`about.versions.cli`) })"
|
|
151
160
|
>
|
|
152
161
|
{{ appName }} {{ t("about.versions.cli") }}
|
|
153
162
|
</a>
|
|
@@ -159,6 +168,8 @@ export default {
|
|
|
159
168
|
href="https://github.com/rancher/helm"
|
|
160
169
|
target="_blank"
|
|
161
170
|
rel="nofollow noopener noreferrer"
|
|
171
|
+
role="link"
|
|
172
|
+
:aria-label="t('about.versions.githubRepo', {name: t(`about.versions.helm`) })"
|
|
162
173
|
>
|
|
163
174
|
{{ t("about.versions.helm") }}
|
|
164
175
|
</a>
|
|
@@ -170,6 +181,8 @@ export default {
|
|
|
170
181
|
href="https://github.com/rancher/machine"
|
|
171
182
|
target="_blank"
|
|
172
183
|
rel="nofollow noopener noreferrer"
|
|
184
|
+
role="link"
|
|
185
|
+
:aria-label="t('about.versions.githubRepo', {name: t(`about.versions.machine`) })"
|
|
173
186
|
>
|
|
174
187
|
{{ t("about.versions.machine") }}
|
|
175
188
|
</a>
|
|
@@ -178,9 +191,12 @@ export default {
|
|
|
178
191
|
</table>
|
|
179
192
|
<p class="pt-20">
|
|
180
193
|
<a
|
|
194
|
+
class="release-notes-link"
|
|
181
195
|
:href="releaseNotesUrl"
|
|
182
196
|
target="_blank"
|
|
183
197
|
rel="nofollow noopener noreferrer"
|
|
198
|
+
role="link"
|
|
199
|
+
:aria-label="t('about.versions.releaseNotes')"
|
|
184
200
|
>
|
|
185
201
|
{{ t('about.versions.releaseNotes') }}
|
|
186
202
|
</a>
|
|
@@ -202,8 +218,12 @@ export default {
|
|
|
202
218
|
<td>
|
|
203
219
|
<a
|
|
204
220
|
v-if="d.imageList"
|
|
221
|
+
tabindex="0"
|
|
205
222
|
:data-testid="`image_list_download_link__${d.label}`"
|
|
223
|
+
role="link"
|
|
224
|
+
:aria-label="t('about.versions.downloadImages', { listName: t(d.label) })"
|
|
206
225
|
@click="d.imageList"
|
|
226
|
+
@keyup.enter="d.imageList"
|
|
207
227
|
>
|
|
208
228
|
{{ t('asyncButton.download.action') }}
|
|
209
229
|
</a>
|
|
@@ -230,6 +250,8 @@ export default {
|
|
|
230
250
|
<a
|
|
231
251
|
v-if="d.cliLink"
|
|
232
252
|
:href="d.cliLink"
|
|
253
|
+
role="link"
|
|
254
|
+
:aria-label="t('about.versions.downloadCli', { os: t(d.label) })"
|
|
233
255
|
>{{ d.cliFile }}</a>
|
|
234
256
|
</td>
|
|
235
257
|
</tr>
|
|
@@ -99,7 +99,7 @@ describe('page: cluster dashboard', () => {
|
|
|
99
99
|
[STATES_ENUM.HEALTHY, 'icon-checkmark', true, false, false, [{ status: 'True' }], 1, 0],
|
|
100
100
|
]]
|
|
101
101
|
])('%p cluster - %p agent health box :', (_, agentId, isLocal, agentResources, statuses) => {
|
|
102
|
-
it.each(statuses)('should
|
|
102
|
+
it.each(statuses)('should NOT show %p status due to missing canList permissions', (status, iconClass, isLoaded, disconnected, error, conditions, readyReplicas, unavailableReplicas) => {
|
|
103
103
|
const options = clone(mountOptions);
|
|
104
104
|
|
|
105
105
|
options.global.mocks.$store.getters.currentCluster.isLocal = isLocal;
|
|
@@ -138,41 +138,48 @@ describe('page: cluster dashboard', () => {
|
|
|
138
138
|
|
|
139
139
|
describe.each([
|
|
140
140
|
['local', 'fleet', true, ['fleetDeployment', 'fleetStatefulSet'], [
|
|
141
|
-
[STATES_ENUM.IN_PROGRESS, 'icon-spinner', false, false, false, '', 0, 0],
|
|
142
|
-
[STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, false, [{ status: 'False' }], 0, 0],
|
|
143
|
-
[STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, true, [{ status: 'True' }], 0, 0],
|
|
144
|
-
[STATES_ENUM.WARNING, 'icon-warning', true, true, false, [{ status: 'True' }], 0, 0],
|
|
145
|
-
[STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 0],
|
|
146
|
-
[STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 1],
|
|
147
|
-
[STATES_ENUM.HEALTHY, 'icon-checkmark', true, false, false, [{ status: 'True' }], 1, 0],
|
|
141
|
+
[STATES_ENUM.IN_PROGRESS, 'icon-spinner', false, false, false, false, '', 0, 0],
|
|
142
|
+
[STATES_ENUM.UNHEALTHY, 'icon-warning', true, true, false, false, [{ status: 'False' }], 0, 0],
|
|
143
|
+
[STATES_ENUM.UNHEALTHY, 'icon-warning', true, true, false, true, [{ status: 'True' }], 0, 0],
|
|
144
|
+
[STATES_ENUM.WARNING, 'icon-warning', true, true, true, false, [{ status: 'True' }], 0, 0],
|
|
145
|
+
[STATES_ENUM.WARNING, 'icon-warning', true, true, false, false, [{ status: 'True' }], 0, 0],
|
|
146
|
+
[STATES_ENUM.WARNING, 'icon-warning', true, true, false, false, [{ status: 'True' }], 0, 1],
|
|
147
|
+
[STATES_ENUM.HEALTHY, 'icon-checkmark', false, true, false, false, [{ status: 'True' }], 1, 0],
|
|
148
148
|
]],
|
|
149
149
|
['downstream RKE2', 'fleet', false, ['fleetStatefulSet'], [
|
|
150
|
-
[STATES_ENUM.IN_PROGRESS, 'icon-spinner', false, false, false, '', 0, 0],
|
|
151
|
-
[STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, false, [{ status: 'False' }], 0, 0],
|
|
152
|
-
[STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, true, [{ status: 'True' }], 0, 0],
|
|
153
|
-
[STATES_ENUM.WARNING, 'icon-warning', true, true, false, [{ status: 'True' }], 0, 0],
|
|
154
|
-
[STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 0],
|
|
155
|
-
[STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 1],
|
|
156
|
-
[STATES_ENUM.HEALTHY, 'icon-checkmark', true, false, false, [{ status: 'True' }], 1, 0],
|
|
150
|
+
[STATES_ENUM.IN_PROGRESS, 'icon-spinner', false, false, false, false, '', 0, 0],
|
|
151
|
+
[STATES_ENUM.UNHEALTHY, 'icon-warning', true, true, false, false, [{ status: 'False' }], 0, 0],
|
|
152
|
+
[STATES_ENUM.UNHEALTHY, 'icon-warning', true, true, false, true, [{ status: 'True' }], 0, 0],
|
|
153
|
+
[STATES_ENUM.WARNING, 'icon-warning', true, true, true, false, [{ status: 'True' }], 0, 0],
|
|
154
|
+
[STATES_ENUM.WARNING, 'icon-warning', true, true, false, false, [{ status: 'True' }], 0, 0],
|
|
155
|
+
[STATES_ENUM.WARNING, 'icon-warning', true, true, false, false, [{ status: 'True' }], 0, 1],
|
|
156
|
+
[STATES_ENUM.HEALTHY, 'icon-checkmark', false, true, false, false, [{ status: 'True' }], 1, 0],
|
|
157
157
|
]],
|
|
158
158
|
['downstream RKE2', 'cattle', false, ['cattleDeployment'], [
|
|
159
|
-
[STATES_ENUM.IN_PROGRESS, 'icon-spinner', false, false, false, '', 0, 0],
|
|
160
|
-
[STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, false, [{ status: 'False' }], 0, 0],
|
|
161
|
-
[STATES_ENUM.UNHEALTHY, 'icon-warning', true, true, false, [{ status: 'True' }], 0, 0],
|
|
162
|
-
[STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, true, [{ status: 'True' }], 0, 0],
|
|
163
|
-
[STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 0],
|
|
164
|
-
[STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 1],
|
|
165
|
-
[STATES_ENUM.HEALTHY, 'icon-checkmark', true, false, false, [{ status: 'True' }], 1, 0],
|
|
159
|
+
[STATES_ENUM.IN_PROGRESS, 'icon-spinner', false, false, false, false, '', 0, 0],
|
|
160
|
+
[STATES_ENUM.UNHEALTHY, 'icon-warning', true, true, false, false, [{ status: 'False' }], 0, 0],
|
|
161
|
+
[STATES_ENUM.UNHEALTHY, 'icon-warning', true, true, true, false, [{ status: 'True' }], 0, 0],
|
|
162
|
+
[STATES_ENUM.UNHEALTHY, 'icon-warning', true, true, false, true, [{ status: 'True' }], 0, 0],
|
|
163
|
+
[STATES_ENUM.WARNING, 'icon-warning', true, true, false, false, [{ status: 'True' }], 0, 0],
|
|
164
|
+
[STATES_ENUM.WARNING, 'icon-warning', true, true, false, false, [{ status: 'True' }], 0, 1],
|
|
165
|
+
[STATES_ENUM.HEALTHY, 'icon-checkmark', false, true, false, false, [{ status: 'True' }], 1, 0],
|
|
166
166
|
]]
|
|
167
167
|
])('%p cluster - %p agent health box ::', (_, agentId, isLocal, agentResources, statuses) => {
|
|
168
|
-
it.each(statuses)('should show %p status', (status, iconClass, isLoaded, disconnected, error, conditions, readyReplicas, unavailableReplicas) => {
|
|
168
|
+
it.each(statuses)('should show %p status', async(status, iconClass, clickable, isLoaded, disconnected, error, conditions, readyReplicas, unavailableReplicas) => {
|
|
169
|
+
let agentRoute = null;
|
|
170
|
+
|
|
169
171
|
const options = clone(mountOptions);
|
|
170
172
|
|
|
171
173
|
options.global.mocks.$store.getters.currentCluster.isLocal = isLocal;
|
|
172
174
|
|
|
173
|
-
// let's pass the canList now
|
|
174
175
|
options.global.mocks.$store.getters['cluster/canList'] = (type: string) => !!(type === WORKLOAD_TYPES.DEPLOYMENT) || !!(type === WORKLOAD_TYPES.STATEFUL_SET);
|
|
175
176
|
|
|
177
|
+
options.global.mocks.$router = {
|
|
178
|
+
push: (route: any) => {
|
|
179
|
+
agentRoute = route;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
176
183
|
const resources = agentResources.reduce((acc, r) => {
|
|
177
184
|
const agent = {
|
|
178
185
|
metadata: { state: { error } },
|
|
@@ -204,7 +211,12 @@ describe('page: cluster dashboard', () => {
|
|
|
204
211
|
|
|
205
212
|
expect(box.element).toBeDefined();
|
|
206
213
|
expect(box.element.classList).toContain(status);
|
|
214
|
+
expect(!!(box.element as any).$_popper).toBe(clickable);
|
|
207
215
|
expect(icon.element.classList).toContain(iconClass);
|
|
216
|
+
|
|
217
|
+
await box.trigger('click');
|
|
218
|
+
|
|
219
|
+
expect(!!agentRoute).toBe(clickable);
|
|
208
220
|
});
|
|
209
221
|
});
|
|
210
222
|
|
|
@@ -125,6 +125,13 @@ export default {
|
|
|
125
125
|
ROLES,
|
|
126
126
|
];
|
|
127
127
|
|
|
128
|
+
const clusterServiceIcons = {
|
|
129
|
+
[STATES_ENUM.IN_PROGRESS]: 'icon-spinner icon-spin',
|
|
130
|
+
[STATES_ENUM.HEALTHY]: 'icon-checkmark',
|
|
131
|
+
[STATES_ENUM.WARNING]: 'icon-warning',
|
|
132
|
+
[STATES_ENUM.UNHEALTHY]: 'icon-warning',
|
|
133
|
+
};
|
|
134
|
+
|
|
128
135
|
return {
|
|
129
136
|
nodeHeaders,
|
|
130
137
|
constraints: [],
|
|
@@ -148,6 +155,7 @@ export default {
|
|
|
148
155
|
clusterCounts,
|
|
149
156
|
selectedTab: 'cluster-events',
|
|
150
157
|
extensionCards: getApplicableExtensionEnhancements(this, ExtensionPoint.CARD, CardLocation.CLUSTER_DASHBOARD_CARD, this.$route),
|
|
158
|
+
clusterServiceIcons,
|
|
151
159
|
};
|
|
152
160
|
},
|
|
153
161
|
|
|
@@ -283,52 +291,51 @@ export default {
|
|
|
283
291
|
clusterServices() {
|
|
284
292
|
const services = [];
|
|
285
293
|
|
|
286
|
-
CLUSTER_COMPONENTS.forEach((
|
|
294
|
+
CLUSTER_COMPONENTS.forEach((name) => {
|
|
295
|
+
const component = this.getComponentStatus(name);
|
|
296
|
+
|
|
287
297
|
services.push({
|
|
288
|
-
name
|
|
289
|
-
status:
|
|
290
|
-
labelKey: `clusterIndexPage.sections.componentStatus.${
|
|
298
|
+
name,
|
|
299
|
+
status: component.state,
|
|
300
|
+
labelKey: `clusterIndexPage.sections.componentStatus.component.${ name }.label`,
|
|
301
|
+
icon: this.clusterServiceIcons[component.state],
|
|
302
|
+
tooltip: component.tooltip,
|
|
303
|
+
goTo: () => null,
|
|
291
304
|
});
|
|
292
305
|
});
|
|
293
306
|
|
|
294
307
|
if (this.cattleAgentNamespace) {
|
|
295
308
|
services.push({
|
|
296
309
|
name: 'cattle',
|
|
297
|
-
status: this.
|
|
298
|
-
labelKey: 'clusterIndexPage.sections.componentStatus.cattle',
|
|
310
|
+
status: this.cattleAgent.state,
|
|
311
|
+
labelKey: 'clusterIndexPage.sections.componentStatus.component.cattle.label',
|
|
312
|
+
icon: this.clusterServiceIcons[this.cattleAgent.state],
|
|
313
|
+
tooltip: this.cattleAgent.tooltip,
|
|
314
|
+
goTo: () => this.goToClusterService(this.cattleAgent),
|
|
299
315
|
});
|
|
300
316
|
}
|
|
301
317
|
|
|
302
318
|
if (this.fleetAgentNamespace) {
|
|
303
319
|
services.push({
|
|
304
320
|
name: 'fleet',
|
|
305
|
-
status: this.
|
|
306
|
-
labelKey: 'clusterIndexPage.sections.componentStatus.fleet',
|
|
321
|
+
status: this.fleetAgent.state,
|
|
322
|
+
labelKey: 'clusterIndexPage.sections.componentStatus.component.fleet.label',
|
|
323
|
+
icon: this.clusterServiceIcons[this.fleetAgent.state],
|
|
324
|
+
tooltip: this.fleetAgent.tooltip,
|
|
325
|
+
goTo: () => this.goToClusterService(this.fleetAgent),
|
|
307
326
|
});
|
|
308
327
|
}
|
|
309
328
|
|
|
310
329
|
return services;
|
|
311
330
|
},
|
|
312
331
|
|
|
313
|
-
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
if (resource === 'loading') {
|
|
317
|
-
return STATES_ENUM.IN_PROGRESS;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
if (!resource || this.disconnected || resource.status.conditions?.find((c) => c.status !== 'True') || resource.metadata.state?.error) {
|
|
321
|
-
return STATES_ENUM.UNHEALTHY;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
if (resource.spec.replicas !== resource.status.readyReplicas || resource.status.unavailableReplicas > 0) {
|
|
325
|
-
return STATES_ENUM.WARNING;
|
|
326
|
-
}
|
|
332
|
+
cattleAgent() {
|
|
333
|
+
const resources = [this.cattleDeployment];
|
|
327
334
|
|
|
328
|
-
return
|
|
335
|
+
return this.getAgentStatus(resources, { checkDisconnected: true });
|
|
329
336
|
},
|
|
330
337
|
|
|
331
|
-
|
|
338
|
+
fleetAgent() {
|
|
332
339
|
const resources = this.currentCluster.isLocal ? [
|
|
333
340
|
/**
|
|
334
341
|
* 'fleetStatefulSet' could take a while to be created by rancher.
|
|
@@ -339,23 +346,7 @@ export default {
|
|
|
339
346
|
this.fleetStatefulSet
|
|
340
347
|
];
|
|
341
348
|
|
|
342
|
-
|
|
343
|
-
return STATES_ENUM.IN_PROGRESS;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
for (const resource of resources) {
|
|
347
|
-
if (!resource || resource.status.conditions?.find((c) => c.status !== 'True') || resource.metadata.state?.error) {
|
|
348
|
-
return STATES_ENUM.UNHEALTHY;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
for (const resource of resources) {
|
|
353
|
-
if (resource.spec.replicas !== resource.status.readyReplicas || resource.status.unavailableReplicas > 0) {
|
|
354
|
-
return STATES_ENUM.WARNING;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
return STATES_ENUM.HEALTHY;
|
|
349
|
+
return this.getAgentStatus(resources);
|
|
359
350
|
},
|
|
360
351
|
|
|
361
352
|
totalCountGaugeInput() {
|
|
@@ -532,25 +523,61 @@ export default {
|
|
|
532
523
|
}
|
|
533
524
|
},
|
|
534
525
|
|
|
526
|
+
getAgentStatus(resources, opt = { checkDisconnected: false }) {
|
|
527
|
+
if (resources.find((resource) => resource === 'loading')) {
|
|
528
|
+
return { state: STATES_ENUM.IN_PROGRESS };
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
for (const resource of resources) {
|
|
532
|
+
if (
|
|
533
|
+
!resource ||
|
|
534
|
+
(opt.checkDisconnected && this.disconnected) || // cattle
|
|
535
|
+
resource.status.conditions?.find((c) => c.status !== 'True') ||
|
|
536
|
+
resource.metadata.state?.error
|
|
537
|
+
) {
|
|
538
|
+
return {
|
|
539
|
+
resource,
|
|
540
|
+
tooltip: resource?.stateDescription || this.t(`clusterIndexPage.sections.componentStatus.tooltip.disconnected`),
|
|
541
|
+
state: STATES_ENUM.UNHEALTHY,
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
for (const resource of resources) {
|
|
547
|
+
if (resource.spec.replicas !== resource.status.readyReplicas || resource.status.unavailableReplicas > 0) {
|
|
548
|
+
return {
|
|
549
|
+
resource,
|
|
550
|
+
tooltip: resource?.stateDescription || this.t(`clusterIndexPage.sections.componentStatus.tooltip.unavailableReplicas`),
|
|
551
|
+
state: STATES_ENUM.WARNING,
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return { state: STATES_ENUM.HEALTHY };
|
|
557
|
+
},
|
|
558
|
+
|
|
535
559
|
getComponentStatus(field) {
|
|
536
560
|
const matching = (this.currentCluster?.status?.componentStatuses || []).filter((s) => s.name.startsWith(field));
|
|
537
561
|
|
|
538
562
|
// If there's no matching component status, it's "healthy"
|
|
539
563
|
if ( !matching.length ) {
|
|
540
|
-
return STATES_ENUM.HEALTHY;
|
|
564
|
+
return { state: STATES_ENUM.HEALTHY };
|
|
541
565
|
}
|
|
542
566
|
|
|
543
|
-
const
|
|
544
|
-
const
|
|
567
|
+
const errorConditions = matching.reduce((acc, status) => {
|
|
568
|
+
const condition = status.conditions.find((c) => c.status !== 'True');
|
|
545
569
|
|
|
546
|
-
return !
|
|
547
|
-
},
|
|
570
|
+
return !condition ? acc : [...acc, condition];
|
|
571
|
+
}, []);
|
|
548
572
|
|
|
549
|
-
if (
|
|
550
|
-
return
|
|
573
|
+
if (errorConditions.length > 0) {
|
|
574
|
+
return {
|
|
575
|
+
tooltip: errorConditions[0].message,
|
|
576
|
+
state: STATES_ENUM.UNHEALTHY
|
|
577
|
+
};
|
|
551
578
|
}
|
|
552
579
|
|
|
553
|
-
return STATES_ENUM.HEALTHY;
|
|
580
|
+
return { state: STATES_ENUM.HEALTHY };
|
|
554
581
|
},
|
|
555
582
|
|
|
556
583
|
showActions() {
|
|
@@ -578,6 +605,23 @@ export default {
|
|
|
578
605
|
await provCluster.goToHarvesterCluster();
|
|
579
606
|
} catch {
|
|
580
607
|
}
|
|
608
|
+
},
|
|
609
|
+
|
|
610
|
+
goToClusterService(agent) {
|
|
611
|
+
if (!agent.resource || agent.state === STATES_ENUM.HEALTHY) {
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
this.$router.push({
|
|
616
|
+
name: 'c-cluster-product-resource-namespace-id',
|
|
617
|
+
params: {
|
|
618
|
+
cluster: this.currentCluster.id,
|
|
619
|
+
product: 'explorer',
|
|
620
|
+
resource: agent.resource.type,
|
|
621
|
+
namespace: agent.resource.metadata.namespace,
|
|
622
|
+
id: agent.resource.metadata.name,
|
|
623
|
+
}
|
|
624
|
+
});
|
|
581
625
|
}
|
|
582
626
|
},
|
|
583
627
|
};
|
|
@@ -722,21 +766,15 @@ export default {
|
|
|
722
766
|
<div
|
|
723
767
|
v-for="(service, i) in clusterServices"
|
|
724
768
|
:key="i"
|
|
769
|
+
v-clean-tooltip="service.tooltip"
|
|
725
770
|
class="k8s-service-status"
|
|
726
771
|
:class="{[service.status]: true }"
|
|
727
772
|
:data-testid="`k8s-service-${ service.name }`"
|
|
773
|
+
@click="service.goTo"
|
|
728
774
|
>
|
|
729
775
|
<i
|
|
730
|
-
|
|
731
|
-
class="icon
|
|
732
|
-
/>
|
|
733
|
-
<i
|
|
734
|
-
v-else-if="service.status === STATES_ENUM.HEALTHY"
|
|
735
|
-
class="icon icon-checkmark"
|
|
736
|
-
/>
|
|
737
|
-
<i
|
|
738
|
-
v-else
|
|
739
|
-
class="icon icon-warning"
|
|
776
|
+
class="icon"
|
|
777
|
+
:class="service.icon"
|
|
740
778
|
/>
|
|
741
779
|
<div class="label">
|
|
742
780
|
{{ t(service.labelKey) }}
|
|
@@ -948,6 +986,7 @@ export default {
|
|
|
948
986
|
|
|
949
987
|
&.unhealthy {
|
|
950
988
|
border-color: var(--error-border);
|
|
989
|
+
cursor: pointer;
|
|
951
990
|
|
|
952
991
|
> I {
|
|
953
992
|
color: var(--error)
|
|
@@ -955,6 +994,8 @@ export default {
|
|
|
955
994
|
}
|
|
956
995
|
|
|
957
996
|
&.warning {
|
|
997
|
+
cursor: pointer;
|
|
998
|
+
|
|
958
999
|
> I {
|
|
959
1000
|
color: var(--warning)
|
|
960
1001
|
}
|