@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
package/.DS_Store
ADDED
|
Binary file
|
|
@@ -653,6 +653,10 @@ asyncButton:
|
|
|
653
653
|
action: Save
|
|
654
654
|
success: Saved
|
|
655
655
|
waiting: Saving…
|
|
656
|
+
editAndContinue:
|
|
657
|
+
action: Save and Continue
|
|
658
|
+
success: Saved
|
|
659
|
+
waiting: Saving…
|
|
656
660
|
enable:
|
|
657
661
|
action: Enable
|
|
658
662
|
success: Enabled
|
|
@@ -1673,6 +1677,7 @@ cluster:
|
|
|
1673
1677
|
haveArgInfo: Configuration information is not available for the selected Kubernetes version. The options available on this screen will be limited; you may want to use the YAML editor.
|
|
1674
1678
|
deprecatedPsp: Pod Security Policies are deprecated as of Kubernetes v1.21, and have been removed in Kubernetes v1.25.
|
|
1675
1679
|
removedPsp: Pod Security Policies have been removed in Kubernetes v1.25. Use Pod Security Admission instead.
|
|
1680
|
+
cloudProviderAddConfig: 'On Kubernetes 1.27 or greater, the Amazon Cloud Provider requires additional configuration. See <a href="https://ranchermanager.docs.rancher.com/how-to-guides/new-user-guides/kubernetes-clusters-in-rancher-setup/set-up-cloud-providers/amazon" target="_blank" rel="noopener noreferrer nofollow">the documentation</a> for more information.'
|
|
1676
1681
|
machinePoolError: |-
|
|
1677
1682
|
{count, plural,
|
|
1678
1683
|
=1 { {pool_name}: The provided value for {fields} was not found in the list of expected values. This can happen with clusters provisioned outside of Rancher or when options for the provider have changed. }
|
|
@@ -1841,6 +1846,9 @@ cluster:
|
|
|
1841
1846
|
pspChange:
|
|
1842
1847
|
title: Pod Security Policy deprecation
|
|
1843
1848
|
body: <p>Kubernetes has removed support for Pod Security Policies (PSPs) starting with version 1.25. If your cluster has PodSecurityPolicy admission controller enabled via "kube-apiserver-arg.enable-admission-plugins" in Cluster YAML, it has to be <i>manually</i> removed before proceeding with the upgrade. Additionally, any PSPs that may be present in the cluster will no longer be available/enforced. Do you want to proceed?</p>
|
|
1849
|
+
editYamlMachinePool:
|
|
1850
|
+
title: Save Machine Configurations
|
|
1851
|
+
body: Machine Configurations define how machines in Pools are deployed.<br><br> They will be saved upfront to ensure valid Cluster YAML can be saved.
|
|
1844
1852
|
snapshots:
|
|
1845
1853
|
suffix: Snapshots per node
|
|
1846
1854
|
systemService:
|
|
@@ -2756,7 +2764,7 @@ landing:
|
|
|
2756
2764
|
cpuUsed: CPU Used
|
|
2757
2765
|
memoryUsed: Memory Used
|
|
2758
2766
|
seeWhatsNew: Learn more about the improvements and new capabilities in this version.
|
|
2759
|
-
whatsNewLink: "What's new in 2.
|
|
2767
|
+
whatsNewLink: "What's new in 2.8"
|
|
2760
2768
|
learnMore: Learn More
|
|
2761
2769
|
support: Support
|
|
2762
2770
|
psps: PSPs
|
|
@@ -2826,7 +2834,7 @@ logging:
|
|
|
2826
2834
|
dockerRootDirectory: Docker Root Directory
|
|
2827
2835
|
systemdLogPath: systemd Log Path
|
|
2828
2836
|
tooltip: 'Some kubernetes distributions log to <code>journald</code>. In order to collect these logs the <code>systemdLogPath</code> needs to be defined. While the <code>/run/log/journal</code> directory is used by default, some Linux distributions do not default to this path.'
|
|
2829
|
-
url: '<a href="https://rancher.com/
|
|
2837
|
+
url: '<a href="https://ranchermanager.docs.rancher.com/v2.8/integrations-in-rancher/logging/logging-helm-chart-options" target="_blank" rel="noopener nofollow noreferrer">Learn more</a>'
|
|
2830
2838
|
default: /run/log/journal
|
|
2831
2839
|
elasticsearch:
|
|
2832
2840
|
host: Host
|
|
@@ -4606,6 +4614,7 @@ rbac:
|
|
|
4606
4614
|
restricted-admin:
|
|
4607
4615
|
label: Restricted Administrator
|
|
4608
4616
|
description: Restricted Admins have full control over all resources in all downstream clusters but no access to the local cluster.
|
|
4617
|
+
deprecation: 'Warning: The Restricted Administrator role has been deprecated as of Rancher 2.8.0 and will be removed in a future release - Check out the <a href="{releaseNotesUrl}" target="_blank" rel="noopener noreferrer nofollow">Release Notes</a>'
|
|
4609
4618
|
user:
|
|
4610
4619
|
label: Standard User
|
|
4611
4620
|
description: Standard Users can create new clusters and manage clusters and projects they have been granted access to.
|
|
@@ -7072,7 +7081,6 @@ advancedSettings:
|
|
|
7072
7081
|
'auth-user-session-ttl-minutes': 'Custom TTL (in minutes) on a user auth session.'
|
|
7073
7082
|
'auth-token-max-ttl-minutes': 'Max TTL (in minutes) for all authentication tokens. When set to 0, the token never expires.'
|
|
7074
7083
|
'kubeconfig-generate-token': 'Automatically generate tokens for users when a kubeconfig is requested.'
|
|
7075
|
-
'kubeconfig-token-ttl-minutes': 'TTL used for tokens generated via the CLI. Deprecated: This setting will be removed, and kubeconfig-default-token-ttl-minutes will be used for all kubeconfig tokens.'
|
|
7076
7084
|
'kubeconfig-default-token-ttl-minutes': 'TTL (in minutes) applied on all kubeconfig tokens. When set to 0, the token never expires.'
|
|
7077
7085
|
'rke-metadata-config': 'Configure RKE metadata refresh parameters.'
|
|
7078
7086
|
'ui-banners': 'Classification banner is used to display a custom fixed banner in the header, footer, or both.'
|
|
@@ -2735,7 +2735,7 @@ landing:
|
|
|
2735
2735
|
cpuUsed: 已用 CPU
|
|
2736
2736
|
memoryUsed: 已用内存
|
|
2737
2737
|
seeWhatsNew: 点击右侧链接,了解此版本的新功能和优化。
|
|
2738
|
-
whatsNewLink: "2.
|
|
2738
|
+
whatsNewLink: "2.8 的新功能"
|
|
2739
2739
|
learnMore: 了解更多
|
|
2740
2740
|
support: 支持
|
|
2741
2741
|
psps: PSP
|
|
@@ -2805,7 +2805,7 @@ logging:
|
|
|
2805
2805
|
dockerRootDirectory: Docker 根目录
|
|
2806
2806
|
systemdLogPath: systemd 日志路径
|
|
2807
2807
|
tooltip: '某些 Kubernetes 发行版在 <code>journald</code>中记录日志。你需要定义<code>systemdLogPath</code> 以收集日志。默认路径是<code>/run/log/journal</code>,但某些 Linux 发行版不默认使用该路径。'
|
|
2808
|
-
url: '<a href="https://rancher.com/
|
|
2808
|
+
url: '<a href="https://ranchermanager.docs.rancher.com/v2.8/integrations-in-rancher/logging/logging-helm-chart-options" target="_blank" rel="noopener nofollow noreferrer">了解更多</a>'
|
|
2809
2809
|
default: /run/log/journal
|
|
2810
2810
|
elasticsearch:
|
|
2811
2811
|
host: 主机
|
|
@@ -7051,7 +7051,6 @@ advancedSettings:
|
|
|
7051
7051
|
'auth-user-session-ttl-minutes': '用户认证会话的自定义 TTL(单位:分钟)。'
|
|
7052
7052
|
'auth-token-max-ttl-minutes': '所有身份认证 Token 的最大 TTL(单位:分钟)。如果设置为 0,则 Token 永不过期。'
|
|
7053
7053
|
'kubeconfig-generate-token': '请求 kubeconfig 时自动为用户生成 Token。'
|
|
7054
|
-
'kubeconfig-token-ttl-minutes': '在 CLI 中生成的 Token TTL。已弃用:此设置将被删除,kubeconfig-default-token-ttl-minutes 将用于所有 kubeconfig Token。'
|
|
7055
7054
|
'kubeconfig-default-token-ttl-minutes': '应用于所有 kubeconfig Token 的 TTL(单位:分钟)。如果设置为 0,则 Token 永不过期。'
|
|
7056
7055
|
'rke-metadata-config': '配置 RKE 元数据刷新参数。'
|
|
7057
7056
|
'ui-banners': '分类横幅用于在页眉、页脚或两者中显示自定义的固定横幅。'
|
|
@@ -82,14 +82,16 @@ export default {
|
|
|
82
82
|
},
|
|
83
83
|
|
|
84
84
|
async fetchDeps() {
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
if (this.$store.getters['cluster/canList'](ENDPOINTS)) {
|
|
86
|
+
try {
|
|
87
|
+
const am = await this.$store.dispatch('cluster/find', { type: ENDPOINTS, id: `${ this.monitoringNamespace }/${ this.alertServiceEndpoint }` });
|
|
87
88
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
if (!isEmpty(am) && !isEmpty(am.subsets)) {
|
|
90
|
+
this.alertManagerPoller.start();
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
92
93
|
|
|
94
|
+
}
|
|
93
95
|
}
|
|
94
96
|
},
|
|
95
97
|
}
|
|
@@ -327,10 +327,13 @@ export default {
|
|
|
327
327
|
|
|
328
328
|
async showPreviewYaml() {
|
|
329
329
|
if ( this.applyHooks ) {
|
|
330
|
-
|
|
331
|
-
BEFORE_SAVE_HOOKS,
|
|
332
|
-
|
|
333
|
-
|
|
330
|
+
try {
|
|
331
|
+
await this.applyHooks(BEFORE_SAVE_HOOKS, CONTEXT_HOOK_EDIT_YAML);
|
|
332
|
+
} catch (e) {
|
|
333
|
+
console.warn('Unable to show yaml: ', e); // eslint-disable-line no-console
|
|
334
|
+
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
334
337
|
}
|
|
335
338
|
|
|
336
339
|
const resourceYaml = this.createResourceYaml(this.yamlModifiers);
|
package/components/EmberPage.vue
CHANGED
|
@@ -597,11 +597,11 @@ export default {
|
|
|
597
597
|
|
|
598
598
|
.ember-iframe {
|
|
599
599
|
border: 0;
|
|
600
|
-
left: var(--nav-width);
|
|
600
|
+
left: calc(var(--nav-width) + $app-bar-collapsed-width);
|
|
601
601
|
height: calc(100vh - var(--header-height));
|
|
602
602
|
position: absolute;
|
|
603
603
|
top: var(--header-height);
|
|
604
|
-
width: calc(100vw - var(--nav-width));
|
|
604
|
+
width: calc(100vw - var(--nav-width) - $app-bar-collapsed-width);
|
|
605
605
|
visibility: show;
|
|
606
606
|
}
|
|
607
607
|
|
|
@@ -9,8 +9,18 @@ export default {
|
|
|
9
9
|
components: { Banner, Loading },
|
|
10
10
|
async fetch() {
|
|
11
11
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
let monitoringVersion = '';
|
|
13
|
+
|
|
14
|
+
if (this.$store.getters[`${ inStore }/canList}`](CATALOG.APP)) {
|
|
15
|
+
try {
|
|
16
|
+
const res = await this.$store.dispatch(`${ inStore }/find`, { type: CATALOG.APP, id: 'cattle-monitoring-system/rancher-monitoring' });
|
|
17
|
+
|
|
18
|
+
monitoringVersion = res?.currentVersion;
|
|
19
|
+
} catch (err) {
|
|
20
|
+
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
14
24
|
const leader = await hasLeader(monitoringVersion, this.$store.dispatch, this.currentCluster.id);
|
|
15
25
|
|
|
16
26
|
this.hasLeader = leader ? this.t('generic.yes') : this.t('generic.no');
|
|
@@ -94,6 +94,7 @@ export default {
|
|
|
94
94
|
};
|
|
95
95
|
},
|
|
96
96
|
computed: {
|
|
97
|
+
...mapGetters(['releaseNotesUrl']),
|
|
97
98
|
...mapGetters({ t: 'i18n/t' }),
|
|
98
99
|
|
|
99
100
|
isCreate() {
|
|
@@ -347,6 +348,11 @@ export default {
|
|
|
347
348
|
</div>
|
|
348
349
|
</template>
|
|
349
350
|
</Checkbox>
|
|
351
|
+
<p
|
|
352
|
+
v-if="role.id === 'restricted-admin'"
|
|
353
|
+
v-clean-html="t('rbac.globalRoles.role.restricted-admin.deprecation', { releaseNotesUrl }, true)"
|
|
354
|
+
class="deprecation-notice"
|
|
355
|
+
/>
|
|
350
356
|
</div>
|
|
351
357
|
</div>
|
|
352
358
|
</template>
|
|
@@ -364,6 +370,10 @@ export default {
|
|
|
364
370
|
</style>
|
|
365
371
|
<style lang='scss' scoped>
|
|
366
372
|
$detailSize: 11px;
|
|
373
|
+
|
|
374
|
+
.deprecation-notice {
|
|
375
|
+
margin: 8px 0 8px 20px;
|
|
376
|
+
}
|
|
367
377
|
.role-group {
|
|
368
378
|
.type-title {
|
|
369
379
|
display: flex;
|
|
@@ -40,13 +40,18 @@ export default {
|
|
|
40
40
|
},
|
|
41
41
|
async fetch() {
|
|
42
42
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
|
43
|
-
const res = await this.$store.dispatch(`${ inStore }/find`, { type: CATALOG.APP, id: 'cattle-monitoring-system/rancher-monitoring' });
|
|
44
43
|
|
|
45
|
-
this.
|
|
44
|
+
if (this.$store.getters[`${ inStore }/canList`](CATALOG.APP)) {
|
|
45
|
+
try {
|
|
46
|
+
const res = await this.$store.dispatch(`${ inStore }/find`, { type: CATALOG.APP, id: 'cattle-monitoring-system/rancher-monitoring' });
|
|
47
|
+
|
|
48
|
+
this.monitoringVersion = res?.currentVersion;
|
|
49
|
+
} catch (err) {}
|
|
50
|
+
}
|
|
46
51
|
},
|
|
47
52
|
data() {
|
|
48
53
|
return {
|
|
49
|
-
loading: false, error: false, interval: null, errorTimer: null, monitoringVersion:
|
|
54
|
+
loading: false, error: false, interval: null, errorTimer: null, monitoringVersion: ''
|
|
50
55
|
};
|
|
51
56
|
},
|
|
52
57
|
computed: {
|
package/components/Wizard.vue
CHANGED
|
@@ -182,6 +182,12 @@ export default {
|
|
|
182
182
|
this.activeStep = this.visibleSteps[this.initStepIndex];
|
|
183
183
|
this.goToStep(this.activeStepIndex + 1);
|
|
184
184
|
}
|
|
185
|
+
},
|
|
186
|
+
errors() {
|
|
187
|
+
// Ensurce we scroll the errors into view
|
|
188
|
+
this.$nextTick(() => {
|
|
189
|
+
this.$refs.wizard.scrollTop = this.$refs.wizard.scrollHeight;
|
|
190
|
+
});
|
|
185
191
|
}
|
|
186
192
|
},
|
|
187
193
|
|
|
@@ -253,7 +259,10 @@ export default {
|
|
|
253
259
|
</script>
|
|
254
260
|
|
|
255
261
|
<template>
|
|
256
|
-
<div
|
|
262
|
+
<div
|
|
263
|
+
ref="wizard"
|
|
264
|
+
class="outer-container"
|
|
265
|
+
>
|
|
257
266
|
<Loading
|
|
258
267
|
v-if="!stepsLoaded"
|
|
259
268
|
mode="relative"
|
|
@@ -397,6 +406,7 @@ export default {
|
|
|
397
406
|
color="error"
|
|
398
407
|
:label="err"
|
|
399
408
|
:closable="true"
|
|
409
|
+
class="footer-error"
|
|
400
410
|
@close="errors.splice(idx, 1)"
|
|
401
411
|
/>
|
|
402
412
|
</div>
|
|
@@ -647,6 +657,12 @@ $spacer: 10px;
|
|
|
647
657
|
}
|
|
648
658
|
}
|
|
649
659
|
|
|
660
|
+
// We have to account for the absolute position of the .controls-row
|
|
661
|
+
.footer-error {
|
|
662
|
+
margin-top: -40px;
|
|
663
|
+
margin-bottom: 70px;
|
|
664
|
+
}
|
|
665
|
+
|
|
650
666
|
.controls-row {
|
|
651
667
|
|
|
652
668
|
// Overrides outlet padding
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import ProjectRow from '@shell/components/form/ResourceQuota/ProjectRow.vue';
|
|
2
|
+
import { RANCHER_TYPES } from '@shell/components/form/ResourceQuota/shared';
|
|
3
|
+
import { shallowMount } from '@vue/test-utils';
|
|
4
|
+
import Vue from 'vue';
|
|
5
|
+
|
|
6
|
+
const CONFIGMAP_STRING = RANCHER_TYPES[0].value;
|
|
7
|
+
|
|
8
|
+
describe('component: ProjectRow.vue', () => {
|
|
9
|
+
const wrapper = shallowMount(ProjectRow,
|
|
10
|
+
{
|
|
11
|
+
propsData: {
|
|
12
|
+
mode: 'edit',
|
|
13
|
+
types: RANCHER_TYPES,
|
|
14
|
+
type: CONFIGMAP_STRING,
|
|
15
|
+
value: {
|
|
16
|
+
spec: {
|
|
17
|
+
namespaceDefaultResourceQuota: { limit: {} },
|
|
18
|
+
resourceQuota: { limit: {} }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should render the correct input fields and set the correct computed values, based on the provided data', () => {
|
|
25
|
+
const typeInput = wrapper.find(`[data-testid="projectrow-type-input"]`);
|
|
26
|
+
const projectQuotaInput = wrapper.find(`[data-testid="projectrow-project-quota-input"]`);
|
|
27
|
+
const namespaceQuotaInput = wrapper.find(`[data-testid="projectrow-namespace-quota-input"]`);
|
|
28
|
+
|
|
29
|
+
expect(typeInput.exists()).toBe(true);
|
|
30
|
+
expect(projectQuotaInput.exists()).toBe(true);
|
|
31
|
+
expect(namespaceQuotaInput.exists()).toBe(true);
|
|
32
|
+
expect(wrapper.vm.resourceQuotaLimit).toStrictEqual({});
|
|
33
|
+
expect(wrapper.vm.namespaceDefaultResourceQuotaLimit).toStrictEqual({});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('triggering "updateQuotaLimit" should trigger Vue.set with the correct data', () => {
|
|
37
|
+
const vueSet = jest.spyOn(Vue, 'set');
|
|
38
|
+
|
|
39
|
+
wrapper.vm.updateQuotaLimit('resourceQuota', CONFIGMAP_STRING, 10);
|
|
40
|
+
|
|
41
|
+
expect(vueSet).toHaveBeenCalledTimes(1);
|
|
42
|
+
expect(wrapper.vm.value).toStrictEqual({
|
|
43
|
+
spec: {
|
|
44
|
+
namespaceDefaultResourceQuota: { limit: {} },
|
|
45
|
+
resourceQuota: { limit: { [`${ CONFIGMAP_STRING }`]: 10 } }
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('triggering "updateType" with the same type that existed should clear limits and trigger emit', () => {
|
|
51
|
+
wrapper.vm.updateType(CONFIGMAP_STRING);
|
|
52
|
+
|
|
53
|
+
expect(wrapper.vm.value).toStrictEqual({
|
|
54
|
+
spec: {
|
|
55
|
+
namespaceDefaultResourceQuota: { limit: {} },
|
|
56
|
+
resourceQuota: { limit: {} }
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(wrapper.emitted('type-change')).toBeTruthy();
|
|
61
|
+
expect(wrapper.emitted('type-change')[0]).toStrictEqual([CONFIGMAP_STRING]);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script>
|
|
2
|
+
import { mapGetters } from 'vuex';
|
|
2
3
|
import { MANAGEMENT, RBAC } from '@shell/config/types';
|
|
3
4
|
import CruResource from '@shell/components/CruResource';
|
|
4
5
|
import CreateEditView from '@shell/mixins/create-edit-view';
|
|
@@ -14,6 +15,7 @@ import { ucFirst } from '@shell/utils/string';
|
|
|
14
15
|
import SortableTable from '@shell/components/SortableTable';
|
|
15
16
|
import { _CLONE, _DETAIL } from '@shell/config/query-params';
|
|
16
17
|
import { SCOPED_RESOURCES } from '@shell/config/roles';
|
|
18
|
+
import { Banner } from '@components/Banner';
|
|
17
19
|
|
|
18
20
|
import { SUBTYPE_MAPPING, VERBS } from '@shell/models/management.cattle.io.roletemplate';
|
|
19
21
|
import Loading from '@shell/components/Loading';
|
|
@@ -60,7 +62,8 @@ export default {
|
|
|
60
62
|
Tabbed,
|
|
61
63
|
SortableTable,
|
|
62
64
|
Loading,
|
|
63
|
-
Error
|
|
65
|
+
Error,
|
|
66
|
+
Banner
|
|
64
67
|
},
|
|
65
68
|
|
|
66
69
|
mixins: [CreateEditView, FormValidation],
|
|
@@ -152,12 +155,22 @@ export default {
|
|
|
152
155
|
});
|
|
153
156
|
}
|
|
154
157
|
|
|
158
|
+
if (this.value?.metadata?.name && !this.value.displayName) {
|
|
159
|
+
this.$set(this.value, 'displayName', this.value.metadata.name);
|
|
160
|
+
}
|
|
161
|
+
|
|
155
162
|
this.$nextTick(() => {
|
|
156
163
|
this.$emit('set-subtype', this.label);
|
|
157
164
|
});
|
|
158
165
|
},
|
|
159
166
|
|
|
160
167
|
computed: {
|
|
168
|
+
...mapGetters(['releaseNotesUrl']),
|
|
169
|
+
|
|
170
|
+
showRestrictedAdminDeprecationBanner() {
|
|
171
|
+
return this.value.subtype === GLOBAL && this.value.id === 'restricted-admin';
|
|
172
|
+
},
|
|
173
|
+
|
|
161
174
|
label() {
|
|
162
175
|
return this.t(`rbac.roletemplate.subtypes.${ this.value.subtype }.label`);
|
|
163
176
|
},
|
|
@@ -537,6 +550,13 @@ export default {
|
|
|
537
550
|
@finish="save"
|
|
538
551
|
@cancel="cancel"
|
|
539
552
|
>
|
|
553
|
+
<Banner
|
|
554
|
+
v-if="showRestrictedAdminDeprecationBanner"
|
|
555
|
+
color="warning"
|
|
556
|
+
class="mb-20"
|
|
557
|
+
>
|
|
558
|
+
<span v-clean-html="t('rbac.globalRoles.role.restricted-admin.deprecation', { releaseNotesUrl }, true)" />
|
|
559
|
+
</Banner>
|
|
540
560
|
<template v-if="isDetail">
|
|
541
561
|
<SortableTable
|
|
542
562
|
key-field="index"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import RoleDetailEdit from '@shell/components/auth/RoleDetailEdit.vue';
|
|
3
|
+
import { SUBTYPE_MAPPING } from '@shell/models/management.cattle.io.roletemplate';
|
|
4
|
+
|
|
5
|
+
const role = {
|
|
6
|
+
apiVersion: 'management.cattle.io/v3',
|
|
7
|
+
kind: 'GlobalRole',
|
|
8
|
+
metadata: { name: 'global-role-with-inherited' },
|
|
9
|
+
inheritedClusterRoles: ['cluster-admin'],
|
|
10
|
+
rules:
|
|
11
|
+
[{
|
|
12
|
+
verbs: ['get', 'list'],
|
|
13
|
+
resources: ['pods'],
|
|
14
|
+
apiGroups: ['']
|
|
15
|
+
}],
|
|
16
|
+
subtype: SUBTYPE_MAPPING.GLOBAL.id
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
describe('component: RoleDetailEdit', () => {
|
|
20
|
+
it('does not have validation errors when the role has no displayName', () => {
|
|
21
|
+
const wrapper = mount(RoleDetailEdit, {
|
|
22
|
+
propsData: { value: role },
|
|
23
|
+
mocks: {
|
|
24
|
+
$fetchState: { pending: false },
|
|
25
|
+
$route: { name: 'anything' },
|
|
26
|
+
$store: {
|
|
27
|
+
getters: {
|
|
28
|
+
currentStore: () => 'store', 'i18n/t': jest.fn(), 'store/schemaFor': jest.fn()
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
stubs: {
|
|
33
|
+
CruResource: { template: '<div><slot></slot></div>' },
|
|
34
|
+
// NameNsDescription: true,
|
|
35
|
+
Tab: { template: '<div><slot></slot></div>' },
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
expect((wrapper.vm as any).fvFormIsValid).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -164,6 +164,10 @@ export default {
|
|
|
164
164
|
removeAt(this.rows, index);
|
|
165
165
|
this.queueUpdate();
|
|
166
166
|
},
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Cleanup rows and emit input
|
|
170
|
+
*/
|
|
167
171
|
update() {
|
|
168
172
|
if ( this.isView ) {
|
|
169
173
|
return;
|
|
@@ -180,22 +184,24 @@ export default {
|
|
|
180
184
|
}
|
|
181
185
|
this.$emit('input', out);
|
|
182
186
|
},
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Handle paste event, e.g. split multiple lines in rows
|
|
190
|
+
*/
|
|
183
191
|
onPaste(index, event) {
|
|
184
|
-
if (this.valueMultiline) {
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
192
|
event.preventDefault();
|
|
188
193
|
const text = event.clipboardData.getData('text/plain');
|
|
189
|
-
const split = text.split('\n').map((value) => ({ value }));
|
|
190
|
-
|
|
191
|
-
if (split.length === 1) {
|
|
192
|
-
// It's not multi-line, so don't treat it as such
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
194
|
|
|
196
|
-
|
|
195
|
+
if (this.valueMultiline) {
|
|
196
|
+
// Allow to paste multiple lines
|
|
197
|
+
this.rows[index].value = text;
|
|
198
|
+
} else {
|
|
199
|
+
// Prevent to paste the value and emit text in multiple rows
|
|
200
|
+
const split = text.split('\n').map((value) => ({ value }));
|
|
197
201
|
|
|
198
|
-
|
|
202
|
+
event.preventDefault();
|
|
203
|
+
this.rows.splice(index, 1, ...split);
|
|
204
|
+
}
|
|
199
205
|
|
|
200
206
|
this.update();
|
|
201
207
|
}
|
|
@@ -256,6 +262,7 @@ export default {
|
|
|
256
262
|
v-if="valueMultiline"
|
|
257
263
|
ref="value"
|
|
258
264
|
v-model="row.value"
|
|
265
|
+
:data-testid="`textarea-${idx}`"
|
|
259
266
|
:placeholder="valuePlaceholder"
|
|
260
267
|
:mode="mode"
|
|
261
268
|
:disabled="disabled"
|
|
@@ -266,6 +273,7 @@ export default {
|
|
|
266
273
|
v-else-if="rules.length > 0"
|
|
267
274
|
ref="value"
|
|
268
275
|
v-model="row.value"
|
|
276
|
+
:data-testid="`labeled-input-${idx}`"
|
|
269
277
|
:placeholder="valuePlaceholder"
|
|
270
278
|
:disabled="isView || disabled"
|
|
271
279
|
:rules="rules"
|
|
@@ -277,6 +285,7 @@ export default {
|
|
|
277
285
|
v-else
|
|
278
286
|
ref="value"
|
|
279
287
|
v-model="row.value"
|
|
288
|
+
:data-testid="`input-${idx}`"
|
|
280
289
|
:placeholder="valuePlaceholder"
|
|
281
290
|
:disabled="isView || disabled"
|
|
282
291
|
@paste="onPaste(idx, $event)"
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import Select from '@shell/components/form/Select';
|
|
3
3
|
import UnitInput from '@shell/components/form/UnitInput';
|
|
4
4
|
import { ROW_COMPUTED } from './shared';
|
|
5
|
+
import Vue from 'vue';
|
|
5
6
|
|
|
6
7
|
export default {
|
|
7
8
|
components: { Select, UnitInput },
|
|
@@ -57,10 +58,10 @@ export default {
|
|
|
57
58
|
|
|
58
59
|
updateQuotaLimit(prop, type, val) {
|
|
59
60
|
if (!this.value.spec[prop]) {
|
|
60
|
-
this.value.spec
|
|
61
|
+
Vue.set(this.value.spec, prop, { limit: { } });
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
this.value.spec[prop].limit
|
|
64
|
+
Vue.set(this.value.spec[prop].limit, type, val);
|
|
64
65
|
}
|
|
65
66
|
},
|
|
66
67
|
};
|
|
@@ -75,6 +76,7 @@ export default {
|
|
|
75
76
|
:mode="mode"
|
|
76
77
|
:value="type"
|
|
77
78
|
:options="types"
|
|
79
|
+
data-testid="projectrow-type-input"
|
|
78
80
|
@input="updateType($event)"
|
|
79
81
|
/>
|
|
80
82
|
<UnitInput
|
|
@@ -86,6 +88,7 @@ export default {
|
|
|
86
88
|
:input-exponent="typeOption.inputExponent"
|
|
87
89
|
:base-unit="typeOption.baseUnit"
|
|
88
90
|
:output-modifier="true"
|
|
91
|
+
data-testid="projectrow-project-quota-input"
|
|
89
92
|
@input="updateQuotaLimit('resourceQuota', type, $event)"
|
|
90
93
|
/>
|
|
91
94
|
<UnitInput
|
|
@@ -96,6 +99,7 @@ export default {
|
|
|
96
99
|
:input-exponent="typeOption.inputExponent"
|
|
97
100
|
:base-unit="typeOption.baseUnit"
|
|
98
101
|
:output-modifier="true"
|
|
102
|
+
data-testid="projectrow-namespace-quota-input"
|
|
99
103
|
@input="updateQuotaLimit('namespaceDefaultResourceQuota', type, $event)"
|
|
100
104
|
/>
|
|
101
105
|
</div>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { mount } from '@vue/test-utils';
|
|
2
2
|
import ArrayList from '@shell/components/form/ArrayList.vue';
|
|
3
3
|
import { _EDIT, _VIEW } from '@shell/config/query-params';
|
|
4
|
+
import { ExtendedVue, Vue } from 'vue/types/vue';
|
|
5
|
+
import { DefaultProps } from 'vue/types/options';
|
|
4
6
|
|
|
5
7
|
describe('the ArrayList', () => {
|
|
6
8
|
it('is empty', () => {
|
|
@@ -71,4 +73,46 @@ describe('the ArrayList', () => {
|
|
|
71
73
|
|
|
72
74
|
expect(arrayListButtons).toHaveLength(0);
|
|
73
75
|
});
|
|
76
|
+
|
|
77
|
+
describe('onPaste', () => {
|
|
78
|
+
it('should emit value with updated row text', () => {
|
|
79
|
+
const text = 'test';
|
|
80
|
+
const expectation = [text];
|
|
81
|
+
const wrapper = mount(ArrayList as unknown as ExtendedVue<Vue, {}, {}, {}, DefaultProps>, { propsData: { value: [''] } });
|
|
82
|
+
const event = { preventDefault: jest.fn(), clipboardData: { getData: jest.fn().mockReturnValue(text) } } as any;
|
|
83
|
+
|
|
84
|
+
wrapper.vm.onPaste(0, event);
|
|
85
|
+
|
|
86
|
+
expect(wrapper.emitted().input?.[0][0]).toStrictEqual(expectation);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should emit value with multiple rows', () => {
|
|
90
|
+
const wrapper = mount(ArrayList as unknown as ExtendedVue<Vue, {}, {}, {}, DefaultProps>, { propsData: { value: [''] } });
|
|
91
|
+
const text = `multiline
|
|
92
|
+
rows`;
|
|
93
|
+
const expectation = ['multiline', 'rows'];
|
|
94
|
+
const event = { preventDefault: jest.fn(), clipboardData: { getData: jest.fn().mockReturnValue(text) } } as any;
|
|
95
|
+
|
|
96
|
+
wrapper.vm.onPaste(0, event);
|
|
97
|
+
|
|
98
|
+
expect(wrapper.emitted().input?.[0][0]).toStrictEqual(expectation);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should allow emit multiline pasted values if enabled', () => {
|
|
102
|
+
const wrapper = mount(ArrayList as unknown as ExtendedVue<Vue, {}, {}, {}, DefaultProps>, {
|
|
103
|
+
propsData: {
|
|
104
|
+
value: [''],
|
|
105
|
+
valueMultiline: true,
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
const text = `multiline
|
|
109
|
+
text`;
|
|
110
|
+
const expectation = [text];
|
|
111
|
+
const event = { preventDefault: jest.fn(), clipboardData: { getData: jest.fn().mockReturnValue(text) } } as any;
|
|
112
|
+
|
|
113
|
+
wrapper.vm.onPaste(0, event);
|
|
114
|
+
|
|
115
|
+
expect(wrapper.emitted().input?.[0][0]).toStrictEqual(expectation);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
74
118
|
});
|
|
@@ -706,7 +706,6 @@ export default {
|
|
|
706
706
|
</template>
|
|
707
707
|
|
|
708
708
|
<style lang="scss" scoped>
|
|
709
|
-
$side-menu-logo-margin-left: 5px;
|
|
710
709
|
// It would be nice to grab this from `Group.vue`, but there's margin, padding and border, which is overkill to var
|
|
711
710
|
$side-menu-group-padding-left: 16px;
|
|
712
711
|
|
|
@@ -724,9 +723,11 @@ export default {
|
|
|
724
723
|
&.isSingleProduct {
|
|
725
724
|
display: flex;
|
|
726
725
|
justify-content: center;
|
|
726
|
+
|
|
727
727
|
// Align the icon with the side nav menu items ($side-menu-group-padding-left)
|
|
728
|
-
|
|
729
|
-
|
|
728
|
+
.side-menu-logo {
|
|
729
|
+
margin-left: $side-menu-group-padding-left;
|
|
730
|
+
}
|
|
730
731
|
}
|
|
731
732
|
}
|
|
732
733
|
|
|
@@ -815,7 +816,7 @@ export default {
|
|
|
815
816
|
display: flex;
|
|
816
817
|
margin-right: 8px;
|
|
817
818
|
height: 55px;
|
|
818
|
-
margin-left:
|
|
819
|
+
margin-left: 5px;
|
|
819
820
|
max-width: 200px;
|
|
820
821
|
padding: 12px 0;
|
|
821
822
|
}
|