@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.
Files changed (109) hide show
  1. package/assets/brand/suse/metadata.json +2 -1
  2. package/assets/translations/en-us.yaml +91 -3
  3. package/components/ActionMenuShell.vue +1 -1
  4. package/components/Inactivity.vue +2 -2
  5. package/components/Resource/Detail/Card/ExtrasCard.vue +49 -15
  6. package/components/Resource/Detail/Card/__tests__/ExtrasCard.test.ts +111 -0
  7. package/components/Resource/Detail/Masthead/__tests__/index.test.ts +0 -17
  8. package/components/Resource/Detail/Masthead/index.vue +11 -4
  9. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +3 -1
  10. package/components/Resource/Detail/Metadata/index.vue +1 -1
  11. package/components/Resource/Detail/ResourceRow.vue +1 -1
  12. package/components/ResourceDetail/Masthead/latest.vue +12 -2
  13. package/components/ResourceList/index.vue +9 -0
  14. package/components/ResourceTable.vue +38 -4
  15. package/components/Tabbed/Tab.vue +4 -0
  16. package/components/Tabbed/index.vue +4 -1
  17. package/components/__tests__/ProjectRow.test.ts +60 -0
  18. package/components/form/ChangePassword.vue +41 -35
  19. package/components/form/ResourceQuota/Project.vue +42 -1
  20. package/components/form/ResourceQuota/ProjectRow.vue +71 -4
  21. package/components/form/ResourceQuota/__tests__/Project.test.ts +63 -0
  22. package/components/form/SelectOrCreateAuthSecret.vue +6 -1
  23. package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +35 -0
  24. package/components/formatter/MachineSummaryGraph.vue +10 -2
  25. package/components/nav/TopLevelMenu.helper.ts +50 -2
  26. package/components/nav/TopLevelMenu.vue +14 -0
  27. package/components/nav/Type.vue +5 -0
  28. package/components/nav/__tests__/TopLevelMenu.test.ts +3 -3
  29. package/components/nav/__tests__/Type.test.ts +6 -4
  30. package/config/product/explorer.js +4 -3
  31. package/config/product/manager.js +18 -1
  32. package/config/router/navigation-guards/authentication.js +8 -9
  33. package/config/types.js +10 -2
  34. package/detail/auditlog.cattle.io.auditpolicy.vue +19 -0
  35. package/detail/management.cattle.io.user.vue +1 -2
  36. package/detail/node.vue +0 -1
  37. package/detail/provisioning.cattle.io.cluster.vue +2 -1
  38. package/dialog/ChangePasswordDialog.vue +8 -0
  39. package/dialog/GenericPrompt.vue +20 -3
  40. package/dialog/ScaleMachineDownDialog.vue +65 -15
  41. package/dialog/SearchDialog.vue +10 -2
  42. package/dialog/__tests__/ScaleMachineDownDialog.test.ts +184 -0
  43. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +89 -0
  44. package/edit/__tests__/management.cattle.io.project.test.js +56 -1
  45. package/edit/auditlog.cattle.io.auditpolicy/AdditionalRedactions.vue +114 -0
  46. package/edit/auditlog.cattle.io.auditpolicy/Filters.vue +119 -0
  47. package/edit/auditlog.cattle.io.auditpolicy/General.vue +180 -0
  48. package/edit/auditlog.cattle.io.auditpolicy/__tests__/AdditionalRedactions.test.ts +327 -0
  49. package/edit/auditlog.cattle.io.auditpolicy/__tests__/Filters.test.ts +449 -0
  50. package/edit/auditlog.cattle.io.auditpolicy/__tests__/General.test.ts +472 -0
  51. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/AdditionalRedactions.test.ts.snap +27 -0
  52. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/Filters.test.ts.snap +39 -0
  53. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +174 -0
  54. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +29 -0
  55. package/edit/auditlog.cattle.io.auditpolicy/__tests__/index.test.ts +215 -0
  56. package/edit/auditlog.cattle.io.auditpolicy/index.vue +104 -0
  57. package/edit/auditlog.cattle.io.auditpolicy/types.ts +28 -0
  58. package/edit/fleet.cattle.io.gitrepo.vue +16 -1
  59. package/edit/management.cattle.io.project.vue +8 -2
  60. package/edit/management.cattle.io.user.vue +29 -34
  61. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +178 -0
  62. package/edit/provisioning.cattle.io.cluster/rke2.vue +21 -2
  63. package/edit/provisioning.cattle.io.cluster/shared.ts +4 -0
  64. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +1 -0
  65. package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +1 -1
  66. package/list/auditlog.cattle.io.auditpolicy.vue +63 -0
  67. package/list/group.principal.vue +11 -15
  68. package/list/management.cattle.io.user.vue +11 -21
  69. package/machine-config/azure.vue +14 -0
  70. package/mixins/browser-tab-visibility.js +5 -4
  71. package/mixins/fetch.client.js +6 -0
  72. package/models/__tests__/auditlog.cattle.io.auditpolicy.test.ts +117 -0
  73. package/models/__tests__/workload.test.ts +49 -6
  74. package/models/auditlog.cattle.io.auditpolicy.js +46 -0
  75. package/models/cluster.x-k8s.io.machine.js +1 -1
  76. package/models/cluster.x-k8s.io.machinedeployment.js +5 -5
  77. package/models/event.js +5 -0
  78. package/models/ext.cattle.io.groupmembershiprefreshrequest.js +15 -0
  79. package/models/ext.cattle.io.passwordchangerequest.js +15 -0
  80. package/models/ext.cattle.io.selfuser.js +15 -0
  81. package/models/fleet-application.js +17 -7
  82. package/models/management.cattle.io.user.js +28 -31
  83. package/models/schema.js +18 -0
  84. package/models/secret.js +27 -24
  85. package/models/steve-schema.ts +39 -2
  86. package/models/workload.js +3 -2
  87. package/package.json +1 -1
  88. package/pages/account/index.vue +23 -16
  89. package/pages/auth/login.vue +15 -8
  90. package/pages/auth/setup.vue +52 -15
  91. package/pages/home.vue +9 -3
  92. package/plugins/dashboard-store/__tests__/resource-class.test.ts +1 -3
  93. package/plugins/dashboard-store/actions.js +7 -0
  94. package/plugins/dashboard-store/getters.js +23 -1
  95. package/plugins/dashboard-store/index.js +3 -2
  96. package/plugins/dashboard-store/mutations.js +4 -0
  97. package/plugins/dashboard-store/resource-class.js +12 -5
  98. package/plugins/steve/__tests__/steve-class.test.ts +167 -0
  99. package/plugins/steve/schema.d.ts +5 -0
  100. package/plugins/steve/steve-class.js +19 -0
  101. package/plugins/steve/steve-pagination-utils.ts +2 -1
  102. package/store/auth.js +57 -19
  103. package/store/notifications.ts +1 -1
  104. package/store/type-map.js +12 -1
  105. package/types/shell/index.d.ts +10 -14
  106. package/types/store/dashboard-store.types.ts +7 -0
  107. package/utils/pagination-wrapper.ts +11 -3
  108. package/vue.config.js +26 -13
  109. package/edit/provisioning.cattle.io.cluster/defaults.ts +0 -1
@@ -0,0 +1,117 @@
1
+ import AuditPolicy from '@shell/models/auditlog.cattle.io.auditpolicy';
2
+
3
+ describe('auditPolicy Model', () => {
4
+ let mockDispatch: jest.Mock;
5
+ let mockT: jest.Mock;
6
+ let auditPolicy: any;
7
+
8
+ beforeEach(() => {
9
+ mockDispatch = jest.fn();
10
+ mockT = jest.fn();
11
+
12
+ const mockResource = {
13
+ id: 'test-policy',
14
+ spec: { enabled: false },
15
+ metadata: { name: 'test-policy' }
16
+ };
17
+
18
+ auditPolicy = new AuditPolicy(mockResource, {
19
+ dispatch: mockDispatch,
20
+ rootGetters: { 'i18n/t': mockT },
21
+ getters: { schemaFor: () => ({ linkFor: jest.fn() }) }
22
+ });
23
+ });
24
+
25
+ describe('enable method', () => {
26
+ it('should call enableOrDisable with "enable"', () => {
27
+ const spy = jest.spyOn(auditPolicy, 'enableOrDisable').mockImplementation();
28
+
29
+ auditPolicy.enable();
30
+
31
+ expect(spy).toHaveBeenCalledWith('enable');
32
+ });
33
+ });
34
+
35
+ describe('disable method', () => {
36
+ it('should call enableOrDisable with "disable"', () => {
37
+ const spy = jest.spyOn(auditPolicy, 'enableOrDisable').mockImplementation();
38
+
39
+ auditPolicy.disable();
40
+
41
+ expect(spy).toHaveBeenCalledWith('disable');
42
+ });
43
+ });
44
+
45
+ describe('enableOrDisable method', () => {
46
+ let mockClone: any;
47
+
48
+ beforeEach(() => {
49
+ mockClone = {
50
+ spec: { enabled: false },
51
+ save: jest.fn()
52
+ };
53
+
54
+ mockDispatch.mockImplementation((action: string) => {
55
+ if (action === 'rancher/clone') {
56
+ return Promise.resolve(mockClone);
57
+ }
58
+
59
+ return Promise.resolve();
60
+ });
61
+ });
62
+
63
+ it('should enable policy when flag is "enable"', async() => {
64
+ mockClone.save.mockResolvedValue({});
65
+
66
+ await auditPolicy.enableOrDisable('enable');
67
+
68
+ expect(mockClone.spec.enabled).toBe(true);
69
+ expect(mockClone.save).toHaveBeenCalledWith();
70
+ });
71
+
72
+ it('should disable policy when flag is "disable"', async() => {
73
+ mockClone.save.mockResolvedValue({});
74
+
75
+ await auditPolicy.enableOrDisable('disable');
76
+
77
+ expect(mockClone.spec.enabled).toBe(false);
78
+ expect(mockClone.save).toHaveBeenCalledWith();
79
+ });
80
+
81
+ it('should handle save errors and show growl notification', async() => {
82
+ const saveError = new Error('Save failed');
83
+
84
+ mockClone.save.mockRejectedValue(saveError);
85
+ mockT.mockReturnValue('Error when enabling - test-policy');
86
+
87
+ await auditPolicy.enableOrDisable('enable');
88
+
89
+ expect(mockDispatch).toHaveBeenCalledWith('growl/fromError', {
90
+ title: 'Error when enabling - test-policy',
91
+ err: saveError,
92
+ timeout: 5000
93
+ }, { root: true });
94
+ });
95
+
96
+ it('should call translation with correct parameters', async() => {
97
+ const saveError = new Error('Save failed');
98
+
99
+ mockClone.save.mockRejectedValue(saveError);
100
+
101
+ await auditPolicy.enableOrDisable('enable');
102
+
103
+ expect(mockT).toHaveBeenCalledWith('auditPolicy.error.enableOrDisable', {
104
+ flag: 'enable',
105
+ id: 'test-policy'
106
+ });
107
+ });
108
+
109
+ it('should dispatch rancher/clone with correct parameters', async() => {
110
+ mockClone.save.mockResolvedValue({});
111
+
112
+ await auditPolicy.enableOrDisable('enable');
113
+
114
+ expect(mockDispatch).toHaveBeenCalledWith('rancher/clone', { resource: auditPolicy }, { root: true });
115
+ });
116
+ });
117
+ });
@@ -253,6 +253,8 @@ describe('class: Workload', () => {
253
253
  });
254
254
 
255
255
  describe('getter: podsCard', () => {
256
+ const mockPod = { metadata: { name: 'pod-1', namespace: 'default' } };
257
+
256
258
  it('should return card for Deployment type', () => {
257
259
  const workload = new Workload({
258
260
  type: WORKLOAD_TYPES.DEPLOYMENT,
@@ -264,7 +266,7 @@ describe('class: Workload', () => {
264
266
  rootGetters: { 'i18n/t': (key: string) => key },
265
267
  });
266
268
 
267
- Object.defineProperty(workload, 'pods', { get: () => [] });
269
+ Object.defineProperty(workload, 'pods', { get: () => [mockPod] });
268
270
  Object.defineProperty(workload, 'canUpdate', { get: () => true });
269
271
 
270
272
  const card = workload.podsCard;
@@ -285,7 +287,7 @@ describe('class: Workload', () => {
285
287
  rootGetters: { 'i18n/t': (key: string) => key },
286
288
  });
287
289
 
288
- Object.defineProperty(workload, 'pods', { get: () => [] });
290
+ Object.defineProperty(workload, 'pods', { get: () => [mockPod] });
289
291
  Object.defineProperty(workload, 'canUpdate', { get: () => true });
290
292
 
291
293
  const card = workload.podsCard;
@@ -310,7 +312,7 @@ describe('class: Workload', () => {
310
312
  expect(card).toBeNull();
311
313
  });
312
314
 
313
- it('should hide scaling when canUpdate is false', () => {
315
+ it('should return null when pods array is empty', () => {
314
316
  const workload = new Workload({
315
317
  type: WORKLOAD_TYPES.DEPLOYMENT,
316
318
  metadata: { name: 'test', namespace: 'default' },
@@ -322,6 +324,24 @@ describe('class: Workload', () => {
322
324
  });
323
325
 
324
326
  Object.defineProperty(workload, 'pods', { get: () => [] });
327
+
328
+ const card = workload.podsCard;
329
+
330
+ expect(card).toBeNull();
331
+ });
332
+
333
+ it('should hide scaling when canUpdate is false', () => {
334
+ const workload = new Workload({
335
+ type: WORKLOAD_TYPES.DEPLOYMENT,
336
+ metadata: { name: 'test', namespace: 'default' },
337
+ spec: {}
338
+ }, {
339
+ getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
340
+ dispatch: jest.fn(),
341
+ rootGetters: { 'i18n/t': (key: string) => key },
342
+ });
343
+
344
+ Object.defineProperty(workload, 'pods', { get: () => [mockPod] });
325
345
  Object.defineProperty(workload, 'canUpdate', { get: () => false });
326
346
 
327
347
  const card = workload.podsCard;
@@ -331,6 +351,8 @@ describe('class: Workload', () => {
331
351
  });
332
352
 
333
353
  describe('getter: jobsCard', () => {
354
+ const mockJob = { metadata: { name: 'job-1', namespace: 'default' } };
355
+
334
356
  it('should return card for CronJob type', () => {
335
357
  const workload = new Workload({
336
358
  type: WORKLOAD_TYPES.CRON_JOB,
@@ -342,7 +364,7 @@ describe('class: Workload', () => {
342
364
  rootGetters: { 'i18n/t': (key: string) => key },
343
365
  });
344
366
 
345
- Object.defineProperty(workload, 'jobs', { get: () => [] });
367
+ Object.defineProperty(workload, 'jobs', { get: () => [mockJob] });
346
368
 
347
369
  const card = workload.jobsCard;
348
370
 
@@ -366,9 +388,30 @@ describe('class: Workload', () => {
366
388
 
367
389
  expect(card).toBeNull();
368
390
  });
391
+
392
+ it('should return null when jobs array is empty', () => {
393
+ const workload = new Workload({
394
+ type: WORKLOAD_TYPES.CRON_JOB,
395
+ metadata: { name: 'test', namespace: 'default' },
396
+ spec: {}
397
+ }, {
398
+ getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
399
+ dispatch: jest.fn(),
400
+ rootGetters: { 'i18n/t': (key: string) => key },
401
+ });
402
+
403
+ Object.defineProperty(workload, 'jobs', { get: () => [] });
404
+
405
+ const card = workload.jobsCard;
406
+
407
+ expect(card).toBeNull();
408
+ });
369
409
  });
370
410
 
371
411
  describe('getter: cards', () => {
412
+ const mockPod = { metadata: { name: 'pod-1', namespace: 'default' } };
413
+ const mockJob = { metadata: { name: 'job-1', namespace: 'default' } };
414
+
372
415
  it('should include podsCard for Deployment', () => {
373
416
  const workload = new Workload({
374
417
  type: WORKLOAD_TYPES.DEPLOYMENT,
@@ -384,7 +427,7 @@ describe('class: Workload', () => {
384
427
  },
385
428
  });
386
429
 
387
- Object.defineProperty(workload, 'pods', { get: () => [] });
430
+ Object.defineProperty(workload, 'pods', { get: () => [mockPod] });
388
431
  Object.defineProperty(workload, 'canUpdate', { get: () => true });
389
432
 
390
433
  const cards = workload.cards;
@@ -411,7 +454,7 @@ describe('class: Workload', () => {
411
454
  },
412
455
  });
413
456
 
414
- Object.defineProperty(workload, 'jobs', { get: () => [] });
457
+ Object.defineProperty(workload, 'jobs', { get: () => [mockJob] });
415
458
 
416
459
  const cards = workload.cards;
417
460
  const nonNullCards = cards.filter((c: any) => c !== null);
@@ -0,0 +1,46 @@
1
+ import { insertAt } from '@shell/utils/array';
2
+ import SteveModel from '@shell/plugins/steve/steve-class';
3
+
4
+ export default class AuditPolicy extends SteveModel {
5
+ get _availableActions() {
6
+ const out = super._availableActions;
7
+
8
+ insertAt(out, 0, {
9
+ action: 'enable',
10
+ label: this.t('action.enable'),
11
+ icon: 'icon icon-play',
12
+ enabled: (this.canEdit || this.canEditYaml) && !this.spec.enabled,
13
+ bulkable: true,
14
+ weight: 2,
15
+ });
16
+ insertAt(out, 0, {
17
+ action: 'disable',
18
+ label: this.t('action.disable'),
19
+ icon: 'icon icon-pause',
20
+ enabled: (this.canEdit || this.canEditYaml) && this.spec.enabled,
21
+ bulkable: true,
22
+ weight: 1,
23
+ });
24
+
25
+ return out;
26
+ }
27
+
28
+ enable() {
29
+ this.enableOrDisable('enable');
30
+ }
31
+
32
+ disable() {
33
+ this.enableOrDisable('disable');
34
+ }
35
+
36
+ async enableOrDisable(flag) {
37
+ const clone = await this.$dispatch('rancher/clone', { resource: this }, { root: true });
38
+
39
+ clone.spec.enabled = flag === 'enable';
40
+ await clone.save().catch((err) => {
41
+ this.$dispatch('growl/fromError', {
42
+ title: this.t('auditPolicy.error.enableOrDisable', { flag, id: this.id }), err, timeout: 5000
43
+ }, { root: true });
44
+ });
45
+ }
46
+ }
@@ -138,7 +138,7 @@ export default class CapiMachine extends SteveModel {
138
138
 
139
139
  async machineRef() {
140
140
  const ref = this.spec.infrastructureRef;
141
- const id = `${ ref.namespace }/${ ref.name }`;
141
+ const id = `${ this.metadata.namespace }/${ ref.name }`;
142
142
  const kind = `rke-machine.cattle.io.${ ref.kind.toLowerCase() }`;
143
143
 
144
144
  return await this.$dispatch('find', { type: kind, id });
@@ -41,12 +41,12 @@ export default class CapiMachineDeployment extends SteveModel {
41
41
  }
42
42
 
43
43
  get templateType() {
44
- return this.spec.template.spec.infrastructureRef.kind ? `rke-machine.cattle.io.${ this.spec.template.spec.infrastructureRef.kind.toLowerCase() }` : null;
44
+ return this.infrastructureRefKind ? `rke-machine.cattle.io.${ this.infrastructureRefKind.toLowerCase() }` : null;
45
45
  }
46
46
 
47
47
  get template() {
48
48
  const ref = this.spec.template.spec.infrastructureRef;
49
- const id = `${ ref.namespace }/${ ref.name }`;
49
+ const id = `${ this.metadata.namespace }/${ ref.name }`;
50
50
  const template = this.$rootGetters['management/byId'](this.templateType, id);
51
51
 
52
52
  return template;
@@ -92,15 +92,15 @@ export default class CapiMachineDeployment extends SteveModel {
92
92
  }
93
93
 
94
94
  get outdated() {
95
- return Math.max(0, (this.status?.replicas || 0) - (this.status?.updatedReplicas || 0));
95
+ return Math.max(0, (this.status?.replicas || 0) - (this.status?.upToDateReplicas || 0));
96
96
  }
97
97
 
98
98
  get ready() {
99
- return Math.max(0, (this.status?.replicas || 0) - (this.status?.unavailableReplicas || 0));
99
+ return this.status?.availableReplicas || 0;
100
100
  }
101
101
 
102
102
  get unavailable() {
103
- return this.status?.unavailableReplicas || 0;
103
+ return Math.max(0, (this.status?.replicas || 0) - (this.status?.availableReplicas || 0));
104
104
  }
105
105
 
106
106
  get isControlPlane() {
package/models/event.js CHANGED
@@ -38,4 +38,9 @@ export default class K8sEvent extends SteveModel {
38
38
 
39
39
  return schema && rowValueGetter ? rowValueGetter(schema, 'Last Seen')(this) : null;
40
40
  }
41
+
42
+ // Because we're using eventType which is a non-standard state we don't have a reliable way to provide a state color anymore and have therefore disabled the color.
43
+ get insightsColor() {
44
+ return 'disabled';
45
+ }
41
46
  }
@@ -0,0 +1,15 @@
1
+ import SteveModel from '@shell/plugins/steve/steve-class';
2
+
3
+ export default class GroupMembershipRefreshRequest extends SteveModel {
4
+ get canRefreshMemberships() {
5
+ return this.schema?.collectionMethods.find((x) => x.toLowerCase() === 'post');
6
+ }
7
+
8
+ cleanForSave(data) {
9
+ const val = super.cleanForSave(data);
10
+
11
+ delete val.type;
12
+
13
+ return val;
14
+ }
15
+ }
@@ -0,0 +1,15 @@
1
+ import SteveModel from '@shell/plugins/steve/steve-class';
2
+
3
+ export default class PasswordChangeRequest extends SteveModel {
4
+ get canChangePassword() {
5
+ return this.schema?.collectionMethods.find((x) => x.toLowerCase() === 'post');
6
+ }
7
+
8
+ cleanForSave(data) {
9
+ const val = super.cleanForSave(data);
10
+
11
+ delete val.type;
12
+
13
+ return val;
14
+ }
15
+ }
@@ -0,0 +1,15 @@
1
+ import SteveModel from '@shell/plugins/steve/steve-class';
2
+
3
+ export default class SelfUser extends SteveModel {
4
+ get canGetUser() {
5
+ return this.schema?.collectionMethods.find((x) => x.toLowerCase() === 'post');
6
+ }
7
+
8
+ cleanForSave(data) {
9
+ const val = super.cleanForSave(data);
10
+
11
+ delete val.type;
12
+
13
+ return val;
14
+ }
15
+ }
@@ -1,7 +1,7 @@
1
1
  import { matching, convertSelectorObj } from '@shell/utils/selector';
2
2
  import isEmpty from 'lodash/isEmpty';
3
3
  import { escapeHtml } from '@shell/utils/string';
4
- import { FLEET } from '@shell/config/types';
4
+ import { FLEET, MANAGEMENT } from '@shell/config/types';
5
5
  import { FLEET as FLEET_ANNOTATIONS } from '@shell/config/labels-annotations';
6
6
  import { addObject, addObjects, findBy } from '@shell/utils/array';
7
7
  import SteveModel from '@shell/plugins/steve/steve-class';
@@ -30,18 +30,28 @@ function normalizeStateCounts(data) {
30
30
 
31
31
  export default class FleetApplication extends SteveModel {
32
32
  async getCurrentUser() {
33
- const user = this.$rootGetters['auth/v3User'];
33
+ const user = this.$rootGetters['auth/user'];
34
34
 
35
35
  if (user?.id) {
36
36
  return user;
37
37
  }
38
38
 
39
- const res = await this.$dispatch('rancher/request', {
40
- url: '/v3/users?me=true',
41
- method: 'get',
42
- }, { root: true });
39
+ const selfUser = await this.$dispatch('auth/getSelfUser');
43
40
 
44
- return res?.data?.[0] || {};
41
+ if (selfUser?.canGetUser && selfUser.status?.userID) {
42
+ const user = await this.$dispatch('management/find', {
43
+ type: MANAGEMENT.USER,
44
+ id: selfUser.status?.userID
45
+ }, { root: true });
46
+
47
+ if (user) {
48
+ this.$dispatch('auth/gotUser', user, { root: true });
49
+
50
+ return user;
51
+ }
52
+ }
53
+
54
+ return {};
45
55
  }
46
56
 
47
57
  pause() {
@@ -1,8 +1,8 @@
1
- import { NORMAN } from '@shell/config/types';
2
- import HybridModel, { cleanHybridResources } from '@shell/plugins/steve/hybrid-class';
1
+ import { NORMAN, EXT } from '@shell/config/types';
2
+ import SteveModel from '@shell/plugins/steve/steve-class';
3
3
  import day from 'dayjs';
4
4
 
5
- export default class User extends HybridModel {
5
+ export default class User extends SteveModel {
6
6
  // Preserve description
7
7
  constructor(data, ctx, rehydrateNamespace = null, setClone = false) {
8
8
  const _description = data.description;
@@ -11,16 +11,6 @@ export default class User extends HybridModel {
11
11
  this.description = _description;
12
12
  }
13
13
 
14
- // Clean the Norman properties, but keep description
15
- cleanResource(data) {
16
- const desc = data.description;
17
- const clean = cleanHybridResources(data);
18
-
19
- clean._description = desc;
20
-
21
- return clean;
22
- }
23
-
24
14
  get isSystem() {
25
15
  for ( const p of this.principalIds || [] ) {
26
16
  if ( p.startsWith('system://') ) {
@@ -175,13 +165,13 @@ export default class User extends HybridModel {
175
165
  const clone = await this.$dispatch('clone', { resource: this });
176
166
 
177
167
  // Remove local properties
178
- delete clone.canRefreshAccess;
168
+ delete clone.canRefreshMemberships;
179
169
 
180
170
  return clone._save(opt);
181
171
  }
182
172
 
183
173
  async setEnabled(enabled) {
184
- const clone = await this.$dispatch('rancher/clone', { resource: this.norman }, { root: true });
174
+ const clone = await this.$dispatch('clone', { resource: this });
185
175
 
186
176
  clone.enabled = enabled;
187
177
  await clone.save();
@@ -204,12 +194,21 @@ export default class User extends HybridModel {
204
194
  }
205
195
 
206
196
  async refreshGroupMembership() {
207
- const user = await this.$dispatch('rancher/find', {
208
- type: NORMAN.USER,
209
- id: this.id,
210
- }, { root: true });
197
+ const membershipRefreshRequests = await this.$dispatch('create', { type: EXT.GROUP_MEMBERSHIP_REFRESH_REQUESTS });
211
198
 
212
- await user.doAction('refreshauthprovideraccess');
199
+ // userId specifies the user ID. Use '*' for all users. Check the schemaDefinition for more details.
200
+ membershipRefreshRequests.spec = { userId: this.id };
201
+ await membershipRefreshRequests.save();
202
+ }
203
+
204
+ get canRefreshMemberships() {
205
+ const schema = this.$getters[`schemaFor`](EXT.GROUP_MEMBERSHIP_REFRESH_REQUESTS);
206
+
207
+ if (!schema) {
208
+ return false;
209
+ }
210
+
211
+ return schema?.collectionMethods.find((x) => x.toLowerCase() === 'post');
213
212
  }
214
213
 
215
214
  canActivate(state) {
@@ -243,7 +242,7 @@ export default class User extends HybridModel {
243
242
  action: 'refreshGroupMembership',
244
243
  label: this.t('authGroups.actions.refresh'),
245
244
  icon: 'icon icon-refresh',
246
- enabled: this.canRefreshAccess
245
+ enabled: this.canRefreshMemberships
247
246
  },
248
247
  { divider: true },
249
248
  ...super._availableActions,
@@ -284,19 +283,17 @@ export default class User extends HybridModel {
284
283
  return true;
285
284
  }
286
285
 
287
- get norman() {
288
- return this.$rootGetters['rancher/byId'](NORMAN.USER, this.id);
289
- }
286
+ cleanForSave(data) {
287
+ const val = super.cleanForSave(data);
290
288
 
291
- get canDelete() {
292
- return this.norman?.hasLink('remove') && !this.isCurrentUser;
293
- }
289
+ delete val.type;
294
290
 
295
- get canUpdate() {
296
- return this.norman?.hasLink('update');
291
+ return val;
297
292
  }
298
293
 
299
- remove() {
300
- return this.norman?.remove();
294
+ get norman() {
295
+ console.warn('Norman "user" is deprecated. Use Steve "management.cattle.io.user" user instead.'); // eslint-disable-line no-console
296
+
297
+ return this.$rootGetters['rancher/byId'](NORMAN.USER, this.id);
301
298
  }
302
299
  }
package/models/schema.js CHANGED
@@ -7,6 +7,24 @@ export default class Schema extends Resource {
7
7
  get groupName() {
8
8
  return this.attributes.namespaced ? 'ns' : 'cluster';
9
9
  }
10
+
11
+ /**
12
+ * Legacy check to determine if the user can GET a specific resource of this type.
13
+ *
14
+ * This supports things like spoof or norman schemas.
15
+ */
16
+ get canGet() {
17
+ return this.hasLink('collection');
18
+ }
19
+
20
+ /**
21
+ * Legacy check to determine if the user can LIST a resource of this type.
22
+ *
23
+ * This supports things like spoof or norman schemas.
24
+ */
25
+ get canList() {
26
+ return this.hasLink('collection');
27
+ }
10
28
  }
11
29
 
12
30
  /**
package/models/secret.js CHANGED
@@ -3,6 +3,7 @@ import { CERTMANAGER, KUBERNETES, UI_PROJECT_SECRET, UI_PROJECT_SECRET_COPY } fr
3
3
  import { base64Decode, base64Encode } from '@shell/utils/crypto';
4
4
  import { removeObjects } from '@shell/utils/array';
5
5
  import { MANAGEMENT, SERVICE_ACCOUNT, VIRTUAL_TYPES } from '@shell/config/types';
6
+ import { SECRET_SCOPE, SECRET_QUERY_PARAMS } from '@shell/config/query-params';
6
7
  import { set } from '@shell/utils/object';
7
8
  import { NAME as MANAGER } from '@shell/config/product/manager';
8
9
  import SteveModel from '@shell/plugins/steve/steve-class';
@@ -491,18 +492,16 @@ export default class Secret extends SteveModel {
491
492
  return steveCleanForDownload(yaml, { rootKeys: ['id', 'links', 'actions'] });
492
493
  }
493
494
 
494
- /**
495
- * is this a project scoped secret .... or also a cloned project scoped secret
496
- */
497
- get isProjectScopedRelated() {
498
- return !!this.metadata.labels?.[UI_PROJECT_SECRET];
499
- }
500
-
501
495
  /**
502
496
  * is this a project scoped secret
503
497
  */
504
498
  get isProjectScoped() {
505
- return this.isProjectScopedRelated && !this.isProjectSecretCopy && this.$rootGetters['isRancher'];
499
+ /**
500
+ * is this a project scoped secret .... or also a cloned project scoped secret
501
+ */
502
+ const isProjectScopedRelated = !!this.metadata.labels?.[UI_PROJECT_SECRET];
503
+
504
+ return isProjectScopedRelated && !this.isProjectSecretCopy && this.$rootGetters['isRancher'];
506
505
  }
507
506
 
508
507
  get projectScopedClusterId() {
@@ -588,34 +587,38 @@ export default class Secret extends SteveModel {
588
587
  }
589
588
 
590
589
  get listLocation() {
591
- if (!this.isProjectScoped) {
592
- return super.listLocation;
590
+ if (this.hasProjectScopedUrlQueryParam || this.isProjectScoped) {
591
+ return {
592
+ name: 'c-cluster-product-resource',
593
+ params: {
594
+ product: this.$rootGetters['productId'],
595
+ cluster: this.$rootGetters['clusterId'],
596
+ resource: VIRTUAL_TYPES.PROJECT_SECRETS,
597
+ }
598
+ };
593
599
  }
594
600
 
595
- return {
596
- name: 'c-cluster-product-resource',
597
- params: {
598
- product: this.$rootGetters['productId'],
599
- cluster: this.$rootGetters['clusterId'],
600
- resource: VIRTUAL_TYPES.PROJECT_SECRETS,
601
- }
602
- };
601
+ return super.listLocation;
602
+ }
603
+
604
+ get hasProjectScopedUrlQueryParam() {
605
+ return this.currentRoute()?.query?.[SECRET_SCOPE] === SECRET_QUERY_PARAMS.PROJECT_SCOPED;
603
606
  }
604
607
 
605
608
  get parentNameOverride() {
606
- if (!this.isProjectScoped) {
607
- return super.parentNameOverride;
609
+ if (this.hasProjectScopedUrlQueryParam || this.isProjectScoped) {
610
+ return this.$rootGetters['i18n/t'](`typeLabel."${ VIRTUAL_TYPES.PROJECT_SECRETS }"`, { count: 1 })?.trim();
608
611
  }
609
612
 
610
- return this.$rootGetters['i18n/t'](`typeLabel."${ VIRTUAL_TYPES.PROJECT_SECRETS }"`, { count: 1 })?.trim();
613
+ return super.parentNameOverride;
611
614
  }
612
615
 
613
616
  get parentLocationOverride() {
614
- if (!this.isProjectScoped) {
615
- return super.parentNameOverride;
617
+ if (this.hasProjectScopedUrlQueryParam || this.isProjectScoped) {
618
+ return this.listLocation;
616
619
  }
617
620
 
618
- return this.listLocation;
621
+ return super.parentLocationOverride;
619
622
  }
620
623
 
621
624
  get groupByProject() {