@rancher/shell 3.0.9-rc.2 → 3.0.9-rc.4
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/translations/en-us.yaml +24 -2
- package/assets/translations/zh-hans.yaml +13 -0
- package/components/ActionMenu.vue +7 -8
- package/components/ActionMenuShell.vue +19 -20
- package/components/Resource/Detail/Card/Scaler.vue +10 -2
- package/components/Resource/Detail/Card/StatusCard/index.vue +4 -1
- package/components/ResourceTable.vue +1 -1
- package/components/Tabbed/Tab.vue +4 -0
- package/components/Tabbed/index.vue +11 -3
- package/components/__tests__/ProjectRow.test.ts +102 -15
- package/components/form/ResourceQuota/Project.vue +59 -8
- package/components/form/ResourceQuota/ProjectRow.vue +116 -21
- package/components/form/ResourceQuota/shared.js +42 -18
- package/components/formatter/KubeconfigClusters.vue +74 -0
- package/components/formatter/LinkName.vue +3 -2
- package/components/formatter/__tests__/KubeconfigClusters.test.ts +125 -0
- package/config/product/explorer.js +1 -1
- package/config/product/manager.js +29 -2
- package/config/router/routes.js +4 -1
- package/config/table-headers.js +9 -7
- package/config/types.js +4 -1
- package/detail/management.cattle.io.oidcclient.vue +15 -4
- package/edit/__tests__/management.cattle.io.project.test.js +137 -0
- package/edit/management.cattle.io.project.vue +36 -6
- package/edit/monitoring.coreos.com.alertmanagerconfig/index.vue +16 -3
- package/edit/provisioning.cattle.io.cluster/defaults.ts +1 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +2 -1
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +57 -2
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/__tests__/S3Config.test.ts +109 -0
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +1 -0
- package/initialize/install-plugins.js +0 -2
- package/list/ext.cattle.io.kubeconfig.vue +118 -0
- package/mixins/__tests__/chart.test.ts +147 -0
- package/mixins/chart.js +10 -8
- package/models/__tests__/ext.cattle.io.kubeconfig.test.ts +364 -0
- package/models/__tests__/secret.test.ts +55 -0
- package/models/ext.cattle.io.kubeconfig.ts +97 -0
- package/models/management.cattle.io.cluster.js +22 -30
- package/models/provisioning.cattle.io.cluster.js +2 -2
- package/models/secret.js +1 -1
- package/package.json +2 -2
- package/pages/__tests__/diagnostic.test.ts +71 -0
- package/pages/about.vue +3 -2
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +38 -14
- package/pages/c/_cluster/apps/charts/index.vue +1 -0
- package/pages/c/_cluster/explorer/tools/index.vue +23 -5
- package/pages/c/_cluster/monitoring/alertmanagerconfig/_alertmanagerconfigid/receiver.vue +18 -5
- package/pages/c/_cluster/uiplugins/index.vue +40 -8
- package/pages/diagnostic.vue +17 -3
- package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +1 -0
- package/rancher-components/RcItemCard/RcItemCard.test.ts +20 -8
- package/rancher-components/RcItemCard/RcItemCard.vue +38 -31
- package/store/__tests__/auth.test.ts +21 -5
- package/store/auth.js +6 -3
- package/types/shell/index.d.ts +177 -157
- package/utils/__tests__/chart.test.ts +96 -0
- package/utils/__tests__/version.test.ts +1 -19
- package/utils/chart.js +64 -0
- package/utils/version.js +5 -17
|
@@ -1183,7 +1183,7 @@ catalog:
|
|
|
1183
1183
|
subHeaderItem:
|
|
1184
1184
|
missingVersionDate: Last updated date is not available for this chart
|
|
1185
1185
|
footerItem:
|
|
1186
|
-
ariaLabel: Apply filter
|
|
1186
|
+
ariaLabel: 'Apply {filter} filter'
|
|
1187
1187
|
statusFilterCautions:
|
|
1188
1188
|
installation: Installation status cannot be determined with 100% accuracy
|
|
1189
1189
|
upgradeable: Upgradeable status cannot be determined with 100% accuracy
|
|
@@ -2543,7 +2543,7 @@ cluster:
|
|
|
2543
2543
|
snapshotScheduleCron:
|
|
2544
2544
|
label: Cron Schedule
|
|
2545
2545
|
snapshotRetention:
|
|
2546
|
-
label:
|
|
2546
|
+
label: Local snapshot retention count
|
|
2547
2547
|
tooltip: Each backup records 1 snapshot per etcd node. If you specify 3 snapshots and you have 3 etcd nodes you will only retain 1 full backup.
|
|
2548
2548
|
exportMetric:
|
|
2549
2549
|
label: Metrics
|
|
@@ -2552,6 +2552,13 @@ cluster:
|
|
|
2552
2552
|
s3backups:
|
|
2553
2553
|
label: Save Backups to S3
|
|
2554
2554
|
s3config:
|
|
2555
|
+
snapshotRetention:
|
|
2556
|
+
title: S3 Snapshot Retention
|
|
2557
|
+
label: S3 Snapshot Retention Count
|
|
2558
|
+
options:
|
|
2559
|
+
manual: Set manually
|
|
2560
|
+
localDefined: Same as local ({count} Snaphots)
|
|
2561
|
+
localUndefined: Same as local
|
|
2555
2562
|
bucket:
|
|
2556
2563
|
label: Bucket
|
|
2557
2564
|
folder:
|
|
@@ -3879,6 +3886,8 @@ istio:
|
|
|
3879
3886
|
itemCard:
|
|
3880
3887
|
ariaLabel:
|
|
3881
3888
|
clickable: Click on card for {cardTitle}
|
|
3889
|
+
actionMenu:
|
|
3890
|
+
label: '{cardTitle} actions'
|
|
3882
3891
|
jwt:
|
|
3883
3892
|
title: JWT Authentication
|
|
3884
3893
|
actions:
|
|
@@ -6886,6 +6895,7 @@ tableHeaders:
|
|
|
6886
6895
|
targetPort: Target
|
|
6887
6896
|
template: Template
|
|
6888
6897
|
totalSnapshotQuota: Total Snapshot Quota
|
|
6898
|
+
ttl: TTL
|
|
6889
6899
|
type: Type
|
|
6890
6900
|
updated: Updated
|
|
6891
6901
|
up-to-date: Up To Date
|
|
@@ -8855,6 +8865,7 @@ notifications:
|
|
|
8855
8865
|
showCheckboxLabel: Show custom login error
|
|
8856
8866
|
messageLabel: Text to display
|
|
8857
8867
|
resourceQuota:
|
|
8868
|
+
banner: Limit resource consumption in a project for both standard (e.g. CPU Limit) and custom resource types. For custom resource types you have to provide the resource identifier yourself. Want to learn more about resource quotas? Read our <a href="https://ranchermanager.docs.rancher.com/how-to-guides/advanced-user-guides/manage-projects/manage-project-resource-quotas" target="_blank" rel="noopener noreferrer nofollow">documentation <i class="icon icon-external-link"></i></a><span class="sr-only">Opens in a new tab</span>
|
|
8858
8869
|
label: Resource Quotas
|
|
8859
8870
|
headers:
|
|
8860
8871
|
limit: Limit
|
|
@@ -8862,9 +8873,14 @@ resourceQuota:
|
|
|
8862
8873
|
projectLimit: Project Limit
|
|
8863
8874
|
projectResourceAvailability: Project Resource Availability
|
|
8864
8875
|
resourceType: Resource Type
|
|
8876
|
+
resourceIdentifier: Resource Identifier
|
|
8865
8877
|
helpText: Configure how much of the resources the namespace as a whole can consume.
|
|
8866
8878
|
helpTextDetail: The amount of resources the namespace as a whole can consume.
|
|
8867
8879
|
helpTextHarvester: VMs need to reserve additional memory overhead.
|
|
8880
|
+
custom: Custom
|
|
8881
|
+
resourceIdentifier:
|
|
8882
|
+
placeholder: e.g. configmaps
|
|
8883
|
+
tooltip: Select 'Custom' from the 'Resource Type' list and enter the resource identifier (e.g. requests.nvidia.com/gpu) to add custom resources quotas.
|
|
8868
8884
|
configMaps: Config Maps
|
|
8869
8885
|
limitsCpu: CPU Limit
|
|
8870
8886
|
limitsMemory: Memory Limit
|
|
@@ -8894,6 +8910,8 @@ resourceQuota:
|
|
|
8894
8910
|
unitlessPlaceholder: e.g. 10
|
|
8895
8911
|
add:
|
|
8896
8912
|
label: Add Resource
|
|
8913
|
+
errors:
|
|
8914
|
+
customTypeRequired: Resource identifier is required
|
|
8897
8915
|
tooltip:
|
|
8898
8916
|
reserved: 'Other Namespaces:'
|
|
8899
8917
|
namespace: 'This Namespace:'
|
|
@@ -9298,3 +9316,7 @@ autoscaler:
|
|
|
9298
9316
|
unavailable: Unavailable
|
|
9299
9317
|
tab:
|
|
9300
9318
|
title: Autoscaler
|
|
9319
|
+
|
|
9320
|
+
ext.cattle.io.kubeconfig:
|
|
9321
|
+
deleted: "{name} (deleted)"
|
|
9322
|
+
moreClusterCount: " + {remainingCount} more"
|
|
@@ -651,6 +651,10 @@ asyncButton:
|
|
|
651
651
|
action: 保存
|
|
652
652
|
success: 已保存
|
|
653
653
|
waiting: 正在保存…
|
|
654
|
+
editVersion:
|
|
655
|
+
action: 保存更改
|
|
656
|
+
success: 已保存
|
|
657
|
+
waiting: 正在保存更改…
|
|
654
658
|
enable:
|
|
655
659
|
action: 启用
|
|
656
660
|
success: 启用
|
|
@@ -874,6 +878,13 @@ catalog:
|
|
|
874
878
|
windows: Windows
|
|
875
879
|
search: 筛选
|
|
876
880
|
install:
|
|
881
|
+
warning:
|
|
882
|
+
managed: |-
|
|
883
|
+
警告,{manager} 管理 {name} 应用的部署和升级。不支持升级此应用。<br>
|
|
884
|
+
{version, select,
|
|
885
|
+
null { }
|
|
886
|
+
other { 在大多数情况下,无需用户干预即可确保 {version} 版本与你正在运行的 Rancher 版本兼容。}
|
|
887
|
+
}
|
|
877
888
|
appReadmeMissing: 此 Chart 没有其他 Chart 信息。
|
|
878
889
|
appReadmeTitle: Chart 信息(Helm 自述)
|
|
879
890
|
chart: Chart
|
|
@@ -939,6 +950,8 @@ catalog:
|
|
|
939
950
|
install { 创建 }
|
|
940
951
|
upgrade { 升级 }
|
|
941
952
|
update { 更新 }
|
|
953
|
+
editVersion { 更新 }
|
|
954
|
+
downgrade { 降级 }
|
|
942
955
|
} 这个 {existing, select,
|
|
943
956
|
true { 应用 }
|
|
944
957
|
false { Chart}
|
|
@@ -11,7 +11,7 @@ const SHOW = 'show';
|
|
|
11
11
|
export default {
|
|
12
12
|
name: 'ActionMenu',
|
|
13
13
|
|
|
14
|
-
emits: ['close'],
|
|
14
|
+
emits: ['close', 'action-invoked'],
|
|
15
15
|
|
|
16
16
|
components: { IconOrSvg },
|
|
17
17
|
props: {
|
|
@@ -217,15 +217,14 @@ export default {
|
|
|
217
217
|
// If the state of this component is controlled
|
|
218
218
|
// by props instead of Vuex, we assume you wouldn't want
|
|
219
219
|
// the mutation to have a dependency on Vuex either.
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
action,
|
|
220
|
+
// The parent component should handle the action based on the action property
|
|
221
|
+
// in the 'action-invoked' event payload.
|
|
222
|
+
this.$emit('action-invoked', {
|
|
223
|
+
action: action.action,
|
|
224
|
+
actionData: action,
|
|
226
225
|
event,
|
|
227
226
|
...args,
|
|
228
|
-
route:
|
|
227
|
+
route: this.$route
|
|
229
228
|
});
|
|
230
229
|
} else {
|
|
231
230
|
// If the state of this component is controlled
|
|
@@ -30,7 +30,15 @@ const openChanged = (event: boolean) => {
|
|
|
30
30
|
}
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
export interface ActionMenuSelection {
|
|
34
|
+
action: string;
|
|
35
|
+
actionData: any;
|
|
36
|
+
event: MouseEvent;
|
|
37
|
+
route: ReturnType<typeof useRoute>;
|
|
38
|
+
[key: string]: any;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const emit = defineEmits<{(event: 'action-invoked', payload?: ActionMenuSelection): void;}>();
|
|
34
42
|
const route = useRoute();
|
|
35
43
|
|
|
36
44
|
const execute = (action: any, event: MouseEvent, args?: any) => {
|
|
@@ -38,7 +46,15 @@ const execute = (action: any, event: MouseEvent, args?: any) => {
|
|
|
38
46
|
return;
|
|
39
47
|
}
|
|
40
48
|
|
|
41
|
-
|
|
49
|
+
const payload: ActionMenuSelection = {
|
|
50
|
+
action: action.action,
|
|
51
|
+
actionData: action,
|
|
52
|
+
event,
|
|
53
|
+
...args,
|
|
54
|
+
route,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
emit('action-invoked', payload);
|
|
42
58
|
|
|
43
59
|
// this will come from extensions...
|
|
44
60
|
if (action.invoke) {
|
|
@@ -56,24 +72,7 @@ const execute = (action: any, event: MouseEvent, args?: any) => {
|
|
|
56
72
|
fn.apply(this, [opts, resources]);
|
|
57
73
|
}
|
|
58
74
|
}
|
|
59
|
-
} else if (props.customActions) {
|
|
60
|
-
// If the state of this component is controlled
|
|
61
|
-
// by props instead of Vuex, we assume you wouldn't want
|
|
62
|
-
// the mutation to have a dependency on Vuex either.
|
|
63
|
-
// So in that case we use events to execute actions instead.
|
|
64
|
-
// If an action list item is clicked, this
|
|
65
|
-
// component emits that event, then we assume the parent
|
|
66
|
-
// component will execute the action.
|
|
67
|
-
emit(
|
|
68
|
-
action.action,
|
|
69
|
-
{
|
|
70
|
-
action,
|
|
71
|
-
event,
|
|
72
|
-
...args,
|
|
73
|
-
route,
|
|
74
|
-
}
|
|
75
|
-
);
|
|
76
|
-
} else {
|
|
75
|
+
} else if (!props.customActions) {
|
|
77
76
|
// If the state of this component is controlled
|
|
78
77
|
// by Vuex, mutate the store when an action is clicked.
|
|
79
78
|
const opts = { alt: isAlternate(event) };
|
|
@@ -19,22 +19,30 @@ const i18n = useI18n(store);
|
|
|
19
19
|
</script>
|
|
20
20
|
|
|
21
21
|
<template>
|
|
22
|
-
<div
|
|
22
|
+
<div
|
|
23
|
+
class="scaler"
|
|
24
|
+
data-testid="scaler"
|
|
25
|
+
>
|
|
23
26
|
<button
|
|
24
27
|
class="decrease"
|
|
25
28
|
:aria-label="i18n.t('component.resource.detail.card.scaler.ariaLabel.decrease', {resourceName: props.ariaResourceName})"
|
|
26
29
|
:disabled="!!props.min && (props.value <= props.min)"
|
|
30
|
+
data-testid="scaler-decrease"
|
|
27
31
|
@click="() => emit('decrease', props.value - 1)"
|
|
28
32
|
>
|
|
29
33
|
<i class="icon icon-sm icon-minus" />
|
|
30
34
|
</button>
|
|
31
|
-
<div
|
|
35
|
+
<div
|
|
36
|
+
class="value"
|
|
37
|
+
data-testid="scaler-value"
|
|
38
|
+
>
|
|
32
39
|
{{ props.value }}
|
|
33
40
|
</div>
|
|
34
41
|
<button
|
|
35
42
|
class="increase"
|
|
36
43
|
:aria-label="i18n.t('component.resource.detail.card.scaler.ariaLabel.increase', {resourceName: props.ariaResourceName})"
|
|
37
44
|
:disabled="!!props.max && (props.value >= props.max)"
|
|
45
|
+
data-testid="scaler-increase"
|
|
38
46
|
@click="() => emit('increase', props.value + 1)"
|
|
39
47
|
>
|
|
40
48
|
<i class="icon icon-sm icon-plus" />
|
|
@@ -427,7 +427,7 @@ export default {
|
|
|
427
427
|
},
|
|
428
428
|
|
|
429
429
|
_applicableExtensionTableHooks() {
|
|
430
|
-
if (this.$store.$
|
|
430
|
+
if (this.$store.$extension?.getUIConfig) {
|
|
431
431
|
const extensionTableHooks = getApplicableExtensionEnhancements(this, ExtensionPoint.TABLE, TableLocation.RESOURCE, this.$route);
|
|
432
432
|
|
|
433
433
|
return extensionTableHooks;
|
|
@@ -207,7 +207,7 @@ export default {
|
|
|
207
207
|
return TabLocation.OTHER;
|
|
208
208
|
}
|
|
209
209
|
},
|
|
210
|
-
|
|
210
|
+
hasErrorIcon(tab) {
|
|
211
211
|
return tab.displayAlertIcon || (tab.error && !tab.active);
|
|
212
212
|
},
|
|
213
213
|
hashChange() {
|
|
@@ -346,6 +346,10 @@ export default {
|
|
|
346
346
|
@click.prevent="select(tab.name, $event)"
|
|
347
347
|
@keyup.enter.space="select(tab.name, $event)"
|
|
348
348
|
>
|
|
349
|
+
<i
|
|
350
|
+
v-if="tab.labelIcon"
|
|
351
|
+
:class="`tab-label-icon icon ${tab.labelIcon}`"
|
|
352
|
+
/>
|
|
349
353
|
<span>
|
|
350
354
|
{{ tab.labelDisplay }}
|
|
351
355
|
</span>
|
|
@@ -354,7 +358,7 @@ export default {
|
|
|
354
358
|
class="tab-badge"
|
|
355
359
|
>{{ tab.badge }}</span>
|
|
356
360
|
<i
|
|
357
|
-
v-if="
|
|
361
|
+
v-if="hasErrorIcon(tab)"
|
|
358
362
|
v-clean-tooltip="t('validation.tab')"
|
|
359
363
|
class="conditions-alert-icon icon-error"
|
|
360
364
|
/>
|
|
@@ -514,11 +518,15 @@ export default {
|
|
|
514
518
|
}
|
|
515
519
|
|
|
516
520
|
&.error {
|
|
517
|
-
& A >
|
|
521
|
+
& A > .icon-error {
|
|
518
522
|
color: var(--error);
|
|
519
523
|
}
|
|
520
524
|
}
|
|
521
525
|
|
|
526
|
+
.tab-label-icon {
|
|
527
|
+
margin-right: 8px;
|
|
528
|
+
}
|
|
529
|
+
|
|
522
530
|
.tab-badge {
|
|
523
531
|
margin-left: 5px;
|
|
524
532
|
background-color: var(--link);
|
|
@@ -1,31 +1,39 @@
|
|
|
1
1
|
import ProjectRow from '@shell/components/form/ResourceQuota/ProjectRow.vue';
|
|
2
|
-
import { RANCHER_TYPES } from '@shell/components/form/ResourceQuota/shared';
|
|
2
|
+
import { RANCHER_TYPES, TYPES } from '@shell/components/form/ResourceQuota/shared';
|
|
3
3
|
import { shallowMount } from '@vue/test-utils';
|
|
4
4
|
|
|
5
|
-
const CONFIGMAP_STRING =
|
|
5
|
+
const CONFIGMAP_STRING = TYPES.CONFIG_MAPS;
|
|
6
6
|
|
|
7
7
|
describe('component: ProjectRow.vue', () => {
|
|
8
|
-
const
|
|
9
|
-
{
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
8
|
+
const defaultMountOptions = {
|
|
9
|
+
props: {
|
|
10
|
+
mode: 'edit',
|
|
11
|
+
types: RANCHER_TYPES,
|
|
12
|
+
type: CONFIGMAP_STRING,
|
|
13
|
+
index: 0,
|
|
14
|
+
value: {
|
|
15
|
+
spec: {
|
|
16
|
+
namespaceDefaultResourceQuota: { limit: {} },
|
|
17
|
+
resourceQuota: { limit: {} }
|
|
19
18
|
}
|
|
20
19
|
}
|
|
21
|
-
}
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
22
|
|
|
23
23
|
it('should render the correct input fields and set the correct computed values, based on the provided data', () => {
|
|
24
|
+
const wrapper = shallowMount(
|
|
25
|
+
ProjectRow,
|
|
26
|
+
{ ...defaultMountOptions }
|
|
27
|
+
);
|
|
28
|
+
|
|
24
29
|
const typeInput = wrapper.find(`[data-testid="projectrow-type-input"]`);
|
|
30
|
+
const customTypeInput = wrapper.find(`[data-testid="projectrow-custom-type-input"]`);
|
|
25
31
|
const projectQuotaInput = wrapper.find(`[data-testid="projectrow-project-quota-input"]`);
|
|
26
32
|
const namespaceQuotaInput = wrapper.find(`[data-testid="projectrow-namespace-quota-input"]`);
|
|
27
33
|
|
|
28
34
|
expect(typeInput.exists()).toBe(true);
|
|
35
|
+
expect(customTypeInput.exists()).toBe(true);
|
|
36
|
+
expect(customTypeInput.attributes().disabled).toBe('true');
|
|
29
37
|
expect(projectQuotaInput.exists()).toBe(true);
|
|
30
38
|
expect(namespaceQuotaInput.exists()).toBe(true);
|
|
31
39
|
expect(wrapper.vm.resourceQuotaLimit).toStrictEqual({});
|
|
@@ -33,6 +41,11 @@ describe('component: ProjectRow.vue', () => {
|
|
|
33
41
|
});
|
|
34
42
|
|
|
35
43
|
it('triggering "updateQuotaLimit" should trigger Vue.set with the correct data', () => {
|
|
44
|
+
const wrapper = shallowMount(
|
|
45
|
+
ProjectRow,
|
|
46
|
+
{ ...defaultMountOptions }
|
|
47
|
+
);
|
|
48
|
+
|
|
36
49
|
wrapper.vm.updateQuotaLimit('resourceQuota', CONFIGMAP_STRING, 10);
|
|
37
50
|
|
|
38
51
|
expect(wrapper.vm.value).toStrictEqual({
|
|
@@ -44,6 +57,11 @@ describe('component: ProjectRow.vue', () => {
|
|
|
44
57
|
});
|
|
45
58
|
|
|
46
59
|
it('triggering "updateType" with the same type that existed should clear limits and trigger emit', () => {
|
|
60
|
+
const wrapper = shallowMount(
|
|
61
|
+
ProjectRow,
|
|
62
|
+
{ ...defaultMountOptions }
|
|
63
|
+
);
|
|
64
|
+
|
|
47
65
|
wrapper.vm.updateType(CONFIGMAP_STRING);
|
|
48
66
|
|
|
49
67
|
expect(wrapper.vm.value).toStrictEqual({
|
|
@@ -54,6 +72,75 @@ describe('component: ProjectRow.vue', () => {
|
|
|
54
72
|
});
|
|
55
73
|
|
|
56
74
|
expect(wrapper.emitted('type-change')).toBeTruthy();
|
|
57
|
-
expect(wrapper.emitted('type-change')[0]).toStrictEqual([CONFIGMAP_STRING]);
|
|
75
|
+
expect(wrapper.emitted('type-change')[0]).toStrictEqual([{ index: 0, type: CONFIGMAP_STRING }]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should update standard resource types', async() => {
|
|
79
|
+
const wrapper = shallowMount(
|
|
80
|
+
ProjectRow,
|
|
81
|
+
{ ...defaultMountOptions }
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
expect(wrapper.vm.isCustom).toBe(false);
|
|
85
|
+
|
|
86
|
+
await wrapper.vm.updateQuotaLimit('resourceQuota', 'limitsCpu', '100m');
|
|
87
|
+
await wrapper.vm.updateQuotaLimit('namespaceDefaultResourceQuota', 'limitsCpu', '50m');
|
|
88
|
+
|
|
89
|
+
expect(wrapper.vm.value.spec.resourceQuota.limit.limitsCpu).toBe('100m');
|
|
90
|
+
expect(wrapper.vm.value.spec.namespaceDefaultResourceQuota.limit.limitsCpu).toBe('50m');
|
|
91
|
+
expect(wrapper.vm.value.spec.resourceQuota.limit.extended).toBeUndefined();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should switch to a custom resource type', async() => {
|
|
95
|
+
const wrapper = shallowMount(
|
|
96
|
+
ProjectRow,
|
|
97
|
+
{ ...defaultMountOptions }
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
await wrapper.vm.updateType(TYPES.EXTENDED);
|
|
101
|
+
|
|
102
|
+
expect(wrapper.emitted('type-change')).toHaveLength(1);
|
|
103
|
+
expect(wrapper.emitted('type-change')[0][0]).toStrictEqual({ index: 0, type: TYPES.EXTENDED });
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should update custom resource types', async() => {
|
|
107
|
+
const wrapper = shallowMount(
|
|
108
|
+
ProjectRow,
|
|
109
|
+
{
|
|
110
|
+
...defaultMountOptions,
|
|
111
|
+
props: {
|
|
112
|
+
...defaultMountOptions.props,
|
|
113
|
+
type: TYPES.EXTENDED
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
expect(wrapper.vm.isCustom).toBe(true);
|
|
119
|
+
|
|
120
|
+
const customTypeInput = wrapper.find(`[data-testid="projectrow-custom-type-input"]`);
|
|
121
|
+
|
|
122
|
+
expect(customTypeInput.attributes().disabled).toBe('false');
|
|
123
|
+
await wrapper.vm.updateCustomType('custom.resource/foo');
|
|
124
|
+
|
|
125
|
+
expect(wrapper.vm.customType).toBe('custom.resource/foo');
|
|
126
|
+
|
|
127
|
+
await wrapper.vm.updateQuotaLimit('resourceQuota', 'custom.resource/foo', 1);
|
|
128
|
+
await wrapper.vm.updateQuotaLimit('namespaceDefaultResourceQuota', 'custom.resource/foo', 2);
|
|
129
|
+
|
|
130
|
+
expect(wrapper.vm.value.spec.resourceQuota.limit.extended['custom.resource/foo']).toBe(1);
|
|
131
|
+
expect(wrapper.vm.value.spec.namespaceDefaultResourceQuota.limit.extended['custom.resource/foo']).toBe(2);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should handle custom resource types with periods', () => {
|
|
135
|
+
const wrapper = shallowMount(ProjectRow, {
|
|
136
|
+
...defaultMountOptions,
|
|
137
|
+
props: {
|
|
138
|
+
...defaultMountOptions.props,
|
|
139
|
+
type: 'extended.requests.nvidia.com/gpu'
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
expect(wrapper.vm.isCustom).toBe(true);
|
|
144
|
+
expect(wrapper.vm.customType).toBe('requests.nvidia.com/gpu');
|
|
58
145
|
});
|
|
59
146
|
});
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import ArrayList from '@shell/components/form/ArrayList';
|
|
3
3
|
import Row from './ProjectRow';
|
|
4
|
-
import { QUOTA_COMPUTED } from './shared';
|
|
4
|
+
import { QUOTA_COMPUTED, TYPES } from './shared';
|
|
5
|
+
import Banner from '@components/Banner/Banner.vue';
|
|
5
6
|
|
|
6
7
|
export default {
|
|
7
8
|
emits: ['remove', 'input'],
|
|
8
9
|
|
|
9
|
-
components: {
|
|
10
|
+
components: {
|
|
11
|
+
ArrayList,
|
|
12
|
+
Row,
|
|
13
|
+
Banner,
|
|
14
|
+
},
|
|
10
15
|
|
|
11
16
|
props: {
|
|
12
17
|
mode: {
|
|
@@ -36,18 +41,35 @@ export default {
|
|
|
36
41
|
this.value.spec['namespaceDefaultResourceQuota'] = this.value.spec.namespaceDefaultResourceQuota || { limit: {} };
|
|
37
42
|
this.value.spec['resourceQuota'] = this.value.spec.resourceQuota || { limit: {} };
|
|
38
43
|
|
|
39
|
-
|
|
44
|
+
const limit = this.value.spec.resourceQuota.limit;
|
|
45
|
+
const extendedKeys = Object.keys(limit.extended || {});
|
|
46
|
+
|
|
47
|
+
this.typeValues = Object.keys(limit).flatMap((k) => {
|
|
48
|
+
if (k !== TYPES.EXTENDED) {
|
|
49
|
+
return k;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return extendedKeys.map((ek) => `extended.${ ek }`);
|
|
53
|
+
});
|
|
40
54
|
},
|
|
41
55
|
|
|
42
56
|
computed: { ...QUOTA_COMPUTED },
|
|
43
57
|
|
|
44
58
|
methods: {
|
|
45
|
-
updateType(
|
|
46
|
-
|
|
59
|
+
updateType(event) {
|
|
60
|
+
const { index, type } = event;
|
|
61
|
+
|
|
62
|
+
this.typeValues[index] = type;
|
|
47
63
|
},
|
|
48
64
|
remainingTypes(currentType) {
|
|
49
65
|
return this.mappedTypes
|
|
50
|
-
.filter((mappedType) =>
|
|
66
|
+
.filter((mappedType) => {
|
|
67
|
+
if (mappedType.value === TYPES.EXTENDED) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return !this.typeValues.includes(mappedType.value) || mappedType.value === currentType;
|
|
72
|
+
});
|
|
51
73
|
},
|
|
52
74
|
emitRemove(data) {
|
|
53
75
|
this.$emit('remove', data.row?.value);
|
|
@@ -57,9 +79,33 @@ export default {
|
|
|
57
79
|
</script>
|
|
58
80
|
<template>
|
|
59
81
|
<div>
|
|
82
|
+
<Banner
|
|
83
|
+
color="info"
|
|
84
|
+
label-key="resourceQuota.banner"
|
|
85
|
+
class="mb-20"
|
|
86
|
+
/>
|
|
60
87
|
<div class="headers mb-10">
|
|
61
88
|
<div class="mr-10">
|
|
62
|
-
<label>
|
|
89
|
+
<label>
|
|
90
|
+
{{ t('resourceQuota.headers.resourceType') }}
|
|
91
|
+
<span
|
|
92
|
+
class="required mr-5"
|
|
93
|
+
aria-hidden="true"
|
|
94
|
+
>*</span>
|
|
95
|
+
</label>
|
|
96
|
+
</div>
|
|
97
|
+
<div class="mr-20">
|
|
98
|
+
<label>
|
|
99
|
+
{{ t('resourceQuota.headers.resourceIdentifier') }}
|
|
100
|
+
<span
|
|
101
|
+
class="required mr-5"
|
|
102
|
+
aria-hidden="true"
|
|
103
|
+
>*</span>
|
|
104
|
+
<i
|
|
105
|
+
v-clean-tooltip="t('resourceQuota.resourceIdentifier.tooltip')"
|
|
106
|
+
class="icon icon-info"
|
|
107
|
+
/>
|
|
108
|
+
</label>
|
|
63
109
|
</div>
|
|
64
110
|
<div class="mr-20">
|
|
65
111
|
<label>{{ t('resourceQuota.headers.projectLimit') }}</label>
|
|
@@ -83,8 +129,9 @@ export default {
|
|
|
83
129
|
:mode="mode"
|
|
84
130
|
:types="remainingTypes(typeValues[props.i])"
|
|
85
131
|
:type="typeValues[props.i]"
|
|
132
|
+
:index="props.i"
|
|
86
133
|
@input="$emit('input', $event)"
|
|
87
|
-
@type-change="updateType(
|
|
134
|
+
@type-change="updateType($event)"
|
|
88
135
|
/>
|
|
89
136
|
</template>
|
|
90
137
|
</ArrayList>
|
|
@@ -104,4 +151,8 @@ export default {
|
|
|
104
151
|
width: 100%;
|
|
105
152
|
}
|
|
106
153
|
}
|
|
154
|
+
|
|
155
|
+
.required {
|
|
156
|
+
color: var(--error);
|
|
157
|
+
}
|
|
107
158
|
</style>
|