@rancher/shell 3.0.9-rc.4 → 3.0.9-rc.6

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 (218) hide show
  1. package/assets/brand/suse/metadata.json +2 -1
  2. package/assets/images/providers/oci-open-containers.svg +22 -0
  3. package/assets/images/providers/traefik.png +0 -0
  4. package/assets/styles/themes/_dark.scss +2 -0
  5. package/assets/styles/themes/_light.scss +2 -0
  6. package/assets/styles/themes/_modern.scss +6 -0
  7. package/assets/translations/en-us.yaml +218 -26
  8. package/components/ActionMenuShell.vue +1 -1
  9. package/components/CruResource.vue +3 -1
  10. package/components/ExplorerProjectsNamespaces.vue +12 -12
  11. package/components/Inactivity.vue +2 -2
  12. package/components/Resource/Detail/Card/ExtrasCard.vue +49 -15
  13. package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +109 -0
  14. package/components/Resource/Detail/Card/StatusCard/index.vue +21 -4
  15. package/components/Resource/Detail/Card/__tests__/ExtrasCard.test.ts +111 -0
  16. package/components/Resource/Detail/Masthead/__tests__/index.test.ts +0 -17
  17. package/components/Resource/Detail/Masthead/index.vue +11 -4
  18. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +19 -2
  19. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +19 -11
  20. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +3 -1
  21. package/components/Resource/Detail/Metadata/index.vue +1 -1
  22. package/components/Resource/Detail/ResourcePopover/__tests__/index.test.ts +12 -0
  23. package/components/Resource/Detail/ResourcePopover/index.vue +2 -0
  24. package/components/Resource/Detail/ResourceRow.vue +3 -3
  25. package/components/ResourceDetail/Masthead/latest.vue +12 -2
  26. package/components/ResourceList/index.vue +12 -0
  27. package/components/ResourceTable.vue +38 -4
  28. package/components/Tabbed/Tab.vue +4 -0
  29. package/components/Tabbed/index.vue +4 -1
  30. package/components/Window/ContainerLogs.vue +48 -37
  31. package/components/fleet/FleetClusterTargets/TargetsList.vue +2 -2
  32. package/components/fleet/FleetClusterTargets/index.vue +6 -1
  33. package/components/fleet/GitRepoAdvancedTab.vue +333 -0
  34. package/components/fleet/GitRepoMetadataTab.vue +43 -0
  35. package/components/fleet/GitRepoRepositoryTab.vue +101 -0
  36. package/components/fleet/GitRepoTargetTab.vue +77 -0
  37. package/components/fleet/HelmOpAdvancedTab.vue +247 -0
  38. package/components/fleet/HelmOpChartTab.vue +158 -0
  39. package/components/fleet/HelmOpMetadataTab.vue +46 -0
  40. package/components/fleet/HelmOpTargetTab.vue +84 -0
  41. package/components/fleet/HelmOpValuesTab.vue +147 -0
  42. package/components/fleet/__tests__/FleetClusterTargets.test.ts +119 -70
  43. package/components/form/ChangePassword.vue +41 -35
  44. package/components/form/NodeScheduling.vue +81 -7
  45. package/components/form/PodAffinity.vue +1 -36
  46. package/components/form/ResourceLabeledSelect.vue +8 -4
  47. package/components/form/ResourceQuota/Namespace.vue +30 -9
  48. package/components/form/ResourceQuota/NamespaceRow.vue +25 -7
  49. package/components/form/ResourceQuota/Project.vue +150 -51
  50. package/components/form/ResourceQuota/ResourceQuotaEntry.vue +145 -0
  51. package/components/form/ResourceQuota/__tests__/Namespace.test.ts +307 -0
  52. package/components/form/ResourceQuota/__tests__/NamespaceRow.test.ts +281 -0
  53. package/components/form/ResourceQuota/__tests__/Project.test.ts +310 -0
  54. package/components/form/ResourceQuota/__tests__/ResourceQuotaEntry.test.ts +215 -0
  55. package/components/form/SchedulingCustomization.vue +14 -6
  56. package/components/form/SelectOrCreateAuthSecret.vue +113 -19
  57. package/components/form/__tests__/NodeScheduling.test.ts +12 -9
  58. package/components/form/__tests__/PodAffinity.test.ts +21 -2
  59. package/components/form/__tests__/SchedulingCustomization.test.ts +240 -0
  60. package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +35 -0
  61. package/components/formatter/ClusterLink.vue +8 -0
  62. package/components/formatter/MachineSummaryGraph.vue +10 -2
  63. package/components/formatter/SecretOrigin.vue +79 -0
  64. package/components/nav/TopLevelMenu.helper.ts +50 -2
  65. package/components/nav/TopLevelMenu.vue +14 -0
  66. package/components/nav/Type.vue +5 -0
  67. package/components/nav/__tests__/TopLevelMenu.test.ts +3 -3
  68. package/components/nav/__tests__/Type.test.ts +6 -4
  69. package/config/labels-annotations.js +7 -6
  70. package/config/pagination-table-headers.js +6 -4
  71. package/config/product/explorer.js +5 -14
  72. package/config/product/manager.js +18 -1
  73. package/config/query-params.js +3 -0
  74. package/config/router/navigation-guards/authentication.js +8 -9
  75. package/config/settings.ts +15 -2
  76. package/config/table-headers.js +21 -17
  77. package/config/types.js +33 -10
  78. package/detail/auditlog.cattle.io.auditpolicy.vue +19 -0
  79. package/detail/management.cattle.io.user.vue +1 -2
  80. package/detail/node.vue +0 -1
  81. package/detail/provisioning.cattle.io.cluster.vue +2 -1
  82. package/detail/workload/index.vue +11 -16
  83. package/dialog/ChangePasswordDialog.vue +8 -0
  84. package/dialog/DeactivateDriverDialog.vue +1 -1
  85. package/dialog/GenericPrompt.vue +20 -3
  86. package/dialog/Ipv6NetworkingDialog.vue +156 -0
  87. package/dialog/ScaleMachineDownDialog.vue +65 -15
  88. package/dialog/ScalePoolDownDialog.vue +2 -2
  89. package/dialog/SearchDialog.vue +10 -2
  90. package/dialog/__tests__/ScaleMachineDownDialog.test.ts +184 -0
  91. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +89 -0
  92. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +1 -0
  93. package/edit/__tests__/management.cattle.io.project.test.js +81 -98
  94. package/edit/auditlog.cattle.io.auditpolicy/AdditionalRedactions.vue +114 -0
  95. package/edit/auditlog.cattle.io.auditpolicy/Filters.vue +119 -0
  96. package/edit/auditlog.cattle.io.auditpolicy/General.vue +180 -0
  97. package/edit/auditlog.cattle.io.auditpolicy/__tests__/AdditionalRedactions.test.ts +327 -0
  98. package/edit/auditlog.cattle.io.auditpolicy/__tests__/Filters.test.ts +449 -0
  99. package/edit/auditlog.cattle.io.auditpolicy/__tests__/General.test.ts +472 -0
  100. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/AdditionalRedactions.test.ts.snap +27 -0
  101. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/Filters.test.ts.snap +39 -0
  102. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +174 -0
  103. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +29 -0
  104. package/edit/auditlog.cattle.io.auditpolicy/__tests__/index.test.ts +215 -0
  105. package/edit/auditlog.cattle.io.auditpolicy/index.vue +104 -0
  106. package/edit/auditlog.cattle.io.auditpolicy/types.ts +28 -0
  107. package/edit/auth/oidc.vue +1 -1
  108. package/edit/catalog.cattle.io.clusterrepo.vue +155 -25
  109. package/edit/fleet.cattle.io.gitrepo.vue +161 -276
  110. package/edit/fleet.cattle.io.helmop.vue +190 -332
  111. package/edit/management.cattle.io.project.vue +11 -42
  112. package/edit/management.cattle.io.setting.vue +6 -0
  113. package/edit/management.cattle.io.user.vue +29 -34
  114. package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +55 -24
  115. package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +1 -103
  116. package/edit/provisioning.cattle.io.cluster/__tests__/index.test.ts +13 -1
  117. package/edit/provisioning.cattle.io.cluster/__tests__/rke2-fleet-cluster-agent.test.ts +283 -0
  118. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +196 -2
  119. package/edit/provisioning.cattle.io.cluster/ingress/IngressCards.vue +112 -0
  120. package/edit/provisioning.cattle.io.cluster/ingress/IngressConfiguration.vue +158 -0
  121. package/edit/provisioning.cattle.io.cluster/rke2.vue +184 -66
  122. package/edit/provisioning.cattle.io.cluster/shared.ts +39 -0
  123. package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +2 -1
  124. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +56 -7
  125. package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +319 -0
  126. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +2 -1
  127. package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +1 -1
  128. package/edit/provisioning.cattle.io.cluster/tabs/etcd/__tests__/S3Config.test.ts +13 -1
  129. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +10 -44
  130. package/edit/secret/index.vue +1 -1
  131. package/edit/token.vue +68 -29
  132. package/edit/workload/__tests__/index.test.ts +2 -37
  133. package/edit/workload/index.vue +6 -2
  134. package/edit/workload/mixins/workload.js +0 -32
  135. package/list/__tests__/management.cattle.io.setting.test.ts +198 -0
  136. package/list/auditlog.cattle.io.auditpolicy.vue +63 -0
  137. package/list/group.principal.vue +11 -15
  138. package/list/management.cattle.io.setting.vue +13 -0
  139. package/list/management.cattle.io.user.vue +11 -21
  140. package/list/provisioning.cattle.io.cluster.vue +50 -1
  141. package/list/secret.vue +4 -9
  142. package/list/service.vue +6 -8
  143. package/machine-config/amazonec2.vue +11 -4
  144. package/machine-config/azure.vue +14 -0
  145. package/machine-config/components/EC2Networking.vue +46 -30
  146. package/machine-config/components/__tests__/EC2Networking.test.ts +7 -7
  147. package/machine-config/components/__tests__/utils/vpcSubnetMockData.js +0 -9
  148. package/machine-config/digitalocean.vue +3 -3
  149. package/mixins/browser-tab-visibility.js +5 -4
  150. package/mixins/fetch.client.js +6 -0
  151. package/models/__tests__/auditlog.cattle.io.auditpolicy.test.ts +117 -0
  152. package/models/__tests__/namespace.test.ts +11 -0
  153. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +96 -0
  154. package/models/__tests__/workload.test.ts +90 -6
  155. package/models/auditlog.cattle.io.auditpolicy.js +46 -0
  156. package/models/catalog.cattle.io.clusterrepo.js +30 -4
  157. package/models/cluster.x-k8s.io.machine.js +1 -1
  158. package/models/cluster.x-k8s.io.machinedeployment.js +5 -5
  159. package/models/event.js +5 -0
  160. package/models/ext.cattle.io.groupmembershiprefreshrequest.js +15 -0
  161. package/models/ext.cattle.io.passwordchangerequest.js +15 -0
  162. package/models/ext.cattle.io.selfuser.js +15 -0
  163. package/models/ext.cattle.io.token.js +48 -0
  164. package/models/fleet-application.js +17 -7
  165. package/models/kontainerdriver.js +2 -2
  166. package/models/management.cattle.io.user.js +28 -31
  167. package/models/namespace.js +7 -1
  168. package/models/nodedriver.js +2 -2
  169. package/models/provisioning.cattle.io.cluster.js +28 -7
  170. package/models/schema.js +18 -0
  171. package/models/secret.js +27 -41
  172. package/models/service.js +44 -1
  173. package/models/steve-schema.ts +39 -2
  174. package/models/token.js +4 -0
  175. package/models/workload.js +13 -6
  176. package/package.json +1 -1
  177. package/pages/account/index.vue +108 -72
  178. package/pages/auth/login.vue +15 -8
  179. package/pages/auth/setup.vue +55 -27
  180. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +4 -1
  181. package/pages/c/_cluster/apps/charts/index.vue +93 -4
  182. package/pages/c/_cluster/apps/charts/install.vue +317 -42
  183. package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -4
  184. package/pages/c/_cluster/settings/index.vue +3 -1
  185. package/pages/home.vue +9 -3
  186. package/plugins/dashboard-store/__tests__/getters.test.ts +108 -0
  187. package/plugins/dashboard-store/__tests__/resource-class.test.ts +28 -3
  188. package/plugins/dashboard-store/actions.js +10 -8
  189. package/plugins/dashboard-store/getters.js +30 -6
  190. package/plugins/dashboard-store/index.js +3 -2
  191. package/plugins/dashboard-store/mutations.js +8 -1
  192. package/plugins/dashboard-store/resource-class.js +15 -8
  193. package/plugins/steve/__tests__/steve-class.test.ts +128 -0
  194. package/plugins/steve/schema.d.ts +5 -0
  195. package/plugins/steve/steve-class.js +28 -0
  196. package/plugins/steve/steve-pagination-utils.ts +7 -2
  197. package/rancher-components/RcIcon/types.ts +2 -0
  198. package/rancher-components/RcItemCard/RcItemCard.vue +64 -19
  199. package/store/auth.js +57 -19
  200. package/store/notifications.ts +1 -1
  201. package/store/prefs.js +3 -0
  202. package/store/type-map.js +12 -1
  203. package/types/aws-sdk.d.ts +121 -0
  204. package/types/resources/node.ts +15 -0
  205. package/types/shell/index.d.ts +542 -516
  206. package/types/store/dashboard-store.types.ts +7 -0
  207. package/types/store/pagination.types.ts +5 -5
  208. package/utils/__tests__/array.test.ts +1 -29
  209. package/utils/__tests__/cluster-agent-configuration.test.ts +203 -0
  210. package/utils/array.ts +0 -11
  211. package/utils/aws.ts +21 -0
  212. package/utils/cluster.js +22 -2
  213. package/utils/pagination-wrapper.ts +11 -3
  214. package/utils/selector-typed.ts +1 -1
  215. package/vue.config.js +26 -13
  216. package/components/__tests__/ProjectRow.test.ts +0 -146
  217. package/components/form/ResourceQuota/ProjectRow.vue +0 -210
  218. 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
+ });
@@ -187,6 +187,17 @@ describe('class Namespace', () => {
187
187
  it.todo('should set the resourceQuota as reactive Vue property');
188
188
  it.todo('should reset project with cleanForNew');
189
189
 
190
+ describe('hideDetailLocation', () => {
191
+ it('should not throw when currentProduct is undefined', () => {
192
+ const namespace = new Namespace({});
193
+
194
+ jest.spyOn(namespace, '$rootGetters', 'get').mockReturnValue({ currentProduct: undefined });
195
+
196
+ expect(() => namespace.hideDetailLocation).not.toThrow();
197
+ expect(namespace.hideDetailLocation).toBe(true);
198
+ });
199
+ });
200
+
190
201
  describe('glance', () => {
191
202
  it('should return projectGlance instead of namespace when namespace is in a project', () => {
192
203
  const t = jest.fn((key) => key);
@@ -275,4 +275,100 @@ describe('class ProvCluster', () => {
275
275
  }
276
276
  );
277
277
  });
278
+
279
+ describe('supportsWindows', () => {
280
+ const testCases = [
281
+ {
282
+ description: 'should return false for k3s',
283
+ clusterData: { isK3s: true },
284
+ expected: false
285
+ },
286
+ {
287
+ description: 'should return false for imported k3s',
288
+ clusterData: { isImportedK3s: true },
289
+ expected: false
290
+ },
291
+ {
292
+ description: 'should return windowsPreferedCluster for rke1',
293
+ clusterData: { isRke1: true, mgmt: { spec: { windowsPreferedCluster: true } } },
294
+ expected: true
295
+ },
296
+ {
297
+ description: 'should return false for rke1 if windowsPreferedCluster is false/missing',
298
+ clusterData: { isRke1: true, mgmt: { spec: { windowsPreferedCluster: false } } },
299
+ expected: false
300
+ },
301
+ {
302
+ description: 'should return false if not rke2 (and not rke1 or k3s)',
303
+ clusterData: { isRke2: false },
304
+ expected: false
305
+ },
306
+ {
307
+ description: 'should return false if kubernetesVersion is missing',
308
+ clusterData: { isRke2: true, kubernetesVersion: undefined },
309
+ expected: false
310
+ },
311
+ {
312
+ description: 'should return false if kubernetesVersion is less than v1.21.0',
313
+ clusterData: { isRke2: true, kubernetesVersion: 'v1.20.9' },
314
+ expected: false
315
+ },
316
+ {
317
+ description: 'should return false if cni is not calico or flannel',
318
+ clusterData: {
319
+ isRke2: true, kubernetesVersion: 'v1.34.0', spec: { rkeConfig: { machineGlobalConfig: { cni: 'cilium' } } }
320
+ },
321
+ expected: false
322
+ },
323
+ {
324
+ description: 'should return true if cni is calico',
325
+ clusterData: {
326
+ isRke2: true, kubernetesVersion: 'v1.34.0', spec: { rkeConfig: { machineGlobalConfig: { cni: 'calico' } } }
327
+ },
328
+ expected: true
329
+ },
330
+ {
331
+ description: 'should return false if cni is flannel and kubernetesVersion is less than v1.29.2 (e.g. v1.29.1)',
332
+ clusterData: {
333
+ isRke2: true, kubernetesVersion: 'v1.29.1', spec: { rkeConfig: { machineGlobalConfig: { cni: 'flannel' } } }
334
+ },
335
+ expected: false
336
+ },
337
+ {
338
+ description: 'should return true if cni is flannel and kubernetesVersion is exactly v1.29.2',
339
+ clusterData: {
340
+ isRke2: true, kubernetesVersion: 'v1.29.2', spec: { rkeConfig: { machineGlobalConfig: { cni: 'flannel' } } }
341
+ },
342
+ expected: true
343
+ },
344
+ {
345
+ description: 'should return true if cni is flannel and kubernetesVersion is >= v1.29.2 (e.g. v1.35.0)',
346
+ clusterData: {
347
+ isRke2: true, kubernetesVersion: 'v1.35.0', spec: { rkeConfig: { machineGlobalConfig: { cni: 'flannel' } } }
348
+ },
349
+ expected: true
350
+ },
351
+ {
352
+ description: 'should return true if cni is empty/undefined',
353
+ clusterData: {
354
+ isRke2: true, kubernetesVersion: 'v1.34.0', spec: { rkeConfig: { machineGlobalConfig: {} } }
355
+ },
356
+ expected: true
357
+ },
358
+ ];
359
+
360
+ it.each(testCases)('$description', ({ clusterData, expected }) => {
361
+ const cluster = new ProvCluster({ spec: clusterData.spec });
362
+
363
+ jest.spyOn(cluster, 'mgmt', 'get').mockReturnValue(clusterData.mgmt);
364
+ jest.spyOn(cluster, 'isK3s', 'get').mockReturnValue(clusterData.isK3s || false);
365
+ jest.spyOn(cluster, 'isImportedK3s', 'get').mockReturnValue(clusterData.isImportedK3s || false);
366
+ jest.spyOn(cluster, 'isRke1', 'get').mockReturnValue(clusterData.isRke1 || false);
367
+ jest.spyOn(cluster, 'isRke2', 'get').mockReturnValue(clusterData.isRke2 || false);
368
+ jest.spyOn(cluster, 'kubernetesVersion', 'get').mockReturnValue(clusterData.kubernetesVersion);
369
+
370
+ expect(cluster.supportsWindows).toBe(expected);
371
+ jest.clearAllMocks();
372
+ });
373
+ });
278
374
  });
@@ -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;
@@ -272,6 +274,7 @@ describe('class: Workload', () => {
272
274
  expect(card).not.toBeNull();
273
275
  expect(card.props.title).toBe('component.resource.detail.card.podsCard.title');
274
276
  expect(card.props.showScaling).toBe(true);
277
+ expect(card.props.noResourcesMessage).toBe('component.resource.detail.card.podsCard.noPods');
275
278
  });
276
279
 
277
280
  it('should return card for DaemonSet type without scaling', () => {
@@ -285,7 +288,7 @@ describe('class: Workload', () => {
285
288
  rootGetters: { 'i18n/t': (key: string) => key },
286
289
  });
287
290
 
288
- Object.defineProperty(workload, 'pods', { get: () => [] });
291
+ Object.defineProperty(workload, 'pods', { get: () => [mockPod] });
289
292
  Object.defineProperty(workload, 'canUpdate', { get: () => true });
290
293
 
291
294
  const card = workload.podsCard;
@@ -310,7 +313,7 @@ describe('class: Workload', () => {
310
313
  expect(card).toBeNull();
311
314
  });
312
315
 
313
- it('should hide scaling when canUpdate is false', () => {
316
+ it('should return card when pods array is empty (scaled to 0)', () => {
314
317
  const workload = new Workload({
315
318
  type: WORKLOAD_TYPES.DEPLOYMENT,
316
319
  metadata: { name: 'test', namespace: 'default' },
@@ -322,6 +325,64 @@ describe('class: Workload', () => {
322
325
  });
323
326
 
324
327
  Object.defineProperty(workload, 'pods', { get: () => [] });
328
+ Object.defineProperty(workload, 'canUpdate', { get: () => true });
329
+
330
+ const card = workload.podsCard;
331
+
332
+ expect(card).not.toBeNull();
333
+ expect(card.props.resources).toStrictEqual([]);
334
+ expect(card.props.noResourcesMessage).toBe('component.resource.detail.card.podsCard.noPods');
335
+ });
336
+
337
+ it('should return null for non-scalable type with empty pods', () => {
338
+ const workload = new Workload({
339
+ type: WORKLOAD_TYPES.DAEMON_SET,
340
+ metadata: { name: 'test', namespace: 'default' },
341
+ spec: {}
342
+ }, {
343
+ getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
344
+ dispatch: jest.fn(),
345
+ rootGetters: { 'i18n/t': (key: string) => key },
346
+ });
347
+
348
+ Object.defineProperty(workload, 'pods', { get: () => [] });
349
+ Object.defineProperty(workload, 'canUpdate', { get: () => true });
350
+
351
+ const card = workload.podsCard;
352
+
353
+ expect(card).toBeNull();
354
+ });
355
+
356
+ it('should return null when pods is undefined', () => {
357
+ const workload = new Workload({
358
+ type: WORKLOAD_TYPES.DEPLOYMENT,
359
+ metadata: { name: 'test', namespace: 'default' },
360
+ spec: {}
361
+ }, {
362
+ getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
363
+ dispatch: jest.fn(),
364
+ rootGetters: { 'i18n/t': (key: string) => key },
365
+ });
366
+
367
+ Object.defineProperty(workload, 'pods', { get: () => undefined });
368
+
369
+ const card = workload.podsCard;
370
+
371
+ expect(card).toBeNull();
372
+ });
373
+
374
+ it('should hide scaling when canUpdate is false', () => {
375
+ const workload = new Workload({
376
+ type: WORKLOAD_TYPES.DEPLOYMENT,
377
+ metadata: { name: 'test', namespace: 'default' },
378
+ spec: {}
379
+ }, {
380
+ getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
381
+ dispatch: jest.fn(),
382
+ rootGetters: { 'i18n/t': (key: string) => key },
383
+ });
384
+
385
+ Object.defineProperty(workload, 'pods', { get: () => [mockPod] });
325
386
  Object.defineProperty(workload, 'canUpdate', { get: () => false });
326
387
 
327
388
  const card = workload.podsCard;
@@ -331,6 +392,8 @@ describe('class: Workload', () => {
331
392
  });
332
393
 
333
394
  describe('getter: jobsCard', () => {
395
+ const mockJob = { metadata: { name: 'job-1', namespace: 'default' } };
396
+
334
397
  it('should return card for CronJob type', () => {
335
398
  const workload = new Workload({
336
399
  type: WORKLOAD_TYPES.CRON_JOB,
@@ -342,7 +405,7 @@ describe('class: Workload', () => {
342
405
  rootGetters: { 'i18n/t': (key: string) => key },
343
406
  });
344
407
 
345
- Object.defineProperty(workload, 'jobs', { get: () => [] });
408
+ Object.defineProperty(workload, 'jobs', { get: () => [mockJob] });
346
409
 
347
410
  const card = workload.jobsCard;
348
411
 
@@ -366,9 +429,30 @@ describe('class: Workload', () => {
366
429
 
367
430
  expect(card).toBeNull();
368
431
  });
432
+
433
+ it('should return null when jobs array is empty', () => {
434
+ const workload = new Workload({
435
+ type: WORKLOAD_TYPES.CRON_JOB,
436
+ metadata: { name: 'test', namespace: 'default' },
437
+ spec: {}
438
+ }, {
439
+ getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
440
+ dispatch: jest.fn(),
441
+ rootGetters: { 'i18n/t': (key: string) => key },
442
+ });
443
+
444
+ Object.defineProperty(workload, 'jobs', { get: () => [] });
445
+
446
+ const card = workload.jobsCard;
447
+
448
+ expect(card).toBeNull();
449
+ });
369
450
  });
370
451
 
371
452
  describe('getter: cards', () => {
453
+ const mockPod = { metadata: { name: 'pod-1', namespace: 'default' } };
454
+ const mockJob = { metadata: { name: 'job-1', namespace: 'default' } };
455
+
372
456
  it('should include podsCard for Deployment', () => {
373
457
  const workload = new Workload({
374
458
  type: WORKLOAD_TYPES.DEPLOYMENT,
@@ -384,7 +468,7 @@ describe('class: Workload', () => {
384
468
  },
385
469
  });
386
470
 
387
- Object.defineProperty(workload, 'pods', { get: () => [] });
471
+ Object.defineProperty(workload, 'pods', { get: () => [mockPod] });
388
472
  Object.defineProperty(workload, 'canUpdate', { get: () => true });
389
473
 
390
474
  const cards = workload.cards;
@@ -411,7 +495,7 @@ describe('class: Workload', () => {
411
495
  },
412
496
  });
413
497
 
414
- Object.defineProperty(workload, 'jobs', { get: () => [] });
498
+ Object.defineProperty(workload, 'jobs', { get: () => [mockJob] });
415
499
 
416
500
  const cards = workload.cards;
417
501
  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
+ }
@@ -1,7 +1,7 @@
1
1
  import { parse } from '@shell/utils/url';
2
2
  import { CATALOG } from '@shell/config/labels-annotations';
3
3
  import { insertAt } from '@shell/utils/array';
4
- import { CATALOG as CATALOG_TYPE } from '@shell/config/types';
4
+ import { CLUSTER_REPO_APPCO_AUTH_GENERATE_NAME, CATALOG as CATALOG_TYPE } from '@shell/config/types';
5
5
  import { colorForState, stateDisplay } from '@shell/plugins/dashboard-store/resource-class';
6
6
 
7
7
  import SteveModel from '@shell/plugins/steve/steve-class';
@@ -143,14 +143,27 @@ export default class ClusterRepo extends SteveModel {
143
143
  return this.metadata?.state?.name === 'active';
144
144
  }
145
145
 
146
+ get isSuseAppCollectionFromUI() {
147
+ return this.metadata?.annotations?.[CATALOG.SUSE_APP_COLLECTION];
148
+ }
149
+
150
+ get isSuseAppCollection() {
151
+ // Check annotation set by the UI or if the URL points to the SUSE App Collection registry
152
+ return this.isSuseAppCollectionFromUI || this.spec?.url?.startsWith('oci://dp.apps.rancher.io/charts');
153
+ }
154
+
146
155
  get typeDisplay() {
156
+ if (this.isSuseAppCollectionFromUI) {
157
+ return 'SUSE AppCo';
158
+ }
147
159
  if ( this.spec.gitRepo ) {
148
160
  return 'git';
149
- } else if ( this.spec.url ) {
161
+ }
162
+ if ( this.spec.url ) {
150
163
  return this.isOciType ? 'oci' : 'http';
151
- } else {
152
- return '?';
153
164
  }
165
+
166
+ return '?';
154
167
  }
155
168
 
156
169
  get nameDisplay() {
@@ -220,4 +233,17 @@ export default class ClusterRepo extends SteveModel {
220
233
  });
221
234
  }, `catalog operation fetch`, timeout, interval);
222
235
  }
236
+
237
+ async save() {
238
+ // Add annotation only if the type is SUSE_APP_COLLECTION
239
+ if (this.spec.clientSecret?.name?.search(CLUSTER_REPO_APPCO_AUTH_GENERATE_NAME) === 0) {
240
+ if (!this.metadata.annotations) {
241
+ this.metadata.annotations = {};
242
+ }
243
+ this.metadata.annotations[CATALOG.SUSE_APP_COLLECTION] = 'true';
244
+ }
245
+
246
+ // Call the parent save method
247
+ return super.save();
248
+ }
223
249
  }
@@ -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
+ }
@@ -0,0 +1,48 @@
1
+ import SteveModel from '@shell/plugins/steve/steve-class';
2
+
3
+ export default class SteveToken extends SteveModel {
4
+ // for now, we are only showing the new tokens in the UI. Later we will be able to edit a few of it's fields
5
+ get _availableActions() {
6
+ return super._availableActions.filter((a) => ['viewInApi', 'promptRemove'].includes(a.action));
7
+ }
8
+
9
+ cleanForSave(data) {
10
+ const val = super.cleanForSave(data);
11
+
12
+ delete val.type;
13
+
14
+ return val;
15
+ }
16
+
17
+ get isDeprecated() {
18
+ return undefined;
19
+ }
20
+
21
+ get state() {
22
+ return this.isExpired ? 'expired' : !this.spec?.enabled ? 'inactive' : 'active';
23
+ }
24
+
25
+ get isExpired() {
26
+ return this.status?.expired;
27
+ }
28
+
29
+ get expiresAt() {
30
+ return this.status?.expiresAt || '';
31
+ }
32
+
33
+ get lastUsedAt() {
34
+ return this.status?.lastUsedAt || '';
35
+ }
36
+
37
+ get description() {
38
+ return this.spec?.description || '';
39
+ }
40
+
41
+ get clusterId() {
42
+ return this.spec?.clusterName || '';
43
+ }
44
+
45
+ get created() {
46
+ return this.metadata?.creationTimestamp;
47
+ }
48
+ }