@rancher/shell 0.3.7 → 0.3.9
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 +55 -14
- package/babel.config.js +17 -4
- package/components/CodeMirror.vue +146 -14
- package/components/ContainerResourceLimit.vue +14 -1
- package/components/CruResource.vue +21 -5
- package/components/ExplorerProjectsNamespaces.vue +5 -1
- package/components/GroupPanel.vue +57 -0
- package/components/Inactivity.vue +229 -0
- package/components/YamlEditor.vue +2 -2
- package/components/form/ArrayList.vue +1 -1
- package/components/form/KeyValue.vue +34 -1
- package/components/form/MatchExpressions.vue +120 -21
- package/components/form/NodeAffinity.vue +54 -4
- package/components/form/PodAffinity.vue +160 -47
- package/components/form/Tolerations.vue +40 -4
- package/components/form/__tests__/ArrayList.test.ts +3 -3
- package/components/form/__tests__/MatchExpressions.test.ts +1 -1
- package/components/nav/Header.vue +2 -0
- package/config/settings.ts +10 -1
- package/core/plugins-loader.js +0 -2
- package/creators/app/files/.gitignore +73 -0
- package/creators/app/init +1 -0
- package/edit/configmap.vue +33 -6
- package/edit/provisioning.cattle.io.cluster/AgentConfiguration.vue +326 -0
- package/edit/provisioning.cattle.io.cluster/index.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +63 -15
- package/edit/workload/mixins/workload.js +12 -4
- package/layouts/blank.vue +4 -0
- package/layouts/default.vue +3 -0
- package/layouts/home.vue +4 -1
- package/layouts/plain.vue +4 -1
- package/mixins/chart.js +1 -1
- package/models/batch.cronjob.js +18 -3
- package/models/provisioning.cattle.io.cluster.js +24 -0
- package/models/workload.js +1 -1
- package/package.json +2 -3
- package/pages/auth/login.vue +1 -0
- package/pages/c/_cluster/explorer/index.vue +1 -4
- package/pages/c/_cluster/settings/performance.vue +61 -7
- package/pages/prefs.vue +18 -2
- package/pkg/vue.config.js +0 -1
- package/plugins/codemirror.js +158 -0
- package/public/index.html +1 -1
- package/store/index.js +36 -21
- package/types/shell/index.d.ts +20 -1
- package/utils/create-yaml.js +105 -8
- package/utils/settings.ts +12 -0
- package/vue.config.js +2 -2
|
@@ -6,12 +6,13 @@ import { get, isEmpty, clone } from '@shell/utils/object';
|
|
|
6
6
|
import { NODE } from '@shell/config/types';
|
|
7
7
|
import MatchExpressions from '@shell/components/form/MatchExpressions';
|
|
8
8
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
|
9
|
+
import { LabeledInput } from '@components/Form/LabeledInput';
|
|
9
10
|
import { randomStr } from '@shell/utils/string';
|
|
10
11
|
import ArrayListGrouped from '@shell/components/form/ArrayListGrouped';
|
|
11
12
|
|
|
12
13
|
export default {
|
|
13
14
|
components: {
|
|
14
|
-
ArrayListGrouped, MatchExpressions, LabeledSelect
|
|
15
|
+
ArrayListGrouped, MatchExpressions, LabeledSelect, LabeledInput
|
|
15
16
|
},
|
|
16
17
|
|
|
17
18
|
props: {
|
|
@@ -27,6 +28,13 @@ export default {
|
|
|
27
28
|
type: String,
|
|
28
29
|
default: 'create'
|
|
29
30
|
},
|
|
31
|
+
|
|
32
|
+
// has select for matching fields or expressions (used for node affinity)
|
|
33
|
+
// https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#nodeselectorterm-v1-core
|
|
34
|
+
matchingSelectorDisplay: {
|
|
35
|
+
type: Boolean,
|
|
36
|
+
default: false,
|
|
37
|
+
},
|
|
30
38
|
},
|
|
31
39
|
|
|
32
40
|
data() {
|
|
@@ -89,7 +97,7 @@ export default {
|
|
|
89
97
|
|
|
90
98
|
this.allSelectorTerms.forEach((term) => {
|
|
91
99
|
if (term.weight) {
|
|
92
|
-
const neu = { weight:
|
|
100
|
+
const neu = { weight: term.weight, preference: term };
|
|
93
101
|
|
|
94
102
|
preferredDuringSchedulingIgnoredDuringExecution.push(neu);
|
|
95
103
|
} else {
|
|
@@ -103,6 +111,7 @@ export default {
|
|
|
103
111
|
if (requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms.length) {
|
|
104
112
|
out.requiredDuringSchedulingIgnoredDuringExecution = requiredDuringSchedulingIgnoredDuringExecution;
|
|
105
113
|
}
|
|
114
|
+
|
|
106
115
|
this.$emit('input', out);
|
|
107
116
|
},
|
|
108
117
|
|
|
@@ -124,6 +133,28 @@ export default {
|
|
|
124
133
|
return term.weight ? this.t('workload.scheduling.affinity.preferred') : this.t('workload.scheduling.affinity.required');
|
|
125
134
|
},
|
|
126
135
|
|
|
136
|
+
updateExpressions(row, expressions) {
|
|
137
|
+
const expressionsMatching = {
|
|
138
|
+
matchFields: [],
|
|
139
|
+
matchExpressions: []
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
if (expressions.length) {
|
|
143
|
+
expressions.forEach((expression) => {
|
|
144
|
+
expressionsMatching[expression.matching || 'matchExpressions'].push(expression);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (expressionsMatching.matchFields.length) {
|
|
148
|
+
this.$set(row, 'matchFields', expressionsMatching.matchFields);
|
|
149
|
+
}
|
|
150
|
+
if (expressionsMatching.matchExpressions.length) {
|
|
151
|
+
this.$set(row, 'matchExpressions', expressionsMatching.matchExpressions);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
this.update();
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
|
|
127
158
|
get,
|
|
128
159
|
|
|
129
160
|
isEmpty
|
|
@@ -148,23 +179,42 @@ export default {
|
|
|
148
179
|
>
|
|
149
180
|
<template #default="props">
|
|
150
181
|
<div class="row">
|
|
151
|
-
<div class="col span-
|
|
182
|
+
<div class="col span-9">
|
|
152
183
|
<LabeledSelect
|
|
153
184
|
:options="affinityOptions"
|
|
154
185
|
:value="priorityDisplay(props.row.value)"
|
|
155
186
|
:label="t('workload.scheduling.affinity.priority')"
|
|
156
187
|
:mode="mode"
|
|
188
|
+
:data-testid="`node-affinity-priority-index${props.i}`"
|
|
157
189
|
@input="(changePriority(props.row.value))"
|
|
158
190
|
/>
|
|
159
191
|
</div>
|
|
192
|
+
<div
|
|
193
|
+
v-if="props.row.value.weight"
|
|
194
|
+
class="col span-3"
|
|
195
|
+
>
|
|
196
|
+
<LabeledInput
|
|
197
|
+
v-model.number="props.row.value.weight"
|
|
198
|
+
:mode="mode"
|
|
199
|
+
type="number"
|
|
200
|
+
min="1"
|
|
201
|
+
max="100"
|
|
202
|
+
:label="t('workload.scheduling.affinity.weight.label')"
|
|
203
|
+
:placeholder="t('workload.scheduling.affinity.weight.placeholder')"
|
|
204
|
+
:data-testid="`node-affinity-weight-index${props.i}`"
|
|
205
|
+
/>
|
|
206
|
+
</div>
|
|
160
207
|
</div>
|
|
161
208
|
<MatchExpressions
|
|
162
209
|
:key="rerenderNums"
|
|
163
|
-
|
|
210
|
+
:value="matchingSelectorDisplay ? props.row.value : props.row.value.matchExpressions"
|
|
211
|
+
:matching-selector-display="matchingSelectorDisplay"
|
|
164
212
|
:mode="mode"
|
|
165
213
|
class="col span-12 mt-20"
|
|
166
214
|
:type="node"
|
|
167
215
|
:show-remove="false"
|
|
216
|
+
:data-testid="`node-affinity-expressions-index${props.i}`"
|
|
217
|
+
@input="(updateExpressions(props.row.value, $event))"
|
|
168
218
|
/>
|
|
169
219
|
</template>
|
|
170
220
|
</ArrayListGrouped>
|
|
@@ -12,6 +12,12 @@ import debounce from 'lodash/debounce';
|
|
|
12
12
|
import ArrayListGrouped from '@shell/components/form/ArrayListGrouped';
|
|
13
13
|
import { getUniqueLabelKeys } from '@shell/utils/array';
|
|
14
14
|
|
|
15
|
+
const NAMESPACE_SELECTION_OPTION_VALUES = {
|
|
16
|
+
POD: 'pod',
|
|
17
|
+
ALL: 'all',
|
|
18
|
+
SELECTED: 'selected',
|
|
19
|
+
};
|
|
20
|
+
|
|
15
21
|
export default {
|
|
16
22
|
components: {
|
|
17
23
|
ArrayListGrouped, MatchExpressions, LabeledSelect, RadioGroup, LabeledInput
|
|
@@ -26,6 +32,13 @@ export default {
|
|
|
26
32
|
}
|
|
27
33
|
},
|
|
28
34
|
|
|
35
|
+
// Field key on the value object to store the pod affinity - typically this is 'affinity'
|
|
36
|
+
// Cluster Agent Configuration uses a different field
|
|
37
|
+
field: {
|
|
38
|
+
type: String,
|
|
39
|
+
default: 'affinity'
|
|
40
|
+
},
|
|
41
|
+
|
|
29
42
|
mode: {
|
|
30
43
|
type: String,
|
|
31
44
|
default: 'create'
|
|
@@ -40,6 +53,22 @@ export default {
|
|
|
40
53
|
type: Array,
|
|
41
54
|
default: null
|
|
42
55
|
},
|
|
56
|
+
|
|
57
|
+
allNamespacesOptionAvailable: {
|
|
58
|
+
default: false,
|
|
59
|
+
type: Boolean
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
forceInputNamespaceSelection: {
|
|
63
|
+
default: false,
|
|
64
|
+
type: Boolean
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
removeLabeledInputNamespaceLabel: {
|
|
68
|
+
default: false,
|
|
69
|
+
type: Boolean
|
|
70
|
+
},
|
|
71
|
+
|
|
43
72
|
loading: {
|
|
44
73
|
default: false,
|
|
45
74
|
type: Boolean
|
|
@@ -47,32 +76,38 @@ export default {
|
|
|
47
76
|
},
|
|
48
77
|
|
|
49
78
|
data() {
|
|
50
|
-
if (!this.value.
|
|
51
|
-
this.$set(this.value,
|
|
79
|
+
if (!this.value[this.field]) {
|
|
80
|
+
this.$set(this.value, this.field, {});
|
|
52
81
|
}
|
|
53
|
-
const { podAffinity = {}, podAntiAffinity = {} } = this.value.
|
|
82
|
+
const { podAffinity = {}, podAntiAffinity = {} } = this.value[this.field];
|
|
54
83
|
const allAffinityTerms = [...(podAffinity.preferredDuringSchedulingIgnoredDuringExecution || []), ...(podAffinity.requiredDuringSchedulingIgnoredDuringExecution || [])].map((term) => {
|
|
55
|
-
|
|
84
|
+
let out = clone(term);
|
|
56
85
|
|
|
57
86
|
out._id = randomStr(4);
|
|
58
87
|
out._anti = false;
|
|
59
88
|
if (term.podAffinityTerm) {
|
|
60
89
|
Object.assign(out, term.podAffinityTerm);
|
|
61
|
-
out
|
|
90
|
+
out = this.parsePodAffinityTerm(out);
|
|
91
|
+
|
|
62
92
|
delete out.podAffinityTerm;
|
|
93
|
+
} else {
|
|
94
|
+
out = this.parsePodAffinityTerm(out);
|
|
63
95
|
}
|
|
64
96
|
|
|
65
97
|
return out;
|
|
66
98
|
});
|
|
67
99
|
const allAntiTerms = [...(podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution || []), ...(podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution || [])].map((term) => {
|
|
68
|
-
|
|
100
|
+
let out = clone(term);
|
|
69
101
|
|
|
70
102
|
out._id = randomStr(4);
|
|
71
103
|
out._anti = true;
|
|
72
104
|
if (term.podAffinityTerm) {
|
|
73
105
|
Object.assign(out, term.podAffinityTerm);
|
|
74
|
-
out
|
|
106
|
+
out = this.parsePodAffinityTerm(out);
|
|
107
|
+
|
|
75
108
|
delete out.podAffinityTerm;
|
|
109
|
+
} else {
|
|
110
|
+
out = this.parsePodAffinityTerm(out);
|
|
76
111
|
}
|
|
77
112
|
|
|
78
113
|
return out;
|
|
@@ -82,10 +117,17 @@ export default {
|
|
|
82
117
|
|
|
83
118
|
return {
|
|
84
119
|
allSelectorTerms,
|
|
85
|
-
defaultWeight:
|
|
120
|
+
defaultWeight: 1,
|
|
86
121
|
// rules in MatchExpressions.vue can not catch changes what happens on parent component
|
|
87
122
|
// we need re-render it via key changing
|
|
88
|
-
rerenderNums:
|
|
123
|
+
rerenderNums: randomStr(4),
|
|
124
|
+
NAMESPACE_SELECTION_OPTION_VALUES,
|
|
125
|
+
defaultAddValue: {
|
|
126
|
+
_namespaceOption: NAMESPACE_SELECTION_OPTION_VALUES.POD,
|
|
127
|
+
matchExpressions: [],
|
|
128
|
+
namespaces: null,
|
|
129
|
+
_namespaces: null,
|
|
130
|
+
}
|
|
89
131
|
};
|
|
90
132
|
},
|
|
91
133
|
computed: {
|
|
@@ -101,9 +143,14 @@ export default {
|
|
|
101
143
|
return NODE;
|
|
102
144
|
},
|
|
103
145
|
|
|
104
|
-
|
|
146
|
+
labeledInputNamespaceLabel() {
|
|
147
|
+
return this.removeLabeledInputNamespaceLabel ? '' : this.t('workload.scheduling.affinity.matchExpressions.inNamespaces');
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
allNamespacesOptions() {
|
|
105
151
|
const inStore = this.$store.getters['currentStore'](NAMESPACE);
|
|
106
152
|
const choices = this.namespaces || this.$store.getters[`${ inStore }/all`](NAMESPACE);
|
|
153
|
+
|
|
107
154
|
const out = sortBy(choices.map((obj) => {
|
|
108
155
|
return {
|
|
109
156
|
label: obj.nameDisplay,
|
|
@@ -122,8 +169,38 @@ export default {
|
|
|
122
169
|
return this.nodes.length;
|
|
123
170
|
},
|
|
124
171
|
|
|
172
|
+
namespaceSelectionOptions() {
|
|
173
|
+
if (this.allNamespacesOptionAvailable) {
|
|
174
|
+
return [
|
|
175
|
+
NAMESPACE_SELECTION_OPTION_VALUES.POD,
|
|
176
|
+
NAMESPACE_SELECTION_OPTION_VALUES.ALL,
|
|
177
|
+
NAMESPACE_SELECTION_OPTION_VALUES.SELECTED
|
|
178
|
+
];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return [
|
|
182
|
+
NAMESPACE_SELECTION_OPTION_VALUES.POD,
|
|
183
|
+
NAMESPACE_SELECTION_OPTION_VALUES.SELECTED
|
|
184
|
+
];
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
namespaceSelectionLabels() {
|
|
188
|
+
if (this.allNamespacesOptionAvailable) {
|
|
189
|
+
return [
|
|
190
|
+
this.t('workload.scheduling.affinity.thisPodNamespace'),
|
|
191
|
+
this.t('workload.scheduling.affinity.allNamespaces'),
|
|
192
|
+
this.t('workload.scheduling.affinity.matchExpressions.inNamespaces')
|
|
193
|
+
];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return [
|
|
197
|
+
this.t('workload.scheduling.affinity.thisPodNamespace'),
|
|
198
|
+
this.t('workload.scheduling.affinity.matchExpressions.inNamespaces')
|
|
199
|
+
];
|
|
200
|
+
},
|
|
201
|
+
|
|
125
202
|
hasNamespaces() {
|
|
126
|
-
return this.
|
|
203
|
+
return this.allNamespacesOptions.length;
|
|
127
204
|
},
|
|
128
205
|
},
|
|
129
206
|
|
|
@@ -132,6 +209,20 @@ export default {
|
|
|
132
209
|
},
|
|
133
210
|
|
|
134
211
|
methods: {
|
|
212
|
+
parsePodAffinityTerm(out) {
|
|
213
|
+
if (out.namespaceSelector && typeof out.namespaceSelector === 'object' && !Object.keys(out.namespaceSelector).length && this.allNamespacesOptionAvailable) {
|
|
214
|
+
out._namespaceOption = NAMESPACE_SELECTION_OPTION_VALUES.ALL;
|
|
215
|
+
} else if (out.namespaces?.length) {
|
|
216
|
+
out._namespaceOption = NAMESPACE_SELECTION_OPTION_VALUES.SELECTED;
|
|
217
|
+
} else {
|
|
218
|
+
out._namespaceOption = NAMESPACE_SELECTION_OPTION_VALUES.POD;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
out._namespaces = (out.namespaces || []).toString();
|
|
222
|
+
|
|
223
|
+
return out;
|
|
224
|
+
},
|
|
225
|
+
|
|
135
226
|
update() {
|
|
136
227
|
const podAffinity = { requiredDuringSchedulingIgnoredDuringExecution: [], preferredDuringSchedulingIgnoredDuringExecution: [] };
|
|
137
228
|
const podAntiAffinity = { requiredDuringSchedulingIgnoredDuringExecution: [], preferredDuringSchedulingIgnoredDuringExecution: [] };
|
|
@@ -154,7 +245,7 @@ export default {
|
|
|
154
245
|
}
|
|
155
246
|
});
|
|
156
247
|
|
|
157
|
-
Object.assign(this.value.
|
|
248
|
+
Object.assign(this.value[this.field], { podAffinity, podAntiAffinity });
|
|
158
249
|
this.$emit('update', this.value);
|
|
159
250
|
},
|
|
160
251
|
|
|
@@ -163,17 +254,6 @@ export default {
|
|
|
163
254
|
this.queueUpdate();
|
|
164
255
|
},
|
|
165
256
|
|
|
166
|
-
addSelector() {
|
|
167
|
-
const neu = {
|
|
168
|
-
namespaces: null,
|
|
169
|
-
labelSelector: { matchExpressions: [] },
|
|
170
|
-
topologyKey: '',
|
|
171
|
-
_id: randomStr(4)
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
this.allSelectorTerms.push(neu);
|
|
175
|
-
},
|
|
176
|
-
|
|
177
257
|
changePriority(term, idx) {
|
|
178
258
|
if (term.weight) {
|
|
179
259
|
delete term.weight;
|
|
@@ -189,14 +269,41 @@ export default {
|
|
|
189
269
|
return term.weight ? this.t('workload.scheduling.affinity.preferred') : this.t('workload.scheduling.affinity.required');
|
|
190
270
|
},
|
|
191
271
|
|
|
192
|
-
changeNamespaceMode(term, idx) {
|
|
193
|
-
|
|
272
|
+
changeNamespaceMode(val, term, idx) {
|
|
273
|
+
this.$set(term, '_namespaceOption', val);
|
|
274
|
+
|
|
275
|
+
switch (val) {
|
|
276
|
+
case NAMESPACE_SELECTION_OPTION_VALUES.POD:
|
|
194
277
|
term.namespaces = null;
|
|
195
278
|
term._namespaces = null;
|
|
196
|
-
|
|
279
|
+
|
|
280
|
+
if (term.namespaceSelector || term.namespaceSelector === null) {
|
|
281
|
+
delete term.namespaceSelector;
|
|
282
|
+
}
|
|
283
|
+
break;
|
|
284
|
+
case NAMESPACE_SELECTION_OPTION_VALUES.ALL:
|
|
285
|
+
term.namespaceSelector = {};
|
|
286
|
+
|
|
287
|
+
if (term.namespaces || term.namespaces === null) {
|
|
288
|
+
delete term.namespaces;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (term._namespaces || term._namespaces === null) {
|
|
292
|
+
delete term._namespaces;
|
|
293
|
+
}
|
|
294
|
+
break;
|
|
295
|
+
|
|
296
|
+
default:
|
|
197
297
|
this.$set(term, 'namespaces', []);
|
|
198
298
|
this.$set(term, '_namespaces', '');
|
|
299
|
+
|
|
300
|
+
if (term.namespaceSelector || term.namespaceSelector === null) {
|
|
301
|
+
delete term.namespaceSelector;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
break;
|
|
199
305
|
}
|
|
306
|
+
|
|
200
307
|
this.$set(this.allSelectorTerms, idx, term);
|
|
201
308
|
this.queueUpdate();
|
|
202
309
|
},
|
|
@@ -205,7 +312,7 @@ export default {
|
|
|
205
312
|
let nsArray = namespaces;
|
|
206
313
|
|
|
207
314
|
// namespaces would be String if there is no namespace
|
|
208
|
-
if (
|
|
315
|
+
if (typeof namespaces === 'string') {
|
|
209
316
|
nsArray = namespaces.split(',').map(ns => ns.trim()).filter(ns => ns?.length);
|
|
210
317
|
}
|
|
211
318
|
|
|
@@ -231,9 +338,9 @@ export default {
|
|
|
231
338
|
<ArrayListGrouped
|
|
232
339
|
v-model="allSelectorTerms"
|
|
233
340
|
class="mt-20"
|
|
234
|
-
:default-add-value="
|
|
341
|
+
:default-add-value="defaultAddValue"
|
|
235
342
|
:mode="mode"
|
|
236
|
-
:add-label="t('
|
|
343
|
+
:add-label="t('podAffinity.addLabel')"
|
|
237
344
|
@remove="remove"
|
|
238
345
|
>
|
|
239
346
|
<template #default="props">
|
|
@@ -244,6 +351,7 @@ export default {
|
|
|
244
351
|
:options="[t('workload.scheduling.affinity.affinityOption'),t('workload.scheduling.affinity.antiAffinityOption')]"
|
|
245
352
|
:value="props.row.value._anti ?t('workload.scheduling.affinity.antiAffinityOption') :t('workload.scheduling.affinity.affinityOption') "
|
|
246
353
|
:label="t('workload.scheduling.affinity.type')"
|
|
354
|
+
:data-testid="`pod-affinity-type-index${props.i}`"
|
|
247
355
|
@input="$set(props.row.value, '_anti',!props.row.value._anti)"
|
|
248
356
|
/>
|
|
249
357
|
</div>
|
|
@@ -254,41 +362,44 @@ export default {
|
|
|
254
362
|
:options="[t('workload.scheduling.affinity.preferred'),t('workload.scheduling.affinity.required')]"
|
|
255
363
|
:value="priorityDisplay(props.row.value)"
|
|
256
364
|
:label="t('workload.scheduling.affinity.priority')"
|
|
365
|
+
:data-testid="`pod-affinity-priority-index${props.i}`"
|
|
257
366
|
@input="changePriority(props.row.value, props.i)"
|
|
258
367
|
/>
|
|
259
368
|
</div>
|
|
260
369
|
</div>
|
|
261
370
|
<div class="row">
|
|
262
371
|
<RadioGroup
|
|
263
|
-
:options="
|
|
264
|
-
:labels="
|
|
372
|
+
:options="namespaceSelectionOptions"
|
|
373
|
+
:labels="namespaceSelectionLabels"
|
|
265
374
|
:name="`namespaces-${props.row.value._id}`"
|
|
266
375
|
:mode="mode"
|
|
267
|
-
:value="
|
|
268
|
-
|
|
376
|
+
:value="props.row.value._namespaceOption"
|
|
377
|
+
:data-testid="`pod-affinity-namespacetype-index${props.i}`"
|
|
378
|
+
@input="changeNamespaceMode($event, props.row.value, props.i)"
|
|
269
379
|
/>
|
|
270
380
|
</div>
|
|
271
|
-
<div class="spacer" />
|
|
272
381
|
<div
|
|
273
|
-
v-if="
|
|
274
|
-
class="row mb-20"
|
|
382
|
+
v-if="props.row.value._namespaceOption === NAMESPACE_SELECTION_OPTION_VALUES.SELECTED"
|
|
383
|
+
class="row mt-10 mb-20"
|
|
275
384
|
>
|
|
276
385
|
<LabeledSelect
|
|
277
|
-
v-if="hasNamespaces"
|
|
386
|
+
v-if="hasNamespaces && !forceInputNamespaceSelection"
|
|
278
387
|
v-model="props.row.value.namespaces"
|
|
279
388
|
:mode="mode"
|
|
280
389
|
:multiple="true"
|
|
281
390
|
:taggable="true"
|
|
282
|
-
:options="
|
|
391
|
+
:options="allNamespacesOptions"
|
|
283
392
|
:label="t('workload.scheduling.affinity.matchExpressions.inNamespaces')"
|
|
393
|
+
:data-testid="`pod-affinity-namespace-select-index${props.i}`"
|
|
284
394
|
@input="updateNamespaces(props.row.value, props.row.value.namespaces)"
|
|
285
395
|
/>
|
|
286
396
|
<LabeledInput
|
|
287
397
|
v-else
|
|
288
398
|
v-model="props.row.value._namespaces"
|
|
289
399
|
:mode="mode"
|
|
290
|
-
:label="
|
|
400
|
+
:label="labeledInputNamespaceLabel"
|
|
291
401
|
:placeholder="t('cluster.credential.harvester.affinity.namespaces.placeholder')"
|
|
402
|
+
:data-testid="`pod-affinity-namespace-input-index${props.i}`"
|
|
292
403
|
@input="updateNamespaces(props.row.value, props.row.value._namespaces)"
|
|
293
404
|
/>
|
|
294
405
|
</div>
|
|
@@ -299,11 +410,11 @@ export default {
|
|
|
299
410
|
:type="pod"
|
|
300
411
|
:value="get(props.row.value, 'labelSelector.matchExpressions')"
|
|
301
412
|
:show-remove="false"
|
|
413
|
+
:data-testid="`pod-affinity-expressions-index${props.i}`"
|
|
302
414
|
@input="e=>set(props.row.value, 'labelSelector.matchExpressions', e)"
|
|
303
415
|
/>
|
|
304
|
-
<div class="
|
|
305
|
-
|
|
306
|
-
<div class="col span-12">
|
|
416
|
+
<div class="row mt-20">
|
|
417
|
+
<div class="col span-9">
|
|
307
418
|
<LabeledSelect
|
|
308
419
|
v-if="hasNodes"
|
|
309
420
|
v-model="props.row.value.topologyKey"
|
|
@@ -317,6 +428,7 @@ export default {
|
|
|
317
428
|
:options="existingNodeLabels"
|
|
318
429
|
:disabled="mode==='view'"
|
|
319
430
|
:loading="loading"
|
|
431
|
+
:data-testid="`pod-affinity-topology-select-index${props.i}`"
|
|
320
432
|
@input="update"
|
|
321
433
|
/>
|
|
322
434
|
<LabeledInput
|
|
@@ -326,14 +438,14 @@ export default {
|
|
|
326
438
|
:label="t('workload.scheduling.affinity.topologyKey.label')"
|
|
327
439
|
:placeholder="t('workload.scheduling.affinity.topologyKey.placeholder')"
|
|
328
440
|
required
|
|
441
|
+
:data-testid="`pod-affinity-topology-input-index${props.i}`"
|
|
329
442
|
@input="update"
|
|
330
443
|
/>
|
|
331
444
|
</div>
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
<div class="col span-6">
|
|
445
|
+
<div
|
|
446
|
+
v-if="props.row.value.weight"
|
|
447
|
+
class="col span-3"
|
|
448
|
+
>
|
|
337
449
|
<LabeledInput
|
|
338
450
|
v-model.number="props.row.value.weight"
|
|
339
451
|
:mode="mode"
|
|
@@ -342,6 +454,7 @@ export default {
|
|
|
342
454
|
max="100"
|
|
343
455
|
:label="t('workload.scheduling.affinity.weight.label')"
|
|
344
456
|
:placeholder="t('workload.scheduling.affinity.weight.placeholder')"
|
|
457
|
+
:data-testid="`pod-affinity-weight-index${props.i}`"
|
|
345
458
|
/>
|
|
346
459
|
</div>
|
|
347
460
|
</div>
|
|
@@ -115,7 +115,24 @@ export default {
|
|
|
115
115
|
},
|
|
116
116
|
|
|
117
117
|
update() {
|
|
118
|
-
|
|
118
|
+
// let's delete the vKey prop as it's only poluting the data
|
|
119
|
+
const rules = this.rules.map((rule) => {
|
|
120
|
+
const newRule = { ...rule };
|
|
121
|
+
|
|
122
|
+
// prevent vKey from being sent as data
|
|
123
|
+
if (newRule.vKey) {
|
|
124
|
+
delete newRule.vKey;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// let's clear the value field if operator is Exists
|
|
128
|
+
if (newRule.operator === 'Exists' && newRule.value) {
|
|
129
|
+
newRule.value = null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return newRule;
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
this.$emit('input', rules);
|
|
119
136
|
},
|
|
120
137
|
|
|
121
138
|
addToleration() {
|
|
@@ -126,6 +143,8 @@ export default {
|
|
|
126
143
|
if (neu !== 'NoExecute' && rule.tolerationSeconds) {
|
|
127
144
|
delete rule.tolerationSeconds;
|
|
128
145
|
}
|
|
146
|
+
|
|
147
|
+
this.update();
|
|
129
148
|
}
|
|
130
149
|
}
|
|
131
150
|
|
|
@@ -146,7 +165,7 @@ export default {
|
|
|
146
165
|
<span />
|
|
147
166
|
</div>
|
|
148
167
|
<div
|
|
149
|
-
v-for="rule in rules"
|
|
168
|
+
v-for="(rule, index) in rules"
|
|
150
169
|
:key="rule.vKey"
|
|
151
170
|
class="rule"
|
|
152
171
|
>
|
|
@@ -154,6 +173,9 @@ export default {
|
|
|
154
173
|
<LabeledInput
|
|
155
174
|
v-model="rule.key"
|
|
156
175
|
:mode="mode"
|
|
176
|
+
:data-testid="`toleration-key-index${ index }`"
|
|
177
|
+
class="height-adjust-input"
|
|
178
|
+
@input="update"
|
|
157
179
|
/>
|
|
158
180
|
</div>
|
|
159
181
|
<div class="col">
|
|
@@ -162,6 +184,7 @@ export default {
|
|
|
162
184
|
v-model="rule.operator"
|
|
163
185
|
:options="operatorOpts"
|
|
164
186
|
:mode="mode"
|
|
187
|
+
:data-testid="`toleration-operator-index${ index }`"
|
|
165
188
|
@input="update"
|
|
166
189
|
/>
|
|
167
190
|
</div>
|
|
@@ -171,6 +194,7 @@ export default {
|
|
|
171
194
|
value="n/a"
|
|
172
195
|
:mode="mode"
|
|
173
196
|
disabled
|
|
197
|
+
class="height-adjust-input"
|
|
174
198
|
/>
|
|
175
199
|
</div>
|
|
176
200
|
</template>
|
|
@@ -179,6 +203,9 @@ export default {
|
|
|
179
203
|
<LabeledInput
|
|
180
204
|
v-model="rule.value"
|
|
181
205
|
:mode="mode"
|
|
206
|
+
:data-testid="`toleration-value-index${ index }`"
|
|
207
|
+
class="height-adjust-input"
|
|
208
|
+
@input="update"
|
|
182
209
|
/>
|
|
183
210
|
</div>
|
|
184
211
|
</template>
|
|
@@ -187,6 +214,7 @@ export default {
|
|
|
187
214
|
v-model="rule.effect"
|
|
188
215
|
:options="effectOpts"
|
|
189
216
|
:mode="mode"
|
|
217
|
+
:data-testid="`toleration-effect-index${ index }`"
|
|
190
218
|
@input="e=>updateEffect(e, rule)"
|
|
191
219
|
/>
|
|
192
220
|
</div>
|
|
@@ -196,6 +224,9 @@ export default {
|
|
|
196
224
|
:disabled="rule.effect !== 'NoExecute'"
|
|
197
225
|
:mode="mode"
|
|
198
226
|
suffix="Seconds"
|
|
227
|
+
:data-testid="`toleration-seconds-index${ index }`"
|
|
228
|
+
class="height-adjust-input"
|
|
229
|
+
@input="update"
|
|
199
230
|
/>
|
|
200
231
|
</div>
|
|
201
232
|
<div class="col remove">
|
|
@@ -204,6 +235,7 @@ export default {
|
|
|
204
235
|
type="button"
|
|
205
236
|
class="btn role-link"
|
|
206
237
|
:disabled="mode==='view'"
|
|
238
|
+
:data-testid="`toleration-remove-index${ index }`"
|
|
207
239
|
@click="remove(rule)"
|
|
208
240
|
>
|
|
209
241
|
<t k="generic.remove" />
|
|
@@ -214,6 +246,7 @@ export default {
|
|
|
214
246
|
v-if="!isView"
|
|
215
247
|
type="button"
|
|
216
248
|
class="btn role-tertiary"
|
|
249
|
+
data-testid="add-toleration-btn"
|
|
217
250
|
@click="addToleration"
|
|
218
251
|
>
|
|
219
252
|
<t k="workload.scheduling.tolerations.addToleration" />
|
|
@@ -228,8 +261,8 @@ export default {
|
|
|
228
261
|
|
|
229
262
|
.rule, .toleration-headers{
|
|
230
263
|
display: grid;
|
|
231
|
-
grid-template-columns: 20% 10% 20% 15% 20%
|
|
232
|
-
grid-gap:
|
|
264
|
+
grid-template-columns: 20% 10% 20% 15% 20% 15%;
|
|
265
|
+
grid-gap: 10px;
|
|
233
266
|
align-items: center;
|
|
234
267
|
}
|
|
235
268
|
|
|
@@ -247,4 +280,7 @@ export default {
|
|
|
247
280
|
.remove BUTTON {
|
|
248
281
|
padding: 0px;
|
|
249
282
|
}
|
|
283
|
+
.height-adjust-input {
|
|
284
|
+
min-height: 42px;
|
|
285
|
+
}
|
|
250
286
|
</style>
|
|
@@ -23,7 +23,7 @@ describe('the ArrayList', () => {
|
|
|
23
23
|
initialEmptyRow: true
|
|
24
24
|
},
|
|
25
25
|
});
|
|
26
|
-
const arrayListBoxes = wrapper.findAll('[data-testid
|
|
26
|
+
const arrayListBoxes = wrapper.findAll('[data-testid^="array-list-box"]');
|
|
27
27
|
|
|
28
28
|
expect(arrayListBoxes).toHaveLength(1);
|
|
29
29
|
});
|
|
@@ -40,7 +40,7 @@ describe('the ArrayList', () => {
|
|
|
40
40
|
|
|
41
41
|
await arrayListButton.click();
|
|
42
42
|
await arrayListButton.click();
|
|
43
|
-
const arrayListBoxes = wrapper.findAll('[data-testid
|
|
43
|
+
const arrayListBoxes = wrapper.findAll('[data-testid^="array-list-box"]');
|
|
44
44
|
|
|
45
45
|
expect(arrayListBoxes).toHaveLength(2);
|
|
46
46
|
});
|
|
@@ -55,7 +55,7 @@ describe('the ArrayList', () => {
|
|
|
55
55
|
const deleteButton = wrapper.get('[data-testid^="remove-item"]').element as HTMLElement;
|
|
56
56
|
|
|
57
57
|
await deleteButton.click();
|
|
58
|
-
const arrayListBoxes = wrapper.findAll('[data-testid
|
|
58
|
+
const arrayListBoxes = wrapper.findAll('[data-testid^="array-list-box"]');
|
|
59
59
|
|
|
60
60
|
expect(arrayListBoxes).toHaveLength(1);
|
|
61
61
|
});
|