@rancher/shell 3.0.2-rc.2 → 3.0.2-rc.3
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/styles/base/_basic.scss +5 -7
- package/assets/styles/global/_button.scss +10 -0
- package/assets/styles/global/_tooltip.scss +2 -2
- package/assets/styles/themes/_dark.scss +14 -2
- package/assets/styles/themes/_light.scss +7 -2
- package/assets/styles/vendor/vue-select.scss +4 -0
- package/assets/translations/en-us.yaml +44 -5
- package/components/BannerGraphic.vue +0 -42
- package/components/ButtonMultiAction.vue +1 -1
- package/components/Carousel.vue +36 -29
- package/components/CommunityLinks.vue +6 -1
- package/components/GrowlManager.vue +9 -2
- package/components/LocaleSelector.vue +8 -1
- package/components/PaginatedResourceTable.vue +4 -7
- package/components/ProgressBarMulti.vue +14 -0
- package/components/Questions/Reference.vue +57 -28
- package/components/SelectIconGrid.vue +12 -1
- package/components/SideNav.vue +12 -38
- package/components/SortableTable/index.vue +1 -0
- package/components/Tabbed/index.vue +12 -1
- package/components/YamlEditor.vue +1 -0
- package/components/auth/Principal.vue +5 -3
- package/components/fleet/FleetClusters.vue +82 -1
- package/components/fleet/FleetRepos.vue +13 -30
- package/components/fleet/ForceDirectedTreeChart/index.vue +2 -2
- package/components/form/ChangePassword.vue +2 -0
- package/components/form/ColorInput.vue +24 -1
- package/components/form/FileSelector.vue +2 -0
- package/components/form/KeyValue.vue +230 -160
- package/components/form/LabeledSelect.vue +1 -1
- package/components/form/PlusMinus.vue +14 -2
- package/components/form/ResourceLabeledSelect.vue +13 -53
- package/components/form/ResourceSelector.vue +1 -0
- package/components/form/ResourceTabs/index.vue +79 -36
- package/components/form/SecretSelector.vue +2 -2
- package/components/form/__tests__/KeyValue.test.ts +1 -1
- package/components/formatter/FleetClusterSummaryGraph.vue +2 -2
- package/components/formatter/FleetSummaryGraph.vue +6 -7
- package/components/formatter/WorkloadHealthScale.vue +7 -0
- package/components/nav/Group.vue +30 -4
- package/components/nav/Header.vue +82 -114
- package/components/nav/HeaderPageActionMenu.vue +27 -131
- package/components/nav/NamespaceFilter.vue +1 -1
- package/components/nav/Type.vue +15 -0
- package/config/home-links.js +21 -13
- package/config/labels-annotations.js +2 -0
- package/config/page-actions.js +1 -0
- package/config/pagination-table-headers.js +15 -1
- package/config/product/explorer.js +7 -17
- package/config/table-headers.js +6 -0
- package/config/version.js +5 -1
- package/core/plugin.ts +41 -1
- package/core/plugins.js +125 -72
- package/core/types-provisioning.ts +91 -2
- package/core/types.ts +55 -0
- package/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts +12 -3
- package/detail/catalog.cattle.io.app.vue +1 -1
- package/detail/fleet.cattle.io.cluster.vue +3 -3
- package/detail/namespace.vue +13 -19
- package/detail/networking.k8s.io.ingress.vue +13 -53
- package/detail/provisioning.cattle.io.cluster.vue +12 -1
- package/detail/workload/index.vue +3 -3
- package/dialog/AddCustomBadgeDialog.vue +5 -1
- package/edit/auth/ldap/__tests__/config.test.ts +18 -0
- package/edit/auth/ldap/config.vue +24 -0
- package/edit/auth/saml.vue +8 -6
- package/edit/fleet.cattle.io.gitrepo.vue +7 -1
- package/edit/logging-flow/index.vue +4 -19
- package/edit/networking.k8s.io.ingress/index.vue +18 -65
- package/edit/networking.k8s.io.networkpolicy/index.vue +4 -5
- package/edit/provisioning.cattle.io.cluster/index.vue +13 -1
- package/edit/provisioning.cattle.io.cluster/rke2.vue +31 -115
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +2 -2
- package/edit/provisioning.cattle.io.cluster/tabs/networking/ACE.vue +14 -28
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +25 -12
- package/edit/service.vue +1 -2
- package/list/networking.k8s.io.ingress.vue +1 -1
- package/list/node.vue +15 -8
- package/list/persistentvolume.vue +12 -4
- package/list/service.vue +1 -1
- package/list/workload.vue +4 -0
- package/mixins/chart.js +4 -1
- package/models/catalog.cattle.io.app.js +3 -1
- package/models/catalog.cattle.io.clusterrepo.js +56 -7
- package/models/fleet.cattle.io.bundle.js +0 -11
- package/models/fleet.cattle.io.cluster.js +17 -1
- package/models/fleet.cattle.io.gitrepo.js +86 -50
- package/models/provisioning.cattle.io.cluster.js +47 -2
- package/models/service.js +1 -0
- package/models/workload.js +19 -1
- package/package.json +5 -4
- package/pages/c/_cluster/apps/charts/index.vue +4 -0
- package/pages/c/_cluster/explorer/ConfigBadge.vue +8 -7
- package/pages/c/_cluster/explorer/index.vue +13 -6
- package/pages/c/_cluster/fleet/GitRepoGraphConfig.js +3 -3
- package/pages/c/_cluster/fleet/index.vue +75 -89
- package/pages/c/_cluster/settings/links.vue +2 -2
- package/pages/diagnostic.vue +17 -15
- package/pages/home.vue +32 -6
- package/plugins/clean-html.js +50 -0
- package/plugins/dashboard-store/resource-class.js +4 -0
- package/plugins/plugin.js +54 -49
- package/plugins/steve/mutations.js +1 -1
- package/plugins/steve/steve-class.js +8 -0
- package/plugins/steve/steve-pagination-utils.ts +3 -1
- package/rancher-components/Accordion/Accordion.vue +4 -4
- package/rancher-components/BadgeState/BadgeState.vue +7 -0
- package/rancher-components/Card/Card.vue +27 -1
- package/rancher-components/Form/Checkbox/Checkbox.vue +9 -2
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +18 -1
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +18 -1
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +39 -2
- package/rancher-components/RcButton/RcButton.vue +90 -0
- package/rancher-components/RcButton/index.ts +2 -0
- package/rancher-components/RcButton/types.ts +17 -0
- package/rancher-components/RcDropdown/RcDropdown.vue +111 -0
- package/rancher-components/RcDropdown/RcDropdownItem.vue +127 -0
- package/rancher-components/RcDropdown/RcDropdownSeparator.vue +6 -0
- package/rancher-components/RcDropdown/RcDropdownTrigger.vue +43 -0
- package/rancher-components/RcDropdown/index.ts +4 -0
- package/rancher-components/RcDropdown/types.ts +22 -0
- package/rancher-components/RcDropdown/useDropdownCollection.ts +45 -0
- package/rancher-components/RcDropdown/useDropdownContext.ts +83 -0
- package/scripts/test-plugins-build.sh +2 -0
- package/scripts/typegen.sh +2 -0
- package/store/catalog.js +1 -1
- package/tsconfig.json +2 -1
- package/types/components/paginatedResourceTable.ts +25 -0
- package/types/components/resourceLabeledSelect.ts +48 -0
- package/types/resources/fleet.d.ts +17 -0
- package/types/shell/index.d.ts +61 -0
- package/utils/auth.js +5 -1
- package/utils/cluster.js +106 -0
- package/utils/fleet.ts +35 -3
- package/utils/ingress.ts +64 -0
- package/utils/uiplugins.ts +56 -44
- package/utils/validators/cron-schedule.js +7 -2
- package/utils/validators/formRules/__tests__/index.test.ts +53 -17
- package/utils/validators/formRules/index.ts +20 -5
- package/vue.config.js +1 -1
- package/components/RelatedWorkloadsTable.vue +0 -50
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
import { mapState } from 'vuex';
|
|
3
3
|
import { FLEET } from '@shell/config/types';
|
|
4
4
|
import { WORKSPACE } from '@shell/store/prefs';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
getStateLabel,
|
|
7
|
+
primaryDisplayStatusFromCount,
|
|
8
|
+
STATES,
|
|
9
|
+
STATES_ENUM,
|
|
10
|
+
} from '@shell/plugins/dashboard-store/resource-class';
|
|
6
11
|
import Loading from '@shell/components/Loading';
|
|
7
12
|
import CollapsibleCard from '@shell/components/CollapsibleCard.vue';
|
|
8
13
|
import ResourceTable from '@shell/components/ResourceTable';
|
|
@@ -37,10 +42,15 @@ export default {
|
|
|
37
42
|
inStoreType: 'management',
|
|
38
43
|
type: FLEET.CLUSTER_GROUP
|
|
39
44
|
},
|
|
45
|
+
allBundleDeployments: {
|
|
46
|
+
inStoreType: 'management',
|
|
47
|
+
type: FLEET.BUNDLE_DEPLOYMENT,
|
|
48
|
+
},
|
|
40
49
|
allBundles: {
|
|
41
50
|
inStoreType: 'management',
|
|
42
51
|
type: FLEET.BUNDLE,
|
|
43
52
|
opt: { excludeFields: ['metadata.managedFields', 'spec.resources'] },
|
|
53
|
+
skipWait: true,
|
|
44
54
|
},
|
|
45
55
|
gitRepos: {
|
|
46
56
|
inStoreType: 'management',
|
|
@@ -98,7 +108,6 @@ export default {
|
|
|
98
108
|
}
|
|
99
109
|
],
|
|
100
110
|
schema: {},
|
|
101
|
-
allBundles: [],
|
|
102
111
|
gitRepos: [],
|
|
103
112
|
fleetWorkspacesData: [],
|
|
104
113
|
isCollapsed: {},
|
|
@@ -138,14 +147,24 @@ export default {
|
|
|
138
147
|
});
|
|
139
148
|
},
|
|
140
149
|
workspacesData() {
|
|
141
|
-
return this.fleetWorkspaces.filter((ws) => ws.
|
|
150
|
+
return this.fleetWorkspaces.filter((ws) => ws.counts.gitRepos > 0);
|
|
142
151
|
},
|
|
143
152
|
emptyWorkspaces() {
|
|
144
|
-
return this.fleetWorkspaces.filter((ws) =>
|
|
153
|
+
return this.fleetWorkspaces.filter((ws) => ws.counts.gitRepos === 0);
|
|
145
154
|
},
|
|
146
155
|
areAllCardsExpanded() {
|
|
147
156
|
return Object.keys(this.isCollapsed).every((key) => !this.isCollapsed[key]);
|
|
148
|
-
}
|
|
157
|
+
},
|
|
158
|
+
gitReposCounts() {
|
|
159
|
+
return this.gitRepos.reduce((prev, gitRepo) => {
|
|
160
|
+
prev[gitRepo.id] = {
|
|
161
|
+
bundles: gitRepo.allBundlesStatuses,
|
|
162
|
+
resources: gitRepo.allResourceStatuses,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
return prev;
|
|
166
|
+
}, {});
|
|
167
|
+
},
|
|
149
168
|
},
|
|
150
169
|
methods: {
|
|
151
170
|
setWorkspaceFilterAndLinkToGitRepo(value) {
|
|
@@ -160,108 +179,71 @@ export default {
|
|
|
160
179
|
},
|
|
161
180
|
});
|
|
162
181
|
},
|
|
163
|
-
getStatusInfo(area, row) {
|
|
182
|
+
getStatusInfo(area, row, rowCounts) {
|
|
164
183
|
const defaultStatusInfo = {
|
|
165
184
|
badgeClass: `${ STATES[STATES_ENUM.NOT_READY].color } badge-class-default`,
|
|
166
185
|
icon: STATES[STATES_ENUM.NOT_READY].compoundIcon
|
|
167
186
|
};
|
|
168
187
|
|
|
169
188
|
// classes are defined in the themes SASS files...
|
|
170
|
-
return this.getBadgeClassAndIcon(area, row) || defaultStatusInfo;
|
|
189
|
+
return this.getBadgeClassAndIcon(area, row, rowCounts) || defaultStatusInfo;
|
|
171
190
|
},
|
|
172
|
-
getBadgeClassAndIcon(area, row) {
|
|
173
|
-
let group;
|
|
174
|
-
|
|
191
|
+
getBadgeClassAndIcon(area, row, rowCounts) {
|
|
175
192
|
if (!this.admissableAreas.includes(area)) {
|
|
176
193
|
return false;
|
|
177
194
|
}
|
|
178
195
|
|
|
196
|
+
let group;
|
|
197
|
+
|
|
179
198
|
if (area === 'clusters') {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
badgeClass: STATES[STATES_ENUM.ACTIVE].color,
|
|
183
|
-
icon: STATES[STATES_ENUM.ACTIVE].compoundIcon
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
} else if (area === 'bundles') {
|
|
187
|
-
group = row.bundles;
|
|
188
|
-
} else if (area === 'resources') {
|
|
189
|
-
group = row.status?.resources;
|
|
190
|
-
}
|
|
199
|
+
const clusterInfo = row.clusterInfo;
|
|
200
|
+
const state = clusterInfo.ready === clusterInfo.total ? STATES_ENUM.ACTIVE : STATES_ENUM.NOT_READY;
|
|
191
201
|
|
|
192
|
-
if (group?.length && group?.every((item) => item.state?.toLowerCase() === STATES_ENUM.ACTIVE)) {
|
|
193
|
-
return {
|
|
194
|
-
badgeClass: STATES[STATES_ENUM.ACTIVE].color ? STATES[STATES_ENUM.ACTIVE].color : `${ STATES[STATES_ENUM.UNKNOWN].color } bg-unmapped-state`,
|
|
195
|
-
icon: STATES[STATES_ENUM.ACTIVE].compoundIcon ? STATES[STATES_ENUM.ACTIVE].compoundIcon : `${ STATES[STATES_ENUM.UNKNOWN].compoundIcon } unmapped-icon`
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
if (group?.length && group?.some((item) => item.state?.toLowerCase() === STATES_ENUM.ERR_APPLIED)) {
|
|
199
202
|
return {
|
|
200
|
-
badgeClass:
|
|
201
|
-
icon: STATES[
|
|
203
|
+
badgeClass: `${ STATES[state].color } badge-class-area-${ area }`,
|
|
204
|
+
icon: STATES[state].compoundIcon
|
|
202
205
|
};
|
|
206
|
+
} else if (area === 'bundles') {
|
|
207
|
+
group = rowCounts[row.id].bundles;
|
|
208
|
+
} else if (area === 'resources') {
|
|
209
|
+
group = rowCounts[row.id].resources;
|
|
210
|
+
} else {
|
|
211
|
+
// unreachable
|
|
212
|
+
return false;
|
|
203
213
|
}
|
|
204
|
-
|
|
214
|
+
|
|
215
|
+
if (group.total === group.states.ready) {
|
|
205
216
|
return {
|
|
206
|
-
badgeClass: STATES[STATES_ENUM.
|
|
207
|
-
icon: STATES[STATES_ENUM.
|
|
217
|
+
badgeClass: STATES[STATES_ENUM.ACTIVE].color,
|
|
218
|
+
icon: STATES[STATES_ENUM.ACTIVE].compoundIcon,
|
|
208
219
|
};
|
|
209
220
|
}
|
|
210
|
-
|
|
211
|
-
if (area === 'resources') {
|
|
212
|
-
if (row.status?.resourceCounts?.desiredReady === row.status?.resourceCounts?.ready && row.status?.resourceCounts?.desiredReady) {
|
|
213
|
-
return {
|
|
214
|
-
badgeClass: STATES[STATES_ENUM.ACTIVE].color,
|
|
215
|
-
icon: STATES[STATES_ENUM.ACTIVE].compoundIcon
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
}
|
|
221
|
+
const state = primaryDisplayStatusFromCount(group.states);
|
|
219
222
|
|
|
220
223
|
return {
|
|
221
|
-
badgeClass: `${ STATES[STATES_ENUM.
|
|
222
|
-
icon: STATES[STATES_ENUM.
|
|
224
|
+
badgeClass: STATES[state].color ? STATES[state].color : `${ STATES[STATES_ENUM.UNKNOWN].color } bg-unmapped-state`,
|
|
225
|
+
icon: STATES[state].compoundIcon ? STATES[state].compoundIcon : `${ STATES[STATES_ENUM.UNKNOWN].compoundIcon } unmapped-icon`
|
|
223
226
|
};
|
|
224
227
|
},
|
|
225
|
-
getTooltipInfo(area, row) {
|
|
226
|
-
let group;
|
|
227
|
-
|
|
228
|
+
getTooltipInfo(area, row, rowCounts) {
|
|
228
229
|
if (!this.admissableAreas.includes(area)) {
|
|
229
230
|
return {};
|
|
230
231
|
}
|
|
231
232
|
|
|
232
|
-
if (area === '
|
|
233
|
-
|
|
234
|
-
} else if (area === 'bundles') {
|
|
235
|
-
group = row.bundles;
|
|
233
|
+
if (area === 'bundles') {
|
|
234
|
+
return this.generateTooltipData(rowCounts[row.id].bundles.states);
|
|
236
235
|
} else if (area === 'resources') {
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (group?.length) {
|
|
241
|
-
return this.generateTooltipData(group);
|
|
236
|
+
return this.generateTooltipData(rowCounts[row.id].resources.states);
|
|
242
237
|
}
|
|
243
238
|
|
|
244
239
|
return '';
|
|
245
240
|
},
|
|
246
|
-
generateTooltipData(
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
data.forEach((item) => {
|
|
251
|
-
if (!infoObj[item.state]) {
|
|
252
|
-
infoObj[item.state] = 0;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
infoObj[item.state]++;
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
Object.keys(infoObj).forEach((key) => {
|
|
259
|
-
tooltipData += `${ getStateLabel(key) }: ${ infoObj[key] }<br>`;
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
return tooltipData;
|
|
241
|
+
generateTooltipData(infoObj) {
|
|
242
|
+
return Object.keys(infoObj)
|
|
243
|
+
.filter((key) => infoObj[key] > 0) // filter zero values
|
|
244
|
+
.map((key) => `${ getStateLabel(key) }: ${ infoObj[key] }<br>`).join('');
|
|
263
245
|
},
|
|
264
|
-
getBadgeValue(area, row) {
|
|
246
|
+
getBadgeValue(area, row, rowCounts) {
|
|
265
247
|
let value;
|
|
266
248
|
|
|
267
249
|
if (!this.admissableAreas.includes(area)) {
|
|
@@ -269,11 +251,15 @@ export default {
|
|
|
269
251
|
}
|
|
270
252
|
|
|
271
253
|
if (area === 'clusters') {
|
|
272
|
-
|
|
254
|
+
value = `${ row.clusterInfo.ready }/${ row.clusterInfo.total }`;
|
|
273
255
|
} else if (area === 'bundles') {
|
|
274
|
-
|
|
256
|
+
const bundles = rowCounts[row.id].bundles;
|
|
257
|
+
|
|
258
|
+
value = xOfy(bundles.states.ready || 0, bundles.total);
|
|
275
259
|
} else if (area === 'resources') {
|
|
276
|
-
|
|
260
|
+
const resources = rowCounts[row.id].resources;
|
|
261
|
+
|
|
262
|
+
value = xOfy(resources.states.ready || 0, resources.total);
|
|
277
263
|
}
|
|
278
264
|
|
|
279
265
|
return value;
|
|
@@ -413,10 +399,10 @@ export default {
|
|
|
413
399
|
<CompoundStatusBadge
|
|
414
400
|
v-else
|
|
415
401
|
data-testid="clusters-ready"
|
|
416
|
-
:tooltip-text="getTooltipInfo('clusters', row)"
|
|
417
|
-
:badge-class="getStatusInfo('clusters', row).badgeClass"
|
|
418
|
-
:icon="getStatusInfo('clusters', row).icon"
|
|
419
|
-
:value="getBadgeValue('clusters', row)"
|
|
402
|
+
:tooltip-text="getTooltipInfo('clusters', row, gitReposCounts)"
|
|
403
|
+
:badge-class="getStatusInfo('clusters', row, gitReposCounts).badgeClass"
|
|
404
|
+
:icon="getStatusInfo('clusters', row, gitReposCounts).icon"
|
|
405
|
+
:value="getBadgeValue('clusters', row, gitReposCounts)"
|
|
420
406
|
/>
|
|
421
407
|
</template>
|
|
422
408
|
<template #cell:bundlesReady="{row}">
|
|
@@ -424,19 +410,19 @@ export default {
|
|
|
424
410
|
<CompoundStatusBadge
|
|
425
411
|
v-else
|
|
426
412
|
data-testid="bundles-ready"
|
|
427
|
-
:tooltip-text="getTooltipInfo('bundles', row)"
|
|
428
|
-
:badge-class="getStatusInfo('bundles', row).badgeClass"
|
|
429
|
-
:icon="getStatusInfo('bundles', row).icon"
|
|
430
|
-
:value="getBadgeValue('bundles', row)"
|
|
413
|
+
:tooltip-text="getTooltipInfo('bundles', row, gitReposCounts)"
|
|
414
|
+
:badge-class="getStatusInfo('bundles', row, gitReposCounts).badgeClass"
|
|
415
|
+
:icon="getStatusInfo('bundles', row, gitReposCounts).icon"
|
|
416
|
+
:value="getBadgeValue('bundles', row, gitReposCounts)"
|
|
431
417
|
/>
|
|
432
418
|
</template>
|
|
433
419
|
<template #cell:resourcesReady="{row}">
|
|
434
420
|
<CompoundStatusBadge
|
|
435
421
|
data-testid="resources-ready"
|
|
436
|
-
:tooltip-text="getTooltipInfo('resources', row)"
|
|
437
|
-
:badge-class="getStatusInfo('resources', row).badgeClass"
|
|
438
|
-
:icon="getStatusInfo('resources', row).icon"
|
|
439
|
-
:value="getBadgeValue('resources', row)"
|
|
422
|
+
:tooltip-text="getTooltipInfo('resources', row, gitReposCounts)"
|
|
423
|
+
:badge-class="getStatusInfo('resources', row, gitReposCounts).badgeClass"
|
|
424
|
+
:icon="getStatusInfo('resources', row, gitReposCounts).icon"
|
|
425
|
+
:value="getBadgeValue('resources', row, gitReposCounts)"
|
|
440
426
|
/>
|
|
441
427
|
</template>
|
|
442
428
|
|
|
@@ -10,7 +10,7 @@ import KeyValue from '@shell/components/form/KeyValue';
|
|
|
10
10
|
import { mapGetters } from 'vuex';
|
|
11
11
|
import { isRancherPrime } from '@shell/config/version';
|
|
12
12
|
import DefaultLinksEditor from './DefaultLinksEditor';
|
|
13
|
-
import {
|
|
13
|
+
import { CUSTOM_LINKS_APP_CO_VERSION, fetchLinks } from '@shell/config/home-links';
|
|
14
14
|
import TabTitle from '@shell/components/TabTitle';
|
|
15
15
|
|
|
16
16
|
export default {
|
|
@@ -47,7 +47,7 @@ export default {
|
|
|
47
47
|
|
|
48
48
|
allValues() {
|
|
49
49
|
return {
|
|
50
|
-
version:
|
|
50
|
+
version: CUSTOM_LINKS_APP_CO_VERSION,
|
|
51
51
|
defaults: this.value.defaults.filter((obj) => obj.enabled).map((obj) => obj.key),
|
|
52
52
|
custom: this.value.custom
|
|
53
53
|
};
|
package/pages/diagnostic.vue
CHANGED
|
@@ -346,21 +346,23 @@ export default {
|
|
|
346
346
|
class="full-width"
|
|
347
347
|
>
|
|
348
348
|
<thead @click="toggleTable(cluster.id)">
|
|
349
|
-
<
|
|
350
|
-
<
|
|
351
|
-
<
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
349
|
+
<tr>
|
|
350
|
+
<th colspan="4">
|
|
351
|
+
<div class="cluster-row">
|
|
352
|
+
<span>Cluster: <b>{{ cluster.name }}</b></span>
|
|
353
|
+
<span>Namespace: <b>{{ cluster.namespace }}</b></span>
|
|
354
|
+
<span>Total Resources: <b>{{ sumResourceCount(cluster.counts) }}</b></span>
|
|
355
|
+
<span>Nodes: <b>{{ nodeCount(cluster.counts) }}</b></span>
|
|
356
|
+
<i
|
|
357
|
+
class="icon"
|
|
358
|
+
:class="{
|
|
359
|
+
'icon-chevron-down': !cluster.isTableVisible,
|
|
360
|
+
'icon-chevron-up': cluster.isTableVisible
|
|
361
|
+
}"
|
|
362
|
+
/>
|
|
363
|
+
</div>
|
|
364
|
+
</th>
|
|
365
|
+
</tr>
|
|
364
366
|
</thead>
|
|
365
367
|
<tbody v-show="cluster.isTableVisible">
|
|
366
368
|
<tr>
|
package/pages/home.vue
CHANGED
|
@@ -4,7 +4,7 @@ import { mapPref, AFTER_LOGIN_ROUTE, READ_WHATS_NEW, HIDE_HOME_PAGE_CARDS } from
|
|
|
4
4
|
import { Banner } from '@components/Banner';
|
|
5
5
|
import BannerGraphic from '@shell/components/BannerGraphic.vue';
|
|
6
6
|
import IndentedPanel from '@shell/components/IndentedPanel.vue';
|
|
7
|
-
import PaginatedResourceTable
|
|
7
|
+
import PaginatedResourceTable from '@shell/components/PaginatedResourceTable.vue';
|
|
8
8
|
import { BadgeState } from '@components/BadgeState';
|
|
9
9
|
import CommunityLinks from '@shell/components/CommunityLinks.vue';
|
|
10
10
|
import SingleClusterInfo from '@shell/components/SingleClusterInfo.vue';
|
|
@@ -23,11 +23,12 @@ import { filterHiddenLocalCluster, filterOnlyKubernetesClusters, paginationFilte
|
|
|
23
23
|
import TabTitle from '@shell/components/TabTitle.vue';
|
|
24
24
|
import { ActionFindPageArgs } from '@shell/types/store/dashboard-store.types';
|
|
25
25
|
|
|
26
|
-
import { RESET_CARDS_ACTION, SET_LOGIN_ACTION } from '@shell/config/page-actions';
|
|
26
|
+
import { RESET_CARDS_ACTION, SET_LOGIN_ACTION, SHOW_HIDE_BANNER_ACTION } from '@shell/config/page-actions';
|
|
27
27
|
import { STEVE_NAME_COL, STEVE_STATE_COL } from '@shell/config/pagination-table-headers';
|
|
28
28
|
import { PaginationParamFilter, FilterArgs, PaginationFilterField, PaginationArgs } from '@shell/types/store/pagination.types';
|
|
29
29
|
import ProvCluster from '@shell/models/provisioning.cattle.io.cluster';
|
|
30
30
|
import { sameContents } from '@shell/utils/array';
|
|
31
|
+
import { PagTableFetchPageSecondaryResourcesOpts, PagTableFetchSecondaryResourcesOpts, PagTableFetchSecondaryResourcesReturns } from '@shell/types/components/paginatedResourceTable';
|
|
31
32
|
|
|
32
33
|
export default defineComponent({
|
|
33
34
|
name: 'Home',
|
|
@@ -56,6 +57,10 @@ export default defineComponent({
|
|
|
56
57
|
action: SET_LOGIN_ACTION
|
|
57
58
|
},
|
|
58
59
|
{ separator: true },
|
|
60
|
+
{
|
|
61
|
+
labelKey: 'nav.header.showHideBanner',
|
|
62
|
+
action: SHOW_HIDE_BANNER_ACTION
|
|
63
|
+
},
|
|
59
64
|
{
|
|
60
65
|
labelKey: 'nav.header.restoreCards',
|
|
61
66
|
action: RESET_CARDS_ACTION
|
|
@@ -238,9 +243,9 @@ export default defineComponent({
|
|
|
238
243
|
|
|
239
244
|
methods: {
|
|
240
245
|
/**
|
|
241
|
-
* Of type
|
|
246
|
+
* Of type PagTableFetchSecondaryResources
|
|
242
247
|
*/
|
|
243
|
-
fetchSecondaryResources(opts:
|
|
248
|
+
fetchSecondaryResources(opts: PagTableFetchSecondaryResourcesOpts): PagTableFetchSecondaryResourcesReturns {
|
|
244
249
|
if (opts.canPaginate) {
|
|
245
250
|
return Promise.resolve({});
|
|
246
251
|
}
|
|
@@ -271,7 +276,7 @@ export default defineComponent({
|
|
|
271
276
|
|
|
272
277
|
async fetchPageSecondaryResources({
|
|
273
278
|
canPaginate, force, page, pagResult
|
|
274
|
-
}:
|
|
279
|
+
}: PagTableFetchPageSecondaryResourcesOpts) {
|
|
275
280
|
if (!canPaginate || !page?.length) {
|
|
276
281
|
this.clusterCount = 0;
|
|
277
282
|
|
|
@@ -367,6 +372,10 @@ export default defineComponent({
|
|
|
367
372
|
this.resetCards();
|
|
368
373
|
break;
|
|
369
374
|
|
|
375
|
+
case SHOW_HIDE_BANNER_ACTION:
|
|
376
|
+
this.toggleBanner();
|
|
377
|
+
break;
|
|
378
|
+
|
|
370
379
|
case SET_LOGIN_ACTION:
|
|
371
380
|
this.afterLoginRoute = 'home';
|
|
372
381
|
break;
|
|
@@ -406,10 +415,27 @@ export default defineComponent({
|
|
|
406
415
|
},
|
|
407
416
|
|
|
408
417
|
async resetCards() {
|
|
409
|
-
|
|
418
|
+
const value = this.$store.getters['prefs/get'](HIDE_HOME_PAGE_CARDS) || {};
|
|
419
|
+
|
|
420
|
+
delete value.setLoginPage;
|
|
421
|
+
|
|
422
|
+
await this.$store.dispatch('prefs/set', { key: HIDE_HOME_PAGE_CARDS, value });
|
|
423
|
+
|
|
410
424
|
await this.$store.dispatch('prefs/set', { key: READ_WHATS_NEW, value: '' });
|
|
411
425
|
},
|
|
412
426
|
|
|
427
|
+
async toggleBanner() {
|
|
428
|
+
const value = this.$store.getters['prefs/get'](HIDE_HOME_PAGE_CARDS) || {};
|
|
429
|
+
|
|
430
|
+
if (value.welcomeBanner) {
|
|
431
|
+
delete value.welcomeBanner;
|
|
432
|
+
} else {
|
|
433
|
+
value.welcomeBanner = true;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
await this.$store.dispatch('prefs/set', { key: HIDE_HOME_PAGE_CARDS, value });
|
|
437
|
+
},
|
|
438
|
+
|
|
413
439
|
async closeSetLoginBanner(retry = 0) {
|
|
414
440
|
let value = this.$store.getters['prefs/get'](HIDE_HOME_PAGE_CARDS);
|
|
415
441
|
|
package/plugins/clean-html.js
CHANGED
|
@@ -30,6 +30,8 @@ const ALLOWED_TAGS = [
|
|
|
30
30
|
'blockquote'
|
|
31
31
|
];
|
|
32
32
|
|
|
33
|
+
let linkInterceptors = [];
|
|
34
|
+
|
|
33
35
|
// Allow 'A' tags to keep the target=_blank attribute if they have it
|
|
34
36
|
DOMPurify.addHook('uponSanitizeAttribute', (node, data) => {
|
|
35
37
|
if (node.tagName === 'A' && data.attrName === 'target' && data.attrValue === '_blank') {
|
|
@@ -46,8 +48,56 @@ DOMPurify.addHook('afterSanitizeAttributes', (node) => {
|
|
|
46
48
|
|
|
47
49
|
node.setAttribute('rel', combined.join(' '));
|
|
48
50
|
}
|
|
51
|
+
|
|
52
|
+
if (node.tagName === 'A' && linkInterceptors.length) {
|
|
53
|
+
let link = node.href;
|
|
54
|
+
|
|
55
|
+
// Allow each interceptor to modify the link href
|
|
56
|
+
link = processLink(link);
|
|
57
|
+
|
|
58
|
+
// If the link is different from the original update the href
|
|
59
|
+
if (link !== node.href) {
|
|
60
|
+
node.href = link;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
49
63
|
});
|
|
50
64
|
|
|
51
65
|
export const purifyHTML = (value, options = { ALLOWED_TAGS }) => {
|
|
52
66
|
return DOMPurify.sanitize(value, options);
|
|
53
67
|
};
|
|
68
|
+
|
|
69
|
+
// Link Interceptors are typically used to allow different doc links to be used
|
|
70
|
+
|
|
71
|
+
export function addLinkInterceptor(fn, name) {
|
|
72
|
+
// Check the arg is not undefined and is a function
|
|
73
|
+
if (fn && typeof fn === 'function') {
|
|
74
|
+
linkInterceptors.push(fn);
|
|
75
|
+
} else {
|
|
76
|
+
if (name) {
|
|
77
|
+
console.error(`Invalid link interceptor function for ${ name }`); // eslint-disable-line no-console
|
|
78
|
+
} else {
|
|
79
|
+
console.error('Invalid link interceptor function'); // eslint-disable-line no-console
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function removeLinkInterceptor(fn) {
|
|
85
|
+
linkInterceptors = linkInterceptors.filter((item) => item !== fn);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Process a link through all of the link interceptors
|
|
90
|
+
*/
|
|
91
|
+
export function processLink(link) {
|
|
92
|
+
// Allow each interceptor to modify the link href
|
|
93
|
+
for (let i = 0; i < linkInterceptors.length; i++) {
|
|
94
|
+
const updated = linkInterceptors[i](link);
|
|
95
|
+
|
|
96
|
+
// If a value if returned, use that in place of the original value
|
|
97
|
+
if (updated) {
|
|
98
|
+
link = updated;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return link;
|
|
103
|
+
}
|
package/plugins/plugin.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// This plugin loads any UI
|
|
1
|
+
// This plugin loads any UI Extensions at app load time
|
|
2
2
|
import { allHashSettled } from '@shell/utils/promise';
|
|
3
3
|
import { shouldNotLoadPlugin, UI_PLUGIN_BASE_URL } from '@shell/config/uiplugins';
|
|
4
4
|
import { getKubeVersionData, getVersionData } from '@shell/config/version';
|
|
@@ -11,14 +11,14 @@ export default async function(context) {
|
|
|
11
11
|
|
|
12
12
|
const hash = {};
|
|
13
13
|
|
|
14
|
-
// Provide a mechanism to load the UI without the
|
|
14
|
+
// Provide a mechanism to load the UI without the extensions loaded - in case there is a problem
|
|
15
15
|
let loadPlugins = true;
|
|
16
16
|
|
|
17
17
|
const queryKeys = Object.keys(context.route?.query || {}).map((q) => q.toLowerCase());
|
|
18
18
|
|
|
19
19
|
if (queryKeys.includes('safemode')) {
|
|
20
20
|
loadPlugins = false;
|
|
21
|
-
console.warn('Safe Mode -
|
|
21
|
+
console.warn('Safe Mode - extensions will not be loaded'); // eslint-disable-line no-console
|
|
22
22
|
setTimeout(() => {
|
|
23
23
|
context.store.dispatch('growl/success', {
|
|
24
24
|
title: context.store.getters['i18n/t']('plugins.safeMode.title'),
|
|
@@ -27,62 +27,67 @@ export default async function(context) {
|
|
|
27
27
|
}, 1000);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
const fetches = { versions: versions.fetch(context) };
|
|
31
|
+
|
|
32
|
+
// If we are loading extensions then add the API fetch for the list of extensions to the fetches we will make
|
|
30
33
|
if (loadPlugins) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
headers: { accept: 'application/json' },
|
|
39
|
-
redirectUnauthorized: false,
|
|
40
|
-
})
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
if (res.plugins.status === 'rejected') {
|
|
44
|
-
throw new Error(res.reason);
|
|
45
|
-
}
|
|
34
|
+
fetches.plugins = context.store.dispatch('management/request', {
|
|
35
|
+
url: `${ UI_PLUGIN_BASE_URL }`,
|
|
36
|
+
method: 'GET',
|
|
37
|
+
headers: { accept: 'application/json' },
|
|
38
|
+
redirectUnauthorized: false,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
46
41
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (!shouldNotLoad) {
|
|
57
|
-
hash[plugin.name] = context.$plugin.loadPluginAsync(plugin);
|
|
58
|
-
} else {
|
|
59
|
-
context.store.dispatch('uiplugins/setError', { name: plugin.name, error: shouldNotLoad });
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
} catch (e) {
|
|
63
|
-
if (e?.code === 404) {
|
|
64
|
-
// Not found, so extensions operator probably not installed
|
|
65
|
-
console.log('Could not load UI Extensions list (Extensions Operator may not be installed)'); // eslint-disable-line no-console
|
|
66
|
-
} else {
|
|
67
|
-
console.error('Could not load UI Extensions list', e); // eslint-disable-line no-console
|
|
68
|
-
}
|
|
42
|
+
// Fetch list of installed extensions from the extensions endpoint if needed and the version information
|
|
43
|
+
try {
|
|
44
|
+
const res = await allHashSettled(fetches);
|
|
45
|
+
|
|
46
|
+
// Initialize the built-in extensions now - this is now done here so that built-in extensions get the same, correct environment data (version etc)
|
|
47
|
+
context.$plugin.loadBuiltinExtensions();
|
|
48
|
+
|
|
49
|
+
if (res.plugins?.status === 'rejected') {
|
|
50
|
+
throw new Error(res.reason);
|
|
69
51
|
}
|
|
70
52
|
|
|
71
|
-
|
|
72
|
-
const
|
|
53
|
+
const kubeVersion = getKubeVersionData()?.gitVersion;
|
|
54
|
+
const rancherVersion = getVersionData().Version;
|
|
73
55
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const res = pluginLoads[name];
|
|
56
|
+
const plugins = res.plugins?.value || {};
|
|
57
|
+
const entries = plugins.entries || plugins.Entries || {};
|
|
77
58
|
|
|
78
|
-
|
|
79
|
-
|
|
59
|
+
Object.values(entries).forEach((plugin) => {
|
|
60
|
+
const shouldNotLoad = shouldNotLoadPlugin(plugin, { rancherVersion, kubeVersion }, context.store.getters['uiplugins/plugins'] || []); // Error key string or boolean
|
|
80
61
|
|
|
81
|
-
|
|
82
|
-
|
|
62
|
+
if (!shouldNotLoad) {
|
|
63
|
+
hash[plugin.name] = context.$plugin.loadPluginAsync(plugin);
|
|
64
|
+
} else {
|
|
65
|
+
context.store.dispatch('uiplugins/setError', { name: plugin.name, error: shouldNotLoad });
|
|
83
66
|
}
|
|
84
67
|
});
|
|
68
|
+
} catch (e) {
|
|
69
|
+
if (e?.code === 404) {
|
|
70
|
+
// Not found, so extensions operator probably not installed
|
|
71
|
+
console.log('Could not load UI Extensions list (Extensions Operator may not be installed)'); // eslint-disable-line no-console
|
|
72
|
+
} else {
|
|
73
|
+
console.error('Could not load UI Extensions list', e); // eslint-disable-line no-console
|
|
74
|
+
}
|
|
85
75
|
}
|
|
86
76
|
|
|
77
|
+
// Load all of the extensions
|
|
78
|
+
const pluginLoads = await allHashSettled(hash);
|
|
79
|
+
|
|
80
|
+
// Some extensions may have failed to load - store this
|
|
81
|
+
Object.keys(pluginLoads).forEach((name) => {
|
|
82
|
+
const res = pluginLoads[name];
|
|
83
|
+
|
|
84
|
+
if (res?.status === 'rejected') {
|
|
85
|
+
console.error(`Failed to load extension: ${ name }. `, res.reason || 'Unknown reason'); // eslint-disable-line no-console
|
|
86
|
+
|
|
87
|
+
// Record error in the uiplugins store, so that we can show this to the user
|
|
88
|
+
context.store.dispatch('uiplugins/setError', { name, error: 'plugins.error.load' }); // i18n-uses plugins.error.load
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
87
92
|
return true;
|
|
88
93
|
}
|
|
@@ -31,7 +31,7 @@ function registerNamespace(state, namespace) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* update the podsByNamespace cache with new or changed pods
|
|
34
|
+
* update the podsByNamespace cache with new or changed pods.
|
|
35
35
|
*/
|
|
36
36
|
function updatePodsByNamespaceCache(state, ctx, pods, loadAll) {
|
|
37
37
|
if (loadAll) {
|
|
@@ -2,6 +2,7 @@ import { DESCRIPTION } from '@shell/config/labels-annotations';
|
|
|
2
2
|
import HybridModel from './hybrid-class';
|
|
3
3
|
import { NEVER_ADD } from '@shell/utils/create-yaml';
|
|
4
4
|
import { deleteProperty } from '@shell/utils/object';
|
|
5
|
+
import { EXT_IDS } from '@shell/core/plugin';
|
|
5
6
|
|
|
6
7
|
// Some fields that are removed for YAML (NEVER_ADD) are required via API
|
|
7
8
|
const STEVE_ADD = [
|
|
@@ -41,6 +42,13 @@ export default class SteveModel extends HybridModel {
|
|
|
41
42
|
this._description = value;
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Get all model extensions for this model
|
|
47
|
+
*/
|
|
48
|
+
get modelExtensions() {
|
|
49
|
+
return this.$plugin.getDynamic(EXT_IDS.MODEL_EXTENSION, this.type) || [];
|
|
50
|
+
}
|
|
51
|
+
|
|
44
52
|
cleanForSave(data, forNew) {
|
|
45
53
|
const val = super.cleanForSave(data);
|
|
46
54
|
|