@rancher/shell 0.3.24 → 0.3.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/styles/themes/_light.scss +1 -1
- package/assets/translations/en-us.yaml +29 -7
- package/assets/translations/zh-hans.yaml +1 -1
- package/components/ClusterIconMenu.vue +143 -0
- package/components/CruResource.vue +7 -1
- package/components/ExplorerProjectsNamespaces.vue +11 -1
- package/components/FixedBanner.vue +17 -1
- package/components/Markdown.vue +1 -1
- package/components/Questions/__tests__/Yaml.test.ts +3 -2
- package/components/SortableTable/index.vue +3 -2
- package/components/auth/RoleDetailEdit.vue +15 -2
- package/components/auth/login/saml.vue +12 -1
- package/components/form/LabeledSelect.vue +12 -5
- package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
- package/components/form/Members/MembershipEditor.vue +6 -1
- package/components/form/__tests__/KeyValue.test.ts +6 -3
- package/components/form/__tests__/LabeledSelect.test.ts +18 -0
- package/components/formatter/PodsUsage.vue +11 -36
- package/components/formatter/PrincipalGroupBindings.vue +8 -5
- package/components/formatter/__tests__/PodsUsage.test.ts +36 -19
- package/components/nav/Group.vue +25 -27
- package/components/nav/Header.vue +12 -5
- package/components/nav/Pinned.vue +47 -0
- package/components/nav/TopLevelMenu.vue +233 -60
- package/components/nav/Type.vue +57 -3
- package/config/home-links.js +1 -1
- package/config/product/istio.js +15 -5
- package/config/router.js +3 -9
- package/config/table-headers.js +5 -6
- package/config/uiplugins.js +1 -0
- package/core/plugin-helpers.js +3 -0
- package/core/types.ts +6 -1
- package/creators/app/files/.vscode/settings.json +0 -1
- package/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts +118 -0
- package/detail/autoscaling.horizontalpodautoscaler/index.vue +4 -4
- package/detail/provisioning.cattle.io.cluster.vue +7 -5
- package/edit/__tests__/management.cattle.io.clusterroletemplatebinding.test.ts +58 -0
- package/edit/__tests__/namespace.test.ts +5 -3
- package/edit/management.cattle.io.clusterroletemplatebinding.vue +3 -11
- package/edit/namespace.vue +8 -4
- package/edit/provisioning.cattle.io.cluster/Basics.vue +662 -0
- package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +6 -0
- package/edit/provisioning.cattle.io.cluster/DrainOptions.vue +13 -8
- package/edit/provisioning.cattle.io.cluster/MachinePool.vue +11 -2
- package/edit/provisioning.cattle.io.cluster/MemberRoles.vue +40 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/Basics.tests.ts +237 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/CustomCommand.tests.ts +71 -23
- package/edit/provisioning.cattle.io.cluster/__tests__/DrainOptions.test.ts +52 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +65 -142
- package/edit/provisioning.cattle.io.cluster/rke2.vue +194 -598
- package/edit/workload/storage/__tests__/Storage.test.ts +2 -2
- package/edit/workload/storage/persistentVolumeClaim/__tests__/persistentvolumeclaim.test.ts +36 -0
- package/edit/workload/storage/persistentVolumeClaim/persistentvolumeclaim.vue +15 -7
- package/initialize/index.js +5 -5
- package/layouts/default.vue +6 -6
- package/layouts/home.vue +6 -2
- package/layouts/plain.vue +9 -2
- package/list/fleet.cattle.io.cluster.vue +2 -2
- package/list/management.cattle.io.feature.vue +1 -1
- package/machine-config/vmwarevsphere.vue +48 -7
- package/mixins/brand.js +0 -8
- package/mixins/child-hook.js +2 -2
- package/mixins/create-edit-view/impl.js +3 -3
- package/models/__tests__/management.cattle.io.node.ts +96 -0
- package/models/__tests__/node.ts +74 -0
- package/models/cluster/node.js +6 -5
- package/models/cluster.x-k8s.io.machinedeployment.js +2 -2
- package/models/management.cattle.io.cluster.js +22 -1
- package/models/management.cattle.io.clusterroletemplatebinding.js +3 -3
- package/models/management.cattle.io.globalrole.js +17 -2
- package/models/management.cattle.io.node.js +6 -4
- package/models/management.cattle.io.projectroletemplatebinding.js +3 -3
- package/models/management.cattle.io.roletemplate.js +17 -2
- package/package.json +2 -6
- package/pages/about.vue +2 -0
- package/pages/auth/setup.vue +5 -4
- package/pages/c/_cluster/monitoring/index.vue +8 -3
- package/pages/c/_cluster/uiplugins/CatalogList/CatalogLoadDialog.vue +9 -66
- package/pages/c/_cluster/uiplugins/CatalogList/CatalogUninstallDialog.vue +182 -0
- package/pages/c/_cluster/uiplugins/CatalogList/index.vue +15 -32
- package/pages/c/_cluster/uiplugins/UninstallDialog.vue +8 -46
- package/pages/c/_cluster/uiplugins/index.vue +64 -64
- package/pages/diagnostic.vue +0 -39
- package/pages/home.vue +1 -1
- package/plugins/dashboard-store/normalize.js +4 -4
- package/plugins/int-number.js +5 -2
- package/plugins/positive-int-number.js +19 -0
- package/plugins/steve/__tests__/getters.spec.ts +15 -0
- package/plugins/steve/getters.js +22 -10
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +0 -8
- package/rancher-components/Form/Radio/RadioButton.test.ts +3 -7
- package/store/index.js +4 -0
- package/store/prefs.js +1 -0
- package/types/shell/index.d.ts +13 -4
- package/utils/__tests__/cluster.test.ts +55 -0
- package/utils/__tests__/object.test.ts +21 -2
- package/utils/cluster.js +47 -1
- package/utils/object.js +12 -5
- package/utils/validators/formRules/__tests__/index.test.ts +13 -1
- package/utils/validators/formRules/index.ts +4 -0
- package/utils/validators/role-template.js +9 -1
- package/utils/version.js +1 -1
- package/yarn-error.log +16 -16
- package/components/ClusterProviderIconMenu.vue +0 -161
- package/content/docs/en-us/getting-started.md +0 -224
- package/content/docs/en-us/whats-new.md +0 -29
- package/content/docs/zh-hans/getting-started.md +0 -224
- package/content/docs/zh-hans/whats-new.md +0 -28
- package/pages/docs/_doc.vue +0 -345
- package/pages/docs/toc.js +0 -27
- package/plugins/console.js +0 -34
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Vue from 'vue';
|
|
2
2
|
import { CATALOG, CLUSTER_BADGE } from '@shell/config/labels-annotations';
|
|
3
3
|
import { NODE, FLEET, MANAGEMENT, CAPI } from '@shell/config/types';
|
|
4
|
-
import { insertAt } from '@shell/utils/array';
|
|
4
|
+
import { insertAt, addObject, removeObject } from '@shell/utils/array';
|
|
5
5
|
import { downloadFile } from '@shell/utils/download';
|
|
6
6
|
import { parseSi } from '@shell/utils/units';
|
|
7
7
|
import { parseColor, textColor } from '@shell/utils/color';
|
|
@@ -14,6 +14,7 @@ import { isHarvesterCluster } from '@shell/utils/cluster';
|
|
|
14
14
|
import HybridModel from '@shell/plugins/steve/hybrid-class';
|
|
15
15
|
import { LINUX, WINDOWS } from '@shell/store/catalog';
|
|
16
16
|
import { KONTAINER_TO_DRIVER } from './management.cattle.io.kontainerdriver';
|
|
17
|
+
import { PINNED_CLUSTERS } from '@shell/store/prefs';
|
|
17
18
|
|
|
18
19
|
// See translation file cluster.providers for list of providers
|
|
19
20
|
// If the logo is not named with the provider name, add an override here
|
|
@@ -455,4 +456,24 @@ export default class MgmtCluster extends HybridModel {
|
|
|
455
456
|
|
|
456
457
|
return findRelationship(verb === 'to' ? 'from' : 'to', CAPI.RANCHER_CLUSTER, this.metadata?.relationships);
|
|
457
458
|
}
|
|
459
|
+
|
|
460
|
+
get pinned() {
|
|
461
|
+
return this.$rootGetters['prefs/get'](PINNED_CLUSTERS).includes(this.id);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
pin() {
|
|
465
|
+
const types = this.$rootGetters['prefs/get'](PINNED_CLUSTERS) || [];
|
|
466
|
+
|
|
467
|
+
addObject(types, this.id);
|
|
468
|
+
|
|
469
|
+
this.$dispatch('prefs/set', { key: PINNED_CLUSTERS, value: types }, { root: true });
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
unpin() {
|
|
473
|
+
const types = this.$rootGetters['prefs/get'](PINNED_CLUSTERS) || [];
|
|
474
|
+
|
|
475
|
+
removeObject(types, this.id);
|
|
476
|
+
|
|
477
|
+
this.$dispatch('prefs/set', { key: PINNED_CLUSTERS, value: types }, { root: true });
|
|
478
|
+
}
|
|
458
479
|
}
|
|
@@ -43,7 +43,7 @@ export default class CRTB extends HybridModel {
|
|
|
43
43
|
|
|
44
44
|
get principalId() {
|
|
45
45
|
// We've either set it ourselves or it's comes from native properties
|
|
46
|
-
return this.principalName || this.userPrincipalName || this.groupPrincipalName;
|
|
46
|
+
return this.principalName || this.userPrincipalName || this.groupPrincipalName || '';
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
get nameDisplay() {
|
|
@@ -117,12 +117,12 @@ export default class CRTB extends HybridModel {
|
|
|
117
117
|
get norman() {
|
|
118
118
|
return (async() => {
|
|
119
119
|
const principal = await this.principal;
|
|
120
|
-
const principalProperty = principal
|
|
120
|
+
const principalProperty = principal?.principalType === 'group' ? 'groupPrincipalId' : 'userPrincipalId';
|
|
121
121
|
|
|
122
122
|
return this.$dispatch(`rancher/create`, {
|
|
123
123
|
type: NORMAN.CLUSTER_ROLE_TEMPLATE_BINDING,
|
|
124
124
|
roleTemplateId: this.roleTemplateName,
|
|
125
|
-
[principalProperty]: principal
|
|
125
|
+
[principalProperty]: principal?.id,
|
|
126
126
|
clusterId: this.clusterName,
|
|
127
127
|
id: this.id?.replace('/', ':')
|
|
128
128
|
}, { root: true });
|
|
@@ -4,7 +4,6 @@ import { CATTLE_API_GROUP, SUBTYPE_MAPPING, CREATE_VERBS } from '@shell/models/m
|
|
|
4
4
|
import { uniq } from '@shell/utils/array';
|
|
5
5
|
import { get } from '@shell/utils/object';
|
|
6
6
|
import SteveDescriptionModel from '@shell/plugins/steve/steve-description-class';
|
|
7
|
-
import Role from './rbac.authorization.k8s.io.role';
|
|
8
7
|
import { AS, MODE, _CLONE, _UNFLAG } from '@shell/config/query-params';
|
|
9
8
|
|
|
10
9
|
const BASE = 'user-base';
|
|
@@ -16,7 +15,14 @@ const GLOBAL = SUBTYPE_MAPPING.GLOBAL.key;
|
|
|
16
15
|
|
|
17
16
|
export default class GlobalRole extends SteveDescriptionModel {
|
|
18
17
|
get customValidationRules() {
|
|
19
|
-
return
|
|
18
|
+
return [
|
|
19
|
+
{
|
|
20
|
+
path: 'rules',
|
|
21
|
+
validators: [`roleTemplateRules:${ this.type }`],
|
|
22
|
+
nullable: false,
|
|
23
|
+
type: 'array',
|
|
24
|
+
},
|
|
25
|
+
];
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
get details() {
|
|
@@ -137,6 +143,15 @@ export default class GlobalRole extends SteveDescriptionModel {
|
|
|
137
143
|
async save() {
|
|
138
144
|
const norman = await this.norman;
|
|
139
145
|
|
|
146
|
+
for (const rule of norman.rules) {
|
|
147
|
+
if (rule.nonResourceURLs.length) {
|
|
148
|
+
delete rule.resources;
|
|
149
|
+
delete rule.apiGroups;
|
|
150
|
+
} else {
|
|
151
|
+
delete rule.nonResourceURLs;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
140
155
|
return norman.save();
|
|
141
156
|
}
|
|
142
157
|
|
|
@@ -125,11 +125,14 @@ export default class MgmtNode extends HybridModel {
|
|
|
125
125
|
return false;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
get addresses() {
|
|
129
|
+
return this.status?.addresses || this.status?.internalNodeStatus?.addresses || [];
|
|
130
|
+
}
|
|
131
|
+
|
|
128
132
|
get internalIp() {
|
|
129
133
|
// This shows in the IP address column for RKE1 nodes in the
|
|
130
134
|
// list of nodes in the cluster detail page of Cluster Management.
|
|
131
|
-
|
|
132
|
-
const internal = this.status?.addresses?.find(({ type }) => {
|
|
135
|
+
const internal = this.addresses.find(({ type }) => {
|
|
133
136
|
return type === ADDRESSES.INTERNAL_IP;
|
|
134
137
|
});
|
|
135
138
|
|
|
@@ -147,8 +150,7 @@ export default class MgmtNode extends HybridModel {
|
|
|
147
150
|
}
|
|
148
151
|
|
|
149
152
|
get externalIp() {
|
|
150
|
-
const
|
|
151
|
-
const statusAddress = findLast(addresses, (address) => address.type === 'ExternalIP')?.address;
|
|
153
|
+
const statusAddress = findLast(this.addresses, (address) => address.type === 'ExternalIP')?.address;
|
|
152
154
|
|
|
153
155
|
if (statusAddress) {
|
|
154
156
|
return statusAddress;
|
|
@@ -31,7 +31,7 @@ export default class PRTB extends HybridModel {
|
|
|
31
31
|
|
|
32
32
|
get principalId() {
|
|
33
33
|
// We've either set it ourselves or it's comes from native properties
|
|
34
|
-
return this.principalName || this.userPrincipalName || this.groupPrincipalName;
|
|
34
|
+
return this.principalName || this.userPrincipalName || this.groupPrincipalName || '';
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
get nameDisplay() {
|
|
@@ -123,12 +123,12 @@ export default class PRTB extends HybridModel {
|
|
|
123
123
|
get norman() {
|
|
124
124
|
return (async() => {
|
|
125
125
|
const principal = await this.principal;
|
|
126
|
-
const principalProperty = principal
|
|
126
|
+
const principalProperty = principal?.principalType === 'group' ? 'groupPrincipalId' : 'userPrincipalId';
|
|
127
127
|
|
|
128
128
|
return this.$dispatch(`rancher/create`, {
|
|
129
129
|
type: NORMAN.PROJECT_ROLE_TEMPLATE_BINDING,
|
|
130
130
|
roleTemplateId: this.roleTemplateName,
|
|
131
|
-
[principalProperty]: principal
|
|
131
|
+
[principalProperty]: principal?.id,
|
|
132
132
|
projectId: this.projectName,
|
|
133
133
|
projectRoleTemplateId: '',
|
|
134
134
|
id: this.id?.replace('/', ':')
|
|
@@ -3,7 +3,6 @@ import { get } from '@shell/utils/object';
|
|
|
3
3
|
import { DESCRIPTION } from '@shell/config/labels-annotations';
|
|
4
4
|
import { NORMAN } from '@shell/config/types';
|
|
5
5
|
import SteveDescriptionModel from '@shell/plugins/steve/steve-description-class';
|
|
6
|
-
import Role from './rbac.authorization.k8s.io.role';
|
|
7
6
|
import { AS, MODE, _CLONE, _UNFLAG } from '@shell/config/query-params';
|
|
8
7
|
|
|
9
8
|
export const CATTLE_API_GROUP = '.cattle.io';
|
|
@@ -60,7 +59,14 @@ export const CREATE_VERBS = new Set(['PUT', 'blocked-PUT']);
|
|
|
60
59
|
|
|
61
60
|
export default class RoleTemplate extends SteveDescriptionModel {
|
|
62
61
|
get customValidationRules() {
|
|
63
|
-
return
|
|
62
|
+
return [
|
|
63
|
+
{
|
|
64
|
+
path: 'rules',
|
|
65
|
+
validators: [`roleTemplateRules:${ this.type }`],
|
|
66
|
+
nullable: false,
|
|
67
|
+
type: 'array',
|
|
68
|
+
},
|
|
69
|
+
];
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
get details() {
|
|
@@ -185,6 +191,15 @@ export default class RoleTemplate extends SteveDescriptionModel {
|
|
|
185
191
|
async save() {
|
|
186
192
|
const norman = await this.norman;
|
|
187
193
|
|
|
194
|
+
for (const rule of norman.rules) {
|
|
195
|
+
if (rule.nonResourceURLs.length) {
|
|
196
|
+
delete rule.resources;
|
|
197
|
+
delete rule.apiGroups;
|
|
198
|
+
} else {
|
|
199
|
+
delete rule.nonResourceURLs;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
188
203
|
return norman.save();
|
|
189
204
|
}
|
|
190
205
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rancher/shell",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.25",
|
|
4
4
|
"description": "Rancher Dashboard Shell",
|
|
5
5
|
"repository": "https://github.com/rancherlabs/dashboard",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
"lint": "./node_modules/.bin/eslint --max-warnings 0 --ext .ts,.js,.vue .",
|
|
18
18
|
"test": "./node_modules/.bin/nyc ava --serial --verbose",
|
|
19
19
|
"dev": "./node_modules/.bin/vue-cli-service dev",
|
|
20
|
-
"docker-dev": "docker run --rm --name dashboard-dev -p 8005:8005 -e API=$API -v $(pwd):/src -v dashboard_node:/src/node_modules rancher/dashboard:dev",
|
|
21
20
|
"build": "./node_modules/.bin/vue-cli-service build",
|
|
22
21
|
"analyze": "./node_modules/.bin/vue-cli-service build --report",
|
|
23
22
|
"start": "./node_modules/.bin/vue-cli-service start",
|
|
@@ -38,7 +37,7 @@
|
|
|
38
37
|
"@novnc/novnc": "1.2.0",
|
|
39
38
|
"@nuxt/types": "2.14.6",
|
|
40
39
|
"@nuxt/typescript-build": "2.1.0",
|
|
41
|
-
"@nuxtjs/axios": "5.
|
|
40
|
+
"@nuxtjs/axios": "5.13.6",
|
|
42
41
|
"@nuxtjs/eslint-config-typescript": "6.0.1",
|
|
43
42
|
"@nuxtjs/webpack-profile": "0.1.0",
|
|
44
43
|
"@popperjs/core": "2.4.4",
|
|
@@ -107,9 +106,6 @@
|
|
|
107
106
|
"papaparse": "5.3.0",
|
|
108
107
|
"portal-vue": "2.1.7",
|
|
109
108
|
"rancher-icons": "rancher/icons#v2.0.16",
|
|
110
|
-
"require-extension-hooks": "0.3.3",
|
|
111
|
-
"require-extension-hooks-babel": "1.0.0",
|
|
112
|
-
"require-extension-hooks-vue": "3.0.0",
|
|
113
109
|
"sass": "1.51.0",
|
|
114
110
|
"sass-loader": "10.2.1",
|
|
115
111
|
"serve-static": "1.14.1",
|
package/pages/about.vue
CHANGED
|
@@ -101,6 +101,7 @@ export default {
|
|
|
101
101
|
<n-link
|
|
102
102
|
:to="{ name: 'diagnostic' }"
|
|
103
103
|
class="btn role-primary"
|
|
104
|
+
data-testid="about__diagnostics_button"
|
|
104
105
|
>
|
|
105
106
|
{{ t('about.diagnostic.title') }}
|
|
106
107
|
</n-link>
|
|
@@ -195,6 +196,7 @@ export default {
|
|
|
195
196
|
<td>
|
|
196
197
|
<a
|
|
197
198
|
v-if="d.imageList"
|
|
199
|
+
:data-testid="`image_list_download_link__${d.label}`"
|
|
198
200
|
@click="d.imageList"
|
|
199
201
|
>
|
|
200
202
|
{{ t('asyncButton.download.action') }}
|
package/pages/auth/setup.vue
CHANGED
|
@@ -17,6 +17,7 @@ import { exceptionToErrorsArray } from '@shell/utils/error';
|
|
|
17
17
|
import Password from '@shell/components/form/Password';
|
|
18
18
|
import { applyProducts } from '@shell/store/type-map';
|
|
19
19
|
import BrandImage from '@shell/components/BrandImage';
|
|
20
|
+
import { waitFor } from '@shell/utils/async';
|
|
20
21
|
|
|
21
22
|
const calcIsFirstLogin = (store) => {
|
|
22
23
|
const firstLoginSetting = store.getters['management/byId'](MANAGEMENT.SETTING, SETTING.FIRST_LOGIN);
|
|
@@ -250,10 +251,10 @@ export default {
|
|
|
250
251
|
|
|
251
252
|
await Promise.all(promises);
|
|
252
253
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
254
|
+
await waitFor(() => !calcIsFirstLogin(this.$store), 'first login to be completed', 10000, 1000, true);
|
|
255
|
+
|
|
256
|
+
buttonCb(true);
|
|
257
|
+
this.done();
|
|
257
258
|
} catch (err) {
|
|
258
259
|
console.error(err) ; // eslint-disable-line no-console
|
|
259
260
|
buttonCb(false);
|
|
@@ -94,12 +94,11 @@ export default {
|
|
|
94
94
|
methods: {
|
|
95
95
|
async fetchDeps() {
|
|
96
96
|
const { $store, externalLinks } = this;
|
|
97
|
-
const currentCluster = this.$store.getters['currentCluster'];
|
|
98
97
|
|
|
99
98
|
this.v1Installed = await haveV1MonitoringWorkloads($store);
|
|
100
99
|
const hash = await allHash({
|
|
100
|
+
apps: $store.dispatch('cluster/findAll', { type: CATALOG.APP }),
|
|
101
101
|
endpoints: $store.dispatch('cluster/findAll', { type: ENDPOINTS }),
|
|
102
|
-
app: $store.dispatch(`cluster/find`, { type: CATALOG.APP, id: 'cattle-monitoring-system/rancher-monitoring' })
|
|
103
102
|
});
|
|
104
103
|
|
|
105
104
|
if (!isEmpty(hash.endpoints)) {
|
|
@@ -109,7 +108,13 @@ export default {
|
|
|
109
108
|
(el) => el.group === 'prometheus'
|
|
110
109
|
);
|
|
111
110
|
|
|
112
|
-
|
|
111
|
+
// Generate Grafana link
|
|
112
|
+
const currentCluster = this.$store.getters['currentCluster'];
|
|
113
|
+
const rancherMonitoring = !isEmpty(hash.apps) ? findBy(hash.apps, 'id', 'cattle-monitoring-system/rancher-monitoring') : '';
|
|
114
|
+
const clusterPrefix = getClusterPrefix(rancherMonitoring?.currentVersion || '', currentCluster.id);
|
|
115
|
+
|
|
116
|
+
grafanaMatch.link = `${ clusterPrefix }/api/v1/namespaces/cattle-monitoring-system/services/http:rancher-monitoring-grafana:80/proxy/`;
|
|
117
|
+
|
|
113
118
|
const alertmanager = findBy(
|
|
114
119
|
hash.endpoints,
|
|
115
120
|
'id',
|
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
import { mapGetters } from 'vuex';
|
|
3
3
|
import isEmpty from 'lodash/isEmpty';
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
CATALOG, SECRET, SERVICE, UI_PLUGIN, WORKLOAD_TYPES
|
|
7
|
-
} from '@shell/config/types';
|
|
5
|
+
import { CATALOG, SECRET, SERVICE, WORKLOAD_TYPES } from '@shell/config/types';
|
|
8
6
|
import { UI_PLUGIN_LABELS, UI_PLUGIN_NAMESPACE } from '@shell/config/uiplugins';
|
|
9
7
|
import { TYPES as SECRET_TYPES } from '@shell/models/secret';
|
|
10
8
|
import { allHash } from '@shell/utils/promise';
|
|
@@ -195,11 +193,15 @@ export default {
|
|
|
195
193
|
}
|
|
196
194
|
|
|
197
195
|
if (this.extensionRepo) {
|
|
198
|
-
|
|
199
|
-
|
|
196
|
+
btnCb(true);
|
|
197
|
+
this.closeDialog();
|
|
198
|
+
this.$store.dispatch('growl/success', {
|
|
199
|
+
title: this.t('plugins.manageCatalog.imageLoad.success.title', { name }),
|
|
200
|
+
message: this.t('plugins.manageCatalog.imageLoad.success.message'),
|
|
201
|
+
timeout: 4000,
|
|
202
|
+
}, { root: true });
|
|
203
|
+
this.$emit('refresh');
|
|
200
204
|
}
|
|
201
|
-
|
|
202
|
-
btnCb(true);
|
|
203
205
|
} else {
|
|
204
206
|
throw new Error('Unable to determine image name');
|
|
205
207
|
}
|
|
@@ -313,62 +315,6 @@ export default {
|
|
|
313
315
|
}
|
|
314
316
|
},
|
|
315
317
|
|
|
316
|
-
async loadPlugin(name, url, image, btnCb) {
|
|
317
|
-
// Try and parse version number from the image
|
|
318
|
-
const version = this.extractImageVersion(image) || 'latest';
|
|
319
|
-
|
|
320
|
-
if (!this.extractImageVersion(image)) {
|
|
321
|
-
this.$store.dispatch('growl/warning', {
|
|
322
|
-
title: this.t('plugins.manageCatalog.imageLoad.imageVersion.title'),
|
|
323
|
-
message: this.t('plugins.manageCatalog.imageLoad.imageVersion.message', { image }),
|
|
324
|
-
timeout: 4000,
|
|
325
|
-
}, { root: true });
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
let crdName = name;
|
|
329
|
-
|
|
330
|
-
const parts = name.split('-');
|
|
331
|
-
|
|
332
|
-
if (parts.length >= 2) {
|
|
333
|
-
crdName = parts.join('-');
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
this.extensionCrd = await this.$store.dispatch('management/create', {
|
|
337
|
-
type: UI_PLUGIN,
|
|
338
|
-
metadata: {
|
|
339
|
-
name,
|
|
340
|
-
namespace: UI_PLUGIN_NAMESPACE,
|
|
341
|
-
labels: {
|
|
342
|
-
[UI_PLUGIN_LABELS.CATALOG_IMAGE]: name,
|
|
343
|
-
[UI_PLUGIN_LABELS.REPOSITORY]: this.extensionRepo.metadata.name
|
|
344
|
-
}
|
|
345
|
-
},
|
|
346
|
-
spec: {
|
|
347
|
-
plugin: {
|
|
348
|
-
name: crdName,
|
|
349
|
-
version,
|
|
350
|
-
endpoint: url,
|
|
351
|
-
noCache: false,
|
|
352
|
-
metadata: { [UI_PLUGIN_LABELS.CATALOG]: 'true' }
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
try {
|
|
358
|
-
await this.extensionCrd.save({ url: `/v1/${ UI_PLUGIN }`, method: 'POST' });
|
|
359
|
-
|
|
360
|
-
this.closeDialog();
|
|
361
|
-
this.$store.dispatch('growl/success', {
|
|
362
|
-
title: this.t('plugins.manageCatalog.imageLoad.success.title', { name }),
|
|
363
|
-
message: this.t('plugins.manageCatalog.imageLoad.success.message'),
|
|
364
|
-
timeout: 4000,
|
|
365
|
-
}, { root: true });
|
|
366
|
-
} catch (e) {
|
|
367
|
-
this.handleGrowlError(e, true);
|
|
368
|
-
btnCb(false);
|
|
369
|
-
}
|
|
370
|
-
},
|
|
371
|
-
|
|
372
318
|
parseDeploymentValues(name) {
|
|
373
319
|
let out = {};
|
|
374
320
|
|
|
@@ -459,9 +405,6 @@ export default {
|
|
|
459
405
|
if (this.extensionRepo) {
|
|
460
406
|
this.extensionRepo.remove();
|
|
461
407
|
}
|
|
462
|
-
if (this.extensionCrd) {
|
|
463
|
-
this.extensionCrd.remove();
|
|
464
|
-
}
|
|
465
408
|
},
|
|
466
409
|
|
|
467
410
|
handleGrowlError(e, clean = false) {
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { mapGetters } from 'vuex';
|
|
3
|
+
|
|
4
|
+
import { CATALOG, UI_PLUGIN, SERVICE, WORKLOAD_TYPES } from '@shell/config/types';
|
|
5
|
+
import { UI_PLUGIN_LABELS, UI_PLUGIN_NAMESPACE } from '@shell/config/uiplugins';
|
|
6
|
+
import { allHash } from '@shell/utils/promise';
|
|
7
|
+
|
|
8
|
+
import AsyncButton from '@shell/components/AsyncButton';
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
components: { AsyncButton },
|
|
12
|
+
|
|
13
|
+
async fetch() {
|
|
14
|
+
if ( this.$store.getters['management/schemaFor'](UI_PLUGIN) ) {
|
|
15
|
+
const plugins = this.$store.dispatch('management/findAll', { type: UI_PLUGIN });
|
|
16
|
+
|
|
17
|
+
this.plugins = plugins || [];
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
data() {
|
|
22
|
+
return {
|
|
23
|
+
catalog: undefined, busy: false, plugins: null
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
computed: {
|
|
28
|
+
...mapGetters({ allCharts: 'catalog/charts' }),
|
|
29
|
+
|
|
30
|
+
pluginsFromCatalogImage() {
|
|
31
|
+
return this.plugins.filter((p) => p.metadata?.labels?.[UI_PLUGIN_LABELS.CATALOG_IMAGE]);
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
methods: {
|
|
36
|
+
showDialog(catalog) {
|
|
37
|
+
this.catalog = catalog;
|
|
38
|
+
this.busy = false;
|
|
39
|
+
this.$modal.show('uninstallCatalogDialog');
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
closeDialog(result) {
|
|
43
|
+
this.$modal.hide('uninstallCatalogDialog');
|
|
44
|
+
this.$emit('closed', result);
|
|
45
|
+
|
|
46
|
+
if ( result ) {
|
|
47
|
+
this.$emit('refresh');
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
async uninstall() {
|
|
52
|
+
this.busy = true;
|
|
53
|
+
|
|
54
|
+
const catalog = this.catalog;
|
|
55
|
+
const apps = await this.$store.dispatch('management/findAll', { type: CATALOG.APP });
|
|
56
|
+
const pluginApps = apps.filter((app) => {
|
|
57
|
+
if ( app.namespace === UI_PLUGIN_NAMESPACE ) {
|
|
58
|
+
// Find the related apps from the deployed helm repository
|
|
59
|
+
const charts = this.allCharts.filter((chart) => chart.repoName === catalog.repo?.metadata?.name);
|
|
60
|
+
|
|
61
|
+
return charts.some((chart) => chart.chartName === app.metadata.name);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return false;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
await this.removeCatalogResources(catalog);
|
|
68
|
+
|
|
69
|
+
if ( pluginApps.length ) {
|
|
70
|
+
try {
|
|
71
|
+
pluginApps.forEach((app) => {
|
|
72
|
+
this.$emit('update', app.name, 'uninstall');
|
|
73
|
+
app.remove();
|
|
74
|
+
});
|
|
75
|
+
} catch (e) {
|
|
76
|
+
this.$store.dispatch('growl/error', {
|
|
77
|
+
title: this.t('plugins.error.generic'),
|
|
78
|
+
message: e.message ? e.message : e,
|
|
79
|
+
timeout: 10000
|
|
80
|
+
}, { root: true });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
await this.$store.dispatch('management/findAll', { type: CATALOG.OPERATION });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.closeDialog(catalog);
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
async removeCatalogResources(catalog) {
|
|
90
|
+
const selector = `${ UI_PLUGIN_LABELS.CATALOG_IMAGE }=${ catalog.name }`;
|
|
91
|
+
const namespace = UI_PLUGIN_NAMESPACE;
|
|
92
|
+
|
|
93
|
+
if ( selector ) {
|
|
94
|
+
const hash = await allHash({
|
|
95
|
+
deployment: this.$store.dispatch('management/findMatching', {
|
|
96
|
+
type: WORKLOAD_TYPES.DEPLOYMENT, selector, namespace
|
|
97
|
+
}),
|
|
98
|
+
service: this.$store.dispatch('management/findMatching', {
|
|
99
|
+
type: SERVICE, selector, namespace
|
|
100
|
+
}),
|
|
101
|
+
repo: this.$store.dispatch('management/findMatching', { type: CATALOG.CLUSTER_REPO, selector })
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
for ( const resource of Object.keys(hash) ) {
|
|
105
|
+
if ( hash[resource] ) {
|
|
106
|
+
hash[resource].forEach((r) => r.remove());
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
</script>
|
|
114
|
+
|
|
115
|
+
<template>
|
|
116
|
+
<modal
|
|
117
|
+
name="uninstallCatalogDialog"
|
|
118
|
+
height="auto"
|
|
119
|
+
:scrollable="true"
|
|
120
|
+
>
|
|
121
|
+
<div
|
|
122
|
+
v-if="catalog"
|
|
123
|
+
class="plugin-install-dialog"
|
|
124
|
+
>
|
|
125
|
+
<h4 class="mt-10">
|
|
126
|
+
{{ t('plugins.uninstall.title', { name: catalog.name }) }}
|
|
127
|
+
</h4>
|
|
128
|
+
<div class="mt-10 dialog-panel">
|
|
129
|
+
<div class="dialog-info">
|
|
130
|
+
<p>
|
|
131
|
+
{{ t('plugins.uninstall.catalog') }}
|
|
132
|
+
</p>
|
|
133
|
+
</div>
|
|
134
|
+
<div class="dialog-buttons">
|
|
135
|
+
<button
|
|
136
|
+
:disabled="busy"
|
|
137
|
+
class="btn role-secondary"
|
|
138
|
+
data-testid="uninstall-ext-modal-cancel-btn"
|
|
139
|
+
@click="closeDialog(false)"
|
|
140
|
+
>
|
|
141
|
+
{{ t('generic.cancel') }}
|
|
142
|
+
</button>
|
|
143
|
+
<AsyncButton
|
|
144
|
+
mode="uninstall"
|
|
145
|
+
data-testid="uninstall-ext-modal-uninstall-btn"
|
|
146
|
+
@click="uninstall()"
|
|
147
|
+
/>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</modal>
|
|
152
|
+
</template>
|
|
153
|
+
|
|
154
|
+
<style lang="scss" scoped>
|
|
155
|
+
.plugin-install-dialog {
|
|
156
|
+
padding: 10px;
|
|
157
|
+
|
|
158
|
+
h4 {
|
|
159
|
+
font-weight: bold;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.dialog-panel {
|
|
163
|
+
display: flex;
|
|
164
|
+
flex-direction: column;
|
|
165
|
+
min-height: 100px;
|
|
166
|
+
|
|
167
|
+
.dialog-info {
|
|
168
|
+
flex: 1;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.dialog-buttons {
|
|
173
|
+
display: flex;
|
|
174
|
+
justify-content: flex-end;
|
|
175
|
+
margin-top: 10px;
|
|
176
|
+
|
|
177
|
+
> *:not(:last-child) {
|
|
178
|
+
margin-right: 10px;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
</style>
|
|
@@ -13,13 +13,6 @@ import ResourceTable from '@shell/components/ResourceTable';
|
|
|
13
13
|
export default {
|
|
14
14
|
name: 'CatalogList',
|
|
15
15
|
|
|
16
|
-
props: {
|
|
17
|
-
plugins: {
|
|
18
|
-
type: Array,
|
|
19
|
-
required: true
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
|
|
23
16
|
components: { ActionMenu, ResourceTable },
|
|
24
17
|
|
|
25
18
|
mixins: [ResourceManager],
|
|
@@ -27,7 +20,7 @@ export default {
|
|
|
27
20
|
data() {
|
|
28
21
|
const actions = [
|
|
29
22
|
{
|
|
30
|
-
action: '
|
|
23
|
+
action: 'showCatalogUninstallDialog',
|
|
31
24
|
label: this.t('plugins.uninstall.label'),
|
|
32
25
|
icon: 'icon icon-trash',
|
|
33
26
|
enabled: true,
|
|
@@ -57,36 +50,26 @@ export default {
|
|
|
57
50
|
catalogRows() {
|
|
58
51
|
const rows = [];
|
|
59
52
|
|
|
60
|
-
if (this.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
const pluginName = plugin.metadata?.labels?.[UI_PLUGIN_LABELS.CATALOG_IMAGE];
|
|
53
|
+
if ( !isEmpty(this.namespacedDeployments) ) {
|
|
54
|
+
this.namespacedDeployments.forEach((deploy) => {
|
|
55
|
+
const resources = [this.namespacedServices, this.allRepos];
|
|
56
|
+
const deployName = deploy.metadata?.labels?.[UI_PLUGIN_LABELS.CATALOG_IMAGE];
|
|
65
57
|
|
|
66
|
-
if (
|
|
58
|
+
if ( deployName ) {
|
|
67
59
|
const out = {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
version: plugin.spec?.plugin?.version,
|
|
74
|
-
deployment: null,
|
|
75
|
-
deploymentImage: null,
|
|
76
|
-
service: null,
|
|
77
|
-
repo: null
|
|
60
|
+
name: deployName,
|
|
61
|
+
state: deploy.metadata?.state?.name,
|
|
62
|
+
image: deploy.spec?.template?.spec?.containers[0]?.image,
|
|
63
|
+
service: null,
|
|
64
|
+
repo: null
|
|
78
65
|
};
|
|
79
|
-
const keys = ['
|
|
66
|
+
const keys = ['service', 'repo'];
|
|
80
67
|
|
|
81
68
|
resources.forEach((resource, i) => {
|
|
82
|
-
out[keys[i]] = resource?.filter((item) => item.metadata?.labels?.[UI_PLUGIN_LABELS.CATALOG_IMAGE] ===
|
|
69
|
+
out[keys[i]] = resource?.filter((item) => item.metadata?.labels?.[UI_PLUGIN_LABELS.CATALOG_IMAGE] === deployName)[0];
|
|
83
70
|
});
|
|
84
71
|
|
|
85
|
-
|
|
86
|
-
out.deploymentImage = out.deployment.spec?.template?.spec?.containers[0]?.image;
|
|
87
|
-
|
|
88
|
-
rows.push(out);
|
|
89
|
-
}
|
|
72
|
+
rows.push(out);
|
|
90
73
|
}
|
|
91
74
|
});
|
|
92
75
|
}
|
|
@@ -153,7 +136,7 @@ export default {
|
|
|
153
136
|
:custom-target-element="menuTargetElement"
|
|
154
137
|
:custom-target-event="menuTargetEvent"
|
|
155
138
|
@close="setMenu(false)"
|
|
156
|
-
@
|
|
139
|
+
@showCatalogUninstallDialog="e => $emit('showCatalogUninstallDialog', row, e.event)"
|
|
157
140
|
/>
|
|
158
141
|
</template>
|
|
159
142
|
</ResourceTable>
|