@rancher/shell 3.0.1-rc.2 → 3.0.1-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/app.scss +0 -1
- package/assets/translations/en-us.yaml +1 -1
- package/assets/translations/zh-hans.yaml +0 -3
- package/components/GlobalRoleBindings.vue +0 -7
- package/components/SortableTable/THead.vue +1 -1
- package/components/auth/RoleDetailEdit.vue +0 -16
- package/edit/catalog.cattle.io.clusterrepo.vue +0 -9
- package/edit/provisioning.cattle.io.cluster/rke2.vue +3 -4
- package/models/__tests__/management.cattle.io.cluster.test.ts +0 -42
- package/models/management.cattle.io.cluster.js +0 -24
- package/models/management.cattle.io.globalrole.js +1 -0
- package/models/provisioning.cattle.io.cluster.js +23 -1
- package/package.json +1 -1
- package/pages/c/_cluster/auth/roles/index.vue +1 -11
- package/pages/c/_cluster/explorer/__tests__/index.test.ts +71 -1
- package/pages/c/_cluster/explorer/index.vue +6 -2
- package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +1 -1
- package/types/shell/index.d.ts +20 -0
- package/utils/__tests__/object.test.ts +30 -1
- package/utils/object.js +28 -0
- package/vue.config.js +2 -1
- package/machine-config/__tests__/vmwarevsphere-pool-config-merge.test.ts +0 -30
- package/machine-config/vmwarevsphere-pool-config-merge.ts +0 -25
package/assets/styles/app.scss
CHANGED
|
@@ -1065,7 +1065,7 @@ catalog:
|
|
|
1065
1065
|
target:
|
|
1066
1066
|
git: Git repository containing Helm chart or cluster template definitions
|
|
1067
1067
|
http: http(s) URL to an index generated by Helm
|
|
1068
|
-
oci: OCI Repository
|
|
1068
|
+
oci: OCI Repository
|
|
1069
1069
|
label: Target
|
|
1070
1070
|
url:
|
|
1071
1071
|
label: Index URL
|
|
@@ -4556,9 +4556,6 @@ rbac:
|
|
|
4556
4556
|
admin:
|
|
4557
4557
|
label: 管理员
|
|
4558
4558
|
description: 管理员可以完全控制整个安装以及所有集群中的所有资源。
|
|
4559
|
-
restricted-admin:
|
|
4560
|
-
label: 受限管理员
|
|
4561
|
-
description: 受限管理员可以完全控制所有下游集群的所有资源,但不能访问本地集群。
|
|
4562
4559
|
user:
|
|
4563
4560
|
label: 普通用户
|
|
4564
4561
|
description: 普通用户可以创建集群,并管理其授权访问的集群和项目。
|
|
@@ -108,7 +108,6 @@ export default {
|
|
|
108
108
|
// This not only identifies global roles but the order here is the order we want to display them in the UI
|
|
109
109
|
globalPermissions: [
|
|
110
110
|
'admin',
|
|
111
|
-
'restricted-admin',
|
|
112
111
|
'user',
|
|
113
112
|
'user-base',
|
|
114
113
|
],
|
|
@@ -121,7 +120,6 @@ export default {
|
|
|
121
120
|
};
|
|
122
121
|
},
|
|
123
122
|
computed: {
|
|
124
|
-
...mapGetters(['releaseNotesUrl']),
|
|
125
123
|
...mapGetters({ t: 'i18n/t' }),
|
|
126
124
|
|
|
127
125
|
isCreate() {
|
|
@@ -375,11 +373,6 @@ export default {
|
|
|
375
373
|
</div>
|
|
376
374
|
</template>
|
|
377
375
|
</Checkbox>
|
|
378
|
-
<p
|
|
379
|
-
v-if="role.id === 'restricted-admin'"
|
|
380
|
-
v-clean-html="t('rbac.globalRoles.role.restricted-admin.deprecation', { releaseNotesUrl }, true)"
|
|
381
|
-
class="deprecation-notice"
|
|
382
|
-
/>
|
|
383
376
|
</div>
|
|
384
377
|
</div>
|
|
385
378
|
</template>
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import { mapGetters } from 'vuex';
|
|
3
2
|
import { MANAGEMENT, RBAC } from '@shell/config/types';
|
|
4
3
|
import CruResource from '@shell/components/CruResource';
|
|
5
4
|
import CreateEditView from '@shell/mixins/create-edit-view';
|
|
@@ -15,7 +14,6 @@ import { ucFirst } from '@shell/utils/string';
|
|
|
15
14
|
import SortableTable from '@shell/components/SortableTable';
|
|
16
15
|
import { _CLONE, _DETAIL } from '@shell/config/query-params';
|
|
17
16
|
import { SCOPED_RESOURCES, SCOPED_RESOURCE_GROUPS } from '@shell/config/roles';
|
|
18
|
-
import { Banner } from '@components/Banner';
|
|
19
17
|
import LabeledInput from '@components/Form/LabeledInput/LabeledInput.vue';
|
|
20
18
|
|
|
21
19
|
import { SUBTYPE_MAPPING, VERBS } from '@shell/models/management.cattle.io.roletemplate';
|
|
@@ -66,7 +64,6 @@ export default {
|
|
|
66
64
|
SortableTable,
|
|
67
65
|
Loading,
|
|
68
66
|
Error,
|
|
69
|
-
Banner,
|
|
70
67
|
LabeledInput
|
|
71
68
|
},
|
|
72
69
|
|
|
@@ -163,12 +160,6 @@ export default {
|
|
|
163
160
|
},
|
|
164
161
|
|
|
165
162
|
computed: {
|
|
166
|
-
...mapGetters(['releaseNotesUrl']),
|
|
167
|
-
|
|
168
|
-
showRestrictedAdminDeprecationBanner() {
|
|
169
|
-
return this.value.subtype === GLOBAL && this.value.id === 'restricted-admin';
|
|
170
|
-
},
|
|
171
|
-
|
|
172
163
|
label() {
|
|
173
164
|
return this.t(`rbac.roletemplate.subtypes.${ this.value.subtype }.label`);
|
|
174
165
|
},
|
|
@@ -559,13 +550,6 @@ export default {
|
|
|
559
550
|
@finish="save"
|
|
560
551
|
@cancel="cancel"
|
|
561
552
|
>
|
|
562
|
-
<Banner
|
|
563
|
-
v-if="showRestrictedAdminDeprecationBanner"
|
|
564
|
-
color="warning"
|
|
565
|
-
class="mb-20"
|
|
566
|
-
>
|
|
567
|
-
<span v-clean-html="t('rbac.globalRoles.role.restricted-admin.deprecation', { releaseNotesUrl }, true)" />
|
|
568
|
-
</Banner>
|
|
569
553
|
<template v-if="isDetail">
|
|
570
554
|
<SortableTable
|
|
571
555
|
key-field="index"
|
|
@@ -287,12 +287,3 @@ export default {
|
|
|
287
287
|
/>
|
|
288
288
|
</form>
|
|
289
289
|
</template>
|
|
290
|
-
|
|
291
|
-
<style lang="scss">
|
|
292
|
-
span.oci-experimental-badge {
|
|
293
|
-
background-color: var(--warning);
|
|
294
|
-
color: var(--darker-active-bg);
|
|
295
|
-
font-size: 12px;
|
|
296
|
-
padding: 2px 6px;
|
|
297
|
-
}
|
|
298
|
-
</style>
|
|
@@ -22,11 +22,10 @@ import { _CREATE, _EDIT, _VIEW } from '@shell/config/query-params';
|
|
|
22
22
|
import { findBy, removeObject, clear } from '@shell/utils/array';
|
|
23
23
|
import { createYaml } from '@shell/utils/create-yaml';
|
|
24
24
|
import {
|
|
25
|
-
clone, diff, set, get, isEmpty
|
|
25
|
+
clone, diff, set, get, isEmpty, mergeWithReplaceArrays
|
|
26
26
|
} from '@shell/utils/object';
|
|
27
27
|
import { allHash } from '@shell/utils/promise';
|
|
28
28
|
import { sortBy } from '@shell/utils/sort';
|
|
29
|
-
import { vspherePoolConfigMerge } from '@shell/machine-config/vmwarevsphere-pool-config-merge';
|
|
30
29
|
|
|
31
30
|
import { compare, sortable } from '@shell/utils/version';
|
|
32
31
|
import { isHarvesterSatisfiesVersion, labelForAddon } from '@shell/utils/cluster';
|
|
@@ -1260,7 +1259,7 @@ export default {
|
|
|
1260
1259
|
delete clonedCurrentConfig.metadata;
|
|
1261
1260
|
|
|
1262
1261
|
if (this.provider === VMWARE_VSPHERE) {
|
|
1263
|
-
machinePool.config =
|
|
1262
|
+
machinePool.config = mergeWithReplaceArrays(clonedLatestConfig, clonedCurrentConfig);
|
|
1264
1263
|
} else {
|
|
1265
1264
|
machinePool.config = merge(clonedLatestConfig, clonedCurrentConfig);
|
|
1266
1265
|
}
|
|
@@ -1644,7 +1643,7 @@ export default {
|
|
|
1644
1643
|
const defaultChartValue = this.versionInfo[name];
|
|
1645
1644
|
const key = this.chartVersionKey(name);
|
|
1646
1645
|
|
|
1647
|
-
return
|
|
1646
|
+
return mergeWithReplaceArrays(defaultChartValue?.values, this.userChartValues[key]);
|
|
1648
1647
|
},
|
|
1649
1648
|
|
|
1650
1649
|
initServerAgentArgs() {
|
|
@@ -4,28 +4,6 @@ jest.mock('@shell/utils/clipboard', () => {
|
|
|
4
4
|
return { copyTextToClipboard: jest.fn(() => Promise.resolve({})) };
|
|
5
5
|
});
|
|
6
6
|
|
|
7
|
-
const importedRKE2ClusterInfo = { status: { driver: 'rke2', provider: 'rke2' } };
|
|
8
|
-
|
|
9
|
-
const provisionedRKE2ClusterInfo = { status: { driver: 'rke2', provider: 'imported' } };
|
|
10
|
-
|
|
11
|
-
const importedK3sClusterInfo = { status: { driver: 'k3s', provider: 'k3s' } };
|
|
12
|
-
|
|
13
|
-
const provisionedK3sClusterInfo = { status: { driver: 'k3s', provider: 'imported' } };
|
|
14
|
-
|
|
15
|
-
const importedAksClusterInfo = { spec: { aksConfig: { imported: true } }, status: { provider: 'aks', driver: 'AKS' } };
|
|
16
|
-
|
|
17
|
-
const provisionedAksClusterInfo = { spec: { aksConfig: { imported: false } }, status: { provider: 'aks', driver: 'AKS' } };
|
|
18
|
-
|
|
19
|
-
const importedRKE1ClusterInfo = { status: { provider: 'rke', driver: 'imported' } };
|
|
20
|
-
|
|
21
|
-
const provisionedRKE1ClusterInfo = { status: { provider: 'rke', driver: 'rancherKubernetesEngine' } };
|
|
22
|
-
|
|
23
|
-
const localRKE1ClusterInfo = { status: { provider: 'rke', driver: 'imported' } };
|
|
24
|
-
|
|
25
|
-
const localRKE2ClusterInfo = { status: { provider: 'rke2', driver: 'rke2' } };
|
|
26
|
-
|
|
27
|
-
const localEKSClusterInfo = { status: { provider: 'eks', driver: 'imported' } };
|
|
28
|
-
|
|
29
7
|
describe('class MgmtCluster', () => {
|
|
30
8
|
describe('provisioner', () => {
|
|
31
9
|
const testCases = [
|
|
@@ -42,24 +20,4 @@ describe('class MgmtCluster', () => {
|
|
|
42
20
|
}
|
|
43
21
|
);
|
|
44
22
|
});
|
|
45
|
-
|
|
46
|
-
describe('isImported', () => {
|
|
47
|
-
it.each([
|
|
48
|
-
[importedRKE2ClusterInfo, true],
|
|
49
|
-
[provisionedRKE2ClusterInfo, false],
|
|
50
|
-
[importedK3sClusterInfo, true],
|
|
51
|
-
[provisionedK3sClusterInfo, false],
|
|
52
|
-
[importedAksClusterInfo, true],
|
|
53
|
-
[provisionedAksClusterInfo, false],
|
|
54
|
-
[importedRKE1ClusterInfo, true],
|
|
55
|
-
[provisionedRKE1ClusterInfo, false],
|
|
56
|
-
[localRKE1ClusterInfo, true],
|
|
57
|
-
[localRKE2ClusterInfo, true],
|
|
58
|
-
[localEKSClusterInfo, true]
|
|
59
|
-
])('should return isImported based on props data', (clusterData, expected) => {
|
|
60
|
-
const cluster = new MgmtCluster(clusterData);
|
|
61
|
-
|
|
62
|
-
expect(cluster.isImported).toBe(expected);
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
23
|
});
|
|
@@ -89,30 +89,6 @@ export default class MgmtCluster extends SteveModel {
|
|
|
89
89
|
return pools.filter((x) => x.spec?.clusterName === this.id);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
get isImported() {
|
|
93
|
-
if (this.isLocal) {
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
// imported rke2 and k3s have status.driver === rke2 and k3s respectively
|
|
97
|
-
// Provisioned rke2 and k3s have status.driver === imported
|
|
98
|
-
if (this.status?.provider === 'k3s' || this.status?.provider === 'rke2') {
|
|
99
|
-
return this.status?.driver === this.status?.provider;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// imported KEv2
|
|
103
|
-
const kontainerConfigs = ['aksConfig', 'eksConfig', 'gkeConfig'];
|
|
104
|
-
|
|
105
|
-
const isImportedKontainer = kontainerConfigs.filter((key) => {
|
|
106
|
-
return this.spec?.[key]?.imported === true;
|
|
107
|
-
}).length;
|
|
108
|
-
|
|
109
|
-
if (isImportedKontainer) {
|
|
110
|
-
return true;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return this.provisioner === 'imported';
|
|
114
|
-
}
|
|
115
|
-
|
|
116
92
|
get provisioner() {
|
|
117
93
|
// For imported K3s clusters, this.status.driver is 'k3s.'
|
|
118
94
|
return this.status?.driver ? this.status.driver : 'imported';
|
|
@@ -117,6 +117,7 @@ export default class GlobalRole extends SteveDescriptionModel {
|
|
|
117
117
|
norman.id = this.id;
|
|
118
118
|
norman.name = this.displayName;
|
|
119
119
|
norman.description = this.description;
|
|
120
|
+
norman.inheritedClusterRoles = this.inheritedClusterRoles;
|
|
120
121
|
|
|
121
122
|
return norman;
|
|
122
123
|
})();
|
|
@@ -282,7 +282,29 @@ export default class ProvCluster extends SteveModel {
|
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
get isImported() {
|
|
285
|
-
|
|
285
|
+
if (this.isLocal) {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// imported rke2 and k3s have status.driver === rke2 and k3s respectively
|
|
290
|
+
// Provisioned rke2 and k3s have status.driver === imported
|
|
291
|
+
if (this.mgmt?.status?.provider === 'k3s' || this.mgmt?.status?.provider === 'rke2') {
|
|
292
|
+
return this.mgmt?.status?.driver === this.mgmt?.status?.provider;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// imported KEv2
|
|
296
|
+
// we can't rely on this.provisioner to determine imported-ness for these clusters, as it will return 'aks' 'eks' 'gke' for both provisioned and imported clusters
|
|
297
|
+
const kontainerConfigs = ['aksConfig', 'eksConfig', 'gkeConfig'];
|
|
298
|
+
|
|
299
|
+
const isImportedKontainer = kontainerConfigs.filter((key) => {
|
|
300
|
+
return this.mgmt?.spec?.[key]?.imported === true;
|
|
301
|
+
}).length;
|
|
302
|
+
|
|
303
|
+
if (isImportedKontainer) {
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return this.provisioner === 'imported';
|
|
286
308
|
}
|
|
287
309
|
|
|
288
310
|
get isCustom() {
|
package/package.json
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import { mapGetters } from 'vuex';
|
|
3
2
|
import Tab from '@shell/components/Tabbed/Tab';
|
|
4
3
|
import Tabbed from '@shell/components/Tabbed';
|
|
5
4
|
import { MANAGEMENT } from '@shell/config/types';
|
|
@@ -8,7 +7,6 @@ import Loading from '@shell/components/Loading';
|
|
|
8
7
|
import { SUBTYPE_MAPPING, CREATE_VERBS } from '@shell/models/management.cattle.io.roletemplate';
|
|
9
8
|
import { NAME } from '@shell/config/product/auth';
|
|
10
9
|
import { BLANK_CLUSTER } from '@shell/store/store-types.js';
|
|
11
|
-
import { Banner } from '@components/Banner';
|
|
12
10
|
|
|
13
11
|
const GLOBAL = SUBTYPE_MAPPING.GLOBAL.key;
|
|
14
12
|
const CLUSTER = SUBTYPE_MAPPING.CLUSTER.key;
|
|
@@ -33,7 +31,7 @@ export default {
|
|
|
33
31
|
name: 'Roles',
|
|
34
32
|
|
|
35
33
|
components: {
|
|
36
|
-
Tab, Tabbed, ResourceTable, Loading
|
|
34
|
+
Tab, Tabbed, ResourceTable, Loading
|
|
37
35
|
},
|
|
38
36
|
|
|
39
37
|
async fetch() {
|
|
@@ -100,8 +98,6 @@ export default {
|
|
|
100
98
|
},
|
|
101
99
|
|
|
102
100
|
computed: {
|
|
103
|
-
...mapGetters(['releaseNotesUrl']),
|
|
104
|
-
|
|
105
101
|
globalResources() {
|
|
106
102
|
return this.globalRoles;
|
|
107
103
|
},
|
|
@@ -183,12 +179,6 @@ export default {
|
|
|
183
179
|
:weight="tabs[GLOBAL].weight"
|
|
184
180
|
:label-key="tabs[GLOBAL].labelKey"
|
|
185
181
|
>
|
|
186
|
-
<Banner
|
|
187
|
-
color="warning"
|
|
188
|
-
class="mb-20"
|
|
189
|
-
>
|
|
190
|
-
<span v-clean-html="t('rbac.globalRoles.role.restricted-admin.deprecation', { releaseNotesUrl }, true)" />
|
|
191
|
-
</Banner>
|
|
192
182
|
<ResourceTable
|
|
193
183
|
:schema="tabs[GLOBAL].schema"
|
|
194
184
|
:rows="globalResources"
|
|
@@ -3,6 +3,7 @@ import Dashboard from '@shell/pages/c/_cluster/explorer/index.vue';
|
|
|
3
3
|
import { shallowMount } from '@vue/test-utils';
|
|
4
4
|
import { STATES_ENUM } from '@shell/plugins/dashboard-store/resource-class';
|
|
5
5
|
import { NODE_ARCHITECTURE } from '@shell/config/labels-annotations';
|
|
6
|
+
import { WORKLOAD_TYPES } from '@shell/config/types';
|
|
6
7
|
|
|
7
8
|
describe('page: cluster dashboard', () => {
|
|
8
9
|
const mountOptions = {
|
|
@@ -97,12 +98,81 @@ describe('page: cluster dashboard', () => {
|
|
|
97
98
|
[STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 1],
|
|
98
99
|
[STATES_ENUM.HEALTHY, 'icon-checkmark', true, false, false, [{ status: 'True' }], 1, 0],
|
|
99
100
|
]]
|
|
100
|
-
])('%p cluster - %p agent health box', (_, agentId, isLocal, agentResources, statuses) => {
|
|
101
|
+
])('%p cluster - %p agent health box :', (_, agentId, isLocal, agentResources, statuses) => {
|
|
102
|
+
it.each(statuses)('should NOT show %p status due to missing canList permissions', (status, iconClass, isLoaded, disconnected, error, conditions, readyReplicas, unavailableReplicas) => {
|
|
103
|
+
const options = clone(mountOptions);
|
|
104
|
+
|
|
105
|
+
options.global.mocks.$store.getters.currentCluster.isLocal = isLocal;
|
|
106
|
+
|
|
107
|
+
const resources = agentResources.reduce((acc, r) => {
|
|
108
|
+
const agent = {
|
|
109
|
+
metadata: { state: { error } },
|
|
110
|
+
spec: { replicas: 1 },
|
|
111
|
+
status: {
|
|
112
|
+
readyReplicas,
|
|
113
|
+
unavailableReplicas,
|
|
114
|
+
conditions
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
return isLoaded ? {
|
|
119
|
+
...acc,
|
|
120
|
+
[r]: agent
|
|
121
|
+
} : 'loading';
|
|
122
|
+
}, {});
|
|
123
|
+
|
|
124
|
+
const wrapper = shallowMount(Dashboard, {
|
|
125
|
+
...options,
|
|
126
|
+
data: () => ({
|
|
127
|
+
...resources,
|
|
128
|
+
disconnected,
|
|
129
|
+
canViewAgents: true
|
|
130
|
+
})
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const box = wrapper.find(`[data-testid="k8s-service-${ agentId }"]`);
|
|
134
|
+
|
|
135
|
+
expect(box.exists()).toBe(false);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe.each([
|
|
140
|
+
['local', 'fleet', true, ['fleetDeployment', 'fleetStatefulSet'], [
|
|
141
|
+
[STATES_ENUM.IN_PROGRESS, 'icon-spinner', false, false, false, '', 0, 0],
|
|
142
|
+
[STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, false, [{ status: 'False' }], 0, 0],
|
|
143
|
+
[STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, true, [{ status: 'True' }], 0, 0],
|
|
144
|
+
[STATES_ENUM.WARNING, 'icon-warning', true, true, false, [{ status: 'True' }], 0, 0],
|
|
145
|
+
[STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 0],
|
|
146
|
+
[STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 1],
|
|
147
|
+
[STATES_ENUM.HEALTHY, 'icon-checkmark', true, false, false, [{ status: 'True' }], 1, 0],
|
|
148
|
+
]],
|
|
149
|
+
['downstream RKE2', 'fleet', false, ['fleetStatefulSet'], [
|
|
150
|
+
[STATES_ENUM.IN_PROGRESS, 'icon-spinner', false, false, false, '', 0, 0],
|
|
151
|
+
[STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, false, [{ status: 'False' }], 0, 0],
|
|
152
|
+
[STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, true, [{ status: 'True' }], 0, 0],
|
|
153
|
+
[STATES_ENUM.WARNING, 'icon-warning', true, true, false, [{ status: 'True' }], 0, 0],
|
|
154
|
+
[STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 0],
|
|
155
|
+
[STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 1],
|
|
156
|
+
[STATES_ENUM.HEALTHY, 'icon-checkmark', true, false, false, [{ status: 'True' }], 1, 0],
|
|
157
|
+
]],
|
|
158
|
+
['downstream RKE2', 'cattle', false, ['cattleDeployment'], [
|
|
159
|
+
[STATES_ENUM.IN_PROGRESS, 'icon-spinner', false, false, false, '', 0, 0],
|
|
160
|
+
[STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, false, [{ status: 'False' }], 0, 0],
|
|
161
|
+
[STATES_ENUM.UNHEALTHY, 'icon-warning', true, true, false, [{ status: 'True' }], 0, 0],
|
|
162
|
+
[STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, true, [{ status: 'True' }], 0, 0],
|
|
163
|
+
[STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 0],
|
|
164
|
+
[STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 1],
|
|
165
|
+
[STATES_ENUM.HEALTHY, 'icon-checkmark', true, false, false, [{ status: 'True' }], 1, 0],
|
|
166
|
+
]]
|
|
167
|
+
])('%p cluster - %p agent health box ::', (_, agentId, isLocal, agentResources, statuses) => {
|
|
101
168
|
it.each(statuses)('should show %p status', (status, iconClass, isLoaded, disconnected, error, conditions, readyReplicas, unavailableReplicas) => {
|
|
102
169
|
const options = clone(mountOptions);
|
|
103
170
|
|
|
104
171
|
options.global.mocks.$store.getters.currentCluster.isLocal = isLocal;
|
|
105
172
|
|
|
173
|
+
// let's pass the canList now
|
|
174
|
+
options.global.mocks.$store.getters['cluster/canList'] = (type: string) => !!(type === WORKLOAD_TYPES.DEPLOYMENT) || !!(type === WORKLOAD_TYPES.STATEFUL_SET);
|
|
175
|
+
|
|
106
176
|
const resources = agentResources.reduce((acc, r) => {
|
|
107
177
|
const agent = {
|
|
108
178
|
metadata: { state: { error } },
|
|
@@ -188,7 +188,11 @@ export default {
|
|
|
188
188
|
},
|
|
189
189
|
|
|
190
190
|
fleetAgentNamespace() {
|
|
191
|
-
|
|
191
|
+
if (this.currentCluster.isLocal) {
|
|
192
|
+
return this.$store.getters['cluster/canList'](WORKLOAD_TYPES.DEPLOYMENT) && this.$store.getters['cluster/canList'](WORKLOAD_TYPES.STATEFUL_SET) && this.$store.getters['cluster/byId'](NAMESPACE, 'cattle-fleet-system');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return this.$store.getters['cluster/canList'](WORKLOAD_TYPES.STATEFUL_SET) && this.$store.getters['cluster/byId'](NAMESPACE, 'cattle-fleet-system');
|
|
192
196
|
},
|
|
193
197
|
|
|
194
198
|
cattleAgentNamespace() {
|
|
@@ -196,7 +200,7 @@ export default {
|
|
|
196
200
|
return;
|
|
197
201
|
}
|
|
198
202
|
|
|
199
|
-
return this.$store.getters['cluster/byId'](NAMESPACE, 'cattle-system');
|
|
203
|
+
return this.$store.getters['cluster/canList'](WORKLOAD_TYPES.DEPLOYMENT) && this.$store.getters['cluster/byId'](NAMESPACE, 'cattle-system');
|
|
200
204
|
},
|
|
201
205
|
|
|
202
206
|
canViewAgents() {
|
package/types/shell/index.d.ts
CHANGED
|
@@ -4035,6 +4035,26 @@ export function dropKeys(obj: any, keys: any): void;
|
|
|
4035
4035
|
* @returns
|
|
4036
4036
|
*/
|
|
4037
4037
|
export function deepToRaw(obj: any, cache?: any): any;
|
|
4038
|
+
/**
|
|
4039
|
+
* Helper function to alter Lodash merge function default behaviour on merging arrays while updating machine pool configuration.
|
|
4040
|
+
*
|
|
4041
|
+
* In rke2.vue, the syncMachineConfigWithLatest function updates machine pool configuration by
|
|
4042
|
+
* merging the latest configuration received from the backend with the current configuration updated by the user.
|
|
4043
|
+
* However, Lodash's merge function treats arrays like object so index values are merged and not appended to arrays
|
|
4044
|
+
* resulting in undesired outcomes for us, Example:
|
|
4045
|
+
*
|
|
4046
|
+
* const lastSavedConfigFromBE = { a: ["test"] };
|
|
4047
|
+
* const currentConfigByUser = { a: [] };
|
|
4048
|
+
* merge(lastSavedConfigFromBE, currentConfigByUser); // returns { a: ["test"] }; but we expect { a: [] };
|
|
4049
|
+
*
|
|
4050
|
+
* More info: https://github.com/lodash/lodash/issues/1313
|
|
4051
|
+
*
|
|
4052
|
+
* This helper function addresses the issue by always replacing the old array with the new array during the merge process.
|
|
4053
|
+
*
|
|
4054
|
+
* This helper is used for another case in rke2.vue to handle merging addon chart default values with the user's current values.
|
|
4055
|
+
* It fixed https://github.com/rancher/dashboard/issues/12418
|
|
4056
|
+
*/
|
|
4057
|
+
export function mergeWithReplaceArrays(obj1?: {}, obj2?: {}): any;
|
|
4038
4058
|
export { isEqualBasic as isEqual };
|
|
4039
4059
|
export function toDictionary(array: any, callback: any): any;
|
|
4040
4060
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { reactive, isReactive } from 'vue';
|
|
2
2
|
import {
|
|
3
|
-
clone, get, getter, isEmpty, toDictionary, remove, diff, definedKeys, deepToRaw
|
|
3
|
+
clone, get, getter, isEmpty, toDictionary, remove, diff, definedKeys, deepToRaw, mergeWithReplaceArrays
|
|
4
4
|
} from '@shell/utils/object';
|
|
5
5
|
|
|
6
6
|
describe('fx: get', () => {
|
|
@@ -378,3 +378,32 @@ describe('fx: deepToRaw', () => {
|
|
|
378
378
|
});
|
|
379
379
|
});
|
|
380
380
|
});
|
|
381
|
+
|
|
382
|
+
describe('fx: mergeWithReplaceArrays', () => {
|
|
383
|
+
const testCases: Array<[object?, object?, object?]> = [
|
|
384
|
+
// Some array test cases, an array from the first object should be replaced with the array from the second object
|
|
385
|
+
[{ a: ['one'] }, { a: [] }, { a: [] }],
|
|
386
|
+
[{ a: ['one', 'two'] }, { a: ['one', 'two', 'three'] }, { a: ['one', 'two', 'three'] }],
|
|
387
|
+
[{ a: ['one', 'two'], b: ['three', 'four'] }, { a: ['one'], b: [] }, { a: ['one'], b: [] }],
|
|
388
|
+
[{
|
|
389
|
+
a: ['one', 'two'], b: ['three', 'four'], c: 'five'
|
|
390
|
+
}, { a: ['one'], b: [] }, {
|
|
391
|
+
a: ['one'], b: [], c: 'five'
|
|
392
|
+
}],
|
|
393
|
+
// Some other test cases
|
|
394
|
+
[{ a: 'one' }, { b: 'two' }, { a: 'one', b: 'two' }],
|
|
395
|
+
[{ a: 'one' }, { a: '', b: 'two' }, { a: '', b: 'two' }],
|
|
396
|
+
[{ a: 'one', b: 'two' }, { a: 1, c: { d: null } }, {
|
|
397
|
+
a: 1, b: 'two', c: { d: null }
|
|
398
|
+
}],
|
|
399
|
+
[undefined, undefined, {}],
|
|
400
|
+
[{}, undefined, {}],
|
|
401
|
+
[undefined, {}, {}],
|
|
402
|
+
];
|
|
403
|
+
|
|
404
|
+
it.each(testCases)('should merge properly', (obj1, obj2, expected) => {
|
|
405
|
+
const result = mergeWithReplaceArrays(obj1, obj2);
|
|
406
|
+
|
|
407
|
+
expect(result).toStrictEqual(expected);
|
|
408
|
+
});
|
|
409
|
+
});
|
package/utils/object.js
CHANGED
|
@@ -8,6 +8,7 @@ import isObject from 'lodash/isObject';
|
|
|
8
8
|
import isArray from 'lodash/isArray';
|
|
9
9
|
import isEqual from 'lodash/isEqual';
|
|
10
10
|
import difference from 'lodash/difference';
|
|
11
|
+
import mergeWith from 'lodash/mergeWith';
|
|
11
12
|
import { splitObjectPath, joinObjectPath } from '@shell/utils/string';
|
|
12
13
|
import { addObject } from '@shell/utils/array';
|
|
13
14
|
|
|
@@ -471,3 +472,30 @@ export function deepToRaw(obj, cache = new WeakSet()) {
|
|
|
471
472
|
return result;
|
|
472
473
|
}
|
|
473
474
|
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Helper function to alter Lodash merge function default behaviour on merging arrays while updating machine pool configuration.
|
|
478
|
+
*
|
|
479
|
+
* In rke2.vue, the syncMachineConfigWithLatest function updates machine pool configuration by
|
|
480
|
+
* merging the latest configuration received from the backend with the current configuration updated by the user.
|
|
481
|
+
* However, Lodash's merge function treats arrays like object so index values are merged and not appended to arrays
|
|
482
|
+
* resulting in undesired outcomes for us, Example:
|
|
483
|
+
*
|
|
484
|
+
* const lastSavedConfigFromBE = { a: ["test"] };
|
|
485
|
+
* const currentConfigByUser = { a: [] };
|
|
486
|
+
* merge(lastSavedConfigFromBE, currentConfigByUser); // returns { a: ["test"] }; but we expect { a: [] };
|
|
487
|
+
*
|
|
488
|
+
* More info: https://github.com/lodash/lodash/issues/1313
|
|
489
|
+
*
|
|
490
|
+
* This helper function addresses the issue by always replacing the old array with the new array during the merge process.
|
|
491
|
+
*
|
|
492
|
+
* This helper is used for another case in rke2.vue to handle merging addon chart default values with the user's current values.
|
|
493
|
+
* It fixed https://github.com/rancher/dashboard/issues/12418
|
|
494
|
+
*/
|
|
495
|
+
export function mergeWithReplaceArrays(obj1 = {}, obj2 = {}) {
|
|
496
|
+
return mergeWith(obj1, obj2, (obj1Value, obj2Value) => {
|
|
497
|
+
if (Array.isArray(obj1Value) && Array.isArray(obj2Value)) {
|
|
498
|
+
return obj2Value;
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
}
|
package/vue.config.js
CHANGED
|
@@ -462,7 +462,7 @@ const getWatcherIgnored = (excludes) => {
|
|
|
462
462
|
/dist-pkg/,
|
|
463
463
|
/scripts\/standalone/,
|
|
464
464
|
];
|
|
465
|
-
const pathExcludedPkg = excludes.map((excluded) => new RegExp(`/pkg.${ excluded }
|
|
465
|
+
const pathExcludedPkg = excludes.map((excluded) => new RegExp(`/pkg.${ excluded }/`));
|
|
466
466
|
const pathsCombined = [...paths, ...pathExcludedPkg];
|
|
467
467
|
const regexCombined = new RegExp(pathsCombined.map(({ source }) => source).join('|'));
|
|
468
468
|
|
|
@@ -516,6 +516,7 @@ module.exports = function(dir, _appConfig) {
|
|
|
516
516
|
@import "~shell/assets/styles/base/_variables.scss";
|
|
517
517
|
@import "~shell/assets/styles/base/_functions.scss";
|
|
518
518
|
@import "~shell/assets/styles/base/_mixins.scss";
|
|
519
|
+
@import 'node_modules/xterm/css/xterm.css';
|
|
519
520
|
`
|
|
520
521
|
}
|
|
521
522
|
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { vspherePoolConfigMerge } from '@shell/machine-config/vmwarevsphere-pool-config-merge';
|
|
2
|
-
|
|
3
|
-
describe('vspherePoolConfigMerge', () => {
|
|
4
|
-
const testCases: Array<[object?, object?, object?]> = [
|
|
5
|
-
// Some array test cases, an array from the first object should be replaced with the array from the second object
|
|
6
|
-
[{ a: ['one'] }, { a: [] }, { a: [] }],
|
|
7
|
-
[{ a: ['one', 'two'] }, { a: ['one', 'two', 'three'] }, { a: ['one', 'two', 'three'] }],
|
|
8
|
-
[{ a: ['one', 'two'], b: ['three', 'four'] }, { a: ['one'], b: [] }, { a: ['one'], b: [] }],
|
|
9
|
-
[{
|
|
10
|
-
a: ['one', 'two'], b: ['three', 'four'], c: 'five'
|
|
11
|
-
}, { a: ['one'], b: [] }, {
|
|
12
|
-
a: ['one'], b: [], c: 'five'
|
|
13
|
-
}],
|
|
14
|
-
// Some other test cases
|
|
15
|
-
[{ a: 'one' }, { b: 'two' }, { a: 'one', b: 'two' }],
|
|
16
|
-
[{ a: 'one' }, { a: '', b: 'two' }, { a: '', b: 'two' }],
|
|
17
|
-
[{ a: 'one', b: 'two' }, { a: 1, c: { d: null } }, {
|
|
18
|
-
a: 1, b: 'two', c: { d: null }
|
|
19
|
-
}],
|
|
20
|
-
[undefined, undefined, {}],
|
|
21
|
-
[{}, undefined, {}],
|
|
22
|
-
[undefined, {}, {}],
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
it.each(testCases)('should merge properly', (obj1, obj2, expected) => {
|
|
26
|
-
const result = vspherePoolConfigMerge(obj1, obj2);
|
|
27
|
-
|
|
28
|
-
expect(result).toStrictEqual(expected);
|
|
29
|
-
});
|
|
30
|
-
});
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import mergeWith from 'lodash/mergeWith';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Helper function to alter Lodash merge function default behaviour on merging arrays while updating machine pool configuration.
|
|
5
|
-
*
|
|
6
|
-
* In rke2.vue, the syncMachineConfigWithLatest function updates machine pool configuration by
|
|
7
|
-
* merging the latest configuration received from the backend with the current configuration updated by the user.
|
|
8
|
-
* However, Lodash's merge function treats arrays like object so index values are merged and not appended to arrays
|
|
9
|
-
* resulting in undesired outcomes for us, Example:
|
|
10
|
-
*
|
|
11
|
-
* const lastSavedConfigFromBE = { a: ["test"] };
|
|
12
|
-
* const currentConfigByUser = { a: [] };
|
|
13
|
-
* merge(lastSavedConfigFromBE, currentConfigByUser); // returns { a: ["test"] }; but we expect { a: [] };
|
|
14
|
-
*
|
|
15
|
-
* More info: https://github.com/lodash/lodash/issues/1313
|
|
16
|
-
*
|
|
17
|
-
* This helper function addresses the issue by always replacing the old array with the new array during the merge process.
|
|
18
|
-
*/
|
|
19
|
-
export function vspherePoolConfigMerge(obj1 = {}, obj2 = {}): Object {
|
|
20
|
-
return mergeWith(obj1, obj2, (obj1Value, obj2Value) => {
|
|
21
|
-
if (Array.isArray(obj1Value) && Array.isArray(obj2Value)) {
|
|
22
|
-
return obj2Value;
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
}
|