@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
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import ModalWithCard from '@shell/components/ModalWithCard';
|
|
3
|
+
import { Banner } from '@components/Banner';
|
|
4
|
+
import PercentageBar from '@shell/components/PercentageBar.vue';
|
|
5
|
+
import throttle from 'lodash/throttle';
|
|
6
|
+
import { MANAGEMENT } from '@shell/config/types';
|
|
7
|
+
import { DEFAULT_PERF_SETTING, SETTING } from '@shell/config/settings';
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
name: 'Inactivity',
|
|
11
|
+
components: {
|
|
12
|
+
ModalWithCard, Banner, PercentageBar
|
|
13
|
+
},
|
|
14
|
+
data() {
|
|
15
|
+
return {
|
|
16
|
+
enabled: null,
|
|
17
|
+
isOpen: false,
|
|
18
|
+
isInactive: false,
|
|
19
|
+
showModalAfter: null,
|
|
20
|
+
inactivityTimeoutId: null,
|
|
21
|
+
courtesyTimer: null,
|
|
22
|
+
courtesyTimerId: null,
|
|
23
|
+
courtesyCountdown: null,
|
|
24
|
+
trackInactivity: throttle(this._trackInactivity, 1000),
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
async mounted() {
|
|
28
|
+
// Info: normally, this is done in the fetch hook but for some reasons while awaiting for things that will take a while, it won't be ready by the time mounted() is called, pending for investigation.
|
|
29
|
+
let settings;
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const settingsString = await this.$store.dispatch('management/find', { type: MANAGEMENT.SETTING, id: SETTING.UI_PERFORMANCE });
|
|
33
|
+
|
|
34
|
+
settings = settingsString?.value ? JSON.parse(settingsString.value) : DEFAULT_PERF_SETTING;
|
|
35
|
+
} catch { }
|
|
36
|
+
|
|
37
|
+
if (!settings || !settings?.inactivity || !settings?.inactivity.enabled) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
this.enabled = settings?.inactivity?.enabled || false;
|
|
42
|
+
|
|
43
|
+
// Total amount of time before the user's session is lost
|
|
44
|
+
const thresholdToSeconds = settings?.inactivity?.threshold * 60;
|
|
45
|
+
|
|
46
|
+
// Amount of time the user sees the inactivity warning
|
|
47
|
+
this.courtesyTimer = Math.floor(thresholdToSeconds * 0.1);
|
|
48
|
+
this.courtesyTimer = Math.min(this.courtesyTimer, 60 * 5); // Never show the modal more than 5 minutes
|
|
49
|
+
// Amount of time before the user sees the inactivity warning
|
|
50
|
+
// Note - time before warning is shown + time warning is shown = settings threshold (total amount of time)
|
|
51
|
+
this.showModalAfter = thresholdToSeconds - this.courtesyTimer;
|
|
52
|
+
|
|
53
|
+
console.debug(`Inactivity modal will show after ${ this.showModalAfter / 60 }(m) and be shown for ${ this.courtesyTimer / 60 }(m)`); // eslint-disable-line no-console
|
|
54
|
+
|
|
55
|
+
this.courtesyCountdown = this.courtesyTimer;
|
|
56
|
+
|
|
57
|
+
if (settings?.inactivity.enabled) {
|
|
58
|
+
this.trackInactivity();
|
|
59
|
+
this.addIdleListeners();
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
beforeDestroy() {
|
|
63
|
+
this.removeEventListener();
|
|
64
|
+
this.clearAllTimeouts();
|
|
65
|
+
},
|
|
66
|
+
methods: {
|
|
67
|
+
_trackInactivity() {
|
|
68
|
+
if (this.isInactive || this.isOpen || !this.showModalAfter) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.clearAllTimeouts();
|
|
73
|
+
const endTime = Date.now() + this.showModalAfter * 1000;
|
|
74
|
+
|
|
75
|
+
const checkInactivityTimer = () => {
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
|
|
78
|
+
if (now >= endTime) {
|
|
79
|
+
this.isOpen = true;
|
|
80
|
+
this.startCountdown();
|
|
81
|
+
|
|
82
|
+
this.$modal.show('inactivityModal');
|
|
83
|
+
} else {
|
|
84
|
+
this.inactivityTimeoutId = setTimeout(checkInactivityTimer, 1000);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
checkInactivityTimer();
|
|
89
|
+
},
|
|
90
|
+
startCountdown() {
|
|
91
|
+
const endTime = Date.now() + (this.courtesyCountdown * 1000);
|
|
92
|
+
|
|
93
|
+
const checkCountdown = () => {
|
|
94
|
+
const now = Date.now();
|
|
95
|
+
|
|
96
|
+
if (now >= endTime) {
|
|
97
|
+
this.isInactive = true;
|
|
98
|
+
this.unsubscribe();
|
|
99
|
+
this.clearAllTimeouts();
|
|
100
|
+
} else {
|
|
101
|
+
this.courtesyCountdown = Math.floor((endTime - now) / 1000);
|
|
102
|
+
this.courtesyTimerId = setTimeout(checkCountdown, 1000);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
checkCountdown();
|
|
107
|
+
},
|
|
108
|
+
addIdleListeners() {
|
|
109
|
+
document.addEventListener('mousemove', this.trackInactivity);
|
|
110
|
+
document.addEventListener('mousedown', this.trackInactivity);
|
|
111
|
+
document.addEventListener('keypress', this.trackInactivity);
|
|
112
|
+
document.addEventListener('touchmove', this.trackInactivity);
|
|
113
|
+
document.addEventListener('visibilitychange', this.trackInactivity);
|
|
114
|
+
},
|
|
115
|
+
removeEventListener() {
|
|
116
|
+
document.removeEventListener('mousemove', this.trackInactivity);
|
|
117
|
+
document.removeEventListener('mousedown', this.trackInactivity);
|
|
118
|
+
document.removeEventListener('keypress', this.trackInactivity);
|
|
119
|
+
document.removeEventListener('touchmove', this.trackInactivity);
|
|
120
|
+
document.removeEventListener('visibilitychange', this.trackInactivity);
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
resume() {
|
|
124
|
+
this.isInactive = false;
|
|
125
|
+
this.isOpen = false;
|
|
126
|
+
this.courtesyCountdown = this.courtesyTimer;
|
|
127
|
+
this.clearAllTimeouts();
|
|
128
|
+
|
|
129
|
+
this.$modal.hide('inactivityModal');
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
refresh() {
|
|
133
|
+
window.location.reload();
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
unsubscribe() {
|
|
137
|
+
this.$store.dispatch('unsubscribe');
|
|
138
|
+
},
|
|
139
|
+
clearAllTimeouts() {
|
|
140
|
+
clearTimeout(this.inactivityTimeoutId);
|
|
141
|
+
clearTimeout(this.courtesyTimerId);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
},
|
|
145
|
+
computed: {
|
|
146
|
+
isInactiveTexts() {
|
|
147
|
+
return this.isInactive ? {
|
|
148
|
+
title: this.t('inactivity.titleExpired'),
|
|
149
|
+
banner: this.t('inactivity.bannerExpired'),
|
|
150
|
+
content: this.t('inactivity.contentExpired'),
|
|
151
|
+
} : {
|
|
152
|
+
title: this.t('inactivity.title'),
|
|
153
|
+
banner: this.t('inactivity.banner'),
|
|
154
|
+
content: this.t('inactivity.content'),
|
|
155
|
+
};
|
|
156
|
+
},
|
|
157
|
+
timerPercentageLeft() {
|
|
158
|
+
return Math.floor((this.courtesyCountdown / this.courtesyTimer ) * 100);
|
|
159
|
+
},
|
|
160
|
+
colorStops() {
|
|
161
|
+
return {
|
|
162
|
+
0: '--info', 30: '--info', 70: '--info'
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
</script>
|
|
168
|
+
|
|
169
|
+
<template>
|
|
170
|
+
<ModalWithCard
|
|
171
|
+
ref="inactivityModal"
|
|
172
|
+
name="inactivityModal"
|
|
173
|
+
save-text="Continue"
|
|
174
|
+
:v-if="isOpen"
|
|
175
|
+
@finish="resume"
|
|
176
|
+
>
|
|
177
|
+
<template #title>
|
|
178
|
+
{{ isInactiveTexts.title }}
|
|
179
|
+
</template>
|
|
180
|
+
<span>{{ courtesyCountdown }}</span>
|
|
181
|
+
|
|
182
|
+
<template #content>
|
|
183
|
+
<Banner color="info">
|
|
184
|
+
{{ isInactiveTexts.banner }}
|
|
185
|
+
</Banner>
|
|
186
|
+
|
|
187
|
+
<p>
|
|
188
|
+
{{ isInactiveTexts.content }}
|
|
189
|
+
</p>
|
|
190
|
+
|
|
191
|
+
<PercentageBar
|
|
192
|
+
v-if="!isInactive"
|
|
193
|
+
class="mt-20"
|
|
194
|
+
:value="timerPercentageLeft"
|
|
195
|
+
:color-stops="colorStops"
|
|
196
|
+
/>
|
|
197
|
+
</template>
|
|
198
|
+
|
|
199
|
+
<template
|
|
200
|
+
#footer
|
|
201
|
+
>
|
|
202
|
+
<div class="card-actions">
|
|
203
|
+
<button
|
|
204
|
+
v-if="!isInactive"
|
|
205
|
+
class="btn role-tertiary bg-primary"
|
|
206
|
+
@click.prevent="resume"
|
|
207
|
+
>
|
|
208
|
+
<t k="inactivity.cta" />
|
|
209
|
+
</button>
|
|
210
|
+
|
|
211
|
+
<button
|
|
212
|
+
v-if="isInactive"
|
|
213
|
+
class="btn role-tertiary bg-primary"
|
|
214
|
+
@click.prevent="refresh"
|
|
215
|
+
>
|
|
216
|
+
<t k="inactivity.ctaExpired" />
|
|
217
|
+
</button>
|
|
218
|
+
</div>
|
|
219
|
+
</template>
|
|
220
|
+
</ModalWithCard>
|
|
221
|
+
</template>
|
|
222
|
+
|
|
223
|
+
<style lang="scss" scoped>
|
|
224
|
+
.card-actions {
|
|
225
|
+
display: flex;
|
|
226
|
+
width: 100%;
|
|
227
|
+
justify-content: flex-end;
|
|
228
|
+
}
|
|
229
|
+
</style>
|
|
@@ -86,7 +86,7 @@ export default {
|
|
|
86
86
|
},
|
|
87
87
|
|
|
88
88
|
computed: {
|
|
89
|
-
|
|
89
|
+
codeMirrorOptions() {
|
|
90
90
|
const readOnly = this.editorMode === EDITOR_MODES.VIEW_CODE;
|
|
91
91
|
|
|
92
92
|
const gutters = [];
|
|
@@ -228,7 +228,7 @@ export default {
|
|
|
228
228
|
ref="cm"
|
|
229
229
|
:class="{fill: true, scrolling: scrolling}"
|
|
230
230
|
:value="curValue"
|
|
231
|
-
:options="
|
|
231
|
+
:options="codeMirrorOptions"
|
|
232
232
|
:data-testid="componentTestid + '-code-mirror'"
|
|
233
233
|
@onInput="onInput"
|
|
234
234
|
@onReady="onReady"
|
|
@@ -10,11 +10,13 @@ import Select from '@shell/components/form/Select';
|
|
|
10
10
|
import FileSelector from '@shell/components/form/FileSelector';
|
|
11
11
|
import { _EDIT, _VIEW } from '@shell/config/query-params';
|
|
12
12
|
import { asciiLike } from '@shell/utils/string';
|
|
13
|
+
import CodeMirror from '@shell/components/CodeMirror';
|
|
13
14
|
|
|
14
15
|
export default {
|
|
15
16
|
name: 'KeyValue',
|
|
16
17
|
|
|
17
18
|
components: {
|
|
19
|
+
CodeMirror,
|
|
18
20
|
Select,
|
|
19
21
|
TextAreaAutoGrow,
|
|
20
22
|
FileSelector
|
|
@@ -139,6 +141,10 @@ export default {
|
|
|
139
141
|
type: Boolean,
|
|
140
142
|
default: false,
|
|
141
143
|
},
|
|
144
|
+
valueMarkdownMultiline: {
|
|
145
|
+
type: Boolean,
|
|
146
|
+
default: false,
|
|
147
|
+
},
|
|
142
148
|
valueMultiline: {
|
|
143
149
|
type: Boolean,
|
|
144
150
|
default: true,
|
|
@@ -245,7 +251,10 @@ export default {
|
|
|
245
251
|
data() {
|
|
246
252
|
const rows = this.getRows(this.value);
|
|
247
253
|
|
|
248
|
-
return {
|
|
254
|
+
return {
|
|
255
|
+
rows,
|
|
256
|
+
codeMirrorFocus: {},
|
|
257
|
+
};
|
|
249
258
|
},
|
|
250
259
|
|
|
251
260
|
computed: {
|
|
@@ -519,6 +528,19 @@ export default {
|
|
|
519
528
|
return this.t('detailText.binary', { n }, true);
|
|
520
529
|
},
|
|
521
530
|
get,
|
|
531
|
+
/**
|
|
532
|
+
* Update 'rows' variable with the user's input and prevents to update queue before the row model is updated
|
|
533
|
+
*/
|
|
534
|
+
onInputMarkdownMultiline(idx, value) {
|
|
535
|
+
this.rows = this.rows.map((row, i) => i === idx ? { ...row, value } : row);
|
|
536
|
+
this.queueUpdate();
|
|
537
|
+
},
|
|
538
|
+
/**
|
|
539
|
+
* Set focus on CodeMirror fields
|
|
540
|
+
*/
|
|
541
|
+
onFocusMarkdownMultiline(idx, value) {
|
|
542
|
+
this.$set(this.codeMirrorFocus, idx, value);
|
|
543
|
+
}
|
|
522
544
|
}
|
|
523
545
|
};
|
|
524
546
|
</script>
|
|
@@ -635,6 +657,16 @@ export default {
|
|
|
635
657
|
<div v-else-if="row.binary">
|
|
636
658
|
{{ binaryTextSize(row.value) }}
|
|
637
659
|
</div>
|
|
660
|
+
<CodeMirror
|
|
661
|
+
v-else-if="valueMarkdownMultiline"
|
|
662
|
+
ref="cm"
|
|
663
|
+
:class="{['focus']: codeMirrorFocus[i]}"
|
|
664
|
+
:value="row[valueName]"
|
|
665
|
+
:as-text-area="true"
|
|
666
|
+
:mode="mode"
|
|
667
|
+
@onInput="onInputMarkdownMultiline(i, $event)"
|
|
668
|
+
@onFocus="onFocusMarkdownMultiline(i, $event)"
|
|
669
|
+
/>
|
|
638
670
|
<TextAreaAutoGrow
|
|
639
671
|
v-else-if="valueMultiline"
|
|
640
672
|
v-model="row[valueName]"
|
|
@@ -750,6 +782,7 @@ export default {
|
|
|
750
782
|
&.value textarea{
|
|
751
783
|
padding: 10px 10px 10px 10px;
|
|
752
784
|
}
|
|
785
|
+
|
|
753
786
|
.text-monospace:not(.conceal) {
|
|
754
787
|
font-family: monospace, monospace;
|
|
755
788
|
}
|
|
@@ -29,6 +29,13 @@ export default {
|
|
|
29
29
|
default: NODE
|
|
30
30
|
},
|
|
31
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
|
+
},
|
|
38
|
+
|
|
32
39
|
// whether or not to show an initial empty row of inputs when value is empty in editing modes
|
|
33
40
|
initialEmptyRow: {
|
|
34
41
|
type: Boolean,
|
|
@@ -83,28 +90,39 @@ export default {
|
|
|
83
90
|
|
|
84
91
|
let rules;
|
|
85
92
|
|
|
86
|
-
|
|
93
|
+
// special case for matchFields and matchExpressions
|
|
94
|
+
// https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#nodeselectorterm-v1-core
|
|
95
|
+
if ( this.matchingSelectorDisplay) {
|
|
96
|
+
const rulesByType = {
|
|
97
|
+
matchFields: [],
|
|
98
|
+
matchExpressions: []
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
['matchFields', 'matchExpressions'].forEach((type) => {
|
|
102
|
+
rulesByType[type] = this.parseRules(this.value[type], type);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
rules = [...rulesByType.matchFields, ...rulesByType.matchExpressions];
|
|
106
|
+
} else if ( isArray(this.value) ) {
|
|
87
107
|
rules = [...this.value];
|
|
108
|
+
rules = this.parseRules(rules);
|
|
88
109
|
} else {
|
|
89
110
|
rules = convert(this.value.matchLabels, this.value.matchExpressions);
|
|
111
|
+
rules = this.parseRules(rules);
|
|
90
112
|
}
|
|
91
113
|
|
|
92
|
-
rules = rules.map((rule) => {
|
|
93
|
-
const newRule = clone(rule);
|
|
94
|
-
|
|
95
|
-
if (newRule.values && typeof newRule.values !== 'string') {
|
|
96
|
-
newRule.values = newRule.values.join(', ');
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return newRule;
|
|
100
|
-
});
|
|
101
|
-
|
|
102
114
|
if (!rules.length && this.initialEmptyRow && !this.isView) {
|
|
103
|
-
|
|
115
|
+
const newRule = {
|
|
104
116
|
key: '',
|
|
105
117
|
operator: 'In',
|
|
106
118
|
values: ''
|
|
107
|
-
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
if (this.matchingSelectorDisplay) {
|
|
122
|
+
newRule.matching = 'matchExpressions';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
rules.push(newRule);
|
|
108
126
|
}
|
|
109
127
|
|
|
110
128
|
return {
|
|
@@ -131,27 +149,71 @@ export default {
|
|
|
131
149
|
return !!this.keysSelectOptions?.length;
|
|
132
150
|
},
|
|
133
151
|
|
|
152
|
+
matchingSelectOptions() {
|
|
153
|
+
return [
|
|
154
|
+
{
|
|
155
|
+
label: this.t('workload.scheduling.affinity.matchExpressions.label'),
|
|
156
|
+
value: 'matchExpressions',
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
label: this.t('workload.scheduling.affinity.matchFields.label'),
|
|
160
|
+
value: 'matchFields',
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
},
|
|
164
|
+
|
|
134
165
|
...mapGetters({ t: 'i18n/t' })
|
|
135
166
|
},
|
|
136
167
|
|
|
137
168
|
methods: {
|
|
169
|
+
parseRules(rules, matching) {
|
|
170
|
+
if (rules?.length) {
|
|
171
|
+
return rules.map((rule) => {
|
|
172
|
+
const newRule = clone(rule);
|
|
173
|
+
|
|
174
|
+
if (newRule.values && typeof newRule.values !== 'string') {
|
|
175
|
+
newRule.values = newRule.values.join(', ');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (matching) {
|
|
179
|
+
newRule.matching = matching;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return newRule;
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return [];
|
|
187
|
+
},
|
|
188
|
+
|
|
138
189
|
removeRule(row) {
|
|
139
190
|
removeObject(this.rules, row);
|
|
140
191
|
this.update();
|
|
141
192
|
},
|
|
142
193
|
|
|
143
194
|
addRule() {
|
|
144
|
-
|
|
195
|
+
const newRule = {
|
|
145
196
|
key: '',
|
|
146
197
|
operator: 'In',
|
|
147
198
|
values: ''
|
|
148
|
-
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
if (this.matchingSelectorDisplay) {
|
|
202
|
+
newRule.matching = 'matchExpressions';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
this.rules.push(newRule);
|
|
149
206
|
},
|
|
150
207
|
|
|
151
208
|
update() {
|
|
152
209
|
this.$nextTick(() => {
|
|
153
210
|
const out = this.rules.map((rule) => {
|
|
154
|
-
const
|
|
211
|
+
const expression = { key: rule.key, operator: rule.operator };
|
|
212
|
+
|
|
213
|
+
if (this.matchingSelectorDisplay) {
|
|
214
|
+
expression.matching = rule.matching;
|
|
215
|
+
}
|
|
216
|
+
|
|
155
217
|
let val = (rule.values || '').trim();
|
|
156
218
|
|
|
157
219
|
if ( rule.operator === 'Exists' || rule.operator === 'DoesNotExist') {
|
|
@@ -161,13 +223,13 @@ export default {
|
|
|
161
223
|
}
|
|
162
224
|
|
|
163
225
|
if ( val !== null ) {
|
|
164
|
-
|
|
226
|
+
expression.values = val.split(/\s*,\s*/).filter(x => !!x);
|
|
165
227
|
}
|
|
166
228
|
|
|
167
|
-
return
|
|
229
|
+
return expression;
|
|
168
230
|
}).filter(x => !!x);
|
|
169
231
|
|
|
170
|
-
if ( isArray(this.value) ) {
|
|
232
|
+
if ( isArray(this.value) || this.matchingSelectorDisplay ) {
|
|
171
233
|
this.$emit('input', out);
|
|
172
234
|
} else {
|
|
173
235
|
this.$emit('input', simplify(out));
|
|
@@ -192,8 +254,11 @@ export default {
|
|
|
192
254
|
<div
|
|
193
255
|
v-if="rules.length"
|
|
194
256
|
class="match-expression-header"
|
|
195
|
-
:class="{'view':isView}"
|
|
257
|
+
:class="{ 'view':isView, 'match-expression-header-matching': matchingSelectorDisplay }"
|
|
196
258
|
>
|
|
259
|
+
<label v-if="matchingSelectorDisplay">
|
|
260
|
+
{{ t('workload.scheduling.affinity.matchExpressions.matchType') }}
|
|
261
|
+
</label>
|
|
197
262
|
<label>
|
|
198
263
|
{{ t('workload.scheduling.affinity.matchExpressions.key') }}
|
|
199
264
|
</label>
|
|
@@ -209,8 +274,25 @@ export default {
|
|
|
209
274
|
v-for="(row, index) in rules"
|
|
210
275
|
:key="row.id"
|
|
211
276
|
class="match-expression-row"
|
|
212
|
-
:class="{'view':isView, 'mb-10': index !== rules.length - 1}"
|
|
277
|
+
:class="{'view':isView, 'mb-10': index !== rules.length - 1, 'match-expression-row-matching': matchingSelectorDisplay}"
|
|
213
278
|
>
|
|
279
|
+
<!-- Select for matchFields and matchExpressions -->
|
|
280
|
+
<div
|
|
281
|
+
v-if="matchingSelectorDisplay"
|
|
282
|
+
:data-testid="`input-match-type-field-${index}`"
|
|
283
|
+
>
|
|
284
|
+
<div v-if="isView">
|
|
285
|
+
{{ row.matching }}
|
|
286
|
+
</div>
|
|
287
|
+
<LabeledSelect
|
|
288
|
+
v-else
|
|
289
|
+
v-model="row.matching"
|
|
290
|
+
:mode="mode"
|
|
291
|
+
:options="matchingSelectOptions"
|
|
292
|
+
:data-testid="`input-match-type-field-control-${index}`"
|
|
293
|
+
@selecting="update"
|
|
294
|
+
/>
|
|
295
|
+
</div>
|
|
214
296
|
<div
|
|
215
297
|
:data-testid="`input-match-expression-key-${index}`"
|
|
216
298
|
>
|
|
@@ -221,6 +303,7 @@ export default {
|
|
|
221
303
|
v-else-if="!hasKeySelectOptions"
|
|
222
304
|
v-model="row.key"
|
|
223
305
|
:mode="mode"
|
|
306
|
+
:data-testid="`input-match-expression-key-control-${index}`"
|
|
224
307
|
@input="update"
|
|
225
308
|
>
|
|
226
309
|
<LabeledSelect
|
|
@@ -228,6 +311,7 @@ export default {
|
|
|
228
311
|
v-model="row.key"
|
|
229
312
|
:mode="mode"
|
|
230
313
|
:options="keysSelectOptions"
|
|
314
|
+
:data-testid="`input-match-expression-key-control-select-${index}`"
|
|
231
315
|
/>
|
|
232
316
|
</div>
|
|
233
317
|
<div
|
|
@@ -244,6 +328,7 @@ export default {
|
|
|
244
328
|
:clearable="false"
|
|
245
329
|
:reduce="opt=>opt.value"
|
|
246
330
|
:mode="mode"
|
|
331
|
+
:data-testid="`input-match-expression-operator-control-${index}`"
|
|
247
332
|
@input="update"
|
|
248
333
|
/>
|
|
249
334
|
</div>
|
|
@@ -266,6 +351,7 @@ export default {
|
|
|
266
351
|
v-model="row.values"
|
|
267
352
|
:mode="mode"
|
|
268
353
|
:disabled="row.operator==='Exists' || row.operator==='DoesNotExist'"
|
|
354
|
+
:data-testid="`input-match-expression-values-control-${index}`"
|
|
269
355
|
@input="update"
|
|
270
356
|
>
|
|
271
357
|
</div>
|
|
@@ -280,6 +366,7 @@ export default {
|
|
|
280
366
|
:style="{padding:'0px'}"
|
|
281
367
|
|
|
282
368
|
:disabled="mode==='view'"
|
|
369
|
+
:data-testid="`input-match-expression-remove-control-${index}`"
|
|
283
370
|
@click="removeRule(row)"
|
|
284
371
|
>
|
|
285
372
|
<t k="generic.remove" />
|
|
@@ -293,6 +380,7 @@ export default {
|
|
|
293
380
|
<button
|
|
294
381
|
type="button"
|
|
295
382
|
class="btn role-tertiary add"
|
|
383
|
+
:data-testid="`input-match-expression-add-rule`"
|
|
296
384
|
@click="addRule"
|
|
297
385
|
>
|
|
298
386
|
<t k="workload.scheduling.affinity.matchExpressions.addRule" />
|
|
@@ -344,4 +432,15 @@ export default {
|
|
|
344
432
|
grid-template-columns: repeat(3, 1fr) 50px;
|
|
345
433
|
}
|
|
346
434
|
}
|
|
435
|
+
|
|
436
|
+
.match-expression-row > div > input {
|
|
437
|
+
min-height: 40px !important;
|
|
438
|
+
}
|
|
439
|
+
.match-expression-row-matching, .match-expression-header-matching {
|
|
440
|
+
grid-template-columns: 1fr 1fr 1fr 1fr;
|
|
441
|
+
|
|
442
|
+
&:not(.view){
|
|
443
|
+
grid-template-columns: 1fr 1fr 1fr 1fr 100px;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
347
446
|
</style>
|