@rancher/shell 3.0.9-rc.3 → 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 (128) hide show
  1. package/assets/brand/suse/metadata.json +2 -1
  2. package/assets/translations/en-us.yaml +105 -5
  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/KubeconfigClusters.vue +74 -0
  25. package/components/formatter/MachineSummaryGraph.vue +10 -2
  26. package/components/formatter/__tests__/KubeconfigClusters.test.ts +125 -0
  27. package/components/nav/TopLevelMenu.helper.ts +50 -2
  28. package/components/nav/TopLevelMenu.vue +14 -0
  29. package/components/nav/Type.vue +5 -0
  30. package/components/nav/__tests__/TopLevelMenu.test.ts +3 -3
  31. package/components/nav/__tests__/Type.test.ts +6 -4
  32. package/config/product/explorer.js +4 -3
  33. package/config/product/manager.js +47 -3
  34. package/config/router/navigation-guards/authentication.js +8 -9
  35. package/config/router/routes.js +4 -1
  36. package/config/types.js +10 -2
  37. package/detail/auditlog.cattle.io.auditpolicy.vue +19 -0
  38. package/detail/management.cattle.io.user.vue +1 -2
  39. package/detail/node.vue +0 -1
  40. package/detail/provisioning.cattle.io.cluster.vue +2 -1
  41. package/dialog/ChangePasswordDialog.vue +8 -0
  42. package/dialog/GenericPrompt.vue +20 -3
  43. package/dialog/ScaleMachineDownDialog.vue +65 -15
  44. package/dialog/SearchDialog.vue +10 -2
  45. package/dialog/__tests__/ScaleMachineDownDialog.test.ts +184 -0
  46. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +89 -0
  47. package/edit/__tests__/management.cattle.io.project.test.js +56 -1
  48. package/edit/auditlog.cattle.io.auditpolicy/AdditionalRedactions.vue +114 -0
  49. package/edit/auditlog.cattle.io.auditpolicy/Filters.vue +119 -0
  50. package/edit/auditlog.cattle.io.auditpolicy/General.vue +180 -0
  51. package/edit/auditlog.cattle.io.auditpolicy/__tests__/AdditionalRedactions.test.ts +327 -0
  52. package/edit/auditlog.cattle.io.auditpolicy/__tests__/Filters.test.ts +449 -0
  53. package/edit/auditlog.cattle.io.auditpolicy/__tests__/General.test.ts +472 -0
  54. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/AdditionalRedactions.test.ts.snap +27 -0
  55. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/Filters.test.ts.snap +39 -0
  56. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +174 -0
  57. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +29 -0
  58. package/edit/auditlog.cattle.io.auditpolicy/__tests__/index.test.ts +215 -0
  59. package/edit/auditlog.cattle.io.auditpolicy/index.vue +104 -0
  60. package/edit/auditlog.cattle.io.auditpolicy/types.ts +28 -0
  61. package/edit/fleet.cattle.io.gitrepo.vue +16 -1
  62. package/edit/management.cattle.io.project.vue +8 -2
  63. package/edit/management.cattle.io.user.vue +29 -34
  64. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +178 -0
  65. package/edit/provisioning.cattle.io.cluster/rke2.vue +22 -2
  66. package/edit/provisioning.cattle.io.cluster/shared.ts +4 -0
  67. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +1 -0
  68. package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +57 -2
  69. package/edit/provisioning.cattle.io.cluster/tabs/etcd/__tests__/S3Config.test.ts +109 -0
  70. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +1 -0
  71. package/list/auditlog.cattle.io.auditpolicy.vue +63 -0
  72. package/list/ext.cattle.io.kubeconfig.vue +118 -0
  73. package/list/group.principal.vue +11 -15
  74. package/list/management.cattle.io.user.vue +11 -21
  75. package/machine-config/azure.vue +14 -0
  76. package/mixins/__tests__/chart.test.ts +147 -0
  77. package/mixins/browser-tab-visibility.js +5 -4
  78. package/mixins/chart.js +10 -8
  79. package/mixins/fetch.client.js +6 -0
  80. package/models/__tests__/auditlog.cattle.io.auditpolicy.test.ts +117 -0
  81. package/models/__tests__/ext.cattle.io.kubeconfig.test.ts +364 -0
  82. package/models/__tests__/secret.test.ts +55 -0
  83. package/models/__tests__/workload.test.ts +49 -6
  84. package/models/auditlog.cattle.io.auditpolicy.js +46 -0
  85. package/models/cluster.x-k8s.io.machine.js +1 -1
  86. package/models/cluster.x-k8s.io.machinedeployment.js +5 -5
  87. package/models/event.js +5 -0
  88. package/models/ext.cattle.io.groupmembershiprefreshrequest.js +15 -0
  89. package/models/ext.cattle.io.kubeconfig.ts +97 -0
  90. package/models/ext.cattle.io.passwordchangerequest.js +15 -0
  91. package/models/ext.cattle.io.selfuser.js +15 -0
  92. package/models/fleet-application.js +17 -7
  93. package/models/management.cattle.io.user.js +28 -31
  94. package/models/schema.js +18 -0
  95. package/models/secret.js +28 -25
  96. package/models/steve-schema.ts +39 -2
  97. package/models/workload.js +3 -2
  98. package/package.json +2 -2
  99. package/pages/about.vue +3 -2
  100. package/pages/account/index.vue +23 -16
  101. package/pages/auth/login.vue +15 -8
  102. package/pages/auth/setup.vue +52 -15
  103. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +38 -14
  104. package/pages/c/_cluster/apps/charts/index.vue +1 -0
  105. package/pages/home.vue +9 -3
  106. package/plugins/dashboard-store/__tests__/resource-class.test.ts +1 -3
  107. package/plugins/dashboard-store/actions.js +7 -0
  108. package/plugins/dashboard-store/getters.js +23 -1
  109. package/plugins/dashboard-store/index.js +3 -2
  110. package/plugins/dashboard-store/mutations.js +4 -0
  111. package/plugins/dashboard-store/resource-class.js +12 -5
  112. package/plugins/steve/__tests__/steve-class.test.ts +167 -0
  113. package/plugins/steve/schema.d.ts +5 -0
  114. package/plugins/steve/steve-class.js +19 -0
  115. package/plugins/steve/steve-pagination-utils.ts +2 -1
  116. package/rancher-components/RcItemCard/RcItemCard.test.ts +4 -2
  117. package/rancher-components/RcItemCard/RcItemCard.vue +27 -10
  118. package/store/auth.js +57 -19
  119. package/store/notifications.ts +1 -1
  120. package/store/type-map.js +12 -1
  121. package/types/shell/index.d.ts +24 -15
  122. package/types/store/dashboard-store.types.ts +7 -0
  123. package/utils/__tests__/chart.test.ts +96 -0
  124. package/utils/__tests__/version.test.ts +1 -19
  125. package/utils/chart.js +64 -0
  126. package/utils/pagination-wrapper.ts +11 -3
  127. package/utils/version.js +5 -17
  128. package/vue.config.js +26 -13
@@ -0,0 +1,125 @@
1
+ import { mount, RouterLinkStub } from '@vue/test-utils';
2
+ import KubeconfigClusters from '@shell/components/formatter/KubeconfigClusters.vue';
3
+
4
+ describe('component: KubeconfigClusters', () => {
5
+ const MAX_DISPLAY = 25;
6
+
7
+ const createCluster = (label: string, hasLocation = true) => ({
8
+ label,
9
+ location: hasLocation ? { name: 'cluster-detail', params: { cluster: label } } : null
10
+ });
11
+
12
+ const createClusters = (count: number, hasLocation = true) => {
13
+ return Array.from({ length: count }, (_, i) => createCluster(`cluster-${ i + 1 }`, hasLocation));
14
+ };
15
+
16
+ const defaultMocks = { t: (key: string, args: Record<string, unknown>) => `+ ${ args.remainingCount } more` };
17
+
18
+ const mountComponent = (clusters: unknown[] = [], mocks = defaultMocks) => {
19
+ return mount(KubeconfigClusters, {
20
+ props: { row: { id: 'test-row', sortedReferencedClusters: clusters } },
21
+ global: {
22
+ mocks,
23
+ stubs: { 'router-link': RouterLinkStub }
24
+ }
25
+ });
26
+ };
27
+
28
+ describe('displaying clusters', () => {
29
+ it('should display a dash when there are no clusters', () => {
30
+ const wrapper = mountComponent([]);
31
+ const emptySpan = wrapper.find('.text-muted');
32
+
33
+ expect(emptySpan.text()).toBe('—');
34
+ });
35
+
36
+ it('should display cluster labels with router-links when clusters have locations', () => {
37
+ const clusters = [createCluster('local'), createCluster('downstream')];
38
+ const wrapper = mountComponent(clusters);
39
+ const links = wrapper.findAllComponents(RouterLinkStub);
40
+
41
+ expect(links).toHaveLength(2);
42
+ expect(links[0].text()).toBe('local');
43
+ expect(links[1].text()).toBe('downstream');
44
+ });
45
+
46
+ it('should display cluster labels as text-muted spans when clusters have no location', () => {
47
+ const clusters = [createCluster('deleted-cluster', false)];
48
+ const wrapper = mountComponent(clusters);
49
+ const mutedSpan = wrapper.find('.text-muted');
50
+
51
+ expect(mutedSpan.text()).toBe('deleted-cluster');
52
+ expect(wrapper.findComponent(RouterLinkStub).exists()).toBe(false);
53
+ });
54
+
55
+ it('should separate clusters with commas', () => {
56
+ const clusters = [createCluster('cluster-1'), createCluster('cluster-2')];
57
+ const wrapper = mountComponent(clusters);
58
+
59
+ expect(wrapper.text()).toContain(',');
60
+ });
61
+ });
62
+
63
+ describe('max display limit', () => {
64
+ it('should display all clusters when count is at or below the limit', () => {
65
+ const clusters = createClusters(MAX_DISPLAY);
66
+ const wrapper = mountComponent(clusters);
67
+ const links = wrapper.findAllComponents(RouterLinkStub);
68
+
69
+ expect(links).toHaveLength(MAX_DISPLAY);
70
+ expect(wrapper.text()).not.toContain('more');
71
+ });
72
+
73
+ it('should limit displayed clusters to MAX_DISPLAY', () => {
74
+ const clusters = createClusters(MAX_DISPLAY + 10);
75
+ const wrapper = mountComponent(clusters);
76
+ const links = wrapper.findAllComponents(RouterLinkStub);
77
+
78
+ expect(links).toHaveLength(MAX_DISPLAY);
79
+ });
80
+
81
+ it('should show remaining count when clusters exceed the limit', () => {
82
+ const totalClusters = MAX_DISPLAY + 5;
83
+ const clusters = createClusters(totalClusters);
84
+ const wrapper = mountComponent(clusters);
85
+
86
+ expect(wrapper.text()).toContain('+ 5 more');
87
+ });
88
+
89
+ it('should show correct remaining count for large cluster lists', () => {
90
+ const totalClusters = MAX_DISPLAY + 100;
91
+ const clusters = createClusters(totalClusters);
92
+ const wrapper = mountComponent(clusters);
93
+
94
+ expect(wrapper.text()).toContain('+ 100 more');
95
+ });
96
+ });
97
+
98
+ describe('computed properties', () => {
99
+ it('should return empty array for allClusters when row has no sortedReferencedClusters', () => {
100
+ const wrapper = mount(KubeconfigClusters, {
101
+ props: { row: { id: 'test-row' } },
102
+ global: {
103
+ mocks: defaultMocks,
104
+ stubs: { 'router-link': RouterLinkStub }
105
+ }
106
+ });
107
+
108
+ expect(wrapper.vm.allClusters).toStrictEqual([]);
109
+ });
110
+
111
+ it('should calculate remainingCount as 0 when clusters are at or below limit', () => {
112
+ const clusters = createClusters(MAX_DISPLAY);
113
+ const wrapper = mountComponent(clusters);
114
+
115
+ expect(wrapper.vm.remainingCount).toBe(0);
116
+ });
117
+
118
+ it('should calculate correct remainingCount when clusters exceed limit', () => {
119
+ const clusters = createClusters(MAX_DISPLAY + 15);
120
+ const wrapper = mountComponent(clusters);
121
+
122
+ expect(wrapper.vm.remainingCount).toBe(15);
123
+ });
124
+ });
125
+ });
@@ -1,5 +1,6 @@
1
- import { CAPI, MANAGEMENT } from '@shell/config/types';
1
+ import { CAPI, MANAGEMENT, SAVED_COUNTS } from '@shell/config/types';
2
2
  import { STORE } from '@shell/store/store-types';
3
+ import { ActionFindPageArgs } from '@shell/types/store/dashboard-store.types';
3
4
  import { PaginationParam, PaginationParamFilter, PaginationSort } from '@shell/types/store/pagination.types';
4
5
  import { VuexStore } from '@shell/types/store/vuex';
5
6
  import { filterHiddenLocalCluster, filterOnlyKubernetesClusters, paginationFilterClusters } from '@shell/utils/cluster';
@@ -100,6 +101,8 @@ export interface TopLevelMenuHelper {
100
101
  * Cleanup on destroy of TopLevelMenu
101
102
  */
102
103
  destroy: () => Promise<void>;
104
+
105
+ updateCount: (count: number) => Promise<void>;
103
106
  }
104
107
 
105
108
  export abstract class BaseTopLevelMenuHelper {
@@ -172,6 +175,8 @@ export class TopLevelMenuHelperPagination extends BaseTopLevelMenuHelper impleme
172
175
  private clustersOthersWrapper: PaginationWrapper<any>;
173
176
  private provClusterWrapper: PaginationWrapper<any>;
174
177
 
178
+ private clusterCount = 0;
179
+
175
180
  constructor({ $store }: {
176
181
  $store: VuexStore,
177
182
  }) {
@@ -216,7 +221,7 @@ export class TopLevelMenuHelperPagination extends BaseTopLevelMenuHelper impleme
216
221
  context: 'side-bar',
217
222
  }
218
223
  },
219
- formatResponse: { classify: true }
224
+ formatResponse: { classify: true },
220
225
  });
221
226
  // Fetch all prov clusters for the mgmt clusters we have
222
227
  this.provClusterWrapper = new PaginationWrapper({
@@ -400,6 +405,47 @@ export class TopLevelMenuHelperPagination extends BaseTopLevelMenuHelper impleme
400
405
  .then((r) => r.data);
401
406
  }
402
407
 
408
+ /**
409
+ * Update the cluster count used when showing lists of home page + resource menu cluster count
410
+ *
411
+ * This is a convenient place to make the request
412
+ */
413
+ public async updateCount(count: number) {
414
+ if (count === this.clusterCount) {
415
+ return;
416
+ }
417
+
418
+ this.clusterCount = count;
419
+
420
+ try {
421
+ const commonClusterFilters = paginationFilterClusters({ getters: this.$store.getters });
422
+
423
+ if (commonClusterFilters.length === 0) {
424
+ // We're not filtering out harvester clusters or local cluster, so no need to tweak the saved count for clusters
425
+ return;
426
+ }
427
+
428
+ const args:ActionFindPageArgs = {
429
+ pagination: {
430
+ filters: commonClusterFilters,
431
+ page: 1,
432
+ pageSize: 1,
433
+ sort: [],
434
+ projectsOrNamespaces: [],
435
+ },
436
+ transient: true,
437
+ saveCountAs: SAVED_COUNTS.K8S_CLUSTERS
438
+ };
439
+
440
+ await this.$store.dispatch('management/findPage', {
441
+ type: MANAGEMENT.CLUSTER,
442
+ opt: args
443
+ });
444
+ } catch (err) {
445
+ console.warn('Unable to set saved count for clusters', err); // eslint-disable-line no-console
446
+ }
447
+ }
448
+
403
449
  /**
404
450
  * Find all provisioning clusters associated with the displayed mgmt clusters
405
451
  */
@@ -595,6 +641,8 @@ export class TopLevelMenuHelperLegacy extends BaseTopLevelMenuHelper implements
595
641
 
596
642
  return sorted;
597
643
  }
644
+
645
+ public async updateCount(count: number) {}
598
646
  }
599
647
 
600
648
  /**
@@ -270,6 +270,12 @@ export default {
270
270
  const value = hideLocalSetting.value || hideLocalSetting.default || 'false';
271
271
 
272
272
  return value === 'true';
273
+ },
274
+
275
+ clusterCountsFromCounts() {
276
+ const counts = this.$store.getters[`management/all`](COUNT)?.[0]?.counts || {};
277
+
278
+ return counts[CAPI.RANCHER_CLUSTER]?.summary.count;
273
279
  }
274
280
  },
275
281
 
@@ -331,7 +337,15 @@ export default {
331
337
 
332
338
  hideLocalCluster() {
333
339
  this.updateClusters(this.pinnedIds, 'slow');
340
+ },
341
+
342
+ clusterCountsFromCounts: {
343
+ async handler(neu, old) {
344
+ await this.helper.updateCount(neu);
345
+ },
346
+ immediate: true,
334
347
  }
348
+
335
349
  },
336
350
 
337
351
  mounted() {
@@ -58,6 +58,11 @@ export default {
58
58
  }
59
59
 
60
60
  const inStore = this.$store.getters['currentStore'](this.type.name);
61
+ const typeOptions = this.$store.getters[`type-map/optionsFor`](this.type.name);
62
+
63
+ if (typeOptions?.custom?.countGetter && typeof typeOptions.custom?.countGetter === 'function') {
64
+ return typeOptions.custom.countGetter(this.$store.getters);
65
+ }
61
66
 
62
67
  return this.$store.getters[`${ inStore }/count`]({ name: this.type.name });
63
68
  },
@@ -595,7 +595,7 @@ describe('topLevelMenu', () => {
595
595
  jest.spyOn(sideNavService, 'init').mockImplementation(() => {});
596
596
  const updateSpy = jest.fn();
597
597
  const mockHelper = {
598
- update: updateSpy, clustersPinned: [], clustersOthers: []
598
+ update: updateSpy, clustersPinned: [], clustersOthers: [], updateCount: () => {}
599
599
  };
600
600
 
601
601
  jest.spyOn(sideNavService, 'helper', 'get').mockReturnValue(mockHelper as any);
@@ -624,7 +624,7 @@ describe('topLevelMenu', () => {
624
624
  jest.spyOn(sideNavService, 'init').mockImplementation(() => {});
625
625
  const updateSpy = jest.fn();
626
626
  const mockHelper = {
627
- update: updateSpy, clustersPinned: [], clustersOthers: []
627
+ update: updateSpy, clustersPinned: [], clustersOthers: [], updateCount: () => {}
628
628
  };
629
629
 
630
630
  jest.spyOn(sideNavService, 'helper', 'get').mockReturnValue(mockHelper as any);
@@ -653,7 +653,7 @@ describe('topLevelMenu', () => {
653
653
  jest.spyOn(sideNavService, 'init').mockImplementation(() => {});
654
654
  const updateSpy = jest.fn();
655
655
  const mockHelper = {
656
- update: updateSpy, clustersPinned: [], clustersOthers: []
656
+ update: updateSpy, clustersPinned: [], clustersOthers: [], updateCount: () => {}
657
657
  };
658
658
 
659
659
  jest.spyOn(sideNavService, 'helper', 'get').mockReturnValue(mockHelper as any);
@@ -21,8 +21,9 @@ describe('component: Type', () => {
21
21
  const defaultCount = 1;
22
22
  const storeMock = {
23
23
  getters: {
24
- currentStore: () => 'cluster',
25
- 'cluster/count': () => defaultCount,
24
+ currentStore: () => 'cluster',
25
+ 'cluster/count': () => defaultCount,
26
+ 'type-map/optionsFor': () => {},
26
27
  }
27
28
  };
28
29
  const routerMock = {
@@ -449,8 +450,9 @@ describe('component: Type', () => {
449
450
 
450
451
  $store: {
451
452
  getters: {
452
- currentStore: () => 'cluster',
453
- 'cluster/count': () => null,
453
+ currentStore: () => 'cluster',
454
+ 'cluster/count': () => null,
455
+ 'type-map/optionsFor': () => {},
454
456
  }
455
457
  },
456
458
  $router: routerMock,
@@ -3,7 +3,7 @@ import {
3
3
  CONFIG_MAP,
4
4
  EVENT,
5
5
  NODE, SECRET, INGRESS,
6
- WORKLOAD, WORKLOAD_TYPES, SERVICE, HPA, NETWORK_POLICY, PV, PVC, STORAGE_CLASS, POD, POD_DISRUPTION_BUDGET, LIMIT_RANGE, RESOURCE_QUOTA,
6
+ WORKLOAD, WORKLOAD_TYPES, SERVICE, HPA, NETWORK_POLICY, PV, PVC, STORAGE_CLASS, POD, POD_DISRUPTION_BUDGET, LIMIT_RANGE, RESOURCE_QUOTA, AUDIT_POLICY,
7
7
  MANAGEMENT,
8
8
  NAMESPACE,
9
9
  NORMAN,
@@ -83,6 +83,7 @@ export function init(store) {
83
83
  NETWORK_POLICY,
84
84
  POD_DISRUPTION_BUDGET,
85
85
  RESOURCE_QUOTA,
86
+ AUDIT_POLICY,
86
87
  ], 'policy');
87
88
 
88
89
  basicType([
@@ -118,6 +119,7 @@ export function init(store) {
118
119
  weightGroup('storage', 95, true);
119
120
  weightGroup('policy', 94, true);
120
121
  weightType(POD, -1, true);
122
+ weightType(AUDIT_POLICY, -1, true);
121
123
 
122
124
  // here is where we define the usage of the WORKLOAD custom list view for
123
125
  // all the workload types (ex: deployments, cron jobs, daemonsets, etc)
@@ -152,6 +154,7 @@ export function init(store) {
152
154
  mapGroup('autoscaling', 'Autoscaling');
153
155
  mapGroup('policy', 'Policy');
154
156
  mapGroup('networking.k8s.io', 'Networking');
157
+ mapGroup('auditlog.cattle.io', 'Policy');
155
158
  mapGroup(/^(.+\.)?api(server)?.*\.k8s\.io$/, 'API');
156
159
  mapGroup('rbac.authorization.k8s.io', 'RBAC');
157
160
  mapGroup('admissionregistration.k8s.io', 'admission');
@@ -181,7 +184,6 @@ export function init(store) {
181
184
 
182
185
  const dePaginateBindings = configureConditionalDepaginate({ maxResourceCount: 5000 });
183
186
  const dePaginateNormanBindings = configureConditionalDepaginate({ maxResourceCount: 5000, isNorman: true });
184
- const dePaginateNormanUsers = configureConditionalDepaginate({ maxResourceCount: 5000, isNorman: true });
185
187
 
186
188
  configureType(NODE, { isCreatable: false, isEditable: true });
187
189
  configureType(WORKLOAD_TYPES.JOB, { isEditable: false, match: WORKLOAD_TYPES.JOB });
@@ -190,7 +192,6 @@ export function init(store) {
190
192
  configureType(MANAGEMENT.PROJECT, { displayName: store.getters['i18n/t']('namespace.project.label') });
191
193
  configureType(NORMAN.CLUSTER_ROLE_TEMPLATE_BINDING, { depaginate: dePaginateNormanBindings });
192
194
  configureType(NORMAN.PROJECT_ROLE_TEMPLATE_BINDING, { depaginate: dePaginateNormanBindings });
193
- configureType(NORMAN.USER, { depaginate: dePaginateNormanUsers });
194
195
  configureType(SNAPSHOT, { depaginate: true });
195
196
 
196
197
  configureType(SECRET, { showListMasthead: false });
@@ -2,16 +2,21 @@ import { AGE, NAME as NAME_COL, STATE } from '@shell/config/table-headers';
2
2
  import {
3
3
  CAPI,
4
4
  CATALOG,
5
+ EXT,
6
+ COUNT,
5
7
  NORMAN,
6
8
  HCI,
7
9
  MANAGEMENT,
8
10
  SNAPSHOT,
9
11
  VIRTUAL_TYPES,
10
- HOSTED_PROVIDER
12
+ HOSTED_PROVIDER,
13
+ SAVED_COUNTS
11
14
  } from '@shell/config/types';
12
15
  import { MULTI_CLUSTER } from '@shell/store/features';
13
16
  import { DSL } from '@shell/store/type-map';
14
17
  import { BLANK_CLUSTER } from '@shell/store/store-types.js';
18
+ import { markRaw } from 'vue';
19
+
15
20
  export const NAME = 'manager';
16
21
 
17
22
  export function init(store) {
@@ -125,14 +130,17 @@ export function init(store) {
125
130
  weightType(CAPI.MACHINE_DEPLOYMENT, 4, true);
126
131
  weightType(CAPI.MACHINE_SET, 3, true);
127
132
  weightType(CAPI.MACHINE, 2, true);
128
- weightType(CATALOG.CLUSTER_REPO, 1, true);
133
+ configureType(EXT.KUBECONFIG, { canYaml: false });
134
+ weightType(EXT.KUBECONFIG, 1, true);
135
+ weightType(CATALOG.CLUSTER_REPO, 0, true);
129
136
  weightType(MANAGEMENT.PSA, 5, true);
130
- weightType(VIRTUAL_TYPES.JWT_AUTHENTICATION, 0, true);
137
+ weightType(VIRTUAL_TYPES.JWT_AUTHENTICATION, -1, true);
131
138
 
132
139
  basicType([
133
140
  CAPI.MACHINE_DEPLOYMENT,
134
141
  CAPI.MACHINE_SET,
135
142
  CAPI.MACHINE,
143
+ EXT.KUBECONFIG,
136
144
  CATALOG.CLUSTER_REPO,
137
145
  MANAGEMENT.PSA,
138
146
  VIRTUAL_TYPES.JWT_AUTHENTICATION
@@ -192,4 +200,40 @@ export function init(store) {
192
200
  MACHINE_SUMMARY,
193
201
  AGE
194
202
  ]);
203
+
204
+ headers(EXT.KUBECONFIG, [
205
+ STATE,
206
+ {
207
+ name: 'clusters',
208
+ labelKey: 'tableHeaders.clusters',
209
+ value: 'spec.clusters',
210
+ sort: ['referencedClustersSortable'],
211
+ search: ['referencedClustersSortable'],
212
+ formatter: 'KubeconfigClusters',
213
+ },
214
+ {
215
+ name: 'ttl',
216
+ labelKey: 'tableHeaders.ttl',
217
+ value: 'expiresAt',
218
+ formatter: 'LiveDate',
219
+ formatterOpts: { isCountdown: true },
220
+ },
221
+ {
222
+ ...AGE,
223
+ defaultSort: true,
224
+ },
225
+ ]);
226
+
227
+ // Configure custom count getter for cluster count (so we don't include Harvester clusters)
228
+ configureType(CAPI.RANCHER_CLUSTER, {
229
+ custom: {
230
+ countGetter: markRaw((getters) => {
231
+ const savedClusterCount = getters['management/getSavedCount'](SAVED_COUNTS.K8S_CLUSTERS);
232
+ const counts = getters[`management/all`](COUNT)?.[0]?.counts || {};
233
+ const clusterCount = counts[CAPI.RANCHER_CLUSTER]?.summary.count;
234
+
235
+ return savedClusterCount || clusterCount;
236
+ })
237
+ }
238
+ });
195
239
  }
@@ -28,16 +28,16 @@ export function install(router, context) {
28
28
  }
29
29
 
30
30
  /**
31
- * Generate an object that includes both the v3User and the me data
32
- * @param {*} v3User V3 user information
31
+ * Generate an object that includes both the user and the me data
32
+ * @param {*} user User information
33
33
  * @param {*} me Me user data
34
34
  * @returns User info to be passed to `isLoggedIn`
35
35
  */
36
- function getUserObject(v3User, me) {
36
+ function getUserObject(user, me) {
37
37
  return {
38
38
  id: me.id,
39
39
  me,
40
- v3User,
40
+ user,
41
41
  };
42
42
  }
43
43
 
@@ -59,10 +59,9 @@ export async function authenticate(to, from, next, { store }) {
59
59
  if ( store.getters['auth/enabled'] !== false && !store.getters['auth/loggedIn'] ) {
60
60
  // `await` so we have one successfully request whilst possibly logged in (ensures fromHeader is populated from `x-api-cattle-auth`)
61
61
  await store.dispatch('auth/getUser');
62
+ const user = store.getters['auth/user'] || {};
62
63
 
63
- const v3User = store.getters['auth/v3User'] || {};
64
-
65
- if (v3User?.mustChangePassword) {
64
+ if (user?.mustChangePassword) {
66
65
  return next({ name: 'auth-setup' });
67
66
  }
68
67
 
@@ -75,7 +74,7 @@ export async function authenticate(to, from, next, { store }) {
75
74
  } else if ( fromHeader === 'true' ) {
76
75
  const me = await findMe(store);
77
76
 
78
- await isLoggedIn(store, getUserObject(v3User, me));
77
+ await isLoggedIn(store, getUserObject(user, me));
79
78
  handleOidcRedirectToCallbackUrl();
80
79
  } else if ( fromHeader === 'false' ) {
81
80
  notLoggedIn(store, next, to);
@@ -86,7 +85,7 @@ export async function authenticate(to, from, next, { store }) {
86
85
  try {
87
86
  const me = await findMe(store);
88
87
 
89
- await isLoggedIn(store, getUserObject(v3User, me));
88
+ await isLoggedIn(store, getUserObject(user, me));
90
89
  handleOidcRedirectToCallbackUrl();
91
90
  } catch (e) {
92
91
  const status = e?._status;
@@ -514,7 +514,10 @@ export default [
514
514
  name: 'c-cluster-product-resource-id',
515
515
  meta: { asyncSetup: true }
516
516
  }, {
517
- path: `/c/:cluster/:product/${ VIRTUAL_TYPES.PROJECT_SECRETS }/:namespace/:id`,
517
+ // Used this regex syntax in order to strict match the 'projectsecret' path segment
518
+ // while simultaneously capturing it as the 'resource' parameter.
519
+ // This is required because the Side Navigation relies on route.params.resource to determine which menu item to highlight.
520
+ path: `/c/:cluster/:product/:resource(${ VIRTUAL_TYPES.PROJECT_SECRETS })/:namespace/:id`,
518
521
  component: () => interopDefault(import(`@shell/pages/c/_cluster/explorer/${ VIRTUAL_TYPES.PROJECT_SECRETS }.vue`)),
519
522
  name: `c-cluster-product-${ VIRTUAL_TYPES.PROJECT_SECRETS }-namespace-id`,
520
523
  }, {
package/config/types.js CHANGED
@@ -61,6 +61,7 @@ export const POD_DISRUPTION_BUDGET = 'policy.poddisruptionbudget';
61
61
  export const PV = 'persistentvolume';
62
62
  export const PVC = 'persistentvolumeclaim';
63
63
  export const RESOURCE_QUOTA = 'resourcequota';
64
+ export const AUDIT_POLICY = 'auditlog.cattle.io.auditpolicy';
64
65
  export const SCHEMA = 'schema';
65
66
  export const SERVICE = 'service';
66
67
  export const SECRET = 'secret';
@@ -263,8 +264,11 @@ export const BRAND = {
263
264
  };
264
265
 
265
266
  export const EXT = {
266
- USER_ACTIVITY: 'ext.cattle.io.useractivity',
267
- KUBECONFIG: 'ext.cattle.io.kubeconfig',
267
+ USER_ACTIVITY: 'ext.cattle.io.useractivity',
268
+ SELFUSER: 'ext.cattle.io.selfuser',
269
+ GROUP_MEMBERSHIP_REFRESH_REQUESTS: 'ext.cattle.io.groupmembershiprefreshrequest',
270
+ PASSWORD_CHANGE_REQUESTS: 'ext.cattle.io.passwordchangerequest',
271
+ KUBECONFIG: 'ext.cattle.io.kubeconfig',
268
272
  };
269
273
 
270
274
  export const CAPI = {
@@ -409,3 +413,7 @@ export const DEPRECATED = 'Deprecated';
409
413
  export const EXPERIMENTAL = 'Experimental';
410
414
  export const AUTOSCALER_CONFIG_MAP_ID = 'kube-system/cluster-autoscaler-status';
411
415
  export const HOSTED_PROVIDER = 'hostedprovider';
416
+
417
+ // Named saved counts
418
+
419
+ export const SAVED_COUNTS = { K8S_CLUSTERS: 'k8sClusters' };
@@ -0,0 +1,19 @@
1
+ <script lang="ts">
2
+ // Shared AuditPolicy interface for all audit log policy components
3
+ import CreateEditView from '@shell/mixins/create-edit-view';
4
+ import ResourceTabs from '@shell/components/form/ResourceTabs/index.vue';
5
+
6
+ export default {
7
+ name: 'DetailAuditPolicy',
8
+ components: { ResourceTabs },
9
+ mixins: [CreateEditView],
10
+ };
11
+ </script>
12
+
13
+ <template>
14
+ <Loading v-if="$fetchState.pending" />
15
+ <ResourceTabs
16
+ :mode="mode"
17
+ :value="value"
18
+ />
19
+ </template>
@@ -3,7 +3,7 @@ import CreateEditView from '@shell/mixins/create-edit-view';
3
3
  import Tab from '@shell/components/Tabbed/Tab';
4
4
  import ResourceTabs from '@shell/components/form/ResourceTabs';
5
5
  import SortableTable from '@shell/components/SortableTable';
6
- import { MANAGEMENT, NORMAN } from '@shell/config/types';
6
+ import { MANAGEMENT } from '@shell/config/types';
7
7
  import Loading from '@shell/components/Loading';
8
8
  import { NAME } from '@shell/config/table-headers';
9
9
 
@@ -31,7 +31,6 @@ export default {
31
31
  if (this.canSeeRoleTemplates) {
32
32
  // Upfront fetch, avoid async computes
33
33
  await Promise.all([
34
- await this.$store.dispatch('rancher/find', { type: NORMAN.USER, id: this.value.id }),
35
34
  await this.$store.dispatch('management/findAll', { type: MANAGEMENT.ROLE_TEMPLATE }),
36
35
  await this.$store.dispatch('management/findAll', { type: MANAGEMENT.CLUSTER_ROLE_TEMPLATE_BINDING }),
37
36
  await this.$store.dispatch('management/findAll', { type: MANAGEMENT.PROJECT_ROLE_TEMPLATE_BINDING })
package/detail/node.vue CHANGED
@@ -196,7 +196,6 @@ export default {
196
196
  v-else
197
197
  class="node"
198
198
  >
199
- <div class="spacer" />
200
199
  <div class="alerts">
201
200
  <Alert
202
201
  class="mr-10"
@@ -364,7 +364,8 @@ export default {
364
364
  const machineFullName = machineNameFn(this.value.name, mp.name);
365
365
 
366
366
  const machines = this.value.machines.filter((machine) => {
367
- const isElementalCluster = machine.spec?.infrastructureRef?.apiVersion.startsWith('elemental.cattle.io');
367
+ // elemental machines use an older version of CAPI CRDs that use apiVersion instead of apiGroup
368
+ const isElementalCluster = (machine.spec?.infrastructureRef?.apiGroup || machine.spec?.infrastructureRef?.apiVersion).startsWith('elemental.cattle.io');
368
369
  const machinePoolInfName = machine.spec?.infrastructureRef?.name;
369
370
 
370
371
  if (isElementalCluster) {
@@ -9,6 +9,13 @@ export default {
9
9
  components: {
10
10
  Card, AsyncButton, ChangePassword
11
11
  },
12
+ props: {
13
+ user: {
14
+ type: Object,
15
+ default: () => null,
16
+ required: true
17
+ }
18
+ },
12
19
  data() {
13
20
  return { valid: false, password: '' };
14
21
  },
@@ -45,6 +52,7 @@ export default {
45
52
  <form @submit.prevent>
46
53
  <ChangePassword
47
54
  ref="changePassword"
55
+ :user="user"
48
56
  @valid="valid = $event"
49
57
  />
50
58
  </form>
@@ -38,9 +38,24 @@ export default {
38
38
  type: Function,
39
39
  default: () => { }
40
40
  },
41
+ actionColor: {
42
+ type: String,
43
+ default: 'role-primary',
44
+ },
45
+ errors: {
46
+ type: Array,
47
+ default: () => []
48
+ }
41
49
  },
50
+
42
51
  data() {
43
- return { errors: [] };
52
+ return { applyErrors: [] };
53
+ },
54
+
55
+ computed: {
56
+ allErrors() {
57
+ return [...this.errors, ...this.applyErrors];
58
+ }
44
59
  },
45
60
 
46
61
  methods: {
@@ -51,13 +66,14 @@ export default {
51
66
  },
52
67
 
53
68
  async apply(buttonDone) {
69
+ this.applyErrors = [];
54
70
  try {
55
71
  await this.applyAction(buttonDone);
56
72
  this.confirm(true);
57
73
  this.$emit('close');
58
74
  } catch (err) {
59
75
  console.error(err); // eslint-disable-line
60
- this.errors = exceptionToErrorsArray(err);
76
+ this.applyErrors = exceptionToErrorsArray(err);
61
77
  buttonDone(false);
62
78
  }
63
79
  }
@@ -92,7 +108,7 @@ export default {
92
108
  <template #actions>
93
109
  <div class="bottom">
94
110
  <Banner
95
- v-for="(err, i) in errors"
111
+ v-for="(err, i) in allErrors"
96
112
  :key="i"
97
113
  color="error"
98
114
  :label="err"
@@ -107,6 +123,7 @@ export default {
107
123
 
108
124
  <AsyncButton
109
125
  :mode="applyMode"
126
+ :action-color="actionColor"
110
127
  @click="apply"
111
128
  />
112
129
  </div>