@rancher/shell 3.0.9-rc.5 → 3.0.9-rc.6
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/images/providers/oci-open-containers.svg +22 -0
- package/assets/images/providers/traefik.png +0 -0
- package/assets/styles/themes/_dark.scss +2 -0
- package/assets/styles/themes/_light.scss +2 -0
- package/assets/styles/themes/_modern.scss +6 -0
- package/assets/translations/en-us.yaml +129 -25
- package/components/CruResource.vue +3 -1
- package/components/ExplorerProjectsNamespaces.vue +12 -12
- package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +109 -0
- package/components/Resource/Detail/Card/StatusCard/index.vue +21 -4
- package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +19 -2
- package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +19 -11
- package/components/Resource/Detail/ResourcePopover/__tests__/index.test.ts +12 -0
- package/components/Resource/Detail/ResourcePopover/index.vue +2 -0
- package/components/Resource/Detail/ResourceRow.vue +2 -2
- package/components/ResourceList/index.vue +7 -4
- package/components/Window/ContainerLogs.vue +48 -37
- package/components/fleet/FleetClusterTargets/TargetsList.vue +2 -2
- package/components/fleet/FleetClusterTargets/index.vue +6 -1
- package/components/fleet/GitRepoAdvancedTab.vue +333 -0
- package/components/fleet/GitRepoMetadataTab.vue +43 -0
- package/components/fleet/GitRepoRepositoryTab.vue +101 -0
- package/components/fleet/GitRepoTargetTab.vue +77 -0
- package/components/fleet/HelmOpAdvancedTab.vue +247 -0
- package/components/fleet/HelmOpChartTab.vue +158 -0
- package/components/fleet/HelmOpMetadataTab.vue +46 -0
- package/components/fleet/HelmOpTargetTab.vue +84 -0
- package/components/fleet/HelmOpValuesTab.vue +147 -0
- package/components/fleet/__tests__/FleetClusterTargets.test.ts +119 -70
- package/components/form/NodeScheduling.vue +81 -7
- package/components/form/PodAffinity.vue +1 -36
- package/components/form/ResourceLabeledSelect.vue +8 -4
- package/components/form/ResourceQuota/Namespace.vue +30 -9
- package/components/form/ResourceQuota/NamespaceRow.vue +25 -7
- package/components/form/ResourceQuota/Project.vue +140 -82
- package/components/form/ResourceQuota/ResourceQuotaEntry.vue +145 -0
- package/components/form/ResourceQuota/__tests__/Namespace.test.ts +307 -0
- package/components/form/ResourceQuota/__tests__/NamespaceRow.test.ts +281 -0
- package/components/form/ResourceQuota/__tests__/Project.test.ts +274 -27
- package/components/form/ResourceQuota/__tests__/ResourceQuotaEntry.test.ts +215 -0
- package/components/form/SchedulingCustomization.vue +14 -6
- package/components/form/SelectOrCreateAuthSecret.vue +107 -18
- package/components/form/__tests__/NodeScheduling.test.ts +12 -9
- package/components/form/__tests__/PodAffinity.test.ts +21 -2
- package/components/form/__tests__/SchedulingCustomization.test.ts +240 -0
- package/components/formatter/ClusterLink.vue +8 -0
- package/components/formatter/SecretOrigin.vue +79 -0
- package/config/labels-annotations.js +7 -6
- package/config/pagination-table-headers.js +6 -4
- package/config/product/explorer.js +1 -11
- package/config/query-params.js +3 -0
- package/config/settings.ts +15 -2
- package/config/table-headers.js +21 -17
- package/config/types.js +23 -8
- package/detail/workload/index.vue +11 -16
- package/dialog/DeactivateDriverDialog.vue +1 -1
- package/dialog/Ipv6NetworkingDialog.vue +156 -0
- package/dialog/ScalePoolDownDialog.vue +2 -2
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -1
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +1 -0
- package/edit/__tests__/management.cattle.io.project.test.js +56 -128
- package/edit/auth/oidc.vue +1 -1
- package/edit/catalog.cattle.io.clusterrepo.vue +155 -25
- package/edit/fleet.cattle.io.gitrepo.vue +153 -283
- package/edit/fleet.cattle.io.helmop.vue +190 -332
- package/edit/management.cattle.io.project.vue +5 -42
- package/edit/management.cattle.io.setting.vue +6 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +55 -24
- package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +1 -103
- package/edit/provisioning.cattle.io.cluster/__tests__/index.test.ts +13 -1
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2-fleet-cluster-agent.test.ts +283 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +65 -49
- package/edit/provisioning.cattle.io.cluster/ingress/IngressCards.vue +112 -0
- package/edit/provisioning.cattle.io.cluster/ingress/IngressConfiguration.vue +158 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +171 -72
- package/edit/provisioning.cattle.io.cluster/shared.ts +36 -1
- package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +2 -1
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +55 -7
- package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +319 -0
- package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +2 -1
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/__tests__/S3Config.test.ts +13 -1
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +10 -44
- package/edit/secret/index.vue +1 -1
- package/edit/token.vue +68 -29
- package/edit/workload/__tests__/index.test.ts +2 -37
- package/edit/workload/index.vue +6 -2
- package/edit/workload/mixins/workload.js +0 -32
- package/list/__tests__/management.cattle.io.setting.test.ts +198 -0
- package/list/management.cattle.io.setting.vue +13 -0
- package/list/provisioning.cattle.io.cluster.vue +50 -1
- package/list/secret.vue +4 -9
- package/list/service.vue +6 -8
- package/machine-config/amazonec2.vue +11 -4
- package/machine-config/components/EC2Networking.vue +46 -30
- package/machine-config/components/__tests__/EC2Networking.test.ts +7 -7
- package/machine-config/components/__tests__/utils/vpcSubnetMockData.js +0 -9
- package/machine-config/digitalocean.vue +3 -3
- package/models/__tests__/namespace.test.ts +11 -0
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +96 -0
- package/models/__tests__/workload.test.ts +42 -1
- package/models/catalog.cattle.io.clusterrepo.js +30 -4
- package/models/ext.cattle.io.token.js +48 -0
- package/models/kontainerdriver.js +2 -2
- package/models/namespace.js +7 -1
- package/models/nodedriver.js +2 -2
- package/models/provisioning.cattle.io.cluster.js +28 -7
- package/models/secret.js +0 -17
- package/models/service.js +44 -1
- package/models/token.js +4 -0
- package/models/workload.js +12 -6
- package/package.json +1 -1
- package/pages/account/index.vue +96 -67
- package/pages/auth/setup.vue +5 -14
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +4 -1
- package/pages/c/_cluster/apps/charts/index.vue +93 -4
- package/pages/c/_cluster/apps/charts/install.vue +317 -42
- package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -4
- package/pages/c/_cluster/settings/index.vue +3 -1
- package/plugins/dashboard-store/__tests__/getters.test.ts +108 -0
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +27 -0
- package/plugins/dashboard-store/actions.js +3 -8
- package/plugins/dashboard-store/getters.js +7 -5
- package/plugins/dashboard-store/mutations.js +4 -1
- package/plugins/dashboard-store/resource-class.js +3 -3
- package/plugins/steve/__tests__/steve-class.test.ts +102 -141
- package/plugins/steve/steve-class.js +12 -3
- package/plugins/steve/steve-pagination-utils.ts +6 -2
- package/rancher-components/RcIcon/types.ts +2 -0
- package/rancher-components/RcItemCard/RcItemCard.vue +64 -19
- package/store/prefs.js +3 -0
- package/types/aws-sdk.d.ts +121 -0
- package/types/resources/node.ts +15 -0
- package/types/shell/index.d.ts +536 -506
- package/types/store/pagination.types.ts +5 -5
- package/utils/__tests__/array.test.ts +1 -29
- package/utils/__tests__/cluster-agent-configuration.test.ts +203 -0
- package/utils/array.ts +0 -11
- package/utils/aws.ts +21 -0
- package/utils/cluster.js +22 -2
- package/utils/selector-typed.ts +1 -1
- package/components/__tests__/ProjectRow.test.ts +0 -206
- package/components/form/ResourceQuota/ProjectRow.vue +0 -277
|
@@ -1,17 +1,24 @@
|
|
|
1
|
-
<script>
|
|
1
|
+
<script lang="ts">
|
|
2
2
|
import { mapGetters } from 'vuex';
|
|
3
3
|
import { RadioGroup } from '@components/Form/Radio';
|
|
4
|
-
import
|
|
5
|
-
import NodeAffinity from '@shell/components/form/NodeAffinity';
|
|
4
|
+
import ResourceLabeledSelect from '@shell/components/form/ResourceLabeledSelect.vue';
|
|
5
|
+
import NodeAffinity from '@shell/components/form/NodeAffinity.vue';
|
|
6
6
|
import { HARVESTER_NAME as VIRTUAL } from '@shell/config/features';
|
|
7
7
|
import { _VIEW } from '@shell/config/query-params';
|
|
8
8
|
import { isEmpty } from '@shell/utils/object';
|
|
9
9
|
import { HOSTNAME } from '@shell/config/labels-annotations';
|
|
10
|
+
import { ResourceLabeledSelectPaginateSettings, ResourceLabeledSelectSettings } from '@shell/types/components/resourceLabeledSelect';
|
|
11
|
+
import { NODE } from '@shell/config/types';
|
|
12
|
+
import { LabelSelectPaginationFunctionOptions } from '@shell/components/form/labeled-select-utils/labeled-select.utils';
|
|
13
|
+
import { PaginationFilterEquality, PaginationParamFilter } from '@shell/types/store/pagination.types';
|
|
14
|
+
import { KubeNode, KubeNodeTaint } from '@shell/types/resources/node';
|
|
15
|
+
|
|
16
|
+
const parseNode = (node: string | KubeNode) => typeof node === 'string' ? node : node.id;
|
|
10
17
|
|
|
11
18
|
export default {
|
|
12
19
|
components: {
|
|
13
20
|
RadioGroup,
|
|
14
|
-
|
|
21
|
+
ResourceLabeledSelect,
|
|
15
22
|
NodeAffinity,
|
|
16
23
|
},
|
|
17
24
|
|
|
@@ -23,6 +30,9 @@ export default {
|
|
|
23
30
|
}
|
|
24
31
|
},
|
|
25
32
|
|
|
33
|
+
/**
|
|
34
|
+
* HARVESTER ONLY PROPERTY
|
|
35
|
+
*/
|
|
26
36
|
nodes: {
|
|
27
37
|
type: Array,
|
|
28
38
|
default: () => []
|
|
@@ -39,12 +49,71 @@ export default {
|
|
|
39
49
|
},
|
|
40
50
|
},
|
|
41
51
|
|
|
42
|
-
data() {
|
|
52
|
+
data(): {
|
|
53
|
+
selectNode: string | null;
|
|
54
|
+
nodeName: string;
|
|
55
|
+
nodeAffinity: any;
|
|
56
|
+
nodeSelector: any;
|
|
57
|
+
nodeSchedulingAllSettings: ResourceLabeledSelectSettings;
|
|
58
|
+
nodeSchedulingPaginationSettings: ResourceLabeledSelectPaginateSettings;
|
|
59
|
+
NODE: string;
|
|
60
|
+
} {
|
|
61
|
+
const keys = [
|
|
62
|
+
`node-role.kubernetes.io/control-plane`,
|
|
63
|
+
`node-role.kubernetes.io/etcd`
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
// Settings used by ResourceLabeledSelect when node pagination disabled
|
|
67
|
+
const nodeSchedulingAllSettings: ResourceLabeledSelectSettings = {
|
|
68
|
+
updateResources(nodes: (string | KubeNode)[]) {
|
|
69
|
+
return nodes
|
|
70
|
+
.filter((node) => {
|
|
71
|
+
if (typeof node === 'string') {
|
|
72
|
+
// Already passed check
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const taints = node.spec?.taints || [];
|
|
77
|
+
|
|
78
|
+
return taints.every((taint: KubeNodeTaint) => !keys.includes(taint.key));
|
|
79
|
+
})
|
|
80
|
+
.map(parseNode);
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Settings used by ResourceLabeledSelect when node pagination enabled
|
|
85
|
+
const nodeSchedulingPaginationSettings: ResourceLabeledSelectPaginateSettings = {
|
|
86
|
+
updateResources(nodes: (string | KubeNode)[]) {
|
|
87
|
+
return nodes.map(parseNode);
|
|
88
|
+
},
|
|
89
|
+
requestSettings: (opts: LabelSelectPaginationFunctionOptions) => {
|
|
90
|
+
const { filter } = opts.opts;
|
|
91
|
+
const filters = !!filter ? [
|
|
92
|
+
PaginationParamFilter.createSingleField({
|
|
93
|
+
field: 'metadata.name', value: filter, exact: false
|
|
94
|
+
})
|
|
95
|
+
] : [];
|
|
96
|
+
|
|
97
|
+
filters.push(...keys.map((k) => PaginationParamFilter.createSingleField( ({
|
|
98
|
+
field: 'spec.taints.key', value: k, equality: PaginationFilterEquality.NOT_CONTAINS
|
|
99
|
+
}))));
|
|
100
|
+
|
|
101
|
+
opts.filters = filters;
|
|
102
|
+
opts.groupByNamespace = false;
|
|
103
|
+
opts.sort = [{ asc: true, field: 'metadata.name' }];
|
|
104
|
+
|
|
105
|
+
return opts;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
43
109
|
return {
|
|
44
110
|
selectNode: null,
|
|
45
111
|
nodeName: '',
|
|
46
112
|
nodeAffinity: {},
|
|
47
113
|
nodeSelector: {},
|
|
114
|
+
nodeSchedulingAllSettings,
|
|
115
|
+
nodeSchedulingPaginationSettings,
|
|
116
|
+
NODE
|
|
48
117
|
};
|
|
49
118
|
},
|
|
50
119
|
|
|
@@ -147,6 +216,9 @@ export default {
|
|
|
147
216
|
watch: {
|
|
148
217
|
'value.nodeSelector': {
|
|
149
218
|
handler(nodeSelector) {
|
|
219
|
+
// Harvester specific code should not live in rancher/dashboard components
|
|
220
|
+
// This was brought into harvester/dashboard via https://github.com/harvester/dashboard/pull/342
|
|
221
|
+
// rancher/dashboard via https://github.com/rancher/dashboard/pull/6310
|
|
150
222
|
if (this.isHarvester && nodeSelector?.[HOSTNAME]) {
|
|
151
223
|
this.selectNode = 'nodeSelector';
|
|
152
224
|
const nodeName = nodeSelector[HOSTNAME];
|
|
@@ -187,14 +259,16 @@ export default {
|
|
|
187
259
|
<template v-if="selectNode === 'nodeSelector'">
|
|
188
260
|
<div class="row">
|
|
189
261
|
<div class="col span-6">
|
|
190
|
-
<
|
|
262
|
+
<ResourceLabeledSelect
|
|
191
263
|
v-model:value="nodeName"
|
|
192
264
|
:label="t('workload.scheduling.affinity.nodeName')"
|
|
193
|
-
:
|
|
265
|
+
:resource-type="NODE"
|
|
194
266
|
:mode="mode"
|
|
195
267
|
:multiple="false"
|
|
196
268
|
:loading="loading"
|
|
197
269
|
:data-testid="'node-scheduling-nodeSelector'"
|
|
270
|
+
:allResourcesSettings="nodeSchedulingAllSettings"
|
|
271
|
+
:paginatedResourceSettings="nodeSchedulingPaginationSettings"
|
|
198
272
|
@update:value="update"
|
|
199
273
|
/>
|
|
200
274
|
</div>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { mapGetters } from 'vuex';
|
|
3
3
|
import { _VIEW } from '@shell/config/query-params';
|
|
4
4
|
import { get, set, isEmpty, clone } from '@shell/utils/object';
|
|
5
|
-
import { POD,
|
|
5
|
+
import { POD, NAMESPACE } from '@shell/config/types';
|
|
6
6
|
import MatchExpressions from '@shell/components/form/MatchExpressions';
|
|
7
7
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
|
8
8
|
import { RadioGroup } from '@components/Form/Radio';
|
|
@@ -11,7 +11,6 @@ import { randomStr } from '@shell/utils/string';
|
|
|
11
11
|
import { sortBy } from '@shell/utils/sort';
|
|
12
12
|
import debounce from 'lodash/debounce';
|
|
13
13
|
import ArrayListGrouped from '@shell/components/form/ArrayListGrouped';
|
|
14
|
-
import { getUniqueLabelKeys } from '@shell/utils/array';
|
|
15
14
|
|
|
16
15
|
const NAMESPACE_SELECTION_OPTION_VALUES = {
|
|
17
16
|
POD: 'pod',
|
|
@@ -47,11 +46,6 @@ export default {
|
|
|
47
46
|
default: 'create'
|
|
48
47
|
},
|
|
49
48
|
|
|
50
|
-
nodes: {
|
|
51
|
-
type: Array,
|
|
52
|
-
default: () => []
|
|
53
|
-
},
|
|
54
|
-
|
|
55
49
|
namespaces: {
|
|
56
50
|
type: Array,
|
|
57
51
|
default: null
|
|
@@ -110,10 +104,6 @@ export default {
|
|
|
110
104
|
return POD;
|
|
111
105
|
},
|
|
112
106
|
|
|
113
|
-
node() {
|
|
114
|
-
return NODE;
|
|
115
|
-
},
|
|
116
|
-
|
|
117
107
|
labeledInputNamespaceLabel() {
|
|
118
108
|
return this.removeLabeledInputNamespaceLabel ? '' : this.overwriteLabels?.namespaceInputLabel || this.t('workload.scheduling.affinity.matchExpressions.inNamespaces');
|
|
119
109
|
},
|
|
@@ -132,14 +122,6 @@ export default {
|
|
|
132
122
|
return out;
|
|
133
123
|
},
|
|
134
124
|
|
|
135
|
-
existingNodeLabels() {
|
|
136
|
-
return getUniqueLabelKeys(this.nodes);
|
|
137
|
-
},
|
|
138
|
-
|
|
139
|
-
hasNodes() {
|
|
140
|
-
return this.nodes.length;
|
|
141
|
-
},
|
|
142
|
-
|
|
143
125
|
namespaceSelectionOptions() {
|
|
144
126
|
if (this.allNamespacesOptionAvailable) {
|
|
145
127
|
return [
|
|
@@ -440,24 +422,7 @@ export default {
|
|
|
440
422
|
/>
|
|
441
423
|
<div class="row mt-20">
|
|
442
424
|
<div class="col span-9">
|
|
443
|
-
<LabeledSelect
|
|
444
|
-
v-if="hasNodes"
|
|
445
|
-
v-model:value="props.row.value.topologyKey"
|
|
446
|
-
:taggable="true"
|
|
447
|
-
:searchable="true"
|
|
448
|
-
:close-on-select="false"
|
|
449
|
-
:mode="mode"
|
|
450
|
-
required
|
|
451
|
-
:label="t('workload.scheduling.affinity.topologyKey.label')"
|
|
452
|
-
:placeholder="topologyKeyPlaceholder"
|
|
453
|
-
:options="existingNodeLabels"
|
|
454
|
-
:disabled="mode==='view'"
|
|
455
|
-
:loading="loading"
|
|
456
|
-
:data-testid="`pod-affinity-topology-select-index${props.i}`"
|
|
457
|
-
@update:value="update"
|
|
458
|
-
/>
|
|
459
425
|
<LabeledInput
|
|
460
|
-
v-else
|
|
461
426
|
v-model:value="props.row.value.topologyKey"
|
|
462
427
|
:mode="mode"
|
|
463
428
|
:label="t('workload.scheduling.affinity.topologyKey.label')"
|
|
@@ -137,13 +137,17 @@ export default defineComponent({
|
|
|
137
137
|
const filters = !!filter ? [PaginationParamFilter.createSingleField({
|
|
138
138
|
field: 'metadata.name', value: filter, exact: false
|
|
139
139
|
})] : [];
|
|
140
|
+
const schema = this.$store.getters[`${ this.validInStore }/schema`](this.resourceType);
|
|
141
|
+
const namespaced = typeof schema?.attributes?.namespaced !== 'undefined' ? schema.attributes.namespaced : false;
|
|
142
|
+
|
|
140
143
|
const defaultOptions: LabelSelectPaginationFunctionOptions = {
|
|
141
144
|
opts,
|
|
142
145
|
filters,
|
|
143
|
-
type:
|
|
144
|
-
ctx:
|
|
145
|
-
sort:
|
|
146
|
-
store:
|
|
146
|
+
type: this.resourceType,
|
|
147
|
+
ctx: { getters: this.$store.getters, dispatch: this.$store.dispatch },
|
|
148
|
+
sort: [{ asc: true, field: 'metadata.name' }],
|
|
149
|
+
store: this.validInStore,
|
|
150
|
+
groupByNamespace: namespaced,
|
|
147
151
|
};
|
|
148
152
|
const options = this.paginatedResourceSettings?.requestSettings ? this.paginatedResourceSettings.requestSettings(defaultOptions) : defaultOptions;
|
|
149
153
|
const res = await labelSelectPaginationFunction(options);
|
|
@@ -35,11 +35,11 @@ export default {
|
|
|
35
35
|
computed: {
|
|
36
36
|
...QUOTA_COMPUTED,
|
|
37
37
|
projectResourceQuotaLimits() {
|
|
38
|
-
return this.project?.spec?.resourceQuota?.limit || {};
|
|
38
|
+
return this.flatListFromLimits(this.project?.spec?.resourceQuota?.limit || {});
|
|
39
39
|
},
|
|
40
40
|
namespaceResourceQuotaLimits() {
|
|
41
41
|
return this.project.namespaces.map((namespace) => ({
|
|
42
|
-
...namespace.resourceQuota.limit,
|
|
42
|
+
...this.flatListFromLimits(namespace.resourceQuota.limit),
|
|
43
43
|
id: namespace.id
|
|
44
44
|
}));
|
|
45
45
|
},
|
|
@@ -47,7 +47,7 @@ export default {
|
|
|
47
47
|
return Object.keys(this.projectResourceQuotaLimits);
|
|
48
48
|
},
|
|
49
49
|
defaultResourceQuotaLimits() {
|
|
50
|
-
return this.project.spec.namespaceDefaultResourceQuota.limit;
|
|
50
|
+
return this.flatListFromLimits(this.project.spec.namespaceDefaultResourceQuota.limit || {});
|
|
51
51
|
}
|
|
52
52
|
},
|
|
53
53
|
|
|
@@ -57,14 +57,35 @@ export default {
|
|
|
57
57
|
.filter((type) => !this.types.includes(type.value) || type.value === currentType);
|
|
58
58
|
},
|
|
59
59
|
update(key, value) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
this.value['resourceQuota'] = { limit: this.limitsFromFlatList(key, value) };
|
|
61
|
+
},
|
|
62
|
+
flatListFromLimits(limit) {
|
|
63
|
+
const result = {};
|
|
64
|
+
|
|
65
|
+
Object.keys(limit || {}).forEach((key) => {
|
|
66
|
+
if (key === 'extended') {
|
|
67
|
+
Object.keys(limit.extended || {}).forEach((extKey) => {
|
|
68
|
+
result[`extended.${ extKey }`] = limit.extended[extKey];
|
|
69
|
+
});
|
|
70
|
+
} else {
|
|
71
|
+
result[key] = limit[key];
|
|
64
72
|
}
|
|
65
|
-
};
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return result;
|
|
76
|
+
},
|
|
77
|
+
limitsFromFlatList(key, value) {
|
|
78
|
+
const limit = { ...this.value.resourceQuota.limit };
|
|
79
|
+
|
|
80
|
+
if (key.startsWith('extended.')) {
|
|
81
|
+
const resourceIdentifier = key.slice('extended.'.length);
|
|
82
|
+
|
|
83
|
+
limit.extended = { ...(limit.extended || {}), [resourceIdentifier]: value };
|
|
84
|
+
} else {
|
|
85
|
+
limit[key] = value;
|
|
86
|
+
}
|
|
66
87
|
|
|
67
|
-
|
|
88
|
+
return limit;
|
|
68
89
|
}
|
|
69
90
|
},
|
|
70
91
|
};
|
|
@@ -61,17 +61,35 @@ export default {
|
|
|
61
61
|
// We want to update the value first so that the value will be rounded to the project limit.
|
|
62
62
|
// This is relevant when switching projects. If the value is 1200 and the project that it was
|
|
63
63
|
// switched to only has capacity for 800 more this will force the value to be set to 800.
|
|
64
|
-
if (this.
|
|
65
|
-
this.update(this.
|
|
64
|
+
if (this.currentLimit) {
|
|
65
|
+
this.update(this.currentLimit);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
if (!this.
|
|
68
|
+
if (!this.currentLimit) {
|
|
69
69
|
this.update(this.defaultResourceQuotaLimits[this.type]);
|
|
70
70
|
}
|
|
71
71
|
},
|
|
72
72
|
|
|
73
73
|
computed: {
|
|
74
74
|
...ROW_COMPUTED,
|
|
75
|
+
currentLimit() {
|
|
76
|
+
const limit = this.value.limit || {};
|
|
77
|
+
|
|
78
|
+
if (this.type.startsWith('extended.')) {
|
|
79
|
+
const resourceIdentifier = this.type.slice('extended.'.length);
|
|
80
|
+
|
|
81
|
+
return (limit.extended || {})[resourceIdentifier];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return limit[this.type];
|
|
85
|
+
},
|
|
86
|
+
displayType() {
|
|
87
|
+
if (this.type.startsWith('extended.')) {
|
|
88
|
+
return this.type.slice('extended.'.length);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return this.type;
|
|
92
|
+
},
|
|
75
93
|
limitValue() {
|
|
76
94
|
return parseSi(this.projectResourceQuotaLimits[this.type]);
|
|
77
95
|
},
|
|
@@ -92,7 +110,7 @@ export default {
|
|
|
92
110
|
return this.namespaceLimits.reduce((sum, limit) => sum + limit, 0);
|
|
93
111
|
},
|
|
94
112
|
totalContribution() {
|
|
95
|
-
return this.namespaceContribution + parseSi(this.
|
|
113
|
+
return this.namespaceContribution + parseSi(this.currentLimit || '0', this.siOptions);
|
|
96
114
|
},
|
|
97
115
|
percentageUsed() {
|
|
98
116
|
return Math.min(this.totalContribution * 100 / this.projectLimit, 100);
|
|
@@ -127,7 +145,7 @@ export default {
|
|
|
127
145
|
},
|
|
128
146
|
{
|
|
129
147
|
label: t('resourceQuota.tooltip.namespace'),
|
|
130
|
-
value: this.
|
|
148
|
+
value: this.currentLimit
|
|
131
149
|
},
|
|
132
150
|
{
|
|
133
151
|
label: t('resourceQuota.tooltip.available'),
|
|
@@ -178,7 +196,7 @@ export default {
|
|
|
178
196
|
<Select
|
|
179
197
|
class="mr-10"
|
|
180
198
|
:mode="mode"
|
|
181
|
-
:value="
|
|
199
|
+
:value="displayType"
|
|
182
200
|
:disabled="true"
|
|
183
201
|
:options="types"
|
|
184
202
|
/>
|
|
@@ -192,7 +210,7 @@ export default {
|
|
|
192
210
|
/>
|
|
193
211
|
</div>
|
|
194
212
|
<UnitInput
|
|
195
|
-
:value="
|
|
213
|
+
:value="currentLimit"
|
|
196
214
|
:mode="mode"
|
|
197
215
|
:placeholder="typeOption.placeholder"
|
|
198
216
|
:increment="typeOption.increment"
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import ArrayList from '@shell/components/form/ArrayList';
|
|
3
|
-
import Row from './ProjectRow';
|
|
4
2
|
import { QUOTA_COMPUTED, TYPES } from './shared';
|
|
5
3
|
import Banner from '@components/Banner/Banner.vue';
|
|
4
|
+
import ResourceQuota from '@shell/components/form/ResourceQuota/ResourceQuotaEntry.vue';
|
|
5
|
+
import { RcButton } from '@components/RcButton';
|
|
6
|
+
import { uniqueId } from 'lodash';
|
|
6
7
|
|
|
7
8
|
export default {
|
|
8
9
|
emits: [
|
|
9
|
-
'remove',
|
|
10
10
|
'input',
|
|
11
11
|
'validationChanged',
|
|
12
12
|
],
|
|
13
13
|
|
|
14
14
|
components: {
|
|
15
|
-
ArrayList,
|
|
16
|
-
Row,
|
|
17
15
|
Banner,
|
|
16
|
+
RcButton,
|
|
17
|
+
ResourceQuota,
|
|
18
18
|
},
|
|
19
19
|
|
|
20
20
|
props: {
|
|
@@ -37,7 +37,7 @@ export default {
|
|
|
37
37
|
},
|
|
38
38
|
|
|
39
39
|
data() {
|
|
40
|
-
return {
|
|
40
|
+
return { resourceQuotas: [] };
|
|
41
41
|
},
|
|
42
42
|
|
|
43
43
|
created() {
|
|
@@ -45,85 +45,129 @@ export default {
|
|
|
45
45
|
this.value.spec['namespaceDefaultResourceQuota'] = this.value.spec.namespaceDefaultResourceQuota || { limit: {} };
|
|
46
46
|
this.value.spec['resourceQuota'] = this.value.spec.resourceQuota || { limit: {} };
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
const extendedKeys = Object.keys(limit.extended || {});
|
|
48
|
+
this.quotasFromSpec();
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Register watcher using the imperative API to reduce churn when initialing
|
|
52
|
+
* data on first render
|
|
53
|
+
*/
|
|
54
|
+
this.$watch(
|
|
55
|
+
'resourceQuotas',
|
|
56
|
+
() => {
|
|
57
|
+
const { projectLimit, nsLimit } = this.specFromQuotas();
|
|
58
|
+
|
|
59
|
+
this.$emit('input', { projectLimit, nsLimit });
|
|
60
|
+
|
|
61
|
+
const hasMissingExtendedIdentifier = this.resourceQuotas.some(
|
|
62
|
+
(quota) => quota.resourceType === TYPES.EXTENDED && !quota.resourceIdentifier
|
|
63
|
+
);
|
|
55
64
|
|
|
56
|
-
|
|
57
|
-
|
|
65
|
+
this.$emit('validationChanged', !hasMissingExtendedIdentifier);
|
|
66
|
+
},
|
|
67
|
+
{ deep: true }
|
|
68
|
+
);
|
|
58
69
|
},
|
|
59
70
|
|
|
60
71
|
computed: { ...QUOTA_COMPUTED },
|
|
61
72
|
|
|
62
73
|
methods: {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
74
|
+
addResource() {
|
|
75
|
+
this.resourceQuotas.push({
|
|
76
|
+
id: uniqueId(),
|
|
77
|
+
resourceType: TYPES.EXTENDED,
|
|
78
|
+
resourceIdentifier: '',
|
|
79
|
+
projectLimit: '',
|
|
80
|
+
namespaceDefaultLimit: '',
|
|
81
|
+
});
|
|
69
82
|
},
|
|
70
|
-
updateResourceIdentifier({ type, customType, index }) {
|
|
71
|
-
if (type.startsWith(TYPES.EXTENDED)) {
|
|
72
|
-
this.typeValues[index] = `extended.${ customType }`;
|
|
73
|
-
}
|
|
74
83
|
|
|
75
|
-
|
|
84
|
+
removeResource(id) {
|
|
85
|
+
this.resourceQuotas = this.resourceQuotas.filter((quota) => quota.id !== id);
|
|
76
86
|
},
|
|
77
|
-
validateTypes(isValid = true) {
|
|
78
|
-
if (!isValid) {
|
|
79
|
-
this.$emit('validationChanged', false);
|
|
80
|
-
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
87
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
+
remainingTypes(currentType) {
|
|
89
|
+
const usedTypes = this.resourceQuotas
|
|
90
|
+
.map((quota) => quota.resourceType)
|
|
91
|
+
.filter((resourceType) => resourceType !== TYPES.EXTENDED);
|
|
88
92
|
|
|
89
|
-
|
|
93
|
+
return this.mappedTypes.filter((mappedType) => mappedType.value === TYPES.EXTENDED ||
|
|
94
|
+
!usedTypes.includes(mappedType.value) ||
|
|
95
|
+
mappedType.value === currentType
|
|
96
|
+
);
|
|
97
|
+
},
|
|
90
98
|
|
|
91
|
-
|
|
92
|
-
}
|
|
99
|
+
specFromQuotas() {
|
|
100
|
+
const projectLimit = {};
|
|
101
|
+
const nsLimit = {};
|
|
93
102
|
|
|
94
|
-
this
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
103
|
+
this.resourceQuotas.forEach((quota) => {
|
|
104
|
+
if (quota.resourceType === TYPES.EXTENDED) {
|
|
105
|
+
if (quota.resourceIdentifier) {
|
|
106
|
+
if (!projectLimit.extended) {
|
|
107
|
+
projectLimit.extended = {};
|
|
108
|
+
}
|
|
109
|
+
if (!nsLimit.extended) {
|
|
110
|
+
nsLimit.extended = {};
|
|
111
|
+
}
|
|
112
|
+
projectLimit.extended[quota.resourceIdentifier] = quota.projectLimit;
|
|
113
|
+
nsLimit.extended[quota.resourceIdentifier] = quota.namespaceDefaultLimit;
|
|
101
114
|
}
|
|
115
|
+
} else {
|
|
116
|
+
projectLimit[quota.resourceType] = quota.projectLimit;
|
|
117
|
+
nsLimit[quota.resourceType] = quota.namespaceDefaultLimit;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
102
120
|
|
|
103
|
-
|
|
104
|
-
});
|
|
121
|
+
return { projectLimit, nsLimit };
|
|
105
122
|
},
|
|
106
|
-
emitRemove(data) {
|
|
107
|
-
this.typeValues = this.typeValues.filter((_typeValue, index) => {
|
|
108
|
-
return index !== data.index;
|
|
109
|
-
});
|
|
110
123
|
|
|
111
|
-
|
|
124
|
+
quotasFromSpec() {
|
|
125
|
+
const projectLimit = this.value?.spec?.resourceQuota?.limit || {};
|
|
126
|
+
const nsLimit = this.value?.spec?.namespaceDefaultResourceQuota?.limit || {};
|
|
112
127
|
|
|
113
|
-
|
|
128
|
+
Object.keys(projectLimit).forEach((key) => {
|
|
129
|
+
if (key !== TYPES.EXTENDED) {
|
|
130
|
+
this.resourceQuotas.push({
|
|
131
|
+
id: uniqueId(),
|
|
132
|
+
resourceType: key,
|
|
133
|
+
resourceIdentifier: key,
|
|
134
|
+
projectLimit: projectLimit[key],
|
|
135
|
+
namespaceDefaultLimit: nsLimit[key] || '',
|
|
136
|
+
});
|
|
137
|
+
} else {
|
|
138
|
+
Object.keys(projectLimit.extended || {}).forEach((extKey) => {
|
|
139
|
+
this.resourceQuotas.push({
|
|
140
|
+
id: uniqueId(),
|
|
141
|
+
resourceType: TYPES.EXTENDED,
|
|
142
|
+
resourceIdentifier: extKey,
|
|
143
|
+
projectLimit: projectLimit.extended[extKey],
|
|
144
|
+
namespaceDefaultLimit: (nsLimit.extended || {})[extKey] || '',
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
});
|
|
114
149
|
}
|
|
115
150
|
},
|
|
116
151
|
};
|
|
117
152
|
</script>
|
|
118
153
|
<template>
|
|
119
|
-
<div
|
|
154
|
+
<div
|
|
155
|
+
role="grid"
|
|
156
|
+
:aria-label="t('resourceQuota.ariaLabel.grid')"
|
|
157
|
+
>
|
|
120
158
|
<Banner
|
|
121
159
|
color="info"
|
|
122
160
|
label-key="resourceQuota.banner"
|
|
123
161
|
class="mb-20"
|
|
124
162
|
/>
|
|
125
|
-
<div
|
|
126
|
-
|
|
163
|
+
<div
|
|
164
|
+
role="row"
|
|
165
|
+
class="headers mb-10"
|
|
166
|
+
>
|
|
167
|
+
<div
|
|
168
|
+
role="columnheader"
|
|
169
|
+
class="mr-10"
|
|
170
|
+
>
|
|
127
171
|
<label>
|
|
128
172
|
{{ t('resourceQuota.headers.resourceType') }}
|
|
129
173
|
<span
|
|
@@ -132,7 +176,10 @@ export default {
|
|
|
132
176
|
>*</span>
|
|
133
177
|
</label>
|
|
134
178
|
</div>
|
|
135
|
-
<div
|
|
179
|
+
<div
|
|
180
|
+
role="columnheader"
|
|
181
|
+
class="mr-20"
|
|
182
|
+
>
|
|
136
183
|
<label>
|
|
137
184
|
{{ t('resourceQuota.headers.resourceIdentifier') }}
|
|
138
185
|
<span
|
|
@@ -145,37 +192,44 @@ export default {
|
|
|
145
192
|
/>
|
|
146
193
|
</label>
|
|
147
194
|
</div>
|
|
148
|
-
<div
|
|
195
|
+
<div
|
|
196
|
+
role="columnheader"
|
|
197
|
+
class="mr-20"
|
|
198
|
+
>
|
|
149
199
|
<label>{{ t('resourceQuota.headers.projectLimit') }}</label>
|
|
150
200
|
</div>
|
|
151
|
-
<div
|
|
201
|
+
<div
|
|
202
|
+
role="columnheader"
|
|
203
|
+
class="mr-10"
|
|
204
|
+
>
|
|
152
205
|
<label>{{ t('resourceQuota.headers.namespaceDefaultLimit') }}</label>
|
|
153
206
|
</div>
|
|
154
207
|
</div>
|
|
155
|
-
<
|
|
156
|
-
v-
|
|
157
|
-
|
|
158
|
-
:add-label="t('resourceQuota.add.label')"
|
|
159
|
-
:default-add-value="remainingTypes()[0] ? remainingTypes()[0].value : ''"
|
|
160
|
-
:add-allowed="remainingTypes().length > 0"
|
|
161
|
-
:mode="mode"
|
|
162
|
-
@remove="emitRemove"
|
|
163
|
-
@add="validateTypes(false)"
|
|
208
|
+
<template
|
|
209
|
+
v-for="(resourceQuota, resourceQuotaIndex) in resourceQuotas"
|
|
210
|
+
:key="resourceQuota.id"
|
|
164
211
|
>
|
|
165
|
-
<
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
212
|
+
<ResourceQuota
|
|
213
|
+
:id="resourceQuota.id"
|
|
214
|
+
v-model:resource-type="resourceQuota.resourceType"
|
|
215
|
+
v-model:resource-identifier="resourceQuota.resourceIdentifier"
|
|
216
|
+
v-model:project-limit="resourceQuota.projectLimit"
|
|
217
|
+
v-model:namespace-default-limit="resourceQuota.namespaceDefaultLimit"
|
|
218
|
+
:index="resourceQuotaIndex + 1"
|
|
219
|
+
:types="remainingTypes(resourceQuota.resourceType)"
|
|
220
|
+
:mode="mode"
|
|
221
|
+
@remove="removeResource"
|
|
222
|
+
/>
|
|
223
|
+
</template>
|
|
224
|
+
<div class="project-quotas-footer">
|
|
225
|
+
<rc-button
|
|
226
|
+
variant="tertiary"
|
|
227
|
+
data-testid="btn-add-resource"
|
|
228
|
+
@click="addResource"
|
|
229
|
+
>
|
|
230
|
+
{{ t('resourceQuota.add.label') }}
|
|
231
|
+
</rc-button>
|
|
232
|
+
</div>
|
|
179
233
|
</div>
|
|
180
234
|
</template>
|
|
181
235
|
<style lang="scss" scoped>
|
|
@@ -196,4 +250,8 @@ export default {
|
|
|
196
250
|
.required {
|
|
197
251
|
color: var(--error);
|
|
198
252
|
}
|
|
253
|
+
|
|
254
|
+
.project-quotas-footer {
|
|
255
|
+
margin-top: 24px;
|
|
256
|
+
}
|
|
199
257
|
</style>
|