@rancher/shell 0.3.23 → 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/base/_variables.scss +1 -0
- package/assets/styles/themes/_dark.scss +1 -0
- package/assets/styles/themes/_light.scss +6 -5
- package/assets/translations/en-us.yaml +44 -17
- package/assets/translations/zh-hans.yaml +2 -2
- 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/Loading.vue +1 -1
- package/components/Markdown.vue +1 -1
- package/components/Questions/__tests__/Yaml.test.ts +3 -2
- package/components/SideNav.vue +1 -1
- 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/SelectOrCreateAuthSecret.vue +7 -0
- 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 +62 -34
- package/components/nav/Header.vue +13 -6
- package/components/nav/Pinned.vue +47 -0
- package/components/nav/TopLevelMenu.vue +673 -325
- package/components/nav/Type.vue +88 -8
- 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/creators/pkg/init +2 -2
- 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/fleet.cattle.io.gitrepo.vue +43 -15
- package/edit/logging.banzaicloud.io.output/index.vue +7 -0
- 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 +9 -8
- 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 +253 -582
- package/edit/workload/storage/ContainerMountPaths.vue +7 -5
- 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/App.js +2 -0
- package/initialize/client.js +63 -51
- package/initialize/index.js +7 -5
- package/layouts/default.vue +10 -2
- 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/amazonec2.vue +1 -0
- 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/mixins/fetch.client.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/__tests__/prefs.test.ts +1 -1
- package/pages/about.vue +2 -0
- package/pages/auth/setup.vue +5 -4
- package/pages/c/_cluster/explorer/ConfigBadge.vue +1 -0
- 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/pages/prefs.vue +3 -13
- package/plugins/dashboard-store/normalize.js +4 -4
- package/plugins/dashboard-store/resource-class.js +1 -1
- 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/public/index.html +4 -2
- package/rancher-components/BadgeState/BadgeState.vue +5 -1
- package/rancher-components/Banner/Banner.test.ts +51 -1
- package/rancher-components/Banner/Banner.vue +134 -53
- package/rancher-components/Card/Card.test.ts +37 -0
- package/rancher-components/Card/Card.vue +24 -7
- package/rancher-components/Form/Checkbox/Checkbox.test.ts +20 -29
- package/rancher-components/Form/Checkbox/Checkbox.vue +45 -20
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +2 -8
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +22 -10
- package/rancher-components/Form/Radio/RadioButton.test.ts +31 -0
- package/rancher-components/Form/Radio/RadioButton.vue +30 -13
- package/rancher-components/Form/Radio/RadioGroup.vue +26 -7
- package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +7 -6
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.test.ts +25 -38
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +23 -11
- package/rancher-components/LabeledTooltip/LabeledTooltip.vue +19 -5
- package/rancher-components/StringList/StringList.test.ts +453 -49
- package/rancher-components/StringList/StringList.vue +92 -58
- package/scripts/extension/parse-tag-name +0 -0
- package/store/index.js +4 -0
- package/store/prefs.js +4 -4
- package/store/type-map.js +2 -16
- package/types/shell/index.d.ts +26 -14
- package/utils/__tests__/cluster.test.ts +55 -0
- package/utils/__tests__/object.test.ts +21 -2
- package/utils/__tests__/sort.test.ts +61 -0
- package/utils/cluster.js +47 -1
- package/utils/object.js +12 -5
- package/utils/string.js +12 -0
- 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/vue.config.js +1 -4
- package/yarn-error.log +200 -0
- 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
package/mixins/fetch.client.js
CHANGED
|
@@ -75,9 +75,9 @@ async function $_fetch() { // eslint-disable-line camelcase
|
|
|
75
75
|
try {
|
|
76
76
|
await this.$options.fetch.call(this);
|
|
77
77
|
} catch (err) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
// In most cases we don't handle errors at all in `fetch`es. Lets always log to help in production
|
|
79
|
+
console.error('Error in fetch():', err); // eslint-disable-line no-console
|
|
80
|
+
|
|
81
81
|
error = normalizeError(err);
|
|
82
82
|
}
|
|
83
83
|
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import MgmtNode from '@shell/models/management.cattle.io.node';
|
|
2
|
+
|
|
3
|
+
describe('class MgmtNode', () => {
|
|
4
|
+
const foo = 'foo';
|
|
5
|
+
const bar = 'bar';
|
|
6
|
+
const t = jest.fn(() => bar);
|
|
7
|
+
const ctx = { rootGetters: { 'i18n/t': t } };
|
|
8
|
+
|
|
9
|
+
const resetMocks = () => {
|
|
10
|
+
// Clear all mock function calls:
|
|
11
|
+
jest.clearAllMocks();
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
it('should not return addresses if they are not present in the resource status, the internalNodeStatus, or the rkeNode key in status', () => {
|
|
15
|
+
const mgmtNode = new MgmtNode({ status: {} });
|
|
16
|
+
|
|
17
|
+
expect(mgmtNode.addresses).toStrictEqual([]);
|
|
18
|
+
resetMocks();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('should return addresses', () => {
|
|
22
|
+
const addresses = [foo];
|
|
23
|
+
|
|
24
|
+
it('if they are present directly on the resource status', () => {
|
|
25
|
+
const mgmtNode = new MgmtNode({ status: { addresses } });
|
|
26
|
+
|
|
27
|
+
expect(mgmtNode.addresses).toStrictEqual(addresses);
|
|
28
|
+
});
|
|
29
|
+
it('if they are not present directly on the resource status but are on "status.internalNodeStatus"', () => {
|
|
30
|
+
const mgmtNode = new MgmtNode({ status: { internalNodeStatus: { addresses } } });
|
|
31
|
+
|
|
32
|
+
expect(mgmtNode.addresses).toStrictEqual(addresses);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('should return an internalIp', () => {
|
|
37
|
+
const addresses = [{ type: 'InternalIP', address: foo }];
|
|
38
|
+
const internalAddress = foo;
|
|
39
|
+
|
|
40
|
+
it('if addresses includes an object with an appropriate type and address', () => {
|
|
41
|
+
const mgmtNode = new MgmtNode({ status: { addresses } });
|
|
42
|
+
|
|
43
|
+
expect(mgmtNode.internalIp).toStrictEqual(foo);
|
|
44
|
+
});
|
|
45
|
+
it('if internalNodeStatus.addresses includes an object with an appropriate type and address', () => {
|
|
46
|
+
const mgmtNode = new MgmtNode({ status: { internalNodeStatus: { addresses } } });
|
|
47
|
+
|
|
48
|
+
expect(mgmtNode.internalIp).toStrictEqual(foo);
|
|
49
|
+
});
|
|
50
|
+
it('if addresses and internalNodeStatus.addresses do not provide an internal ip and the status includes an rkeNode key with an appropriate type and address', () => {
|
|
51
|
+
const mgmtNode = new MgmtNode({ status: { rkeNode: { internalAddress } } });
|
|
52
|
+
|
|
53
|
+
expect(mgmtNode.internalIp).toStrictEqual(internalAddress);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('should return an externalIp', () => {
|
|
58
|
+
const addresses = [{ type: 'ExternalIP', address: foo }];
|
|
59
|
+
const address = foo;
|
|
60
|
+
|
|
61
|
+
it('if addresses includes an object with an appropriate type and address', () => {
|
|
62
|
+
const mgmtNode = new MgmtNode({ status: { addresses } });
|
|
63
|
+
|
|
64
|
+
expect(mgmtNode.externalIp).toStrictEqual(foo);
|
|
65
|
+
});
|
|
66
|
+
it('if internalNodeStatus.addresses includes an object with an appropriate type and address', () => {
|
|
67
|
+
const mgmtNode = new MgmtNode({ status: { internalNodeStatus: { addresses } } });
|
|
68
|
+
|
|
69
|
+
expect(mgmtNode.externalIp).toStrictEqual(foo);
|
|
70
|
+
});
|
|
71
|
+
it('if addresses and internalNodeStatus.addresses do not provide an external ip and the status includes an rkeNode key with an appropriate type and address', () => {
|
|
72
|
+
const mgmtNode = new MgmtNode({ status: { rkeNode: { address } } });
|
|
73
|
+
|
|
74
|
+
expect(mgmtNode.externalIp).toStrictEqual(address);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('should return an appropriate message', () => {
|
|
79
|
+
it('if there is no internalIp to display', () => {
|
|
80
|
+
const mgmtNode = new MgmtNode({ status: {} }, ctx);
|
|
81
|
+
|
|
82
|
+
expect(mgmtNode.internalIp).toStrictEqual(bar);
|
|
83
|
+
expect(t).toHaveBeenCalledTimes(1);
|
|
84
|
+
expect(t).toHaveBeenCalledWith('generic.none');
|
|
85
|
+
resetMocks();
|
|
86
|
+
});
|
|
87
|
+
it('if there is no externalIp to display', () => {
|
|
88
|
+
const mgmtNode = new MgmtNode({ status: {} }, ctx);
|
|
89
|
+
|
|
90
|
+
expect(mgmtNode.externalIp).toStrictEqual(bar);
|
|
91
|
+
expect(t).toHaveBeenCalledTimes(1);
|
|
92
|
+
expect(t).toHaveBeenCalledWith('generic.none');
|
|
93
|
+
resetMocks();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import Node from '@shell/models/management.cattle.io.node';
|
|
2
|
+
|
|
3
|
+
describe('class Node', () => {
|
|
4
|
+
const foo = 'foo';
|
|
5
|
+
const bar = 'bar';
|
|
6
|
+
const t = jest.fn(() => bar);
|
|
7
|
+
const ctx = { rootGetters: { 'i18n/t': t } };
|
|
8
|
+
|
|
9
|
+
const resetMocks = () => {
|
|
10
|
+
// Clear all mock function calls:
|
|
11
|
+
jest.clearAllMocks();
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
it('should not return addresses if they are not present in the resource status', () => {
|
|
15
|
+
const node = new Node({ status: {} });
|
|
16
|
+
|
|
17
|
+
expect(node.addresses).toStrictEqual([]);
|
|
18
|
+
resetMocks();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('should return addresses', () => {
|
|
22
|
+
const addresses = [foo];
|
|
23
|
+
|
|
24
|
+
it('if they are present directly on the resource status', () => {
|
|
25
|
+
const node = new Node({ status: { addresses } });
|
|
26
|
+
|
|
27
|
+
expect(node.addresses).toStrictEqual(addresses);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('should return an internalIp', () => {
|
|
32
|
+
const addresses = [{ type: 'InternalIP', address: foo }];
|
|
33
|
+
|
|
34
|
+
it('if addresses includes an object with an appropriate type and address', () => {
|
|
35
|
+
const node = new Node({ status: { addresses } });
|
|
36
|
+
|
|
37
|
+
expect(node.internalIp).toStrictEqual(foo);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('should return an externalIp', () => {
|
|
42
|
+
const addresses = [{ type: 'ExternalIP', address: foo }];
|
|
43
|
+
|
|
44
|
+
it('if addresses includes an object with an appropriate type and address', () => {
|
|
45
|
+
const node = new Node({ status: { addresses } });
|
|
46
|
+
|
|
47
|
+
expect(node.externalIp).toStrictEqual(foo);
|
|
48
|
+
});
|
|
49
|
+
it('if internalNodeStatus.addresses includes an object with an appropriate type and address', () => {
|
|
50
|
+
const node = new Node({ status: { internalNodeStatus: { addresses } } });
|
|
51
|
+
|
|
52
|
+
expect(node.externalIp).toStrictEqual(foo);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('should return an appropriate message', () => {
|
|
57
|
+
it('if there is no internalIp to display', () => {
|
|
58
|
+
const node = new Node({ status: {} }, ctx);
|
|
59
|
+
|
|
60
|
+
expect(node.internalIp).toStrictEqual(bar);
|
|
61
|
+
expect(t).toHaveBeenCalledTimes(1);
|
|
62
|
+
expect(t).toHaveBeenCalledWith('generic.none');
|
|
63
|
+
resetMocks();
|
|
64
|
+
});
|
|
65
|
+
it('if there is no externalIp to display', () => {
|
|
66
|
+
const node = new Node({ status: {} }, ctx);
|
|
67
|
+
|
|
68
|
+
expect(node.externalIp).toStrictEqual(bar);
|
|
69
|
+
expect(t).toHaveBeenCalledTimes(1);
|
|
70
|
+
expect(t).toHaveBeenCalledWith('generic.none');
|
|
71
|
+
resetMocks();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
});
|
package/models/cluster/node.js
CHANGED
|
@@ -92,16 +92,17 @@ export default class ClusterNode extends SteveModel {
|
|
|
92
92
|
return this.metadata.name;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
get
|
|
96
|
-
|
|
95
|
+
get addresses() {
|
|
96
|
+
return this.status?.addresses || [];
|
|
97
|
+
}
|
|
97
98
|
|
|
98
|
-
|
|
99
|
+
get internalIp() {
|
|
100
|
+
return findLast(this.addresses, (address) => address.type === 'InternalIP')?.address;
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
get externalIp() {
|
|
102
|
-
const addresses = this.status?.addresses || [];
|
|
103
104
|
const annotationAddress = this.metadata.annotations[RKE.EXTERNAL_IP];
|
|
104
|
-
const statusAddress = findLast(addresses, (address) => address.type === 'ExternalIP')?.address;
|
|
105
|
+
const statusAddress = findLast(this.addresses, (address) => address.type === 'ExternalIP')?.address;
|
|
105
106
|
|
|
106
107
|
return statusAddress || annotationAddress;
|
|
107
108
|
}
|
|
@@ -127,11 +127,11 @@ export default class CapiMachineDeployment extends SteveModel {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
this.scaleTimer = setTimeout(() => {
|
|
130
|
-
this.cluster.save().catch((err) => {
|
|
130
|
+
this.cluster.save().catch(async(err) => {
|
|
131
131
|
let errors = exceptionToErrorsArray(err);
|
|
132
132
|
|
|
133
133
|
if ( err.status === 409 && depth < 2 ) {
|
|
134
|
-
const conflicts = handleConflict(initialValue, value, liveModel, this.$rootGetters, this.$
|
|
134
|
+
const conflicts = await handleConflict(initialValue, value, liveModel, this.$rootGetters, { dispatch: this.$dispatch }, 'management');
|
|
135
135
|
|
|
136
136
|
if ( conflicts === false ) {
|
|
137
137
|
// It was automatically figured out, save again
|
|
@@ -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) {
|