@rancher/shell 3.0.9-rc.4 → 3.0.9-rc.5
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/brand/suse/metadata.json +2 -1
- package/assets/translations/en-us.yaml +91 -3
- package/components/ActionMenuShell.vue +1 -1
- package/components/Inactivity.vue +2 -2
- package/components/Resource/Detail/Card/ExtrasCard.vue +49 -15
- package/components/Resource/Detail/Card/__tests__/ExtrasCard.test.ts +111 -0
- package/components/Resource/Detail/Masthead/__tests__/index.test.ts +0 -17
- package/components/Resource/Detail/Masthead/index.vue +11 -4
- package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +3 -1
- package/components/Resource/Detail/Metadata/index.vue +1 -1
- package/components/Resource/Detail/ResourceRow.vue +1 -1
- package/components/ResourceDetail/Masthead/latest.vue +12 -2
- package/components/ResourceList/index.vue +9 -0
- package/components/ResourceTable.vue +38 -4
- package/components/Tabbed/Tab.vue +4 -0
- package/components/Tabbed/index.vue +4 -1
- package/components/__tests__/ProjectRow.test.ts +60 -0
- package/components/form/ChangePassword.vue +41 -35
- package/components/form/ResourceQuota/Project.vue +42 -1
- package/components/form/ResourceQuota/ProjectRow.vue +71 -4
- package/components/form/ResourceQuota/__tests__/Project.test.ts +63 -0
- package/components/form/SelectOrCreateAuthSecret.vue +6 -1
- package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +35 -0
- package/components/formatter/MachineSummaryGraph.vue +10 -2
- package/components/nav/TopLevelMenu.helper.ts +50 -2
- package/components/nav/TopLevelMenu.vue +14 -0
- package/components/nav/Type.vue +5 -0
- package/components/nav/__tests__/TopLevelMenu.test.ts +3 -3
- package/components/nav/__tests__/Type.test.ts +6 -4
- package/config/product/explorer.js +4 -3
- package/config/product/manager.js +18 -1
- package/config/router/navigation-guards/authentication.js +8 -9
- package/config/types.js +10 -2
- package/detail/auditlog.cattle.io.auditpolicy.vue +19 -0
- package/detail/management.cattle.io.user.vue +1 -2
- package/detail/node.vue +0 -1
- package/detail/provisioning.cattle.io.cluster.vue +2 -1
- package/dialog/ChangePasswordDialog.vue +8 -0
- package/dialog/GenericPrompt.vue +20 -3
- package/dialog/ScaleMachineDownDialog.vue +65 -15
- package/dialog/SearchDialog.vue +10 -2
- package/dialog/__tests__/ScaleMachineDownDialog.test.ts +184 -0
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +89 -0
- package/edit/__tests__/management.cattle.io.project.test.js +56 -1
- package/edit/auditlog.cattle.io.auditpolicy/AdditionalRedactions.vue +114 -0
- package/edit/auditlog.cattle.io.auditpolicy/Filters.vue +119 -0
- package/edit/auditlog.cattle.io.auditpolicy/General.vue +180 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/AdditionalRedactions.test.ts +327 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/Filters.test.ts +449 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/General.test.ts +472 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/AdditionalRedactions.test.ts.snap +27 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/Filters.test.ts.snap +39 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +174 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +29 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/index.test.ts +215 -0
- package/edit/auditlog.cattle.io.auditpolicy/index.vue +104 -0
- package/edit/auditlog.cattle.io.auditpolicy/types.ts +28 -0
- package/edit/fleet.cattle.io.gitrepo.vue +16 -1
- package/edit/management.cattle.io.project.vue +8 -2
- package/edit/management.cattle.io.user.vue +29 -34
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +178 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +21 -2
- package/edit/provisioning.cattle.io.cluster/shared.ts +4 -0
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +1 -1
- package/list/auditlog.cattle.io.auditpolicy.vue +63 -0
- package/list/group.principal.vue +11 -15
- package/list/management.cattle.io.user.vue +11 -21
- package/machine-config/azure.vue +14 -0
- package/mixins/browser-tab-visibility.js +5 -4
- package/mixins/fetch.client.js +6 -0
- package/models/__tests__/auditlog.cattle.io.auditpolicy.test.ts +117 -0
- package/models/__tests__/workload.test.ts +49 -6
- package/models/auditlog.cattle.io.auditpolicy.js +46 -0
- package/models/cluster.x-k8s.io.machine.js +1 -1
- package/models/cluster.x-k8s.io.machinedeployment.js +5 -5
- package/models/event.js +5 -0
- package/models/ext.cattle.io.groupmembershiprefreshrequest.js +15 -0
- package/models/ext.cattle.io.passwordchangerequest.js +15 -0
- package/models/ext.cattle.io.selfuser.js +15 -0
- package/models/fleet-application.js +17 -7
- package/models/management.cattle.io.user.js +28 -31
- package/models/schema.js +18 -0
- package/models/secret.js +27 -24
- package/models/steve-schema.ts +39 -2
- package/models/workload.js +3 -2
- package/package.json +1 -1
- package/pages/account/index.vue +23 -16
- package/pages/auth/login.vue +15 -8
- package/pages/auth/setup.vue +52 -15
- package/pages/home.vue +9 -3
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +1 -3
- package/plugins/dashboard-store/actions.js +7 -0
- package/plugins/dashboard-store/getters.js +23 -1
- package/plugins/dashboard-store/index.js +3 -2
- package/plugins/dashboard-store/mutations.js +4 -0
- package/plugins/dashboard-store/resource-class.js +12 -5
- package/plugins/steve/__tests__/steve-class.test.ts +167 -0
- package/plugins/steve/schema.d.ts +5 -0
- package/plugins/steve/steve-class.js +19 -0
- package/plugins/steve/steve-pagination-utils.ts +2 -1
- package/store/auth.js +57 -19
- package/store/notifications.ts +1 -1
- package/store/type-map.js +12 -1
- package/types/shell/index.d.ts +10 -14
- package/types/store/dashboard-store.types.ts +7 -0
- package/utils/pagination-wrapper.ts +11 -3
- package/vue.config.js +26 -13
- package/edit/provisioning.cattle.io.cluster/defaults.ts +0 -1
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
import ScaleMachineDownDialog from '@shell/dialog/ScaleMachineDownDialog.vue';
|
|
3
|
+
import { CAPI as CAPI_LABELS } from '@shell/config/labels-annotations';
|
|
4
|
+
|
|
5
|
+
const defaultStubs = { GenericPrompt: true };
|
|
6
|
+
|
|
7
|
+
const defaultGetters = { 'type-map/labelFor': jest.fn(() => 'Node') };
|
|
8
|
+
|
|
9
|
+
const defaultMocks = {
|
|
10
|
+
$store: {
|
|
11
|
+
dispatch: jest.fn(),
|
|
12
|
+
getters: defaultGetters
|
|
13
|
+
},
|
|
14
|
+
t: jest.fn((key) => key),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const defaultCluster = {
|
|
18
|
+
isRke2: true,
|
|
19
|
+
machines: [],
|
|
20
|
+
save: jest.fn()
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const defaultResource = {
|
|
24
|
+
cluster: defaultCluster,
|
|
25
|
+
isWorker: true,
|
|
26
|
+
poolName: 'pool1',
|
|
27
|
+
pool: { scalePool: jest.fn() },
|
|
28
|
+
save: jest.fn(),
|
|
29
|
+
setAnnotation: jest.fn(),
|
|
30
|
+
nameDisplay: 'machine-1',
|
|
31
|
+
namespace: 'default',
|
|
32
|
+
schema: 'machine'
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
describe('component: ScaleMachineDownDialog', () => {
|
|
36
|
+
const createWrapper = (propsData = {}, mocks = {}) => {
|
|
37
|
+
return shallowMount(ScaleMachineDownDialog, {
|
|
38
|
+
propsData: {
|
|
39
|
+
resources: [defaultResource],
|
|
40
|
+
...propsData
|
|
41
|
+
},
|
|
42
|
+
global: {
|
|
43
|
+
mocks: {
|
|
44
|
+
...defaultMocks,
|
|
45
|
+
...mocks
|
|
46
|
+
},
|
|
47
|
+
stubs: defaultStubs,
|
|
48
|
+
directives: { 'clean-html': true }
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
jest.clearAllMocks();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should render correctly', () => {
|
|
58
|
+
const wrapper = createWrapper();
|
|
59
|
+
|
|
60
|
+
expect(wrapper.exists()).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('data initialization', () => {
|
|
64
|
+
it('should identify safe machines to delete (Worker)', () => {
|
|
65
|
+
const wrapper = createWrapper();
|
|
66
|
+
|
|
67
|
+
expect(wrapper.vm.safeMachinesToDelete).toHaveLength(1);
|
|
68
|
+
expect(wrapper.vm.ignored).toHaveLength(0);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should ignore deletion if it is the last control plane', () => {
|
|
72
|
+
const cluster = {
|
|
73
|
+
isRke2: true,
|
|
74
|
+
machines: [{ isControlPlane: true }]
|
|
75
|
+
};
|
|
76
|
+
const resource = {
|
|
77
|
+
...defaultResource,
|
|
78
|
+
cluster,
|
|
79
|
+
isControlPlane: true,
|
|
80
|
+
isWorker: false
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const wrapper = createWrapper({ resources: [resource] });
|
|
84
|
+
|
|
85
|
+
expect(wrapper.vm.safeMachinesToDelete).toHaveLength(0);
|
|
86
|
+
expect(wrapper.vm.ignored).toHaveLength(1);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should allow deletion if multiple control planes exist', () => {
|
|
90
|
+
const cluster = {
|
|
91
|
+
isRke2: true,
|
|
92
|
+
machines: [{ isControlPlane: true }, { isControlPlane: true }]
|
|
93
|
+
};
|
|
94
|
+
const resource = {
|
|
95
|
+
...defaultResource,
|
|
96
|
+
cluster,
|
|
97
|
+
isControlPlane: true,
|
|
98
|
+
isWorker: false
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const wrapper = createWrapper({ resources: [resource] });
|
|
102
|
+
|
|
103
|
+
expect(wrapper.vm.safeMachinesToDelete).toHaveLength(1);
|
|
104
|
+
expect(wrapper.vm.ignored).toHaveLength(0);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('fetch', () => {
|
|
109
|
+
it('should fetch machine sets and update loading state', async() => {
|
|
110
|
+
const wrapper = createWrapper();
|
|
111
|
+
|
|
112
|
+
// Mock dispatch to return empty array for findAll
|
|
113
|
+
(wrapper.vm as any).$store.dispatch.mockResolvedValue([]);
|
|
114
|
+
|
|
115
|
+
await ScaleMachineDownDialog.fetch.call(wrapper.vm);
|
|
116
|
+
|
|
117
|
+
expect(wrapper.vm.$store.dispatch).toHaveBeenCalledWith('management/findAll', expect.anything());
|
|
118
|
+
expect(wrapper.vm.loading).toBe(false);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('remove', () => {
|
|
123
|
+
it('should perform RKE2 removal steps', async() => {
|
|
124
|
+
const wrapper = createWrapper();
|
|
125
|
+
const resource = (wrapper.vm as any).resources[0];
|
|
126
|
+
|
|
127
|
+
await (wrapper.vm as any).remove();
|
|
128
|
+
|
|
129
|
+
expect(resource.setAnnotation).toHaveBeenCalledWith(CAPI_LABELS.DELETE_MACHINE, 'true');
|
|
130
|
+
expect(resource.save).toHaveBeenCalledWith();
|
|
131
|
+
expect(resource.pool.scalePool).toHaveBeenCalledWith(-1, false);
|
|
132
|
+
expect(resource.cluster.save).toHaveBeenCalledWith();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should perform non-RKE2 removal steps', async() => {
|
|
136
|
+
const normanAction = jest.fn();
|
|
137
|
+
const resource = {
|
|
138
|
+
cluster: { isRke2: false },
|
|
139
|
+
provisioningCluster: { nodes: [] },
|
|
140
|
+
norman: { doAction: normanAction },
|
|
141
|
+
nameDisplay: 'node-1',
|
|
142
|
+
schema: 'node'
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const wrapper = createWrapper({ resources: [resource] });
|
|
146
|
+
|
|
147
|
+
await (wrapper.vm as any).remove();
|
|
148
|
+
|
|
149
|
+
expect(normanAction).toHaveBeenCalledWith('scaledown');
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('showScaling', () => {
|
|
154
|
+
it('should return true if replicas do not match readyReplicas', async() => {
|
|
155
|
+
const wrapper = createWrapper();
|
|
156
|
+
|
|
157
|
+
await wrapper.setData({
|
|
158
|
+
workerMachineSets: [{
|
|
159
|
+
data: [{
|
|
160
|
+
spec: { replicas: 2 },
|
|
161
|
+
status: { readyReplicas: 1 }
|
|
162
|
+
}]
|
|
163
|
+
}]
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
expect(wrapper.vm.showScaling).toBe(true);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should return false if replicas match readyReplicas', async() => {
|
|
170
|
+
const wrapper = createWrapper();
|
|
171
|
+
|
|
172
|
+
await wrapper.setData({
|
|
173
|
+
workerMachineSets: [{
|
|
174
|
+
data: [{
|
|
175
|
+
spec: { replicas: 2 },
|
|
176
|
+
status: { readyReplicas: 2 }
|
|
177
|
+
}]
|
|
178
|
+
}]
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
expect(wrapper.vm.showScaling).toBe(false);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mount } from '@vue/test-utils';
|
|
2
2
|
import { _CREATE, _EDIT, _VIEW } from '@shell/config/query-params';
|
|
3
|
+
import { AUTH_TYPE } from '@shell/config/types';
|
|
3
4
|
import GitRepo from '@shell/models/fleet.cattle.io.gitrepo';
|
|
4
5
|
import GitRepoComponent from '@shell/edit/fleet.cattle.io.gitrepo.vue';
|
|
5
6
|
|
|
@@ -237,3 +238,91 @@ describe.each([
|
|
|
237
238
|
|
|
238
239
|
it.todo('test paths and subpaths');
|
|
239
240
|
});
|
|
241
|
+
|
|
242
|
+
describe('view: fleet.cattle.io.gitrepo, GitHub password banner - should', () => {
|
|
243
|
+
it('show GitHub password banner when GitHub.com repository and basic auth is selected', async() => {
|
|
244
|
+
const wrapper = mount(GitRepoComponent, initGitRepo({ mode: _CREATE }, { spec: { repo: 'https://github.com/rancher/fleet-examples' } }));
|
|
245
|
+
|
|
246
|
+
// Check computed properties
|
|
247
|
+
expect(wrapper.vm.isGitHubDotComRepository).toBeTruthy();
|
|
248
|
+
|
|
249
|
+
// Set basic auth selection in tempCachedValues
|
|
250
|
+
await wrapper.setData({ tempCachedValues: { clientSecretName: { selected: AUTH_TYPE._BASIC } } });
|
|
251
|
+
|
|
252
|
+
// Check computed property after setting data
|
|
253
|
+
expect(wrapper.vm.isBasicAuthSelected).toBeTruthy();
|
|
254
|
+
|
|
255
|
+
await wrapper.vm.$nextTick();
|
|
256
|
+
|
|
257
|
+
const githubBanner = wrapper.find('[data-testid="gitrepo-githubdotcom-password-warning"]');
|
|
258
|
+
|
|
259
|
+
expect(githubBanner.exists()).toBeTruthy();
|
|
260
|
+
|
|
261
|
+
// Check the banner element
|
|
262
|
+
const bannerElement = wrapper.find('.banner.warning');
|
|
263
|
+
|
|
264
|
+
expect(bannerElement.exists()).toBeTruthy();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('show GitHub password banner in edit mode when conditions are met', async() => {
|
|
268
|
+
const wrapper = mount(GitRepoComponent, initGitRepo({ mode: _EDIT }, { spec: { repo: 'https://github.com/rancher/fleet-examples' } }));
|
|
269
|
+
|
|
270
|
+
await wrapper.setData({ tempCachedValues: { clientSecretName: { selected: AUTH_TYPE._BASIC } } });
|
|
271
|
+
|
|
272
|
+
const githubBanner = wrapper.find('[data-testid="gitrepo-githubdotcom-password-warning"]');
|
|
273
|
+
|
|
274
|
+
expect(githubBanner.exists()).toBeTruthy();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('hide GitHub password banner when not using GitHub.com repository', async() => {
|
|
278
|
+
const wrapper = mount(GitRepoComponent, initGitRepo({ mode: _CREATE }, { spec: { repo: 'https://gitlab.com/user/repo' } }));
|
|
279
|
+
|
|
280
|
+
await wrapper.setData({ tempCachedValues: { clientSecretName: { selected: AUTH_TYPE._SSH } } });
|
|
281
|
+
|
|
282
|
+
const githubBanner = wrapper.find('[data-testid="gitrepo-githubdotcom-password-warning"]');
|
|
283
|
+
|
|
284
|
+
expect(githubBanner.exists()).toBeFalsy();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('hide GitHub password banner when not using basic auth', async() => {
|
|
288
|
+
const wrapper = mount(GitRepoComponent, initGitRepo({ mode: _CREATE }, { spec: { repo: 'https://github.com/rancher/fleet-examples' } }));
|
|
289
|
+
|
|
290
|
+
await wrapper.setData({ tempCachedValues: { clientSecretName: { selected: AUTH_TYPE._SSH } } });
|
|
291
|
+
|
|
292
|
+
const githubBanner = wrapper.find('[data-testid="gitrepo-githubdotcom-password-warning"]');
|
|
293
|
+
|
|
294
|
+
expect(githubBanner.exists()).toBeFalsy();
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('hide GitHub password banner when no auth is selected', async() => {
|
|
298
|
+
const wrapper = mount(GitRepoComponent, initGitRepo({ mode: _CREATE }, { spec: { repo: 'https://github.com/rancher/fleet-examples' } }));
|
|
299
|
+
|
|
300
|
+
await wrapper.setData({ tempCachedValues: { clientSecretName: { selected: AUTH_TYPE._NONE } } });
|
|
301
|
+
|
|
302
|
+
const githubBanner = wrapper.find('[data-testid="gitrepo-githubdotcom-password-warning"]');
|
|
303
|
+
|
|
304
|
+
expect(githubBanner.exists()).toBeFalsy();
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it.each([
|
|
308
|
+
['https://github.com/user/repo', true],
|
|
309
|
+
['https://GitHub.com/user/repo', true],
|
|
310
|
+
['HTTPS://GITHUB.COM/user/repo', true],
|
|
311
|
+
['https://api.github.com/user/repo', false], // subdomain doesn't match
|
|
312
|
+
['https://raw.github.com/user/repo', false], // subdomain doesn't match
|
|
313
|
+
['https://company-github.com/user/repo', false], // doesn't contain exact 'https://github.com'
|
|
314
|
+
['https://github.company.com/user/repo', true], // contains exact 'https://github.com' at start
|
|
315
|
+
['https://gitlab.com/user/repo', false],
|
|
316
|
+
['https://bitbucket.org/user/repo', false],
|
|
317
|
+
['http://github.com/user/repo', false], // not https
|
|
318
|
+
['git@github.com:user/repo.git', false], // not https
|
|
319
|
+
])('correctly detect GitHub.com repository for URL: %s (expected: %s)', async(repoUrl, shouldShowBanner) => {
|
|
320
|
+
const wrapper = mount(GitRepoComponent, initGitRepo({ mode: _CREATE }, { spec: { repo: repoUrl } }));
|
|
321
|
+
|
|
322
|
+
await wrapper.setData({ tempCachedValues: { clientSecretName: { selected: AUTH_TYPE._BASIC } } });
|
|
323
|
+
|
|
324
|
+
const githubBanner = wrapper.find('[data-testid="gitrepo-githubdotcom-password-warning"]');
|
|
325
|
+
|
|
326
|
+
expect(githubBanner.exists()).toBe(shouldShowBanner);
|
|
327
|
+
});
|
|
328
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
import CruResource from '@shell/components/CruResource';
|
|
2
3
|
import ManagementCattleIoProject from '@shell/edit/management.cattle.io.project.vue';
|
|
3
4
|
import { _EDIT } from '@shell/config/query-params';
|
|
4
5
|
|
|
@@ -15,7 +16,13 @@ const mockStore = {
|
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
const defaultMountOptions = {
|
|
18
|
-
props:
|
|
19
|
+
props: {
|
|
20
|
+
mode: _EDIT,
|
|
21
|
+
value: {
|
|
22
|
+
spec: { containerDefaultResourceLimit: {} },
|
|
23
|
+
metadata: { namespace: 'test-ns', annotations: {} },
|
|
24
|
+
}
|
|
25
|
+
},
|
|
19
26
|
global: { mocks: { $store: mockStore } }
|
|
20
27
|
};
|
|
21
28
|
|
|
@@ -134,4 +141,52 @@ describe('component: ManagementCattleIoProject', () => {
|
|
|
134
141
|
expect(wrapper.vm.value.spec.namespaceDefaultResourceQuota.limit.extended.test1).toBeUndefined();
|
|
135
142
|
expect(wrapper.vm.value.spec.namespaceDefaultResourceQuota.limit.extended.test2).toBe('4');
|
|
136
143
|
});
|
|
144
|
+
|
|
145
|
+
it('should update isQuotasValid when validateQuotas is called', () => {
|
|
146
|
+
const wrapper = shallowMount(ManagementCattleIoProject, defaultMountOptions);
|
|
147
|
+
|
|
148
|
+
wrapper.vm.validateQuotas(false);
|
|
149
|
+
expect(wrapper.vm.isQuotasValid).toStrictEqual(false);
|
|
150
|
+
|
|
151
|
+
wrapper.vm.validateQuotas(true);
|
|
152
|
+
expect(wrapper.vm.isQuotasValid).toStrictEqual(true);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('cruResource validation-passed should be false if isQuotasValid is false', async() => {
|
|
156
|
+
const wrapper = shallowMount(ManagementCattleIoProject, defaultMountOptions);
|
|
157
|
+
|
|
158
|
+
await wrapper.setData({ isQuotasValid: false });
|
|
159
|
+
|
|
160
|
+
const cruResource = wrapper.findComponent(CruResource);
|
|
161
|
+
|
|
162
|
+
expect(cruResource.props('validationPassed')).toStrictEqual(false);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should remove an extended resource quota correctly with fixed startsWith', async() => {
|
|
166
|
+
const value = {
|
|
167
|
+
spec: {
|
|
168
|
+
resourceQuota: { limit: { extended: { test2: '1' } } },
|
|
169
|
+
namespaceDefaultResourceQuota: { limit: { extended: { test2: '2' } } }
|
|
170
|
+
},
|
|
171
|
+
metadata: { namespace: 'test-ns', annotations: {} },
|
|
172
|
+
save: jest.fn(),
|
|
173
|
+
listLocation: { name: 'list' }
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const wrapper = shallowMount(
|
|
177
|
+
ManagementCattleIoProject,
|
|
178
|
+
{
|
|
179
|
+
...defaultMountOptions,
|
|
180
|
+
props: {
|
|
181
|
+
...defaultMountOptions.props,
|
|
182
|
+
value,
|
|
183
|
+
},
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
wrapper.vm.removeQuota('extended.test2');
|
|
188
|
+
|
|
189
|
+
expect(wrapper.vm.value.spec.resourceQuota.limit.extended).toBeUndefined();
|
|
190
|
+
expect(wrapper.vm.value.spec.namespaceDefaultResourceQuota.limit.extended).toBeUndefined();
|
|
191
|
+
});
|
|
137
192
|
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref } from 'vue';
|
|
3
|
+
import Tab from '@shell/components/Tabbed/Tab.vue';
|
|
4
|
+
import Tabbed from '@shell/components/Tabbed/index.vue';
|
|
5
|
+
import ArrayList from '@shell/components/form/ArrayList.vue';
|
|
6
|
+
import { AuditPolicy } from '@shell/edit/auditlog.cattle.io.auditpolicy/types';
|
|
7
|
+
|
|
8
|
+
// Component Props & Emits
|
|
9
|
+
const props = defineProps({
|
|
10
|
+
value: {
|
|
11
|
+
type: Object,
|
|
12
|
+
default: () => ({})
|
|
13
|
+
},
|
|
14
|
+
mode: {
|
|
15
|
+
type: String,
|
|
16
|
+
default: 'create'
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const emit = defineEmits<{
|
|
21
|
+
'update:value': [value: AuditPolicy];
|
|
22
|
+
}>();
|
|
23
|
+
|
|
24
|
+
// Default Values & Reactive Spec
|
|
25
|
+
const defaults: AuditPolicy = { additionalRedactions: [] };
|
|
26
|
+
const spec = ref<AuditPolicy>({ ...defaults, ...props.value });
|
|
27
|
+
|
|
28
|
+
// Methods
|
|
29
|
+
function addRedaction() {
|
|
30
|
+
const valueToEmit = { ...props.value, ...spec.value };
|
|
31
|
+
|
|
32
|
+
valueToEmit.additionalRedactions?.push({
|
|
33
|
+
headers: [],
|
|
34
|
+
paths: [],
|
|
35
|
+
});
|
|
36
|
+
emit('update:value', valueToEmit);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function removeRedaction(tab: number) {
|
|
40
|
+
const valueToEmit = { ...props.value, ...spec.value };
|
|
41
|
+
|
|
42
|
+
valueToEmit.additionalRedactions?.splice(tab, 1);
|
|
43
|
+
emit('update:value', valueToEmit);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const redactionLabel = computed(() => {
|
|
47
|
+
return (i: number) => `Rule ${ i + 1 }`;
|
|
48
|
+
});
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<template>
|
|
52
|
+
<div>
|
|
53
|
+
<div class="row mb-40">
|
|
54
|
+
<div class="col span-12">
|
|
55
|
+
<Tabbed
|
|
56
|
+
:side-tabs="true"
|
|
57
|
+
:show-tabs-add-remove="mode !== 'view'"
|
|
58
|
+
:use-hash="true"
|
|
59
|
+
@addTab="addRedaction"
|
|
60
|
+
@removeTab="removeRedaction"
|
|
61
|
+
>
|
|
62
|
+
<Tab
|
|
63
|
+
v-for="(redaction, idx) in spec.additionalRedactions"
|
|
64
|
+
:key="idx"
|
|
65
|
+
:name="`rule-${idx}`"
|
|
66
|
+
:label="redactionLabel(idx)"
|
|
67
|
+
:show-header="false"
|
|
68
|
+
class="container-group"
|
|
69
|
+
>
|
|
70
|
+
<fieldset>
|
|
71
|
+
<h2>{{ t("auditPolicy.additionalRedactions.headers.title") }}</h2>
|
|
72
|
+
<div class="row">
|
|
73
|
+
<div class="col span-12">
|
|
74
|
+
<ArrayList
|
|
75
|
+
key="headers"
|
|
76
|
+
v-model:value="redaction.headers"
|
|
77
|
+
:value-placeholder="t('auditPolicy.additionalRedactions.headers.placeholder')"
|
|
78
|
+
:add-label="t('auditPolicy.additionalRedactions.headers.add')"
|
|
79
|
+
:mode="mode"
|
|
80
|
+
:protip="false"
|
|
81
|
+
/>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</fieldset>
|
|
85
|
+
<div class="spacer" />
|
|
86
|
+
<fieldset>
|
|
87
|
+
<h2>
|
|
88
|
+
{{ t("auditPolicy.additionalRedactions.paths.title") }} <i
|
|
89
|
+
v-clean-tooltip="{content: t('auditPolicy.additionalRedactions.paths.tooltip'), triggers: ['hover', 'touch', 'focus'] }"
|
|
90
|
+
v-stripped-aria-label="t('auditPolicy.additionalRedactions.paths.tooltip')"
|
|
91
|
+
class="icon icon-info"
|
|
92
|
+
tabindex="0"
|
|
93
|
+
role="tooltip"
|
|
94
|
+
/>
|
|
95
|
+
</h2>
|
|
96
|
+
<div class="row">
|
|
97
|
+
<div class="col span-12">
|
|
98
|
+
<ArrayList
|
|
99
|
+
key="paths"
|
|
100
|
+
v-model:value="redaction.paths"
|
|
101
|
+
:value-placeholder="t('auditPolicy.additionalRedactions.paths.placeholder')"
|
|
102
|
+
:add-label="t('auditPolicy.additionalRedactions.paths.add')"
|
|
103
|
+
:mode="mode"
|
|
104
|
+
:protip="false"
|
|
105
|
+
/>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
</fieldset>
|
|
109
|
+
</Tab>
|
|
110
|
+
</Tabbed>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</template>
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue';
|
|
3
|
+
import LabeledInput from '@components/Form/LabeledInput/LabeledInput.vue';
|
|
4
|
+
import LabeledSelect from '@shell/components/form/LabeledSelect.vue';
|
|
5
|
+
import ArrayList from '@shell/components/form/ArrayList.vue';
|
|
6
|
+
import { AuditPolicy, FilterRule } from '@shell/edit/auditlog.cattle.io.auditpolicy/types';
|
|
7
|
+
|
|
8
|
+
// Component Props & Emits
|
|
9
|
+
const props = defineProps({
|
|
10
|
+
value: {
|
|
11
|
+
type: Object,
|
|
12
|
+
default: () => ({})
|
|
13
|
+
},
|
|
14
|
+
mode: {
|
|
15
|
+
type: String,
|
|
16
|
+
default: 'create'
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const emit = defineEmits<{
|
|
21
|
+
'update:value': [value: AuditPolicy];
|
|
22
|
+
}>();
|
|
23
|
+
|
|
24
|
+
// Default Values & Reactive Spec
|
|
25
|
+
const defaults: AuditPolicy = { filters: [] };
|
|
26
|
+
const spec = ref<AuditPolicy>({ ...defaults, ...props.value });
|
|
27
|
+
const defaultAddValue: FilterRule = {
|
|
28
|
+
action: 'allow',
|
|
29
|
+
requestURI: '',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Methods
|
|
33
|
+
function addRow(key: 'action' | 'requestURI', filters: FilterRule[]) {
|
|
34
|
+
const valueToEmit = { ...props.value, ...spec.value };
|
|
35
|
+
|
|
36
|
+
valueToEmit.filters = filters;
|
|
37
|
+
|
|
38
|
+
emit('update:value', valueToEmit);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function updateRow(key: 'action' | 'requestURI', index: number, value: string) {
|
|
42
|
+
const valueToEmit = { ...props.value, ...spec.value };
|
|
43
|
+
|
|
44
|
+
if (!valueToEmit.filters) {
|
|
45
|
+
valueToEmit.filters = [];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Ensure the filter exists at the given index
|
|
49
|
+
if (!valueToEmit.filters[index]) {
|
|
50
|
+
valueToEmit.filters[index] = { action: '', requestURI: '' };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
valueToEmit.filters[index][key] = value;
|
|
54
|
+
emit('update:value', valueToEmit);
|
|
55
|
+
}
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<template>
|
|
59
|
+
<div>
|
|
60
|
+
<div class="row mb-40">
|
|
61
|
+
<div class="col span-12">
|
|
62
|
+
<ArrayList
|
|
63
|
+
key="headers"
|
|
64
|
+
v-model:value="spec.filters"
|
|
65
|
+
:value-placeholder="t('auditPolicy.filters.placeholder')"
|
|
66
|
+
:add-label="t('auditPolicy.filters.add')"
|
|
67
|
+
:mode="mode"
|
|
68
|
+
:protip="false"
|
|
69
|
+
:defaultAddValue="defaultAddValue"
|
|
70
|
+
:show-header="true"
|
|
71
|
+
@update:value="addRow('action', $event)"
|
|
72
|
+
>
|
|
73
|
+
<template v-slot:column-headers>
|
|
74
|
+
<div class="filters-heading mb-10">
|
|
75
|
+
<div
|
|
76
|
+
class="row"
|
|
77
|
+
>
|
|
78
|
+
<div class="col span-6">
|
|
79
|
+
<span class="text-label">{{ t('auditPolicy.filters.action.title') }}</span>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="col span-6 send-to">
|
|
82
|
+
<span class="text-label">{{ t('auditPolicy.filters.requestURI.title') }}</span>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</template>
|
|
87
|
+
<template v-slot:columns="scope">
|
|
88
|
+
<div class="row">
|
|
89
|
+
<div class="col span-6">
|
|
90
|
+
<LabeledSelect
|
|
91
|
+
v-model:value="scope.row.value.action"
|
|
92
|
+
:options="[{ value: 'allow', label: t('auditPolicy.filters.action.allow')},{ value: 'deny', label: t('auditPolicy.filters.action.deny') }]"
|
|
93
|
+
:mode="mode"
|
|
94
|
+
:placeholder="t('auditPolicy.filters.action.placeholder')"
|
|
95
|
+
@update:value="updateRow('action', scope.i, $event)"
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
<div class="col span-6">
|
|
99
|
+
<LabeledInput
|
|
100
|
+
v-model:value="scope.row.value.requestURI"
|
|
101
|
+
:mode="mode"
|
|
102
|
+
:placeholder="t('auditPolicy.filters.requestURI.placeholder')"
|
|
103
|
+
@update:value="updateRow('requestURI', scope.i, $event, )"
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</template>
|
|
108
|
+
</ArrayList>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
</template>
|
|
113
|
+
|
|
114
|
+
<style lang="scss" scoped>
|
|
115
|
+
.filters-heading {
|
|
116
|
+
display: grid;
|
|
117
|
+
grid-template-columns: auto $array-list-remove-margin;
|
|
118
|
+
}
|
|
119
|
+
</style>
|