@rancher/shell 3.0.9-rc.2 → 3.0.9-rc.4
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 +24 -2
- package/assets/translations/zh-hans.yaml +13 -0
- package/components/ActionMenu.vue +7 -8
- package/components/ActionMenuShell.vue +19 -20
- package/components/Resource/Detail/Card/Scaler.vue +10 -2
- package/components/Resource/Detail/Card/StatusCard/index.vue +4 -1
- package/components/ResourceTable.vue +1 -1
- package/components/Tabbed/Tab.vue +4 -0
- package/components/Tabbed/index.vue +11 -3
- package/components/__tests__/ProjectRow.test.ts +102 -15
- package/components/form/ResourceQuota/Project.vue +59 -8
- package/components/form/ResourceQuota/ProjectRow.vue +116 -21
- package/components/form/ResourceQuota/shared.js +42 -18
- package/components/formatter/KubeconfigClusters.vue +74 -0
- package/components/formatter/LinkName.vue +3 -2
- package/components/formatter/__tests__/KubeconfigClusters.test.ts +125 -0
- package/config/product/explorer.js +1 -1
- package/config/product/manager.js +29 -2
- package/config/router/routes.js +4 -1
- package/config/table-headers.js +9 -7
- package/config/types.js +4 -1
- package/detail/management.cattle.io.oidcclient.vue +15 -4
- package/edit/__tests__/management.cattle.io.project.test.js +137 -0
- package/edit/management.cattle.io.project.vue +36 -6
- package/edit/monitoring.coreos.com.alertmanagerconfig/index.vue +16 -3
- package/edit/provisioning.cattle.io.cluster/defaults.ts +1 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +2 -1
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +57 -2
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/__tests__/S3Config.test.ts +109 -0
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +1 -0
- package/initialize/install-plugins.js +0 -2
- package/list/ext.cattle.io.kubeconfig.vue +118 -0
- package/mixins/__tests__/chart.test.ts +147 -0
- package/mixins/chart.js +10 -8
- package/models/__tests__/ext.cattle.io.kubeconfig.test.ts +364 -0
- package/models/__tests__/secret.test.ts +55 -0
- package/models/ext.cattle.io.kubeconfig.ts +97 -0
- package/models/management.cattle.io.cluster.js +22 -30
- package/models/provisioning.cattle.io.cluster.js +2 -2
- package/models/secret.js +1 -1
- package/package.json +2 -2
- package/pages/__tests__/diagnostic.test.ts +71 -0
- package/pages/about.vue +3 -2
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +38 -14
- package/pages/c/_cluster/apps/charts/index.vue +1 -0
- package/pages/c/_cluster/explorer/tools/index.vue +23 -5
- package/pages/c/_cluster/monitoring/alertmanagerconfig/_alertmanagerconfigid/receiver.vue +18 -5
- package/pages/c/_cluster/uiplugins/index.vue +40 -8
- package/pages/diagnostic.vue +17 -3
- package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +1 -0
- package/rancher-components/RcItemCard/RcItemCard.test.ts +20 -8
- package/rancher-components/RcItemCard/RcItemCard.vue +38 -31
- package/store/__tests__/auth.test.ts +21 -5
- package/store/auth.js +6 -3
- package/types/shell/index.d.ts +177 -157
- package/utils/__tests__/chart.test.ts +96 -0
- package/utils/__tests__/version.test.ts +1 -19
- package/utils/chart.js +64 -0
- package/utils/version.js +5 -17
package/config/table-headers.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CATTLE_PUBLIC_ENDPOINTS, UI_PROJECT_SECRET_COPY } from '@shell/config/labels-annotations';
|
|
2
|
-
import { NODE as NODE_TYPE } from '@shell/config/types';
|
|
2
|
+
import { NODE as NODE_TYPE, NAMESPACE as NAMESPACE_TYPE } from '@shell/config/types';
|
|
3
3
|
import { COLUMN_BREAKPOINTS } from '@shell/types/store/type-map';
|
|
4
4
|
|
|
5
5
|
// Note: 'id' is always the last sort, so you don't have to specify it here.
|
|
@@ -158,12 +158,14 @@ export const NAME_UNLINKED = {
|
|
|
158
158
|
};
|
|
159
159
|
|
|
160
160
|
export const NAMESPACE = {
|
|
161
|
-
name:
|
|
162
|
-
labelKey:
|
|
163
|
-
value:
|
|
164
|
-
getValue:
|
|
165
|
-
sort:
|
|
166
|
-
dashIfEmpty:
|
|
161
|
+
name: 'namespace',
|
|
162
|
+
labelKey: 'tableHeaders.namespace',
|
|
163
|
+
value: 'namespace',
|
|
164
|
+
getValue: (row) => row.namespace,
|
|
165
|
+
sort: 'namespace',
|
|
166
|
+
dashIfEmpty: true,
|
|
167
|
+
formatter: 'LinkName',
|
|
168
|
+
formatterOpts: { type: NAMESPACE_TYPE },
|
|
167
169
|
};
|
|
168
170
|
|
|
169
171
|
export const NODE = {
|
package/config/types.js
CHANGED
|
@@ -262,7 +262,10 @@ export const BRAND = {
|
|
|
262
262
|
RGS: 'rgs',
|
|
263
263
|
};
|
|
264
264
|
|
|
265
|
-
export const EXT = {
|
|
265
|
+
export const EXT = {
|
|
266
|
+
USER_ACTIVITY: 'ext.cattle.io.useractivity',
|
|
267
|
+
KUBECONFIG: 'ext.cattle.io.kubeconfig',
|
|
268
|
+
};
|
|
266
269
|
|
|
267
270
|
export const CAPI = {
|
|
268
271
|
CAPI_CLUSTER: 'cluster.x-k8s.io.cluster',
|
|
@@ -7,7 +7,7 @@ import { defineComponent } from 'vue';
|
|
|
7
7
|
import CopyToClipboardText from '@shell/components/CopyToClipboardText.vue';
|
|
8
8
|
import DateComponent from '@shell/components/formatter/Date.vue';
|
|
9
9
|
import { RcItemCard } from '@components/RcItemCard';
|
|
10
|
-
import ActionMenu from '@shell/components/ActionMenuShell.vue';
|
|
10
|
+
import ActionMenu, { type ActionMenuSelection } from '@shell/components/ActionMenuShell.vue';
|
|
11
11
|
import { Banner } from '@components/Banner';
|
|
12
12
|
|
|
13
13
|
type SecretActionType = 'create-secret' | 'regen-secret' | 'remove-secret'
|
|
@@ -32,7 +32,6 @@ interface SecretManageData {
|
|
|
32
32
|
const OIDC_SECRETS_NAMESPACE = 'cattle-oidc-client-secrets';
|
|
33
33
|
|
|
34
34
|
export default defineComponent({
|
|
35
|
-
emits: ['regenerateSecret', 'removeSecret'],
|
|
36
35
|
|
|
37
36
|
components: {
|
|
38
37
|
CopyToClipboardText,
|
|
@@ -100,6 +99,19 @@ export default defineComponent({
|
|
|
100
99
|
},
|
|
101
100
|
|
|
102
101
|
methods: {
|
|
102
|
+
handleSecretAction(secret: SecretManageData, payload?: ActionMenuSelection) {
|
|
103
|
+
switch (payload?.action) {
|
|
104
|
+
case 'regenerateSecret':
|
|
105
|
+
this.promptSecretsModal(OIDC_CLIENT_SECRET_ACTION.REGEN, secret);
|
|
106
|
+
break;
|
|
107
|
+
case 'removeSecret':
|
|
108
|
+
this.promptSecretsModal(OIDC_CLIENT_SECRET_ACTION.REMOVE, secret);
|
|
109
|
+
break;
|
|
110
|
+
default:
|
|
111
|
+
console.warn(`Unknown secret action: ${ payload?.action }`); // eslint-disable-line no-console
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
|
|
103
115
|
promptSecretsModal(actionType: SecretActionType, secret: SecretManageData) {
|
|
104
116
|
this.errors = [];
|
|
105
117
|
|
|
@@ -320,8 +332,7 @@ export default defineComponent({
|
|
|
320
332
|
:data-testid="`oidc-client-secret-${i}-action-menu`"
|
|
321
333
|
:resource="secret"
|
|
322
334
|
:custom-actions="cardActions"
|
|
323
|
-
@
|
|
324
|
-
@removeSecret="promptSecretsModal(OIDC_CLIENT_SECRET_ACTION.REMOVE, secret)"
|
|
335
|
+
@action-invoked="(payload) => handleSecretAction(secret, payload)"
|
|
325
336
|
/>
|
|
326
337
|
</template>
|
|
327
338
|
</rc-item-card>
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
import ManagementCattleIoProject from '@shell/edit/management.cattle.io.project.vue';
|
|
3
|
+
import { _EDIT } from '@shell/config/query-params';
|
|
4
|
+
|
|
5
|
+
const mockStore = {
|
|
6
|
+
getters: {
|
|
7
|
+
'management/schemaFor': () => ({}),
|
|
8
|
+
currentCluster: () => ({ spec: { kubernetesVersion: 'v1.23.0' } }),
|
|
9
|
+
isStandaloneHarvester: () => false,
|
|
10
|
+
currentProduct: () => ({ inStore: 'rancher' }),
|
|
11
|
+
'auth/principalId': () => 'local://user',
|
|
12
|
+
'i18n/t': (key) => key,
|
|
13
|
+
},
|
|
14
|
+
dispatch: jest.fn(),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const defaultMountOptions = {
|
|
18
|
+
props: { mode: _EDIT },
|
|
19
|
+
global: { mocks: { $store: mockStore } }
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
describe('component: ManagementCattleIoProject', () => {
|
|
23
|
+
it('should remove a standard resource quota correctly', async() => {
|
|
24
|
+
const value = {
|
|
25
|
+
spec: {
|
|
26
|
+
resourceQuota: { limit: { limitsCpu: '100m', limitsMemory: '1Gi' } },
|
|
27
|
+
namespaceDefaultResourceQuota: { limit: { limitsCpu: '50m', limitsMemory: '500Mi' } }
|
|
28
|
+
},
|
|
29
|
+
metadata: { namespace: 'test-ns', annotations: {} },
|
|
30
|
+
save: jest.fn(),
|
|
31
|
+
listLocation: { name: 'list' }
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const wrapper = shallowMount(
|
|
35
|
+
ManagementCattleIoProject,
|
|
36
|
+
{
|
|
37
|
+
...defaultMountOptions,
|
|
38
|
+
props: {
|
|
39
|
+
...defaultMountOptions.props,
|
|
40
|
+
value,
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
wrapper.vm.removeQuota('limitsCpu');
|
|
46
|
+
|
|
47
|
+
expect(wrapper.vm.value.spec.resourceQuota.limit.limitsCpu).toBeUndefined();
|
|
48
|
+
expect(wrapper.vm.value.spec.namespaceDefaultResourceQuota.limit.limitsCpu).toBeUndefined();
|
|
49
|
+
expect(wrapper.vm.value.spec.resourceQuota.limit.limitsMemory).toBe('1Gi'); // Ensure other quotas are not affected
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should remove a custom resource quota with single period in name correctly', async() => {
|
|
53
|
+
const value = {
|
|
54
|
+
spec: {
|
|
55
|
+
resourceQuota: { limit: { extended: { test2: '1' } } },
|
|
56
|
+
namespaceDefaultResourceQuota: { limit: { extended: { test2: '2' } } }
|
|
57
|
+
},
|
|
58
|
+
metadata: { namespace: 'test-ns', annotations: {} },
|
|
59
|
+
save: jest.fn(),
|
|
60
|
+
listLocation: { name: 'list' }
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const wrapper = shallowMount(
|
|
64
|
+
ManagementCattleIoProject,
|
|
65
|
+
{
|
|
66
|
+
...defaultMountOptions,
|
|
67
|
+
props: {
|
|
68
|
+
...defaultMountOptions.props,
|
|
69
|
+
value,
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
wrapper.vm.removeQuota(`extended.test2`);
|
|
75
|
+
|
|
76
|
+
expect(wrapper.vm.value.spec.resourceQuota.limit.extended).toBeUndefined();
|
|
77
|
+
expect(wrapper.vm.value.spec.namespaceDefaultResourceQuota.limit.extended).toBeUndefined();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should remove a custom resource quota with multiple periods in name correctly', async() => {
|
|
81
|
+
const value = {
|
|
82
|
+
spec: {
|
|
83
|
+
resourceQuota: { limit: { extended: { 'requests.nvidia.com/gpu': '4' } } },
|
|
84
|
+
namespaceDefaultResourceQuota: { limit: { extended: { 'requests.nvidia.com/gpu': '2' } } }
|
|
85
|
+
},
|
|
86
|
+
metadata: { namespace: 'test-ns', annotations: {} },
|
|
87
|
+
save: jest.fn(),
|
|
88
|
+
listLocation: { name: 'list' }
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const wrapper = shallowMount(
|
|
92
|
+
ManagementCattleIoProject,
|
|
93
|
+
{
|
|
94
|
+
...defaultMountOptions,
|
|
95
|
+
props: {
|
|
96
|
+
...defaultMountOptions.props,
|
|
97
|
+
value,
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
wrapper.vm.removeQuota(`extended.requests.nvidia.com/gpu`);
|
|
103
|
+
|
|
104
|
+
expect(wrapper.vm.value.spec.resourceQuota.limit.extended).toBeUndefined();
|
|
105
|
+
expect(wrapper.vm.value.spec.namespaceDefaultResourceQuota.limit.extended).toBeUndefined();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should remove one of multiple custom resource quotas correctly', async() => {
|
|
109
|
+
const value = {
|
|
110
|
+
spec: {
|
|
111
|
+
resourceQuota: { limit: { extended: { test1: '1', test2: '2' } } },
|
|
112
|
+
namespaceDefaultResourceQuota: { limit: { extended: { test1: '3', test2: '4' } } }
|
|
113
|
+
},
|
|
114
|
+
metadata: { namespace: 'test-ns', annotations: {} },
|
|
115
|
+
save: jest.fn(),
|
|
116
|
+
listLocation: { name: 'list' }
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const wrapper = shallowMount(
|
|
120
|
+
ManagementCattleIoProject,
|
|
121
|
+
{
|
|
122
|
+
...defaultMountOptions,
|
|
123
|
+
props: {
|
|
124
|
+
...defaultMountOptions.props,
|
|
125
|
+
value,
|
|
126
|
+
},
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
wrapper.vm.removeQuota('extended.test1');
|
|
131
|
+
|
|
132
|
+
expect(wrapper.vm.value.spec.resourceQuota.limit.extended.test1).toBeUndefined();
|
|
133
|
+
expect(wrapper.vm.value.spec.resourceQuota.limit.extended.test2).toBe('2');
|
|
134
|
+
expect(wrapper.vm.value.spec.namespaceDefaultResourceQuota.limit.extended.test1).toBeUndefined();
|
|
135
|
+
expect(wrapper.vm.value.spec.namespaceDefaultResourceQuota.limit.extended.test2).toBe('4');
|
|
136
|
+
});
|
|
137
|
+
});
|
|
@@ -6,7 +6,7 @@ import FormValidation from '@shell/mixins/form-validation';
|
|
|
6
6
|
import CruResource from '@shell/components/CruResource';
|
|
7
7
|
import Labels from '@shell/components/form/Labels';
|
|
8
8
|
import ResourceQuota from '@shell/components/form/ResourceQuota/Project';
|
|
9
|
-
import { HARVESTER_TYPES, RANCHER_TYPES } from '@shell/components/form/ResourceQuota/shared';
|
|
9
|
+
import { HARVESTER_TYPES, RANCHER_TYPES, TYPES } from '@shell/components/form/ResourceQuota/shared';
|
|
10
10
|
import Tab from '@shell/components/Tabbed/Tab';
|
|
11
11
|
import Tabbed from '@shell/components/Tabbed';
|
|
12
12
|
import NameNsDescription from '@shell/components/form/NameNsDescription';
|
|
@@ -49,6 +49,7 @@ export default {
|
|
|
49
49
|
HARVESTER_TYPES,
|
|
50
50
|
RANCHER_TYPES,
|
|
51
51
|
fvFormRuleSets: [{ path: 'spec.displayName', rules: ['required'] }],
|
|
52
|
+
resourceQuotaKey: 0,
|
|
52
53
|
};
|
|
53
54
|
},
|
|
54
55
|
computed: {
|
|
@@ -157,14 +158,42 @@ export default {
|
|
|
157
158
|
},
|
|
158
159
|
|
|
159
160
|
removeQuota(key) {
|
|
161
|
+
const isExtended = key.startsWith(`${ TYPES.EXTENDED }.`);
|
|
162
|
+
let resourceKey = key;
|
|
163
|
+
|
|
164
|
+
if (isExtended) {
|
|
165
|
+
resourceKey = key.substring(`${ TYPES.EXTENDED }.`.length);
|
|
166
|
+
}
|
|
167
|
+
|
|
160
168
|
['resourceQuota', 'namespaceDefaultResourceQuota'].forEach((specProp) => {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if (
|
|
165
|
-
|
|
169
|
+
const limit = this.value?.spec[specProp]?.limit;
|
|
170
|
+
const usedLimit = this.value?.spec[specProp]?.usedLimit;
|
|
171
|
+
|
|
172
|
+
if (isExtended) {
|
|
173
|
+
if (limit?.extended && typeof limit.extended[resourceKey] !== 'undefined') {
|
|
174
|
+
delete limit.extended[resourceKey];
|
|
175
|
+
if (Object.keys(limit.extended).length === 0) {
|
|
176
|
+
delete limit.extended;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (usedLimit?.extended && typeof usedLimit.extended[resourceKey] !== 'undefined') {
|
|
180
|
+
delete usedLimit.extended[resourceKey];
|
|
181
|
+
if (Object.keys(usedLimit.extended).length === 0) {
|
|
182
|
+
delete usedLimit.extended;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
} else {
|
|
186
|
+
if (limit && typeof limit[resourceKey] !== 'undefined') {
|
|
187
|
+
delete limit[resourceKey];
|
|
188
|
+
}
|
|
189
|
+
if (usedLimit && typeof usedLimit[resourceKey] !== 'undefined') {
|
|
190
|
+
delete usedLimit[resourceKey];
|
|
191
|
+
}
|
|
166
192
|
}
|
|
167
193
|
});
|
|
194
|
+
|
|
195
|
+
// Incrementing the key forces the ResourceQuota component to re-render
|
|
196
|
+
this.resourceQuotaKey++;
|
|
168
197
|
}
|
|
169
198
|
},
|
|
170
199
|
};
|
|
@@ -224,6 +253,7 @@ export default {
|
|
|
224
253
|
:weight="9"
|
|
225
254
|
>
|
|
226
255
|
<ResourceQuota
|
|
256
|
+
:key="resourceQuotaKey"
|
|
227
257
|
:value="value"
|
|
228
258
|
:mode="canEditTabElements"
|
|
229
259
|
:types="isStandaloneHarvester ? HARVESTER_TYPES : RANCHER_TYPES"
|
|
@@ -177,6 +177,21 @@ export default {
|
|
|
177
177
|
this.alertmanagerConfigResource.spec.receivers = receiversMinusDeletedItem;
|
|
178
178
|
// After saving the AlertmanagerConfig, the resource has been deleted.
|
|
179
179
|
this.alertmanagerConfigResource.save(...arguments);
|
|
180
|
+
},
|
|
181
|
+
handleReceiverAction(payload) {
|
|
182
|
+
switch (payload?.action) {
|
|
183
|
+
case 'goToEdit':
|
|
184
|
+
this.goToEdit();
|
|
185
|
+
break;
|
|
186
|
+
case 'goToEditYaml':
|
|
187
|
+
this.goToEditYaml();
|
|
188
|
+
break;
|
|
189
|
+
case 'promptRemove':
|
|
190
|
+
this.promptRemove();
|
|
191
|
+
break;
|
|
192
|
+
default:
|
|
193
|
+
console.warn(`Unknown receiver action: ${ payload?.action }`); // eslint-disable-line no-console
|
|
194
|
+
}
|
|
180
195
|
}
|
|
181
196
|
}
|
|
182
197
|
};
|
|
@@ -263,9 +278,7 @@ export default {
|
|
|
263
278
|
:custom-target-element="actionMenuTargetElement"
|
|
264
279
|
:custom-target-event="actionMenuTargetEvent"
|
|
265
280
|
@close="receiverActionMenuIsOpen = false"
|
|
266
|
-
@
|
|
267
|
-
@goToEditYaml="goToEditYaml"
|
|
268
|
-
@promptRemove="promptRemove"
|
|
281
|
+
@action-invoked="handleReceiverAction"
|
|
269
282
|
/>
|
|
270
283
|
</CruResource>
|
|
271
284
|
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const RETENTION_DEFAULT = 5;
|
|
@@ -66,6 +66,7 @@ import { DEFAULT_COMMON_BASE_PATH, DEFAULT_SUBDIRS } from '@shell/edit/provision
|
|
|
66
66
|
import ClusterAppearance from '@shell/components/form/ClusterAppearance';
|
|
67
67
|
import AddOnAdditionalManifest from '@shell/edit/provisioning.cattle.io.cluster/tabs/AddOnAdditionalManifest';
|
|
68
68
|
import VsphereUtils, { VMWARE_VSPHERE } from '@shell/utils/v-sphere';
|
|
69
|
+
import { RETENTION_DEFAULT } from '@shell/edit/provisioning.cattle.io.cluster/defaults';
|
|
69
70
|
import { mapGetters } from 'vuex';
|
|
70
71
|
const HARVESTER = 'harvester';
|
|
71
72
|
const GOOGLE = 'google';
|
|
@@ -1061,7 +1062,7 @@ export default {
|
|
|
1061
1062
|
this.rkeConfig.etcd = {
|
|
1062
1063
|
disableSnapshots: false,
|
|
1063
1064
|
s3: null,
|
|
1064
|
-
snapshotRetention:
|
|
1065
|
+
snapshotRetention: RETENTION_DEFAULT,
|
|
1065
1066
|
snapshotScheduleCron: '0 */5 * * *',
|
|
1066
1067
|
};
|
|
1067
1068
|
} else if (typeof this.rkeConfig.etcd.disableSnapshots === 'undefined') {
|
|
@@ -4,6 +4,10 @@ import { LabeledInput } from '@components/Form/LabeledInput';
|
|
|
4
4
|
import SelectOrCreateAuthSecret from '@shell/components/form/SelectOrCreateAuthSecret';
|
|
5
5
|
import { NORMAN } from '@shell/config/types';
|
|
6
6
|
import FormValidation from '@shell/mixins/form-validation';
|
|
7
|
+
import UnitInput from '@shell/components/form/UnitInput';
|
|
8
|
+
import RadioGroup from '@components/Form/Radio/RadioGroup.vue';
|
|
9
|
+
import { _CREATE } from '@shell/config/query-params';
|
|
10
|
+
import { RETENTION_DEFAULT } from '@shell/edit/provisioning.cattle.io.cluster/defaults';
|
|
7
11
|
|
|
8
12
|
export default {
|
|
9
13
|
emits: ['update:value', 'validationChanged'],
|
|
@@ -12,6 +16,8 @@ export default {
|
|
|
12
16
|
LabeledInput,
|
|
13
17
|
Checkbox,
|
|
14
18
|
SelectOrCreateAuthSecret,
|
|
19
|
+
UnitInput,
|
|
20
|
+
RadioGroup
|
|
15
21
|
},
|
|
16
22
|
mixins: [FormValidation],
|
|
17
23
|
|
|
@@ -35,6 +41,10 @@ export default {
|
|
|
35
41
|
type: Function,
|
|
36
42
|
required: true,
|
|
37
43
|
},
|
|
44
|
+
localRetentionCount: {
|
|
45
|
+
type: Number,
|
|
46
|
+
default: null,
|
|
47
|
+
}
|
|
38
48
|
},
|
|
39
49
|
|
|
40
50
|
data() {
|
|
@@ -47,9 +57,11 @@ export default {
|
|
|
47
57
|
folder: '',
|
|
48
58
|
region: '',
|
|
49
59
|
skipSSLVerify: false,
|
|
60
|
+
retention: null,
|
|
50
61
|
...(this.value || {}),
|
|
51
62
|
},
|
|
52
|
-
|
|
63
|
+
differentRetention: false,
|
|
64
|
+
fvFormRuleSets: [
|
|
53
65
|
{
|
|
54
66
|
path: 'endpoint', rootObject: this.config, rules: ['awsStyleEndpoint']
|
|
55
67
|
},
|
|
@@ -59,6 +71,9 @@ export default {
|
|
|
59
71
|
]
|
|
60
72
|
};
|
|
61
73
|
},
|
|
74
|
+
mounted() {
|
|
75
|
+
this.differentRetention = !(this.mode === _CREATE || this.value?.retention === this.localRetentionCount);
|
|
76
|
+
},
|
|
62
77
|
|
|
63
78
|
computed: {
|
|
64
79
|
|
|
@@ -73,10 +88,25 @@ export default {
|
|
|
73
88
|
|
|
74
89
|
return {};
|
|
75
90
|
},
|
|
91
|
+
|
|
92
|
+
localCountToUse() {
|
|
93
|
+
return this.localRetentionCount === null || this.localRetentionCount === undefined ? RETENTION_DEFAULT : this.localRetentionCount;
|
|
94
|
+
},
|
|
95
|
+
retentionOptionsOptions() {
|
|
96
|
+
return [
|
|
97
|
+
{ label: this.t('cluster.rke2.etcd.s3config.snapshotRetention.options.localDefined', { count: this.localCountToUse }), value: false }, { label: this.t('cluster.rke2.etcd.s3config.snapshotRetention.options.manual'), value: true }
|
|
98
|
+
];
|
|
99
|
+
}
|
|
76
100
|
},
|
|
77
101
|
watch: {
|
|
78
102
|
fvFormIsValid(newValue) {
|
|
79
103
|
this.$emit('validationChanged', !!newValue);
|
|
104
|
+
},
|
|
105
|
+
localRetentionCount(neu) {
|
|
106
|
+
if (!this.differentRetention) {
|
|
107
|
+
this.config.retention = this.localCountToUse;
|
|
108
|
+
this.update();
|
|
109
|
+
}
|
|
80
110
|
}
|
|
81
111
|
},
|
|
82
112
|
|
|
@@ -86,6 +116,10 @@ export default {
|
|
|
86
116
|
|
|
87
117
|
this.$emit('update:value', out);
|
|
88
118
|
},
|
|
119
|
+
resetRetention() {
|
|
120
|
+
this.config.retention = this.localCountToUse;
|
|
121
|
+
this.update();
|
|
122
|
+
}
|
|
89
123
|
},
|
|
90
124
|
};
|
|
91
125
|
</script>
|
|
@@ -150,7 +184,6 @@ export default {
|
|
|
150
184
|
/>
|
|
151
185
|
</div>
|
|
152
186
|
</div>
|
|
153
|
-
|
|
154
187
|
<div
|
|
155
188
|
v-if="!ccData.defaultSkipSSLVerify"
|
|
156
189
|
class="mt-20"
|
|
@@ -172,5 +205,27 @@ export default {
|
|
|
172
205
|
@update:value="update"
|
|
173
206
|
/>
|
|
174
207
|
</div>
|
|
208
|
+
<div class="row mt-20">
|
|
209
|
+
<div class="col span-6">
|
|
210
|
+
<h4>{{ t('cluster.rke2.etcd.s3config.snapshotRetention.title') }}</h4>
|
|
211
|
+
<RadioGroup
|
|
212
|
+
v-model:value="differentRetention"
|
|
213
|
+
name="s3config-retention"
|
|
214
|
+
:mode="mode"
|
|
215
|
+
:options="retentionOptionsOptions"
|
|
216
|
+
:row="true"
|
|
217
|
+
@update:value="resetRetention"
|
|
218
|
+
/>
|
|
219
|
+
<UnitInput
|
|
220
|
+
v-if="differentRetention"
|
|
221
|
+
v-model:value="config.retention"
|
|
222
|
+
:label="t('cluster.rke2.etcd.s3config.snapshotRetention.label')"
|
|
223
|
+
:mode="mode"
|
|
224
|
+
:suffix="t('cluster.rke2.snapshots.s3Suffix')"
|
|
225
|
+
class="mt-10"
|
|
226
|
+
@update:value="update"
|
|
227
|
+
/>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
175
230
|
</div>
|
|
176
231
|
</template>
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
import S3Config from '@shell/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue';
|
|
3
|
+
|
|
4
|
+
describe('s3Config', () => {
|
|
5
|
+
const defaultProps = {
|
|
6
|
+
mode: 'create',
|
|
7
|
+
namespace: 'test-ns',
|
|
8
|
+
registerBeforeHook: jest.fn(),
|
|
9
|
+
localRetentionCount: 5,
|
|
10
|
+
value: {}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const mockStore = { getters: { 'rancher/byId': jest.fn() } };
|
|
14
|
+
|
|
15
|
+
const createWrapper = (props = {}) => {
|
|
16
|
+
return shallowMount(S3Config, {
|
|
17
|
+
propsData: {
|
|
18
|
+
...defaultProps,
|
|
19
|
+
...props
|
|
20
|
+
},
|
|
21
|
+
mocks: {
|
|
22
|
+
$store: mockStore,
|
|
23
|
+
t: (key) => key,
|
|
24
|
+
},
|
|
25
|
+
stubs: {
|
|
26
|
+
LabeledInput: true,
|
|
27
|
+
Checkbox: true,
|
|
28
|
+
SelectOrCreateAuthSecret: true,
|
|
29
|
+
UnitInput: true,
|
|
30
|
+
RadioGroup: true,
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
it('renders correctly', () => {
|
|
36
|
+
const wrapper = createWrapper();
|
|
37
|
+
|
|
38
|
+
expect(wrapper.exists()).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('initializes config with default values', () => {
|
|
42
|
+
const wrapper = createWrapper();
|
|
43
|
+
|
|
44
|
+
expect(wrapper.vm.config.bucket).toBe('');
|
|
45
|
+
expect(wrapper.vm.config.skipSSLVerify).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('initializes config with provided value prop', () => {
|
|
49
|
+
const value = {
|
|
50
|
+
bucket: 'test',
|
|
51
|
+
region: 'us-east-1',
|
|
52
|
+
retention: 2
|
|
53
|
+
};
|
|
54
|
+
const wrapper = createWrapper({ value });
|
|
55
|
+
|
|
56
|
+
expect(wrapper.vm.config.bucket).toBe('test');
|
|
57
|
+
expect(wrapper.vm.config.region).toBe('us-east-1');
|
|
58
|
+
expect(wrapper.vm.config.retention).toBe(2);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('retention Logic', () => {
|
|
62
|
+
it('computes localCountToUse correctly', () => {
|
|
63
|
+
const wrapper = createWrapper({ localRetentionCount: 3 });
|
|
64
|
+
|
|
65
|
+
expect(wrapper.vm.localCountToUse).toBe(3);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('uses default retention if localRetentionCount is null', () => {
|
|
69
|
+
const wrapper = createWrapper({ localRetentionCount: null });
|
|
70
|
+
|
|
71
|
+
expect(wrapper.vm.localCountToUse).toBe(5);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('sets differentRetention to false in create mode', () => {
|
|
75
|
+
const wrapper = createWrapper({ mode: 'create' });
|
|
76
|
+
|
|
77
|
+
expect(wrapper.vm.differentRetention).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('sets differentRetention to false in edit mode if retention matches', () => {
|
|
81
|
+
const wrapper = createWrapper({
|
|
82
|
+
mode: 'edit',
|
|
83
|
+
value: { retention: 5 },
|
|
84
|
+
localRetentionCount: 5
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
expect(wrapper.vm.differentRetention).toBe(false);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('sets differentRetention to true in edit mode if retention differs', () => {
|
|
91
|
+
const wrapper = createWrapper({
|
|
92
|
+
mode: 'edit',
|
|
93
|
+
value: { retention: 2 },
|
|
94
|
+
localRetentionCount: 5
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
expect(wrapper.vm.differentRetention).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('updates retention when localRetentionCount changes and differentRetention is false', async() => {
|
|
101
|
+
const wrapper = createWrapper({ localRetentionCount: 5 });
|
|
102
|
+
|
|
103
|
+
// differentRetention is false by default in create mode
|
|
104
|
+
await wrapper.setProps({ localRetentionCount: 10 });
|
|
105
|
+
expect(wrapper.vm.config.retention).toBe(10);
|
|
106
|
+
expect(wrapper.emitted('update:value')).toBeTruthy();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import PortalVue from 'portal-vue';
|
|
2
2
|
import Vue3Resize from 'vue3-resize';
|
|
3
3
|
import FloatingVue from 'floating-vue';
|
|
4
|
-
import vSelect from 'vue-select';
|
|
5
4
|
import 'vue3-resize/dist/vue3-resize.css';
|
|
6
5
|
|
|
7
6
|
// import '@shell/plugins/extend-router';
|
|
@@ -43,7 +42,6 @@ export async function installPlugins(vueApp) {
|
|
|
43
42
|
preventContainer: ['#modal-container-element']
|
|
44
43
|
});
|
|
45
44
|
vueApp.use(InstallCodeMirror);
|
|
46
|
-
vueApp.component('v-select', vSelect);
|
|
47
45
|
}
|
|
48
46
|
|
|
49
47
|
export async function installInjectedPlugins(app, vueApp) {
|