@rancher/shell 0.3.8 → 0.3.10

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.
Files changed (145) hide show
  1. package/assets/translations/en-us.yaml +47 -26
  2. package/assets/translations/zh-hans.yaml +82 -16
  3. package/babel.config.js +17 -4
  4. package/chart/istio.vue +11 -11
  5. package/chart/rancher-backup/S3.vue +1 -1
  6. package/components/AsyncButton.vue +2 -2
  7. package/components/ButtonGroup.vue +1 -1
  8. package/components/CodeMirror.vue +146 -14
  9. package/components/CompoundStatusBadge.vue +1 -1
  10. package/components/ContainerResourceLimit.vue +14 -1
  11. package/components/CopyCode.vue +1 -1
  12. package/components/CruResource.vue +21 -5
  13. package/components/DetailTop.vue +1 -1
  14. package/components/ExplorerProjectsNamespaces.vue +8 -4
  15. package/components/GlobalRoleBindings.vue +1 -1
  16. package/components/GroupPanel.vue +57 -0
  17. package/components/HarvesterServiceAddOnConfig.vue +2 -117
  18. package/components/ResourceDetail/Masthead.vue +1 -1
  19. package/components/ResourceList/Masthead.vue +0 -6
  20. package/components/ResourceList/ResourceLoadingIndicator.vue +1 -9
  21. package/components/ResourceList/index.vue +7 -6
  22. package/components/ResourceTable.vue +13 -3
  23. package/components/SortableTable/THead.vue +3 -3
  24. package/components/SortableTable/index.vue +3 -3
  25. package/components/Tabbed/Tab.vue +1 -1
  26. package/components/Tabbed/index.vue +1 -1
  27. package/components/Wizard.vue +9 -6
  28. package/components/YamlEditor.vue +2 -2
  29. package/components/__tests__/NamespaceFilter.test.ts +26 -7
  30. package/components/auth/RoleDetailEdit.vue +1 -1
  31. package/components/auth/SelectPrincipal.vue +1 -1
  32. package/components/fleet/FleetRepos.vue +1 -1
  33. package/components/form/ArrayList.vue +2 -2
  34. package/components/form/KeyValue.vue +37 -3
  35. package/components/form/Labels.vue +34 -14
  36. package/components/form/MatchExpressions.vue +120 -21
  37. package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
  38. package/components/form/NameNsDescription.vue +1 -1
  39. package/components/form/NodeAffinity.vue +54 -4
  40. package/components/form/PlusMinus.vue +2 -2
  41. package/components/form/PodAffinity.vue +160 -47
  42. package/components/form/Probe.vue +1 -1
  43. package/components/form/ProjectMemberEditor.vue +8 -4
  44. package/components/form/ResourceQuota/NamespaceRow.vue +1 -1
  45. package/components/form/ServicePorts.vue +2 -2
  46. package/components/form/Tolerations.vue +70 -7
  47. package/components/form/WorkloadPorts.vue +2 -1
  48. package/components/form/__tests__/ArrayList.test.ts +3 -3
  49. package/components/form/__tests__/KeyValue.test.ts +17 -0
  50. package/components/form/__tests__/MatchExpressions.test.ts +1 -1
  51. package/components/formatter/ClusterLink.vue +3 -3
  52. package/components/formatter/LiveDate.vue +1 -1
  53. package/components/formatter/PodImages.vue +1 -1
  54. package/components/formatter/RKETemplateName.vue +1 -1
  55. package/components/formatter/Shortened.vue +1 -1
  56. package/components/nav/Header.vue +9 -7
  57. package/components/nav/NamespaceFilter.vue +103 -54
  58. package/config/labels-annotations.js +8 -5
  59. package/config/settings.ts +8 -6
  60. package/config/types.js +6 -4
  61. package/core/plugin-routes.ts +26 -7
  62. package/detail/provisioning.cattle.io.cluster.vue +4 -4
  63. package/edit/cis.cattle.io.clusterscan.vue +1 -1
  64. package/edit/configmap.vue +33 -6
  65. package/edit/k8s.cni.cncf.io.networkattachmentdefinition.vue +19 -149
  66. package/edit/logging-flow/index.vue +2 -2
  67. package/edit/logging.banzaicloud.io.output/providers/elasticsearch.vue +12 -0
  68. package/edit/logging.banzaicloud.io.output/providers/opensearch.vue +12 -0
  69. package/edit/management.cattle.io.project.vue +7 -0
  70. package/edit/monitoring.coreos.com.alertmanagerconfig/index.vue +1 -1
  71. package/edit/monitoring.coreos.com.alertmanagerconfig/routeConfig.vue +2 -2
  72. package/edit/monitoring.coreos.com.prometheusrule/GroupRules.vue +11 -8
  73. package/edit/networking.k8s.io.networkpolicy/PolicyRule.vue +2 -2
  74. package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +12 -4
  75. package/edit/networking.k8s.io.networkpolicy/__tests__/PolicyRuleTarget.spec.ts +140 -0
  76. package/edit/networking.k8s.io.networkpolicy/__tests__/utils/mock.json +158 -0
  77. package/edit/networking.k8s.io.networkpolicy/__tests__/utils/selectors.ts +45 -0
  78. package/edit/networking.k8s.io.networkpolicy/index.vue +1 -1
  79. package/edit/provisioning.cattle.io.cluster/AgentConfiguration.vue +326 -0
  80. package/edit/provisioning.cattle.io.cluster/MachinePool.vue +1 -1
  81. package/edit/provisioning.cattle.io.cluster/RegistryConfigs.vue +1 -1
  82. package/edit/provisioning.cattle.io.cluster/RegistryMirrors.vue +2 -2
  83. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +143 -169
  84. package/edit/provisioning.cattle.io.cluster/index.vue +1 -0
  85. package/edit/provisioning.cattle.io.cluster/rke2.vue +75 -6
  86. package/edit/resources.cattle.io.restore.vue +2 -2
  87. package/edit/service.vue +22 -3
  88. package/edit/storage.k8s.io.storageclass/index.vue +1 -1
  89. package/edit/workload/Job.vue +2 -2
  90. package/edit/workload/index.vue +1 -1
  91. package/edit/workload/mixins/workload.js +7 -1
  92. package/edit/workload/storage/__tests__/Storage.test.ts +84 -5
  93. package/initialize/index.js +1 -0
  94. package/layouts/default.vue +1 -1
  95. package/mixins/chart.js +1 -1
  96. package/mixins/resource-fetch-namespaced.js +19 -27
  97. package/mixins/resource-fetch.js +0 -5
  98. package/models/__tests__/namespace.test.ts +125 -0
  99. package/models/batch.cronjob.js +18 -3
  100. package/models/management.cattle.io.project.js +6 -1
  101. package/models/persistentvolume.js +1 -1
  102. package/models/workload.js +1 -1
  103. package/models/workload.service.js +22 -7
  104. package/package.json +17 -6
  105. package/pages/auth/login.vue +47 -49
  106. package/pages/c/_cluster/apps/charts/chart.vue +1 -1
  107. package/pages/c/_cluster/apps/charts/install.vue +42 -51
  108. package/pages/c/_cluster/explorer/index.vue +1 -1
  109. package/pages/c/_cluster/monitoring/index.vue +1 -1
  110. package/pages/c/_cluster/settings/performance.vue +53 -18
  111. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -1
  112. package/pages/c/_cluster/uiplugins/index.vue +16 -5
  113. package/pages/home.vue +1 -1
  114. package/pages/prefs.vue +18 -2
  115. package/plugins/clean-html-directive.js +1 -1
  116. package/plugins/clean-tooltip-directive.js +33 -0
  117. package/plugins/codemirror.js +158 -0
  118. package/plugins/dashboard-store/actions.js +4 -2
  119. package/plugins/dashboard-store/getters.js +6 -0
  120. package/plugins/dashboard-store/mutations.js +2 -2
  121. package/plugins/plugin.js +6 -1
  122. package/plugins/steve/actions.js +1 -1
  123. package/plugins/steve/getters.js +14 -3
  124. package/plugins/steve/resourceWatcher.js +36 -62
  125. package/plugins/steve/subscribe.js +137 -21
  126. package/plugins/steve/worker/index.js +7 -1
  127. package/plugins/steve/worker/web-worker.advanced.js +26 -8
  128. package/plugins/steve/worker/web-worker.basic.js +23 -4
  129. package/public/index.html +1 -1
  130. package/rancher-components/components/Form/Checkbox/Checkbox.vue +2 -2
  131. package/rancher-components/components/Form/Radio/RadioGroup.vue +2 -2
  132. package/rancher-components/components/LabeledTooltip/LabeledTooltip.vue +1 -1
  133. package/store/index.js +16 -61
  134. package/store/store-types.js +5 -0
  135. package/store/type-map.js +1 -1
  136. package/types/shell/index.d.ts +42 -7
  137. package/utils/__tests__/create-yaml.test.ts +63 -0
  138. package/utils/array.ts +4 -0
  139. package/utils/create-yaml.js +105 -8
  140. package/utils/namespace-filter.js +17 -5
  141. package/utils/projectAndNamespaceFiltering.utils.ts +62 -0
  142. package/utils/selector.js +6 -5
  143. package/utils/settings.ts +17 -7
  144. package/vue.config.js +2 -2
  145. package/models/k8s.cni.cncf.io.networkattachmentdefinition.js +0 -93
@@ -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.affinity) {
51
- this.$set(this.value, 'affinity', {});
79
+ if (!this.value[this.field]) {
80
+ this.$set(this.value, this.field, {});
52
81
  }
53
- const { podAffinity = {}, podAntiAffinity = {} } = this.value.affinity;
82
+ const { podAffinity = {}, podAntiAffinity = {} } = this.value[this.field];
54
83
  const allAffinityTerms = [...(podAffinity.preferredDuringSchedulingIgnoredDuringExecution || []), ...(podAffinity.requiredDuringSchedulingIgnoredDuringExecution || [])].map((term) => {
55
- const out = clone(term);
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._namespaces = (term.podAffinityTerm.namespaces || []).toString();
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
- const out = clone(term);
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._namespaces = (term.podAffinityTerm.namespaces || []).toString();
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: 1,
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: randomStr(4)
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
- allNamespaces() {
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.allNamespaces.length;
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.affinity, { podAffinity, podAntiAffinity });
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
- if (term.namespaces) {
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
- } else {
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 (!this.hasNamespaces) {
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="{ matchExpressions: [] }"
341
+ :default-add-value="defaultAddValue"
235
342
  :mode="mode"
236
- :add-label="t('workload.scheduling.affinity.addNodeSelector')"
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="[false, true]"
264
- :labels="[t('workload.scheduling.affinity.thisPodNamespace'),t('workload.scheduling.affinity.matchExpressions.inNamespaces'),]"
372
+ :options="namespaceSelectionOptions"
373
+ :labels="namespaceSelectionLabels"
265
374
  :name="`namespaces-${props.row.value._id}`"
266
375
  :mode="mode"
267
- :value="!!props.row.value.namespaces"
268
- @input="changeNamespaceMode(props.row.value, props.i)"
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="!!props.row.value.namespaces || !!get(props.row.value, 'podAffinityTerm.namespaces')"
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="allNamespaces"
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="t('workload.scheduling.affinity.matchExpressions.inNamespaces')"
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="spacer" />
305
- <div class="row">
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
- </div>
333
-
334
- <div class="spacer" />
335
- <div class="row">
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>
@@ -149,7 +149,7 @@ export default {
149
149
  {{ label }}
150
150
  <i
151
151
  v-if="description"
152
- v-tooltip="description"
152
+ v-clean-tooltip="description"
153
153
  class="icon icon-info"
154
154
  />
155
155
  </h3>
@@ -7,6 +7,7 @@ import { Card } from '@components/Card';
7
7
  import { RadioGroup } from '@components/Form/Radio';
8
8
  import { Checkbox } from '@components/Form/Checkbox';
9
9
  import { DESCRIPTION } from '@shell/config/labels-annotations';
10
+ import DOMPurify from 'dompurify';
10
11
 
11
12
  export default {
12
13
  components: {
@@ -160,9 +161,9 @@ export default {
160
161
 
161
162
  options() {
162
163
  const customRoles = this.customRoles.map(role => ({
163
- label: role.nameDisplay,
164
- description: role.description || role.metadata?.annotations?.[DESCRIPTION] || this.t('projectMembers.projectPermissions.noDescription'),
165
- value: role.id
164
+ label: this.purifyOption(role.nameDisplay),
165
+ description: this.purifyOption(role.description || role.metadata?.annotations?.[DESCRIPTION] || this.t('projectMembers.projectPermissions.noDescription')),
166
+ value: this.purifyOption(role.id),
166
167
  }));
167
168
 
168
169
  return [
@@ -245,6 +246,9 @@ export default {
245
246
  }
246
247
 
247
248
  return [permissionGroup];
249
+ },
250
+ purifyOption(option) {
251
+ return DOMPurify.sanitize(option, { ALLOWED_TAGS: ['span'] });
248
252
  }
249
253
  }
250
254
  };
@@ -301,7 +305,7 @@ export default {
301
305
  />
302
306
  <i
303
307
  v-if="permission.locked"
304
- v-tooltip="permission.tooltip"
308
+ v-clean-tooltip="permission.tooltip"
305
309
  class="icon icon-lock icon-fw"
306
310
  />
307
311
  </div>
@@ -182,7 +182,7 @@ export default {
182
182
  />
183
183
  <div class="resource-availability mr-10">
184
184
  <PercentageBar
185
- v-tooltip="tooltip"
185
+ v-clean-tooltip="tooltip"
186
186
  class="percentage-bar"
187
187
  :value="percentageUsed"
188
188
  :slices="slices"
@@ -137,7 +137,7 @@ export default {
137
137
  <span class="port">
138
138
  <t k="servicePorts.rules.listening.label" />
139
139
  <i
140
- v-tooltip="t('servicesPage.listeningPorts')"
140
+ v-clean-tooltip="t('servicesPage.listeningPorts')"
141
141
  class="icon icon-info flex"
142
142
  />
143
143
  <span class="text-error">*</span>
@@ -151,7 +151,7 @@ export default {
151
151
  <span class="target-port">
152
152
  <t k="servicePorts.rules.target.label" />
153
153
  <i
154
- v-tooltip="t('servicesPage.targetPorts')"
154
+ v-clean-tooltip="t('servicesPage.targetPorts')"
155
155
  class="icon icon-info flex"
156
156
  />
157
157
  <span class="text-error">*</span>
@@ -27,7 +27,27 @@ export default {
27
27
  },
28
28
 
29
29
  data() {
30
- return { rules: [...this.value] };
30
+ const rules = [];
31
+
32
+ // on creation in agent configuration, the backend "eats"
33
+ // the empty "effect" string, which doesn't happen on edit
34
+ // just to make sure we populate it correcty, let's consider
35
+ // no prop "effect" as an empty string which means all
36
+ // https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#toleration-v1-core
37
+ if (this.value.length) {
38
+ this.value.forEach((v) => {
39
+ if (!Object.keys(v).includes('effect')) {
40
+ rules.push({
41
+ ...v,
42
+ effect: ''
43
+ });
44
+ } else {
45
+ rules.push(v);
46
+ }
47
+ });
48
+ }
49
+
50
+ return { rules };
31
51
  },
32
52
 
33
53
  computed: {
@@ -83,7 +103,7 @@ export default {
83
103
  return [
84
104
  {
85
105
  label: this.t('workload.scheduling.tolerations.effectOptions.all'),
86
- value: 'All'
106
+ value: ''
87
107
  },
88
108
  {
89
109
  label: this.t('workload.scheduling.tolerations.effectOptions.noSchedule'),
@@ -115,17 +135,43 @@ export default {
115
135
  },
116
136
 
117
137
  update() {
118
- this.$emit('input', this.rules);
138
+ // let's delete the vKey prop as it's only poluting the data
139
+ const rules = this.rules.map((rule) => {
140
+ const newRule = { ...rule };
141
+
142
+ // prevent vKey from being sent as data
143
+ if (newRule.vKey) {
144
+ delete newRule.vKey;
145
+ }
146
+
147
+ // let's clear the value field if operator is Exists
148
+ if (newRule.operator === 'Exists' && newRule.value) {
149
+ newRule.value = null;
150
+ }
151
+
152
+ // remove effect from payload sent upstream, as it's empty
153
+ // it should be null, but the Select input doesn't seem to like it
154
+ // so we keep it as '' and sanitize it here
155
+ if (newRule.effect === '') {
156
+ delete newRule.effect;
157
+ }
158
+
159
+ return newRule;
160
+ });
161
+
162
+ this.$emit('input', rules);
119
163
  },
120
164
 
121
165
  addToleration() {
122
- this.rules.push({ vKey: random32() });
166
+ this.rules.push({ vKey: random32(), effect: '' });
123
167
  },
124
168
 
125
169
  updateEffect(neu, rule) {
126
170
  if (neu !== 'NoExecute' && rule.tolerationSeconds) {
127
171
  delete rule.tolerationSeconds;
128
172
  }
173
+
174
+ this.update();
129
175
  }
130
176
  }
131
177
 
@@ -146,7 +192,7 @@ export default {
146
192
  <span />
147
193
  </div>
148
194
  <div
149
- v-for="rule in rules"
195
+ v-for="(rule, index) in rules"
150
196
  :key="rule.vKey"
151
197
  class="rule"
152
198
  >
@@ -154,6 +200,9 @@ export default {
154
200
  <LabeledInput
155
201
  v-model="rule.key"
156
202
  :mode="mode"
203
+ :data-testid="`toleration-key-index${ index }`"
204
+ class="height-adjust-input"
205
+ @input="update"
157
206
  />
158
207
  </div>
159
208
  <div class="col">
@@ -162,6 +211,7 @@ export default {
162
211
  v-model="rule.operator"
163
212
  :options="operatorOpts"
164
213
  :mode="mode"
214
+ :data-testid="`toleration-operator-index${ index }`"
165
215
  @input="update"
166
216
  />
167
217
  </div>
@@ -171,6 +221,7 @@ export default {
171
221
  value="n/a"
172
222
  :mode="mode"
173
223
  disabled
224
+ class="height-adjust-input"
174
225
  />
175
226
  </div>
176
227
  </template>
@@ -179,6 +230,9 @@ export default {
179
230
  <LabeledInput
180
231
  v-model="rule.value"
181
232
  :mode="mode"
233
+ :data-testid="`toleration-value-index${ index }`"
234
+ class="height-adjust-input"
235
+ @input="update"
182
236
  />
183
237
  </div>
184
238
  </template>
@@ -187,6 +241,7 @@ export default {
187
241
  v-model="rule.effect"
188
242
  :options="effectOpts"
189
243
  :mode="mode"
244
+ :data-testid="`toleration-effect-index${ index }`"
190
245
  @input="e=>updateEffect(e, rule)"
191
246
  />
192
247
  </div>
@@ -196,6 +251,9 @@ export default {
196
251
  :disabled="rule.effect !== 'NoExecute'"
197
252
  :mode="mode"
198
253
  suffix="Seconds"
254
+ :data-testid="`toleration-seconds-index${ index }`"
255
+ class="height-adjust-input"
256
+ @input="update"
199
257
  />
200
258
  </div>
201
259
  <div class="col remove">
@@ -204,6 +262,7 @@ export default {
204
262
  type="button"
205
263
  class="btn role-link"
206
264
  :disabled="mode==='view'"
265
+ :data-testid="`toleration-remove-index${ index }`"
207
266
  @click="remove(rule)"
208
267
  >
209
268
  <t k="generic.remove" />
@@ -214,6 +273,7 @@ export default {
214
273
  v-if="!isView"
215
274
  type="button"
216
275
  class="btn role-tertiary"
276
+ data-testid="add-toleration-btn"
217
277
  @click="addToleration"
218
278
  >
219
279
  <t k="workload.scheduling.tolerations.addToleration" />
@@ -228,8 +288,8 @@ export default {
228
288
 
229
289
  .rule, .toleration-headers{
230
290
  display: grid;
231
- grid-template-columns: 20% 10% 20% 15% 20% 10%;
232
- grid-gap: $column-gutter;
291
+ grid-template-columns: 20% 10% 20% 15% 20% 15%;
292
+ grid-gap: 10px;
233
293
  align-items: center;
234
294
  }
235
295
 
@@ -247,4 +307,7 @@ export default {
247
307
  .remove BUTTON {
248
308
  padding: 0px;
249
309
  }
310
+ .height-adjust-input {
311
+ min-height: 42px;
312
+ }
250
313
  </style>
@@ -50,6 +50,8 @@ export default {
50
50
  row._showHost = true;
51
51
  }
52
52
 
53
+ row._ipam = '';
54
+
53
55
  return row;
54
56
  });
55
57
 
@@ -387,7 +389,6 @@ export default {
387
389
  :mode="mode"
388
390
  :options="ipamOptions"
389
391
  :label="t('servicesPage.harvester.ipam.label')"
390
- :disabled="mode === 'edit'"
391
392
  @input="queueUpdate"
392
393
  />
393
394
  </div>