@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
@@ -0,0 +1,140 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import PolicyRuleTarget from '@shell/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget';
3
+ import { cleanHtmlDirective } from '@shell/plugins/clean-html-directive';
4
+ import mock from '@shell/edit/networking.k8s.io.networkpolicy/__tests__/utils/mock.json';
5
+ import { PolicyRuleTargetSelectors } from '@shell/edit/networking.k8s.io.networkpolicy/__tests__/utils/selectors';
6
+
7
+ describe.each([
8
+ 'view',
9
+ 'edit',
10
+ ])('component: PolicyRuleTarget', (mode) => {
11
+ const mockExists = jest.fn().mockReturnValue(true);
12
+ const mockT = jest.fn().mockReturnValue('some-string');
13
+
14
+ const wrapper = mount(PolicyRuleTarget, {
15
+ data() {
16
+ return { throttleTime: 0 };
17
+ },
18
+ propsData: {
19
+ namespace: mock.defaultNamespace,
20
+ allNamespaces: mock.allNamespaces,
21
+ allPods: mock.allPods,
22
+ type: 'ingress',
23
+ mode
24
+ },
25
+
26
+ directives: { cleanHtmlDirective },
27
+
28
+ mocks: {
29
+ $store: {
30
+ getters: {
31
+ 'i18n/exists': mockExists,
32
+ 'i18n/t': mockT
33
+ }
34
+ }
35
+ }
36
+ });
37
+
38
+ describe(`${ mode } mode`, () => {
39
+ it('should display ip-block selector rule', async() => {
40
+ const ipBlock = mock.selectors.ipBlock;
41
+
42
+ await wrapper.setProps({ value: { ipBlock } });
43
+
44
+ const selectors = new PolicyRuleTargetSelectors(wrapper);
45
+
46
+ // Check rule type selector
47
+ expect(selectors.ruleType.vm.$data._value.value).toBe('ipBlock');
48
+
49
+ expect(selectors.namespace.element).toBeUndefined();
50
+ expect(selectors.pod.element).toBeUndefined();
51
+ expect(selectors.namespaceAndPod.namespaceRule.element).toBeUndefined();
52
+ expect(selectors.namespaceAndPod.podRule.element).toBeUndefined();
53
+
54
+ expect(selectors.ipBlock.element._value).toStrictEqual(ipBlock.cidr);
55
+ });
56
+
57
+ it('should display namespace selector rule', async() => {
58
+ const namespaceSelector = mock.selectors.namespace;
59
+
60
+ await wrapper.setProps({ value: { namespaceSelector } });
61
+
62
+ const selectors = new PolicyRuleTargetSelectors(wrapper);
63
+
64
+ // Check rule type selector
65
+ expect(selectors.ruleType.vm.$data._value.value).toBe('namespaceSelector');
66
+
67
+ // Check the matching namespaces displayed by the banner
68
+ expect(wrapper.vm.$data.matchingNamespaces.matched).toBe(1);
69
+
70
+ // Check if namespace's labels match
71
+ expect(wrapper.vm.$data.matchingNamespaces.matches).toHaveLength(1);
72
+ expect(wrapper.vm.$data.matchingNamespaces.matches[0].metadata.name).toBe('default');
73
+ expect(wrapper.vm.$data.matchingNamespaces.matches[0].metadata.labels['user']).toBe('alice');
74
+
75
+ expect(selectors.pod.element).toBeUndefined();
76
+ expect(selectors.namespaceAndPod.namespaceRule.element).toBeUndefined();
77
+ expect(selectors.namespaceAndPod.podRule.element).toBeUndefined();
78
+
79
+ expect(selectors.namespace.element).toBeDefined();
80
+ });
81
+
82
+ it('should display pod selector rule', async() => {
83
+ const podSelector = mock.selectors.pod;
84
+
85
+ await wrapper.setProps({ value: { podSelector } });
86
+
87
+ const selectors = new PolicyRuleTargetSelectors(wrapper);
88
+
89
+ // Check rule type selector
90
+ expect(selectors.ruleType.vm.$data._value.value).toBe('podSelector');
91
+
92
+ // Check if namespace's labels match
93
+ expect(wrapper.vm.$data.matchingPods.matched).toBe(1);
94
+ expect(wrapper.vm.$data.matchingPods.matches).toHaveLength(1);
95
+
96
+ expect(wrapper.vm.$data.matchingPods.matches[0].metadata.name).toBe('test-pod');
97
+ expect(wrapper.vm.$data.matchingPods.matches[0].metadata.labels['foo']).toBe('bar');
98
+
99
+ expect(selectors.namespace.element).toBeUndefined();
100
+ expect(selectors.namespaceAndPod.namespaceRule.element).toBeUndefined();
101
+ expect(selectors.namespaceAndPod.podRule.element).toBeUndefined();
102
+
103
+ expect(selectors.pod.element).toBeDefined();
104
+ });
105
+
106
+ it('should display namespace/pod selector rule', async() => {
107
+ const namespaceSelector = mock.selectors.namespaceAndPod.namespace;
108
+ const podSelector = mock.selectors.namespaceAndPod.pod;
109
+
110
+ await wrapper.setProps({
111
+ value: {
112
+ namespaceSelector,
113
+ podSelector,
114
+ }
115
+ });
116
+
117
+ const selectors = new PolicyRuleTargetSelectors(wrapper);
118
+
119
+ // Check rule type selector
120
+ expect(selectors.ruleType.vm.$data._value.value).toBe('namespaceAndPodSelector');
121
+
122
+ // Check the matching pods displayed by the banner
123
+ expect(wrapper.vm.$data.matchingPods.matched).toBe(1);
124
+
125
+ // Check if namespace's labels match
126
+ expect(wrapper.vm.$data.matchingNamespaces.matches).toHaveLength(1);
127
+ expect(wrapper.vm.$data.matchingNamespaces.matches[0].metadata.name).toBe('default');
128
+ expect(wrapper.vm.$data.matchingNamespaces.matches[0].metadata.labels['user']).toBe('alice');
129
+
130
+ expect(wrapper.vm.$data.matchingPods.matches[0].metadata.name).toBe('test-pod');
131
+ expect(wrapper.vm.$data.matchingPods.matches[0].metadata.labels['foo']).toBe('bar');
132
+
133
+ expect(selectors.namespace.element).toBeUndefined();
134
+ expect(selectors.pod.element).toBeUndefined();
135
+
136
+ expect(selectors.namespaceAndPod.namespaceRule.element).toBeDefined();
137
+ expect(selectors.namespaceAndPod.podRule.element).toBeDefined();
138
+ });
139
+ });
140
+ });
@@ -0,0 +1,158 @@
1
+ {
2
+ "defaultNamespace": "default",
3
+ "selectors": {
4
+ "ipBlock": {
5
+ "cidr": "24.06.19.89/0"
6
+ },
7
+ "namespace": {
8
+ "matchLabels": {
9
+ "user": "alice"
10
+ },
11
+ "matchExpressions": [
12
+ {
13
+ "key": "user",
14
+ "operator": "In",
15
+ "values": "alice"
16
+ }
17
+ ]
18
+ },
19
+ "pod": {
20
+ "matchLabels": {
21
+ "foo": "bar"
22
+ },
23
+ "matchExpressions": [
24
+ {
25
+ "key": "foo",
26
+ "operator": "In",
27
+ "values": "bar"
28
+ }
29
+ ]
30
+ },
31
+ "namespaceAndPod": {
32
+ "namespace": {
33
+ "matchLabels": {
34
+ "user": "alice"
35
+ },
36
+ "matchExpressions": [
37
+ {
38
+ "key": "user",
39
+ "operator": "In",
40
+ "values": "alice"
41
+ }
42
+ ]
43
+ },
44
+ "pod": {
45
+ "matchLabels": {
46
+ "foo": "bar"
47
+ },
48
+ "matchExpressions": [
49
+ {
50
+ "key": "foo",
51
+ "operator": "In",
52
+ "values": "bar"
53
+ }
54
+ ]
55
+ }
56
+ }
57
+ },
58
+ "allNamespaces": [
59
+ {
60
+ "id": "default",
61
+ "type": "namespace",
62
+ "kind": "Namespace",
63
+ "metadata": {
64
+ "annotations": {
65
+ "user": "alice"
66
+ },
67
+ "name": "default",
68
+ "creationTimestamp": "2023-01-31T10:24:03Z",
69
+ "fields": ["default", "Active", "1d"],
70
+ "labels": {
71
+ "user": "alice"
72
+ },
73
+ "relationships": null,
74
+ "resourceVersion": "1",
75
+ "state": {
76
+ "error": false,
77
+ "message": "",
78
+ "name": "active",
79
+ "transitioning": false
80
+ }
81
+ },
82
+ "spec": { "finalizers": ["kubernetes"] },
83
+ "status": { "phase": "Active" }
84
+ },
85
+ {
86
+ "id": "not-default",
87
+ "type": "namespace",
88
+ "kind": "Namespace",
89
+ "metadata": {
90
+ "annotations": {
91
+ "user": "nicole"
92
+ },
93
+ "name": "not-default",
94
+ "creationTimestamp": "2023-01-31T10:24:03Z",
95
+ "fields": ["default", "Active", "1d"],
96
+ "labels": {
97
+ "user": "nicole"
98
+ },
99
+ "relationships": null,
100
+ "resourceVersion": "1",
101
+ "state": {
102
+ "error": false,
103
+ "message": "",
104
+ "name": "active",
105
+ "transitioning": false
106
+ }
107
+ },
108
+ "spec": { "finalizers": ["kubernetes"] },
109
+ "status": { "phase": "Active" }
110
+ }
111
+ ],
112
+ "allPods": [
113
+ {
114
+ "id": "default/test-pod",
115
+ "type": "pod",
116
+ "apiVersion": "v1",
117
+ "kind": "Pod",
118
+ "metadata": {
119
+ "creationTimestamp": "2023-02-27T16:10:55Z",
120
+ "generateName": "test-pod-",
121
+ "labels": {
122
+ "foo": "bar"
123
+ },
124
+ "name": "test-pod",
125
+ "namespace": "default",
126
+ "resourceVersion": "1",
127
+ "state": {
128
+ "error": false,
129
+ "message": "",
130
+ "name": "completed",
131
+ "transitioning": false
132
+ }
133
+ }
134
+ },
135
+ {
136
+ "id": "default/test-pod-2",
137
+ "type": "pod",
138
+ "apiVersion": "v1",
139
+ "kind": "Pod",
140
+ "metadata": {
141
+ "creationTimestamp": "2023-02-27T16:10:55Z",
142
+ "generateName": "test-pod-2-",
143
+ "labels": {
144
+ "foo": "bar"
145
+ },
146
+ "name": "test-pod-2",
147
+ "namespace": "not-default",
148
+ "resourceVersion": "1",
149
+ "state": {
150
+ "error": false,
151
+ "message": "",
152
+ "name": "completed",
153
+ "transitioning": false
154
+ }
155
+ }
156
+ }
157
+ ]
158
+ }
@@ -0,0 +1,45 @@
1
+ export class PolicyRuleTargetSelectors {
2
+ private wrapper;
3
+
4
+ constructor(wrapper: any) {
5
+ this.wrapper = wrapper;
6
+ }
7
+
8
+ get ipBlock() {
9
+ return this.wrapper.find('[data-testid=labeled-input-ip-block-selector]');
10
+ }
11
+
12
+ /**
13
+ * Namespace selector element; matches policies in OR condition, in conjunction with the other rules
14
+ */
15
+ get namespace() {
16
+ return this.wrapper.find(
17
+ '[data-testid=match-expression-namespace-selector]'
18
+ );
19
+ }
20
+
21
+ /**
22
+ * Pod selector element; matches policies in OR condition, in conjunction with the other rules
23
+ */
24
+ get pod() {
25
+ return this.wrapper.find('[data-testid=match-expression-pod-selector]');
26
+ }
27
+
28
+ /**
29
+ * Namespace and pod selector elements, matching policies in AND condition, within the same rule
30
+ */
31
+ get namespaceAndPod() {
32
+ return {
33
+ namespaceRule: this.wrapper.find(
34
+ '[data-testid=match-expression-namespace-and-pod-selector-ns-rule]'
35
+ ),
36
+ podRule: this.wrapper.find(
37
+ '[data-testid=match-expression-namespace-and-pod-selector-pod-rule]'
38
+ ),
39
+ };
40
+ }
41
+
42
+ get ruleType() {
43
+ return this.wrapper.find('[data-testid=labeled-select-type-selector]');
44
+ }
45
+ }
@@ -241,7 +241,7 @@ export default {
241
241
  <h2>
242
242
  {{ t('networkpolicy.selectors.label') }}
243
243
  <i
244
- v-tooltip="t('networkpolicy.selectors.hint')"
244
+ v-clean-tooltip="t('networkpolicy.selectors.hint')"
245
245
  class="icon icon-info"
246
246
  />
247
247
  </h2>
@@ -0,0 +1,326 @@
1
+ <script>
2
+ import { Banner } from '@components/Banner';
3
+ import GroupPanel from '@shell/components/GroupPanel';
4
+ import PodAffinity from '@shell/components/form/PodAffinity';
5
+ import NodeAffinity from '@shell/components/form/NodeAffinity';
6
+ import ContainerResourceLimit from '@shell/components/ContainerResourceLimit';
7
+ import Tolerations from '@shell/components/form/Tolerations';
8
+ import { cleanUp } from '@shell/utils/object';
9
+ import { fetchSetting } from '@shell/utils/settings';
10
+ import { RadioGroup } from '@components/Form/Radio';
11
+
12
+ export function cleanAgentConfiguration(model, key) {
13
+ if (!model || !model[key]) {
14
+ return;
15
+ }
16
+
17
+ const v = model[key];
18
+
19
+ if (Array.isArray(v) && v.length === 0) {
20
+ delete model[key];
21
+ } else if (v && typeof v === 'object') {
22
+ Object.keys(v).forEach((k) => {
23
+ // delete these auxiliary props used in podAffinity and nodeAffinity that shouldn't be sent to the server
24
+ if (k === '_namespaceOption' || k === '_namespaces' || k === '_anti' || k === '_id') {
25
+ delete v[k];
26
+ }
27
+
28
+ // prevent cleanup of "namespaceSelector" when an empty object because it represents all namespaces in pod/node affinity
29
+ // https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#podaffinityterm-v1-core
30
+ if (k !== 'namespaceSelector') {
31
+ cleanAgentConfiguration(v, k);
32
+ }
33
+ });
34
+
35
+ if (Object.keys(v).length === 0) {
36
+ delete model[key];
37
+ }
38
+ }
39
+ }
40
+
41
+ // Affinity radio button choices
42
+ const DEFAULT = 'default';
43
+ const CUSTOM = 'custom';
44
+
45
+ // This is the form for Agent Configuration
46
+ // Used for both Cluster Agent and Fleet Agent configuration
47
+ export default {
48
+ components: {
49
+ Banner,
50
+ ContainerResourceLimit,
51
+ GroupPanel,
52
+ PodAffinity,
53
+ NodeAffinity,
54
+ RadioGroup,
55
+ Tolerations,
56
+ },
57
+ props: {
58
+ value: {
59
+ type: Object,
60
+ default: () => {},
61
+ },
62
+
63
+ mode: {
64
+ type: String,
65
+ required: true,
66
+ },
67
+
68
+ type: {
69
+ type: String,
70
+ required: true,
71
+ }
72
+ },
73
+
74
+ async fetch() {
75
+ // Default affinity
76
+ const settingId = `${ this.type }-agent-default-affinity`;
77
+ const setting = await fetchSetting(this.$store, settingId);
78
+
79
+ if (setting) {
80
+ try {
81
+ const parsed = JSON.parse(setting.value || setting.default);
82
+
83
+ this.defaultAffinity = parsed || {};
84
+ } catch (e) {
85
+ console.error('Could not parse agent default setting', e); // eslint-disable-line no-console
86
+ this.defaultAffinity = {};
87
+ }
88
+ }
89
+ },
90
+
91
+ data() {
92
+ const nodeAffinity = this.value?.overrideAffinity?.nodeAffinity;
93
+ const podAffinity = this.value?.overrideAffinity?.podAffinity;
94
+ const podAntiAffinity = this.value?.overrideAffinity?.podAntiAffinity;
95
+
96
+ let hasAffinityPopulated = false;
97
+
98
+ if ((nodeAffinity && Object.keys(nodeAffinity).length) ||
99
+ (podAffinity && Object.keys(podAffinity).length) ||
100
+ (podAntiAffinity && Object.keys(podAntiAffinity).length)) {
101
+ hasAffinityPopulated = true;
102
+ }
103
+
104
+ return {
105
+ defaultAffinity: {},
106
+ affinitySetting: hasAffinityPopulated ? CUSTOM : DEFAULT,
107
+ nodeAffinity: {}
108
+ };
109
+ },
110
+
111
+ created() {
112
+ this.ensureValue();
113
+ },
114
+
115
+ computed: {
116
+ flatResources: {
117
+ get() {
118
+ const { limits = {}, requests = {} } = this.value.overrideResourceRequirements || {};
119
+ const {
120
+ cpu: limitsCpu,
121
+ memory: limitsMemory,
122
+ } = limits;
123
+ const { cpu: requestsCpu, memory: requestsMemory } = requests;
124
+
125
+ return {
126
+ limitsCpu,
127
+ limitsMemory,
128
+ requestsCpu,
129
+ requestsMemory,
130
+ };
131
+ },
132
+ set(neu) {
133
+ const {
134
+ limitsCpu,
135
+ limitsMemory,
136
+ requestsCpu,
137
+ requestsMemory,
138
+ } = neu;
139
+
140
+ const existing = this.value?.overrideResourceRequirements || {};
141
+
142
+ delete existing.requests;
143
+ delete existing.limits;
144
+
145
+ const out = {
146
+ ...existing,
147
+ requests: {
148
+ cpu: requestsCpu,
149
+ memory: requestsMemory,
150
+ },
151
+ limits: {
152
+ cpu: limitsCpu,
153
+ memory: limitsMemory,
154
+ },
155
+ };
156
+
157
+ this.$set(this.value, 'overrideResourceRequirements', cleanUp(out));
158
+ },
159
+ },
160
+
161
+ affinityOptions() {
162
+ return [{
163
+ label: this.t('cluster.agentConfig.affinity.default'),
164
+ value: DEFAULT,
165
+ }, {
166
+ label: this.t('cluster.agentConfig.affinity.custom'),
167
+ value: CUSTOM,
168
+ }];
169
+ },
170
+
171
+ canEditAffinity() {
172
+ return this.affinitySetting === CUSTOM;
173
+ }
174
+ },
175
+
176
+ watch: {
177
+ value() {
178
+ this.ensureValue();
179
+ }
180
+ },
181
+
182
+ methods: {
183
+ ensureValue() {
184
+ // Ensure we have the model structure needed for the form controls
185
+ if (this.value) {
186
+ this.value.overrideAffinity = this.value.overrideAffinity || {};
187
+ this.value.appendTolerations = this.value.appendTolerations || [];
188
+ this.value.overrideResourceRequirements = this.value.overrideResourceRequirements || {};
189
+
190
+ this.nodeAffinity = this.value?.overrideAffinity?.nodeAffinity || {};
191
+ }
192
+ },
193
+
194
+ affinitySettingChange() {
195
+ if (this.affinitySetting === CUSTOM) {
196
+ const parsedDefaultAffinites = JSON.parse(JSON.stringify(this.defaultAffinity));
197
+
198
+ // Copy the default so that the user can edit it
199
+ // this will cover the pod affinities
200
+ this.$set(this.value, 'overrideAffinity', parsedDefaultAffinites);
201
+
202
+ // in order not to break the node affinity component, let's go for a slightly different way of handling the logic here
203
+ if (parsedDefaultAffinites.nodeAffinity) {
204
+ this.nodeAffinity = parsedDefaultAffinites.nodeAffinity;
205
+ }
206
+ } else {
207
+ this.$set(this.value, 'overrideAffinity', {});
208
+ }
209
+ },
210
+ updateNodeAffinity(val) {
211
+ this.$set(this.value.overrideAffinity, 'nodeAffinity', val);
212
+ }
213
+ }
214
+ };
215
+ </script>
216
+
217
+ <template>
218
+ <div v-if="value && Object.keys(value).length">
219
+ <Banner
220
+ :closable="false"
221
+ color="info"
222
+ label-key="cluster.agentConfig.banners.advanced"
223
+ />
224
+
225
+ <GroupPanel
226
+ label-key="cluster.agentConfig.groups.podRequestsAndLimits"
227
+ class="mt-20"
228
+ >
229
+ <Banner
230
+ :closable="false"
231
+ color="info"
232
+ label-key="cluster.agentConfig.banners.limits"
233
+ />
234
+ <ContainerResourceLimit
235
+ v-model="flatResources"
236
+ :mode="mode"
237
+ :show-tip="false"
238
+ :handle-gpu-limit="false"
239
+ class="mt-10"
240
+ />
241
+ </GroupPanel>
242
+
243
+ <GroupPanel
244
+ label-key="cluster.agentConfig.groups.podTolerations"
245
+ class="mt-20"
246
+ >
247
+ <Banner
248
+ :closable="false"
249
+ color="info"
250
+ label-key="cluster.agentConfig.banners.tolerations"
251
+ />
252
+ <Tolerations
253
+ v-model="value.appendTolerations"
254
+ :mode="mode"
255
+ class="mt-10"
256
+ />
257
+ </GroupPanel>
258
+
259
+ <GroupPanel
260
+ label-key="cluster.agentConfig.groups.podAffinity"
261
+ class="mt-20"
262
+ >
263
+ <RadioGroup
264
+ v-model="affinitySetting"
265
+ name="affinity-override"
266
+ :mode="mode"
267
+ :options="affinityOptions"
268
+ class="mt-10"
269
+ data-testid="affinity-options"
270
+ @input="affinitySettingChange"
271
+ />
272
+
273
+ <Banner
274
+ v-if="canEditAffinity"
275
+ :closable="false"
276
+ color="warning"
277
+ >
278
+ <p v-clean-html="t('cluster.agentConfig.banners.windowsCompatibility', {}, true)" />
279
+ </Banner>
280
+
281
+ <h4 v-if="canEditAffinity">
282
+ {{ t('cluster.agentConfig.subGroups.podAffinityAnti') }}
283
+ </h4>
284
+
285
+ <PodAffinity
286
+ v-if="canEditAffinity"
287
+ v-model="value"
288
+ field="overrideAffinity"
289
+ :mode="mode"
290
+ class="mt-0 mb-20"
291
+ :all-namespaces-option-available="true"
292
+ :force-input-namespace-selection="true"
293
+ :remove-labeled-input-namespace-label="true"
294
+ data-testid="pod-affinity"
295
+ />
296
+
297
+ <div
298
+ v-if="canEditAffinity"
299
+ class="separator"
300
+ />
301
+ <h4
302
+ v-if="canEditAffinity"
303
+ class="mt-20"
304
+ >
305
+ {{ t('cluster.agentConfig.subGroups.nodeAffinity') }}
306
+ </h4>
307
+
308
+ <NodeAffinity
309
+ v-if="canEditAffinity"
310
+ v-model="nodeAffinity"
311
+ :matching-selector-display="true"
312
+ :mode="mode"
313
+ class="mt-0"
314
+ data-testid="node-affinity"
315
+ @input="updateNodeAffinity"
316
+ />
317
+ </GroupPanel>
318
+ </div>
319
+ </template>
320
+
321
+ <style lang="scss" scoped>
322
+ .separator {
323
+ width: 100%;
324
+ border-top: 1px solid var(--border);
325
+ }
326
+ </style>
@@ -268,7 +268,7 @@ export default {
268
268
  <h3>
269
269
  {{ t('cluster.machinePool.autoReplace.label') }}
270
270
  <i
271
- v-tooltip="t('cluster.machinePool.autoReplace.toolTip')"
271
+ v-clean-tooltip="t('cluster.machinePool.autoReplace.toolTip')"
272
272
  class="icon icon-info icon-lg"
273
273
  />
274
274
  </h3>
@@ -119,7 +119,7 @@ export default {
119
119
  <h3>
120
120
  {{ t('registryConfig.header') }}
121
121
  <i
122
- v-tooltip="t('registryConfig.toolTip')"
122
+ v-clean-tooltip="t('registryConfig.toolTip')"
123
123
  class="icon icon-info"
124
124
  />
125
125
  </h3>