@rancher/shell 0.3.26 → 0.3.27
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/.DS_Store +0 -0
- package/assets/translations/en-us.yaml +4 -3
- package/assets/translations/zh-hans.yaml +2 -3
- package/components/AlertTable.vue +8 -6
- package/components/EmberPage.vue +2 -2
- package/components/EtcdInfoBanner.vue +12 -2
- package/components/GlobalRoleBindings.vue +10 -0
- package/components/GrafanaDashboard.vue +8 -3
- package/components/Wizard.vue +17 -1
- package/components/auth/RoleDetailEdit.vue +17 -1
- package/components/form/ArrayList.vue +20 -11
- package/components/form/__tests__/ArrayList.test.ts +44 -0
- package/components/nav/Header.vue +5 -4
- package/components/nav/TopLevelMenu.vue +38 -15
- package/components/nav/__tests__/TopLevelMenu.test.ts +120 -0
- package/components/nav/__tests__/Type.test.ts +139 -0
- package/config/private-label.js +1 -1
- package/config/settings.ts +0 -2
- package/core/types.ts +11 -4
- package/edit/provisioning.cattle.io.cluster/Basics.vue +13 -0
- package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +1 -1
- package/edit/workload/mixins/workload.js +14 -4
- package/models/fleet.cattle.io.cluster.js +11 -1
- package/package.json +1 -1
- package/pages/c/_cluster/auth/roles/index.vue +11 -1
- package/pages/c/_cluster/explorer/index.vue +7 -2
- package/pages/c/_cluster/monitoring/index.vue +26 -39
- package/pages/support/index.vue +1 -8
- package/promptRemove/management.cattle.io.project.vue +6 -9
- package/rancher-components/components/Form/Radio/RadioGroup.test.ts +30 -0
- package/rancher-components/components/Form/Radio/RadioGroup.vue +4 -0
- package/store/features.js +1 -0
- package/types/shell/index.d.ts +4 -1
- package/utils/__tests__/object.test.ts +67 -1
- package/utils/__tests__/version.test.ts +13 -23
- package/utils/cluster.js +1 -1
- package/utils/grafana.js +1 -2
- package/utils/monitoring.js +25 -1
- package/utils/object.js +4 -3
- package/utils/sort.js +1 -1
- package/utils/validators/formRules/index.ts +1 -1
- package/utils/validators/role-template.js +1 -1
- package/utils/version.js +0 -13
|
@@ -71,12 +71,9 @@ export default {
|
|
|
71
71
|
names() {
|
|
72
72
|
return this.filteredNamespaces.map((obj) => obj.nameDisplay).slice(0, 5);
|
|
73
73
|
},
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
// BUT cluster members can also manage projects and namespaces and may want to not delete the namespaces associated with the project
|
|
78
|
-
// as per https://github.com/rancher/dashboard/issues/9517 despite the namespaces cannot be seen afterwards (projectless)
|
|
79
|
-
return this.currentCluster.canUpdate || (this.currentProject.canDelete && this.filteredNamespaces.length && this.filteredNamespaces[0]?.canDelete);
|
|
74
|
+
// Only admins and cluster owners can see namespaces outside of projects
|
|
75
|
+
canSeeProjectlessNamespaces() {
|
|
76
|
+
return this.currentCluster.canUpdate;
|
|
80
77
|
}
|
|
81
78
|
},
|
|
82
79
|
methods: {
|
|
@@ -84,7 +81,7 @@ export default {
|
|
|
84
81
|
remove() {
|
|
85
82
|
// Delete all of thre namespaces and return false - this tells the prompt remove dialog to continue and delete the project
|
|
86
83
|
// Delete all namespaces if the user wouldn't be able to see them after deleting the project
|
|
87
|
-
if (this.deleteProjectNamespaces || !this.
|
|
84
|
+
if (this.deleteProjectNamespaces || !this.canSeeProjectlessNamespaces) {
|
|
88
85
|
return Promise.all(this.filteredNamespaces.map((n) => n.remove())).then(() => false);
|
|
89
86
|
}
|
|
90
87
|
|
|
@@ -100,7 +97,7 @@ export default {
|
|
|
100
97
|
<div>
|
|
101
98
|
<div class="mb-10">
|
|
102
99
|
{{ t('promptRemove.attemptingToRemove', { type }) }} <span class="display-name">{{ `${displayName}.` }}</span>
|
|
103
|
-
<template v-if="!
|
|
100
|
+
<template v-if="!canSeeProjectlessNamespaces">
|
|
104
101
|
<span class="delete-warning"> {{ t('promptRemove.willDeleteAssociatedNamespaces') }}</span> <br>
|
|
105
102
|
<div
|
|
106
103
|
v-clean-html="resourceNames(names, plusMore, t)"
|
|
@@ -109,7 +106,7 @@ export default {
|
|
|
109
106
|
</template>
|
|
110
107
|
</div>
|
|
111
108
|
<div
|
|
112
|
-
v-if="filteredNamespaces.length > 0 &&
|
|
109
|
+
v-if="filteredNamespaces.length > 0 && canSeeProjectlessNamespaces"
|
|
113
110
|
class="mt-20 remove-project-dialog"
|
|
114
111
|
>
|
|
115
112
|
<Checkbox
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import { RadioGroup } from './index';
|
|
3
|
+
|
|
4
|
+
describe('component: RadioGroup', () => {
|
|
5
|
+
describe('when disabled', () => {
|
|
6
|
+
it.each([true, false])('should expose disabled slot prop for indexed slots for %p', (disabled) => {
|
|
7
|
+
const wrapper = mount(RadioGroup, {
|
|
8
|
+
propsData: {
|
|
9
|
+
name: 'whatever',
|
|
10
|
+
options: [{ label: 'whatever', value: 'whatever' }],
|
|
11
|
+
disabled
|
|
12
|
+
},
|
|
13
|
+
scopedSlots: {
|
|
14
|
+
0(props: {isDisabled: boolean}) {
|
|
15
|
+
return this.$createElement('input', {
|
|
16
|
+
attrs: {
|
|
17
|
+
id: 'test',
|
|
18
|
+
disabled: props.isDisabled
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const slot = wrapper.find('#test').element as HTMLInputElement;
|
|
26
|
+
|
|
27
|
+
expect(slot.disabled).toBe(disabled);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -169,6 +169,7 @@ export default Vue.extend({
|
|
|
169
169
|
|
|
170
170
|
<template>
|
|
171
171
|
<div>
|
|
172
|
+
<!-- Label -->
|
|
172
173
|
<div
|
|
173
174
|
v-if="label || labelKey || tooltip || tooltipKey || $slots.label"
|
|
174
175
|
class="radio-group label"
|
|
@@ -195,6 +196,8 @@ export default Vue.extend({
|
|
|
195
196
|
</h3>
|
|
196
197
|
</slot>
|
|
197
198
|
</div>
|
|
199
|
+
|
|
200
|
+
<!-- Group -->
|
|
198
201
|
<div
|
|
199
202
|
class="radio-group"
|
|
200
203
|
:class="{'row':row}"
|
|
@@ -212,6 +215,7 @@ export default Vue.extend({
|
|
|
212
215
|
:is-disabled="isDisabled"
|
|
213
216
|
:name="i"
|
|
214
217
|
>
|
|
218
|
+
<!-- Default input -->
|
|
215
219
|
<RadioButton
|
|
216
220
|
:key="name+'-'+i"
|
|
217
221
|
:name="name"
|
package/store/features.js
CHANGED
|
@@ -31,6 +31,7 @@ export const UNSUPPORTED_STORAGE_DRIVERS = create('unsupported-storage-drivers',
|
|
|
31
31
|
export const FLEET = create('continuous-delivery', true);
|
|
32
32
|
export const HARVESTER = create('harvester', true);
|
|
33
33
|
export const HARVESTER_CONTAINER = create('harvester-baremetal-container-workload', false);
|
|
34
|
+
export const FLEET_WORKSPACE_BACK = create('provisioningv2-fleet-workspace-back-population', false);
|
|
34
35
|
|
|
35
36
|
// Not currently used.. no point defining ones we don't use
|
|
36
37
|
// export const EMBEDDED_CLUSTER_API = create('embedded-cluster-api', true);
|
package/types/shell/index.d.ts
CHANGED
|
@@ -2807,6 +2807,7 @@ export const UNSUPPORTED_STORAGE_DRIVERS: any;
|
|
|
2807
2807
|
export const FLEET: any;
|
|
2808
2808
|
export const HARVESTER: any;
|
|
2809
2809
|
export const HARVESTER_CONTAINER: any;
|
|
2810
|
+
export const FLEET_WORKSPACE_BACK: any;
|
|
2810
2811
|
export namespace getters {
|
|
2811
2812
|
function get(state: any, getters: any, rootState: any, rootGetters: any): (name: any) => any;
|
|
2812
2813
|
}
|
|
@@ -3444,6 +3445,9 @@ export function monitoringStatus(): {
|
|
|
3444
3445
|
export function haveV2Monitoring(getters: any): boolean;
|
|
3445
3446
|
export function haveV1Monitoring(getters: any): boolean;
|
|
3446
3447
|
export function haveV1MonitoringWorkloads(store: any): Promise<boolean>;
|
|
3448
|
+
export function canViewGrafanaLink(store: any): Promise<boolean>;
|
|
3449
|
+
export function canViewAlertManagerLink(store: any): Promise<boolean>;
|
|
3450
|
+
export function canViewPrometheusLink(store: any): Promise<boolean>;
|
|
3447
3451
|
}
|
|
3448
3452
|
|
|
3449
3453
|
// @shell/utils/namespace-filter
|
|
@@ -4188,7 +4192,6 @@ export function seenReleaseNotes(store: any): boolean;
|
|
|
4188
4192
|
export function markSeenReleaseNotes(store: any): Promise<void>;
|
|
4189
4193
|
export function readReleaseNotes(store: any): boolean;
|
|
4190
4194
|
export function markReadReleaseNotes(store: any): Promise<void>;
|
|
4191
|
-
export function generateSupportLink(version: any): string;
|
|
4192
4195
|
}
|
|
4193
4196
|
|
|
4194
4197
|
// @shell/utils/width
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
clone, get, getter, isEmpty, toDictionary, remove
|
|
2
|
+
clone, get, getter, isEmpty, toDictionary, remove, diff, definedKeys
|
|
3
3
|
} from '@shell/utils/object';
|
|
4
4
|
|
|
5
5
|
describe('fx: get', () => {
|
|
@@ -161,3 +161,69 @@ describe('fx: remove', () => {
|
|
|
161
161
|
expect(result).toStrictEqual(expected);
|
|
162
162
|
});
|
|
163
163
|
});
|
|
164
|
+
|
|
165
|
+
describe('fx: diff', () => {
|
|
166
|
+
it('should return an object including only the differences between two objects', () => {
|
|
167
|
+
const from = {
|
|
168
|
+
foo: 'bar',
|
|
169
|
+
baz: 'bang',
|
|
170
|
+
};
|
|
171
|
+
const to = {
|
|
172
|
+
foo: 'bar',
|
|
173
|
+
bang: 'baz'
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const result = diff(from, to);
|
|
177
|
+
const expected = {
|
|
178
|
+
baz: null,
|
|
179
|
+
bang: 'baz'
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
expect(result).toStrictEqual(expected);
|
|
183
|
+
});
|
|
184
|
+
it('should return an object and dot characters in object should still be respected', () => {
|
|
185
|
+
const from = {};
|
|
186
|
+
const to = { foo: { 'bar.baz': 'bang' } };
|
|
187
|
+
|
|
188
|
+
const result = diff(from, to);
|
|
189
|
+
const expected = { foo: { 'bar.baz': 'bang' } };
|
|
190
|
+
|
|
191
|
+
expect(result).toStrictEqual(expected);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe('fx: definedKeys', () => {
|
|
196
|
+
it('should return an array of keys within an array', () => {
|
|
197
|
+
const obj = {
|
|
198
|
+
foo: 'bar',
|
|
199
|
+
baz: 'bang',
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const result = definedKeys(obj);
|
|
203
|
+
const expected = ['"foo"', '"baz"'];
|
|
204
|
+
|
|
205
|
+
expect(result).toStrictEqual(expected);
|
|
206
|
+
});
|
|
207
|
+
it('should return an array of keys with primitive values and their full nested path', () => {
|
|
208
|
+
const obj = {
|
|
209
|
+
foo: 'bar',
|
|
210
|
+
baz: { bang: 'bop' },
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const result = definedKeys(obj);
|
|
214
|
+
const expected = ['"foo"', '"baz"."bang"'];
|
|
215
|
+
|
|
216
|
+
expect(result).toStrictEqual(expected);
|
|
217
|
+
});
|
|
218
|
+
it('should return an array of keys with primitive values and their full nested path with quotation marks to escape keys with dots in them', () => {
|
|
219
|
+
const obj = {
|
|
220
|
+
foo: 'bar',
|
|
221
|
+
baz: { 'bang.bop': 'beep' },
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const result = definedKeys(obj);
|
|
225
|
+
const expected = ['"foo"', '"baz"."bang.bop"'];
|
|
226
|
+
|
|
227
|
+
expect(result).toStrictEqual(expected);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
@@ -1,28 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isDevBuild } from '@shell/utils/version';
|
|
2
2
|
|
|
3
|
-
describe('fx:
|
|
4
|
-
it(
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
describe('fx: isDevBuild', () => {
|
|
4
|
+
it.each([
|
|
5
|
+
'dev',
|
|
6
|
+
'master',
|
|
7
|
+
'head',
|
|
8
|
+
'whatever-head',
|
|
9
|
+
'whatever-rc1',
|
|
10
|
+
'whatever-alpha1',
|
|
11
|
+
])(
|
|
12
|
+
'should exclude version type %p', (version: string) => {
|
|
13
|
+
const result = isDevBuild(version);
|
|
7
14
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
expect(result).toStrictEqual(expectation);
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
const latestVersionSupportURL = 'https://rancher.com/support-maintenance-terms';
|
|
14
|
-
const testCases = [
|
|
15
|
-
['v2.7-0bcf068e1237acafd4aca01385c7c6b432e22fd7-head', latestVersionSupportURL],
|
|
16
|
-
['v2.7.5-rc4', latestVersionSupportURL],
|
|
17
|
-
[undefined, latestVersionSupportURL],
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
it.each(testCases)(
|
|
21
|
-
'should generate support link corresponding to the latest Rancher version when version is unknown or for dev build',
|
|
22
|
-
(version, expected) => {
|
|
23
|
-
const result = generateSupportLink(version);
|
|
24
|
-
|
|
25
|
-
expect(result).toBe(expected);
|
|
15
|
+
expect(result).toBe(true);
|
|
26
16
|
}
|
|
27
17
|
);
|
|
28
18
|
});
|
package/utils/cluster.js
CHANGED
|
@@ -7,7 +7,7 @@ import { SETTING } from '@shell/config/settings';
|
|
|
7
7
|
export function filterOnlyKubernetesClusters(mgmtClusters, store) {
|
|
8
8
|
const openHarvesterContainerWorkload = store.getters['features/get']('harvester-baremetal-container-workload');
|
|
9
9
|
|
|
10
|
-
return mgmtClusters
|
|
10
|
+
return mgmtClusters?.filter((c) => {
|
|
11
11
|
return openHarvesterContainerWorkload ? true : !isHarvesterCluster(c);
|
|
12
12
|
});
|
|
13
13
|
}
|
package/utils/grafana.js
CHANGED
|
@@ -63,14 +63,13 @@ export async function allDashboardsExist(store, clusterId, embeddedUrls, storeNa
|
|
|
63
63
|
|
|
64
64
|
let monitoringVersion = '';
|
|
65
65
|
|
|
66
|
-
if (!projectId) {
|
|
66
|
+
if (!projectId && store.getters[`${ storeName }/canList`](CATALOG.APP)) {
|
|
67
67
|
try {
|
|
68
68
|
res = await store.dispatch(`${ storeName }/find`, {
|
|
69
69
|
type: CATALOG.APP,
|
|
70
70
|
id: 'cattle-monitoring-system/rancher-monitoring'
|
|
71
71
|
});
|
|
72
72
|
} catch (err) {
|
|
73
|
-
return false;
|
|
74
73
|
}
|
|
75
74
|
|
|
76
75
|
monitoringVersion = res?.currentVersion;
|
package/utils/monitoring.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Helpers for determining if V2 or v1 Monitoring are installed
|
|
2
2
|
|
|
3
|
-
import { SCHEMA, MONITORING, WORKLOAD_TYPES } from '@shell/config/types';
|
|
3
|
+
import { SCHEMA, MONITORING, WORKLOAD_TYPES, ENDPOINTS } from '@shell/config/types';
|
|
4
4
|
import { normalizeType } from '@shell/plugins/dashboard-store/normalize';
|
|
5
5
|
import { findBy } from '@shell/utils/array';
|
|
6
6
|
import { isEmpty } from '@shell/utils/object';
|
|
@@ -65,6 +65,30 @@ export async function haveV1MonitoringWorkloads(store) {
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
async function hasEndpointSubsets(store, id) {
|
|
69
|
+
if (store.getters['cluster/schemaFor'](ENDPOINTS)) {
|
|
70
|
+
const endpoints = await store.dispatch('cluster/findAll', { type: ENDPOINTS }) || [];
|
|
71
|
+
|
|
72
|
+
const endpoint = endpoints.find((ep) => ep.id === id);
|
|
73
|
+
|
|
74
|
+
return endpoint && !isEmpty(endpoint) && !isEmpty(endpoint.subsets);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function canViewGrafanaLink(store) {
|
|
81
|
+
return await hasEndpointSubsets(store, `${ CATTLE_MONITORING_NAMESPACE }/rancher-monitoring-grafana`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function canViewAlertManagerLink(store) {
|
|
85
|
+
return await hasEndpointSubsets(store, `${ CATTLE_MONITORING_NAMESPACE }/rancher-monitoring-alertmanager`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function canViewPrometheusLink(store) {
|
|
89
|
+
return await hasEndpointSubsets(store, `${ CATTLE_MONITORING_NAMESPACE }/rancher-monitoring-prometheus`);
|
|
90
|
+
}
|
|
91
|
+
|
|
68
92
|
// Other ways we check for monitoring:
|
|
69
93
|
|
|
70
94
|
// (1) Using counts (requires RBAC permissions)
|
package/utils/object.js
CHANGED
|
@@ -178,11 +178,12 @@ export function definedKeys(obj) {
|
|
|
178
178
|
const val = obj[key];
|
|
179
179
|
|
|
180
180
|
if ( Array.isArray(val) ) {
|
|
181
|
-
return key
|
|
181
|
+
return `"${ key }"`;
|
|
182
182
|
} else if ( isObject(val) ) {
|
|
183
|
-
|
|
183
|
+
// no need for quotes around the subkey since the recursive call will fill that in via one of the other two statements in the if block
|
|
184
|
+
return ( definedKeys(val) || [] ).map((subkey) => `"${ key }".${ subkey }`);
|
|
184
185
|
} else {
|
|
185
|
-
return key
|
|
186
|
+
return `"${ key }"`;
|
|
186
187
|
}
|
|
187
188
|
});
|
|
188
189
|
|
package/utils/sort.js
CHANGED
|
@@ -184,7 +184,7 @@ export function sortBy(ary, keys, desc) {
|
|
|
184
184
|
keys = [keys];
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
return ary.slice().sort((objA, objB) => {
|
|
187
|
+
return (ary || []).slice().sort((objA, objB) => {
|
|
188
188
|
for ( let i = 0 ; i < keys.length ; i++ ) {
|
|
189
189
|
const parsed = parseField(keys[i]);
|
|
190
190
|
const a = get(objA, parsed.field);
|
|
@@ -382,7 +382,7 @@ export default function(t: Translation, { key = 'Value' }: ValidationOptions): {
|
|
|
382
382
|
if (val.some((rule: any) => isEmpty(rule.apiGroups))) {
|
|
383
383
|
return t('validation.roleTemplate.roleTemplateRules.missingApiGroup');
|
|
384
384
|
}
|
|
385
|
-
} else if (val.some((rule: any) => isEmpty(rule.resources) && isEmpty(rule.nonResourceURLs)
|
|
385
|
+
} else if (val.some((rule: any) => isEmpty(rule.resources) && isEmpty(rule.nonResourceURLs))) {
|
|
386
386
|
return t('validation.roleTemplate.roleTemplateRules.missingOneResource');
|
|
387
387
|
}
|
|
388
388
|
|
|
@@ -21,7 +21,7 @@ export function roleTemplateRules(rules = [], getters, errors, validatorArgs = [
|
|
|
21
21
|
errors.push(getters['i18n/t']('validation.roleTemplate.roleTemplateRules.noResourceAndNonResource'));
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
if (rules.some((rule) => isEmpty(rule.resources) && isEmpty(rule.nonResourceURLs)
|
|
24
|
+
if (rules.some((rule) => isEmpty(rule.resources) && isEmpty(rule.nonResourceURLs))) {
|
|
25
25
|
errors.push(getters['i18n/t']('validation.roleTemplate.roleTemplateRules.missingOneResource'));
|
|
26
26
|
}
|
|
27
27
|
}
|
package/utils/version.js
CHANGED
|
@@ -125,16 +125,3 @@ export async function markReadReleaseNotes(store) {
|
|
|
125
125
|
await store.dispatch('prefs/set', { key: READ_WHATS_NEW, value: getVersionInfo(store).fullVersion });
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
|
-
|
|
129
|
-
export function generateSupportLink(version) {
|
|
130
|
-
const defaultSupportURL = 'https://rancher.com/support-maintenance-terms';
|
|
131
|
-
|
|
132
|
-
if (!version || isDevBuild(version)) {
|
|
133
|
-
return defaultSupportURL;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const baseUrl = 'https://www.suse.com/suse-rancher/support-matrix/all-supported-versions/rancher-';
|
|
137
|
-
const formattedVersion = version.split('.').join('-');
|
|
138
|
-
|
|
139
|
-
return baseUrl + formattedVersion;
|
|
140
|
-
}
|