@rancher/shell 0.3.25 → 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 +11 -3
- package/assets/translations/zh-hans.yaml +2 -3
- package/components/AlertTable.vue +8 -6
- package/components/CruResource.vue +7 -4
- 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/__tests__/ProjectRow.test.ts +63 -0
- package/components/auth/RoleDetailEdit.vue +21 -1
- package/components/auth/__tests__/RoleDetailEdit.test.ts +41 -0
- package/components/form/ArrayList.vue +20 -11
- package/components/form/ResourceQuota/ProjectRow.vue +6 -2
- 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/provisioning.cattle.io.cluster/rke2.vue +18 -2
- package/edit/workload/mixins/workload.js +14 -4
- package/models/fleet.cattle.io.cluster.js +11 -1
- package/models/management.cattle.io.globalrole.js +1 -1
- package/models/management.cattle.io.roletemplate.js +1 -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/scripts/extension/helm/charts/ui-plugin-server/Chart.yaml +2 -2
- 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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script>
|
|
2
|
+
import { mapGetters } from 'vuex';
|
|
2
3
|
import Tab from '@shell/components/Tabbed/Tab';
|
|
3
4
|
import Tabbed from '@shell/components/Tabbed';
|
|
4
5
|
import { MANAGEMENT } from '@shell/config/types';
|
|
@@ -7,6 +8,7 @@ import Loading from '@shell/components/Loading';
|
|
|
7
8
|
import { SUBTYPE_MAPPING, CREATE_VERBS } from '@shell/models/management.cattle.io.roletemplate';
|
|
8
9
|
import { NAME } from '@shell/config/product/auth';
|
|
9
10
|
import { BLANK_CLUSTER } from '@shell/store/store-types.js';
|
|
11
|
+
import { Banner } from '@components/Banner';
|
|
10
12
|
|
|
11
13
|
const GLOBAL = SUBTYPE_MAPPING.GLOBAL.key;
|
|
12
14
|
const CLUSTER = SUBTYPE_MAPPING.CLUSTER.key;
|
|
@@ -31,7 +33,7 @@ export default {
|
|
|
31
33
|
name: 'Roles',
|
|
32
34
|
|
|
33
35
|
components: {
|
|
34
|
-
Tab, Tabbed, ResourceTable, Loading
|
|
36
|
+
Tab, Tabbed, ResourceTable, Loading, Banner
|
|
35
37
|
},
|
|
36
38
|
|
|
37
39
|
async asyncData({ store }) {
|
|
@@ -100,6 +102,8 @@ export default {
|
|
|
100
102
|
},
|
|
101
103
|
|
|
102
104
|
computed: {
|
|
105
|
+
...mapGetters(['releaseNotesUrl']),
|
|
106
|
+
|
|
103
107
|
globalResources() {
|
|
104
108
|
return this.globalRoles;
|
|
105
109
|
},
|
|
@@ -181,6 +185,12 @@ export default {
|
|
|
181
185
|
:weight="tabs[GLOBAL].weight"
|
|
182
186
|
:label-key="tabs[GLOBAL].labelKey"
|
|
183
187
|
>
|
|
188
|
+
<Banner
|
|
189
|
+
color="warning"
|
|
190
|
+
class="mb-20"
|
|
191
|
+
>
|
|
192
|
+
<span v-clean-html="t('rbac.globalRoles.role.restricted-admin.deprecation', { releaseNotesUrl }, true)" />
|
|
193
|
+
</Banner>
|
|
184
194
|
<ResourceTable
|
|
185
195
|
:schema="tabs[GLOBAL].schema"
|
|
186
196
|
:rows="globalResources"
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
} from '@shell/config/table-headers';
|
|
29
29
|
|
|
30
30
|
import { mapPref, PSP_DEPRECATION_BANNER } from '@shell/store/prefs';
|
|
31
|
-
import { haveV1Monitoring, monitoringStatus } from '@shell/utils/monitoring';
|
|
31
|
+
import { haveV1Monitoring, monitoringStatus, canViewGrafanaLink } from '@shell/utils/monitoring';
|
|
32
32
|
import Tabbed from '@shell/components/Tabbed';
|
|
33
33
|
import Tab from '@shell/components/Tabbed/Tab';
|
|
34
34
|
import { allDashboardsExist } from '@shell/utils/grafana';
|
|
@@ -103,6 +103,10 @@ export default {
|
|
|
103
103
|
`Determine etcd metrics`
|
|
104
104
|
);
|
|
105
105
|
|
|
106
|
+
// It's not enough to check that the grafana links are working for the current user; embedded cluster-level dashboards should only be shown if the user can view the grafana endpoint
|
|
107
|
+
// https://github.com/rancher/dashboard/issues/9792
|
|
108
|
+
setPromiseResult(canViewGrafanaLink(this.$store), this, 'canViewMetrics', 'Determine Grafana Permission');
|
|
109
|
+
|
|
106
110
|
if (this.currentCluster.isLocal && this.$store.getters['management/schemaFor'](MANAGEMENT.NODE)) {
|
|
107
111
|
this.$store.dispatch('management/findAll', { type: MANAGEMENT.NODE });
|
|
108
112
|
}
|
|
@@ -125,6 +129,7 @@ export default {
|
|
|
125
129
|
showClusterMetrics: false,
|
|
126
130
|
showK8sMetrics: false,
|
|
127
131
|
showEtcdMetrics: false,
|
|
132
|
+
canViewMetrics: false,
|
|
128
133
|
CLUSTER_METRICS_DETAIL_URL,
|
|
129
134
|
CLUSTER_METRICS_SUMMARY_URL,
|
|
130
135
|
K8S_METRICS_DETAIL_URL,
|
|
@@ -346,7 +351,7 @@ export default {
|
|
|
346
351
|
},
|
|
347
352
|
|
|
348
353
|
hasMetricsTabs() {
|
|
349
|
-
return this.showClusterMetrics || this.showK8sMetrics || this.showEtcdMetrics;
|
|
354
|
+
return this.canViewMetrics && ( this.showClusterMetrics || this.showK8sMetrics || this.showEtcdMetrics);
|
|
350
355
|
},
|
|
351
356
|
|
|
352
357
|
hasBadge() {
|
|
@@ -4,16 +4,14 @@ import isEmpty from 'lodash/isEmpty';
|
|
|
4
4
|
import InstallRedirect from '@shell/utils/install-redirect';
|
|
5
5
|
import AlertTable from '@shell/components/AlertTable';
|
|
6
6
|
import { NAME, CHART_NAME } from '@shell/config/product/monitoring';
|
|
7
|
-
import { CATALOG,
|
|
7
|
+
import { CATALOG, MONITORING } from '@shell/config/types';
|
|
8
8
|
import { allHash } from '@shell/utils/promise';
|
|
9
9
|
import { findBy } from '@shell/utils/array';
|
|
10
10
|
import { getClusterPrefix } from '@shell/utils/grafana';
|
|
11
11
|
import { Banner } from '@components/Banner';
|
|
12
12
|
import LazyImage from '@shell/components/LazyImage';
|
|
13
13
|
import SimpleBox from '@shell/components/SimpleBox';
|
|
14
|
-
import { haveV1MonitoringWorkloads } from '@shell/utils/monitoring';
|
|
15
|
-
|
|
16
|
-
const CATTLE_MONITORING_NAMESPACE = 'cattle-monitoring-system';
|
|
14
|
+
import { haveV1MonitoringWorkloads, canViewAlertManagerLink, canViewGrafanaLink, canViewPrometheusLink } from '@shell/utils/monitoring';
|
|
17
15
|
|
|
18
16
|
export default {
|
|
19
17
|
components: {
|
|
@@ -96,52 +94,41 @@ export default {
|
|
|
96
94
|
const { $store, externalLinks } = this;
|
|
97
95
|
|
|
98
96
|
this.v1Installed = await haveV1MonitoringWorkloads($store);
|
|
99
|
-
const hash =
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
97
|
+
const hash = {};
|
|
98
|
+
|
|
99
|
+
if ($store.getters['cluster/canList'](CATALOG.APP)) {
|
|
100
|
+
hash.apps = $store.dispatch('cluster/findAll', { type: CATALOG.APP });
|
|
101
|
+
}
|
|
102
|
+
const res = await allHash(hash);
|
|
103
|
+
|
|
104
|
+
const canViewAlertManager = await canViewAlertManagerLink(this.$store);
|
|
105
|
+
const canViewGrafana = await canViewGrafanaLink(this.$store);
|
|
106
|
+
const canViewPrometheus = await canViewPrometheusLink(this.$store);
|
|
103
107
|
|
|
104
|
-
if (
|
|
108
|
+
if (canViewAlertManager) {
|
|
105
109
|
const amMatch = findBy(externalLinks, 'group', 'alertmanager');
|
|
106
|
-
const grafanaMatch = findBy(externalLinks, 'group', 'grafana');
|
|
107
|
-
const promeMatch = externalLinks.filter(
|
|
108
|
-
(el) => el.group === 'prometheus'
|
|
109
|
-
);
|
|
110
110
|
|
|
111
|
+
amMatch.enabled = true;
|
|
112
|
+
}
|
|
113
|
+
if (canViewGrafana) {
|
|
114
|
+
const grafanaMatch = findBy(externalLinks, 'group', 'grafana');
|
|
111
115
|
// Generate Grafana link
|
|
112
116
|
const currentCluster = this.$store.getters['currentCluster'];
|
|
113
|
-
const rancherMonitoring = !isEmpty(
|
|
117
|
+
const rancherMonitoring = !isEmpty(res.apps) ? findBy(res.apps, 'id', 'cattle-monitoring-system/rancher-monitoring') : '';
|
|
114
118
|
const clusterPrefix = getClusterPrefix(rancherMonitoring?.currentVersion || '', currentCluster.id);
|
|
115
119
|
|
|
116
120
|
grafanaMatch.link = `${ clusterPrefix }/api/v1/namespaces/cattle-monitoring-system/services/http:rancher-monitoring-grafana:80/proxy/`;
|
|
121
|
+
grafanaMatch.enabled = true;
|
|
122
|
+
}
|
|
117
123
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
'
|
|
121
|
-
`${ CATTLE_MONITORING_NAMESPACE }/rancher-monitoring-alertmanager`
|
|
122
|
-
);
|
|
123
|
-
const grafana = findBy(
|
|
124
|
-
hash.endpoints,
|
|
125
|
-
'id',
|
|
126
|
-
`${ CATTLE_MONITORING_NAMESPACE }/rancher-monitoring-grafana`
|
|
127
|
-
);
|
|
128
|
-
const prometheus = findBy(
|
|
129
|
-
hash.endpoints,
|
|
130
|
-
'id',
|
|
131
|
-
`${ CATTLE_MONITORING_NAMESPACE }/rancher-monitoring-prometheus`
|
|
124
|
+
if (canViewPrometheus) {
|
|
125
|
+
const promeMatch = externalLinks.filter(
|
|
126
|
+
(el) => el.group === 'prometheus'
|
|
132
127
|
);
|
|
133
128
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
if (!isEmpty(grafana) && !isEmpty(grafana.subsets)) {
|
|
138
|
-
grafanaMatch.enabled = true;
|
|
139
|
-
}
|
|
140
|
-
if (!isEmpty(prometheus) && !isEmpty(prometheus.subsets)) {
|
|
141
|
-
promeMatch.forEach((match) => {
|
|
142
|
-
match.enabled = true;
|
|
143
|
-
});
|
|
144
|
-
}
|
|
129
|
+
promeMatch.forEach((match) => {
|
|
130
|
+
match.enabled = true;
|
|
131
|
+
});
|
|
145
132
|
}
|
|
146
133
|
},
|
|
147
134
|
},
|
package/pages/support/index.vue
CHANGED
|
@@ -8,7 +8,6 @@ import { SETTING } from '@shell/config/settings';
|
|
|
8
8
|
import { addParam } from '@shell/utils/url';
|
|
9
9
|
import { isRancherPrime } from '@shell/config/version';
|
|
10
10
|
import { hasCspAdapter } from 'mixins/brand';
|
|
11
|
-
import { generateSupportLink } from '@shell/utils/version';
|
|
12
11
|
|
|
13
12
|
export default {
|
|
14
13
|
layout: 'home',
|
|
@@ -112,12 +111,6 @@ export default {
|
|
|
112
111
|
|
|
113
112
|
sccLink() {
|
|
114
113
|
return this.hasAWSSupport ? addParam('https://scc.suse.com', 'from_marketplace', '1') : 'https://scc.suse.com';
|
|
115
|
-
},
|
|
116
|
-
|
|
117
|
-
supportLink() {
|
|
118
|
-
const version = this.settings?.find((s) => s.id === SETTING.VERSION_RANCHER)?.value;
|
|
119
|
-
|
|
120
|
-
return generateSupportLink(version);
|
|
121
114
|
}
|
|
122
115
|
},
|
|
123
116
|
|
|
@@ -139,7 +132,7 @@ export default {
|
|
|
139
132
|
<div class="support-link">
|
|
140
133
|
<a
|
|
141
134
|
class="support-link"
|
|
142
|
-
|
|
135
|
+
href="https://www.rancher.com/support"
|
|
143
136
|
target="_blank"
|
|
144
137
|
rel="noopener noreferrer nofollow"
|
|
145
138
|
>{{ t('support.community.learnMore') }}</a>
|
|
@@ -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"
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
annotations:
|
|
2
2
|
catalog.cattle.io/certified: rancher # Any application we are adding as a helm chart
|
|
3
|
-
catalog.cattle.io/kube-version: '>= 1.16.0-0 < 1.
|
|
3
|
+
catalog.cattle.io/kube-version: '>= 1.16.0-0 < 1.29.0-0'
|
|
4
4
|
catalog.cattle.io/namespace: cattle-ui-plugin-system # Must prefix with cattle- and suffix with -system=
|
|
5
5
|
catalog.cattle.io/os: linux
|
|
6
6
|
catalog.cattle.io/permits-os: linux, windows
|
|
7
|
-
catalog.cattle.io/rancher-version: '>= 2.7.0-0 < 2.
|
|
7
|
+
catalog.cattle.io/rancher-version: '>= 2.7.0-0 < 2.9.0-0'
|
|
8
8
|
catalog.cattle.io/scope: management
|
|
9
9
|
catalog.cattle.io/ui-component: plugins
|
|
10
10
|
apiVersion: v2
|
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
|
-
}
|