@rancher/shell 3.0.5-rc.6 → 3.0.5-rc.8

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 (243) hide show
  1. package/assets/brand/classic/metadata.json +3 -0
  2. package/assets/styles/app.scss +1 -0
  3. package/assets/styles/base/_color.scss +16 -0
  4. package/assets/styles/base/_helpers.scss +10 -0
  5. package/assets/styles/base/_variables.scss +18 -12
  6. package/assets/styles/fonts/_icons.scss +1 -32
  7. package/assets/styles/global/_layout.scss +1 -1
  8. package/assets/styles/themes/_dark.scss +262 -258
  9. package/assets/styles/themes/_light.scss +538 -509
  10. package/assets/styles/themes/_modern.scss +914 -0
  11. package/assets/translations/en-us.yaml +110 -29
  12. package/chart/__tests__/S3.test.ts +2 -1
  13. package/cloud-credential/generic.vue +18 -10
  14. package/cloud-credential/harvester.vue +1 -9
  15. package/components/AdvancedSection.vue +8 -0
  16. package/components/ChartReadme.vue +17 -7
  17. package/components/CodeMirror.vue +1 -1
  18. package/components/Drawer/Chrome.vue +0 -1
  19. package/components/Drawer/ResourceDetailDrawer/__tests__/composables.test.ts +27 -28
  20. package/components/Drawer/ResourceDetailDrawer/composables.ts +4 -24
  21. package/components/Drawer/ResourceDetailDrawer/index.vue +18 -4
  22. package/components/InstallHelmCharts.vue +656 -0
  23. package/components/LazyImage.vue +60 -4
  24. package/components/Loading.vue +1 -1
  25. package/components/LocaleSelector.vue +7 -2
  26. package/components/Markdown.vue +4 -0
  27. package/components/PaginatedResourceTable.vue +46 -1
  28. package/components/PromptRestore.vue +22 -44
  29. package/components/Resource/Detail/Masthead/composable.ts +16 -0
  30. package/components/Resource/Detail/Masthead/index.vue +37 -0
  31. package/components/Resource/Detail/Metadata/IdentifyingInformation/composable.ts +10 -2
  32. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +26 -7
  33. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +8 -1
  34. package/components/Resource/Detail/Metadata/KeyValue.vue +12 -10
  35. package/components/Resource/Detail/Metadata/Rectangle.vue +3 -1
  36. package/components/Resource/Detail/Metadata/__tests__/composables.test.ts +10 -17
  37. package/components/Resource/Detail/Metadata/composables.ts +9 -7
  38. package/components/Resource/Detail/Metadata/index.vue +17 -2
  39. package/components/Resource/Detail/Page.vue +35 -21
  40. package/components/Resource/Detail/SpacedRow.vue +1 -1
  41. package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +8 -9
  42. package/components/Resource/Detail/TitleBar/composables.ts +5 -5
  43. package/components/Resource/Detail/TitleBar/index.vue +12 -3
  44. package/components/ResourceDetail/Masthead/legacy.vue +1 -1
  45. package/components/ResourceDetail/index.vue +569 -72
  46. package/components/ResourceList/index.vue +1 -0
  47. package/components/ResourceTable.vue +6 -1
  48. package/components/ResourceYaml.vue +1 -1
  49. package/components/RichTranslation.vue +106 -0
  50. package/components/SlideInPanelManager.vue +13 -10
  51. package/components/SortableTable/index.vue +5 -5
  52. package/components/SortableTable/selection.js +0 -1
  53. package/components/Tabbed/index.vue +35 -4
  54. package/components/__tests__/LazyImage.spec.ts +121 -0
  55. package/components/__tests__/PromptRestore.test.ts +1 -65
  56. package/components/__tests__/RichTranslation.test.ts +115 -0
  57. package/components/fleet/FleetStatus.vue +4 -0
  58. package/components/fleet/dashboard/ResourcePanel.vue +2 -1
  59. package/components/form/ClusterAppearance.vue +5 -0
  60. package/components/form/FileImageSelector.vue +1 -1
  61. package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
  62. package/components/form/NameNsDescription.vue +1 -0
  63. package/components/form/Networking.vue +24 -19
  64. package/components/form/ProjectMemberEditor.vue +1 -1
  65. package/components/form/ResourceLabeledSelect.vue +22 -8
  66. package/components/form/ResourceTabs/index.vue +20 -0
  67. package/components/form/SecretSelector.vue +9 -0
  68. package/components/form/SelectOrCreateAuthSecret.vue +6 -3
  69. package/components/form/__tests__/Networking.test.ts +116 -0
  70. package/components/form/labeled-select-utils/labeled-select-pagination.ts +3 -38
  71. package/components/formatter/FleetApplicationSource.vue +25 -17
  72. package/components/formatter/PodImages.vue +1 -1
  73. package/components/formatter/__tests__/LiveDate.test.ts +10 -2
  74. package/components/google/AccountAccess.vue +44 -46
  75. package/components/nav/Favorite.vue +4 -0
  76. package/components/nav/Group.vue +4 -1
  77. package/components/nav/NotificationCenter/Notification.vue +1 -27
  78. package/components/nav/WindowManager/index.vue +3 -3
  79. package/composables/resources.ts +2 -2
  80. package/config/labels-annotations.js +3 -2
  81. package/config/pagination-table-headers.js +8 -1
  82. package/config/product/explorer.js +27 -2
  83. package/config/product/manager.js +0 -1
  84. package/config/query-params.js +10 -0
  85. package/config/router/routes.js +21 -1
  86. package/config/system-namespaces.js +1 -1
  87. package/config/table-headers.js +30 -1
  88. package/config/types.js +1 -1
  89. package/config/version.js +1 -1
  90. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +11 -0
  91. package/detail/__tests__/workload.test.ts +164 -0
  92. package/detail/configmap.vue +33 -75
  93. package/detail/projectsecret.vue +11 -0
  94. package/detail/provisioning.cattle.io.cluster.vue +351 -369
  95. package/detail/secret.vue +49 -308
  96. package/detail/workload/index.vue +38 -21
  97. package/dialog/InstallExtensionDialog.vue +8 -5
  98. package/dialog/RotateEncryptionKeyDialog.vue +10 -30
  99. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +224 -0
  100. package/edit/auth/ldap/__tests__/config.test.ts +14 -0
  101. package/edit/auth/ldap/config.vue +24 -0
  102. package/edit/compliance.cattle.io.clusterscan.vue +1 -1
  103. package/edit/configmap.vue +4 -1
  104. package/edit/fleet.cattle.io.gitrepo.vue +5 -6
  105. package/edit/fleet.cattle.io.helmop.vue +78 -56
  106. package/edit/logging.banzaicloud.io.output/index.vue +1 -1
  107. package/edit/logging.banzaicloud.io.output/providers/awsElasticsearch.vue +5 -6
  108. package/edit/networking.k8s.io.ingress/Certificate.vue +20 -22
  109. package/edit/networking.k8s.io.ingress/DefaultBackend.vue +8 -3
  110. package/edit/networking.k8s.io.ingress/Rule.vue +2 -5
  111. package/edit/networking.k8s.io.ingress/RulePath.vue +17 -11
  112. package/edit/networking.k8s.io.ingress/__tests__/Certificate.test.ts +165 -0
  113. package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +11 -10
  114. package/edit/networking.k8s.io.networkpolicy/PolicyRules.vue +1 -3
  115. package/edit/networking.k8s.io.networkpolicy/index.vue +17 -17
  116. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +3 -2
  117. package/edit/provisioning.cattle.io.cluster/rke2.vue +123 -61
  118. package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +9 -7
  119. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +22 -13
  120. package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +10 -12
  121. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +39 -38
  122. package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +41 -19
  123. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +16 -3
  124. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +32 -33
  125. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryMirrors.vue +9 -10
  126. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +1 -3
  127. package/edit/provisioning.cattle.io.cluster/tabs/upgrade/DrainOptions.vue +16 -9
  128. package/edit/secret/basic.vue +1 -0
  129. package/edit/secret/index.vue +126 -15
  130. package/edit/workload/index.vue +5 -14
  131. package/list/projectsecret.vue +345 -0
  132. package/list/provisioning.cattle.io.cluster.vue +1 -69
  133. package/list/secret.vue +109 -0
  134. package/machine-config/__tests__/vmwarevsphere.test.ts +5 -7
  135. package/machine-config/google.vue +9 -1
  136. package/machine-config/vmwarevsphere.vue +7 -17
  137. package/mixins/__tests__/brand.spec.ts +2 -2
  138. package/mixins/chart.js +0 -2
  139. package/mixins/create-edit-view/impl.js +10 -1
  140. package/mixins/resource-fetch-api-pagination.js +11 -12
  141. package/mixins/resource-fetch.js +3 -1
  142. package/models/__tests__/chart.test.ts +111 -80
  143. package/models/__tests__/fleet.cattle.io.helmop.test.ts +224 -0
  144. package/models/__tests__/node.test.ts +7 -63
  145. package/models/catalog.cattle.io.app.js +1 -1
  146. package/models/catalog.cattle.io.operation.js +1 -1
  147. package/models/chart.js +36 -20
  148. package/models/cloudcredential.js +2 -163
  149. package/models/cluster/node.js +7 -7
  150. package/models/cluster.x-k8s.io.machine.js +3 -3
  151. package/models/cluster.x-k8s.io.machinedeployment.js +11 -2
  152. package/models/compliance.cattle.io.clusterscan.js +2 -2
  153. package/models/configmap.js +4 -0
  154. package/models/constraints.gatekeeper.sh.constraint.js +1 -1
  155. package/models/fleet-application.js +0 -17
  156. package/models/fleet.cattle.io.cluster.js +2 -2
  157. package/models/fleet.cattle.io.gitrepo.js +15 -1
  158. package/models/fleet.cattle.io.helmop.js +26 -22
  159. package/models/management.cattle.io.setting.js +4 -0
  160. package/models/persistentvolumeclaim.js +1 -1
  161. package/models/pod.js +2 -2
  162. package/models/provisioning.cattle.io.cluster.js +39 -67
  163. package/models/rke.cattle.io.etcdsnapshot.js +1 -1
  164. package/models/secret.js +161 -2
  165. package/models/storage.k8s.io.storageclass.js +2 -2
  166. package/models/workload.js +3 -3
  167. package/package.json +11 -10
  168. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +1 -0
  169. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +4 -1
  170. package/pages/c/_cluster/apps/charts/__tests__/AppChartCardFooter.spec.js +41 -0
  171. package/pages/c/_cluster/apps/charts/chart.vue +422 -174
  172. package/pages/c/_cluster/apps/charts/index.vue +46 -35
  173. package/pages/c/_cluster/apps/charts/install.vue +1 -1
  174. package/pages/c/_cluster/explorer/projectsecret.vue +24 -0
  175. package/pages/c/_cluster/fleet/__tests__/index.test.ts +608 -314
  176. package/pages/c/_cluster/fleet/index.vue +103 -45
  177. package/pages/c/_cluster/manager/cloudCredential/index.vue +2 -59
  178. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +10 -3
  179. package/pages/c/_cluster/uiplugins/index.vue +36 -25
  180. package/plugins/dashboard-store/__tests__/normalize.test.ts +223 -0
  181. package/plugins/dashboard-store/__tests__/resource-class.test.ts +191 -0
  182. package/plugins/dashboard-store/__tests__/utils/normalize-usecases.ts +1526 -0
  183. package/plugins/dashboard-store/actions.js +42 -22
  184. package/plugins/dashboard-store/normalize.js +29 -17
  185. package/plugins/dashboard-store/resource-class.js +83 -17
  186. package/plugins/steve/__tests__/getters.test.ts +1 -1
  187. package/plugins/steve/__tests__/subscribe.spec.ts +259 -1
  188. package/plugins/steve/getters.js +8 -2
  189. package/plugins/steve/resourceWatcher.js +10 -3
  190. package/plugins/steve/steve-pagination-utils.ts +14 -3
  191. package/plugins/steve/subscribe.js +192 -19
  192. package/plugins/steve/worker/web-worker.advanced.js +2 -0
  193. package/rancher-components/Card/Card.vue +0 -18
  194. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.test.ts +15 -0
  195. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +65 -0
  196. package/rancher-components/Pill/RcStatusBadge/index.ts +2 -0
  197. package/rancher-components/Pill/RcStatusBadge/types.ts +5 -0
  198. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.test.ts +33 -0
  199. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +75 -0
  200. package/rancher-components/Pill/RcStatusIndicator/index.ts +2 -0
  201. package/rancher-components/Pill/RcStatusIndicator/types.ts +7 -0
  202. package/rancher-components/Pill/types.ts +2 -0
  203. package/rancher-components/RcButton/RcButton.vue +1 -1
  204. package/rancher-components/RcDropdown/RcDropdown.test.ts +98 -0
  205. package/rancher-components/RcDropdown/RcDropdown.vue +5 -0
  206. package/rancher-components/RcDropdown/RcDropdownItem.vue +7 -1
  207. package/rancher-components/RcDropdown/RcDropdownItemCheckbox.vue +2 -1
  208. package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +2 -1
  209. package/rancher-components/RcDropdown/useDropdownContext.ts +21 -0
  210. package/rancher-components/RcDropdown/useDropdownItem.ts +30 -1
  211. package/rancher-components/RcItemCard/RcItemCard.test.ts +20 -0
  212. package/rancher-components/RcItemCard/RcItemCard.vue +40 -6
  213. package/store/__tests__/catalog.test.ts +93 -1
  214. package/store/aws.js +19 -8
  215. package/store/catalog.js +8 -3
  216. package/types/kube/kube-api.ts +12 -0
  217. package/types/resources/settings.d.ts +1 -1
  218. package/types/shell/index.d.ts +643 -585
  219. package/types/store/pagination.types.ts +16 -6
  220. package/types/uiplugins.ts +73 -0
  221. package/utils/__tests__/back-off.test.ts +354 -0
  222. package/utils/__tests__/create-yaml.test.ts +235 -0
  223. package/utils/__tests__/kontainer.test.ts +19 -0
  224. package/utils/__tests__/uiplugins.test.ts +84 -0
  225. package/utils/back-off.ts +176 -0
  226. package/utils/create-yaml.js +103 -9
  227. package/utils/dynamic-importer.js +8 -0
  228. package/utils/kontainer.ts +3 -5
  229. package/utils/pagination-utils.ts +18 -0
  230. package/utils/style.ts +3 -0
  231. package/utils/uiplugins.ts +29 -2
  232. package/utils/validators/__tests__/setting.test.js +92 -0
  233. package/utils/validators/formRules/__tests__/index.test.ts +88 -7
  234. package/utils/validators/formRules/index.ts +83 -8
  235. package/utils/validators/setting.js +17 -0
  236. package/cloud-credential/__tests__/harvester.test.ts +0 -18
  237. package/components/ResourceDetail/__tests__/index.test.ts +0 -135
  238. package/components/ResourceDetail/legacy.vue +0 -562
  239. package/components/formatter/CloudCredExpired.vue +0 -69
  240. package/models/etcdbackup.js +0 -45
  241. package/pages/explorer/resource/detail/configmap.vue +0 -42
  242. package/pages/explorer/resource/detail/secret.vue +0 -50
  243. package/utils/aws.js +0 -0
@@ -117,7 +117,16 @@ export default {
117
117
  // If they are resolved, return a false-y value
118
118
  // Else they can't be resolved, return an array of errors to show to the user.
119
119
  async conflict() {
120
- return await handleConflict(this.initialValue.toJSON(), this.value, this.liveValue, this.$store.getters, this.$store, this.storeOverride || this.$store.getters['currentStore'](this.value.type));
120
+ return await handleConflict(
121
+ this.initialValue,
122
+ this.value,
123
+ this.liveValue,
124
+ {
125
+ dispatch: this.$store.dispatch,
126
+ getters: this.$store.getters
127
+ },
128
+ this.storeOverride || this.$store.getters['currentStore'](this.value.type)
129
+ );
121
130
  },
122
131
 
123
132
  async save(buttonDone, url, depth = 0) {
@@ -125,7 +125,7 @@ export default {
125
125
  context: this.context,
126
126
  };
127
127
 
128
- return this.$store.getters[`${ this.inStore }/paginationEnabled`]?.(args);
128
+ return this.$store.getters[`${ this.overrideInStore || this.inStore }/paginationEnabled`]?.(args);
129
129
  }
130
130
  },
131
131
 
@@ -200,7 +200,7 @@ export default {
200
200
  return;
201
201
  }
202
202
 
203
- return this.$store.getters[`${ this.inStore }/havePage`](this.resource);
203
+ return this.$store.getters[`${ this.overrideInStore || this.inStore }/havePage`](this.resource);
204
204
  },
205
205
 
206
206
  /**
@@ -280,12 +280,12 @@ export default {
280
280
  projectsOrNamespaces,
281
281
  filters
282
282
  } = stevePaginationUtils.createParamsFromNsFilter({
283
- allNamespaces: this.$store.getters[`${ this.currentProduct?.inStore }/all`](NAMESPACE),
284
- selection: neu,
285
- isAllNamespaces: this.isAllNamespaces,
286
- isLocalCluster: this.$store.getters['currentCluster'].isLocal,
287
- showDynamicRancherNamespaces: this.showDynamicRancherNamespaces,
288
- productHidesSystemNamespaces: this.productHidesSystemNamespaces,
283
+ allNamespaces: this.$store.getters[`${ this.currentProduct?.inStore }/all`](NAMESPACE),
284
+ selection: neu,
285
+ isAllNamespaces: this.isAllNamespaces,
286
+ isLocalCluster: this.$store.getters['currentCluster'].isLocal,
287
+ showReservedRancherNamespaces: this.showDynamicRancherNamespaces,
288
+ productHidesSystemNamespaces: this.productHidesSystemNamespaces,
289
289
  });
290
290
 
291
291
  this.requestFilters.filters = filters;
@@ -352,7 +352,7 @@ export default {
352
352
  }
353
353
  },
354
354
 
355
- unmounted() {
355
+ async beforeUnmount() {
356
356
  if (this.havePaginated) {
357
357
  // of type @STEVE_WATCH_PARAMS
358
358
  const watchArgs = {
@@ -360,9 +360,8 @@ export default {
360
360
  mode: STEVE_WATCH_MODE.RESOURCE_CHANGES,
361
361
  };
362
362
 
363
- this.$store.dispatch(`${ this.inStore }/forgetType`, this.resource, (watchParams) => {
364
- return watchParams.type === watchArgs.type &&
365
- watchParams.mode === watchArgs.type.mode;
363
+ await this.$store.dispatch(`${ this.overrideInStore || this.inStore }/forgetType`, this.resource, (watchParams) => {
364
+ return watchParams.type === watchArgs.type && watchParams.mode === watchArgs.type.mode;
366
365
  });
367
366
  }
368
367
  }
@@ -242,7 +242,9 @@ export default {
242
242
  },
243
243
 
244
244
  __getCountForResource(resourceName, namespace, storeType) {
245
- const resourceCounts = this.$store.getters[`${ storeType }/all`](COUNT)[0]?.counts[`${ resourceName }`]; // NB `rancher` store behaves differently, lacks counts but has resource
245
+ const currStore = storeType || this.$store.getters['currentStore']();
246
+
247
+ const resourceCounts = this.$store.getters[`${ currStore }/all`](COUNT)[0]?.counts[`${ resourceName }`]; // NB `rancher` store behaves differently, lacks counts but has resource
246
248
  const resourceCount = namespace && resourceCounts?.namespaces ? resourceCounts?.namespaces[namespace]?.count : resourceCounts?.summary?.count;
247
249
 
248
250
  return resourceCount || 0;
@@ -1,6 +1,24 @@
1
1
  import Chart from '@shell/models/chart';
2
2
  import { APP_UPGRADE_STATUS } from '@shell/store/catalog';
3
3
  import { CATALOG as CATALOG_ANNOTATIONS } from '@shell/config/labels-annotations';
4
+ import { ZERO_TIME } from '@shell/config/types';
5
+
6
+ type MockChartContext = {
7
+ rootGetters: {
8
+ 'cluster/all': () => any[];
9
+ 'i18n/t': (key: string) => string;
10
+ };
11
+ dispatch?: jest.Mock;
12
+ };
13
+
14
+ interface CardContent {
15
+ subHeaderItems: { label: string }[];
16
+ footerItems: { labels: string[]; icon?: string }[];
17
+ statuses: { tooltip: { key: string }; color: string }[];
18
+ }
19
+
20
+ const t = jest.fn((key) => key); // mock translation function
21
+ const dispatch = jest.fn();
4
22
 
5
23
  const base = {
6
24
  chartName: 'my-app',
@@ -29,16 +47,31 @@ function makeInstalledApp(upgradeAvailable = APP_UPGRADE_STATUS.NO_UPGRADE) {
29
47
  }
30
48
  }
31
49
  },
50
+ metadata: {},
32
51
  upgradeAvailable
33
52
  };
34
53
  }
35
54
 
36
55
  describe('class Chart', () => {
56
+ let ctx: MockChartContext;
57
+
58
+ beforeEach(() => {
59
+ ctx = {
60
+ rootGetters: {
61
+ 'cluster/all': () => [],
62
+ 'i18n/t': t
63
+ },
64
+ dispatch
65
+ };
66
+ });
67
+
37
68
  describe('matchingInstalledApps', () => {
38
69
  it('matches by name, repo, and home in latest version', () => {
39
70
  const installedApp = makeInstalledApp();
40
71
 
41
- const chart = new Chart(base, { rootGetters: { 'cluster/all': () => [installedApp] } });
72
+ ctx.rootGetters['cluster/all'] = () => [installedApp];
73
+
74
+ const chart = new Chart(base, ctx);
42
75
 
43
76
  expect(chart.matchingInstalledApps).toHaveLength(1);
44
77
  });
@@ -47,8 +80,9 @@ describe('class Chart', () => {
47
80
  const installedApp = makeInstalledApp();
48
81
 
49
82
  installedApp.spec.chart.metadata.name = 'different-app';
83
+ ctx.rootGetters['cluster/all'] = () => [installedApp];
50
84
 
51
- const chart = new Chart(base, { rootGetters: { 'cluster/all': () => [installedApp] } });
85
+ const chart = new Chart(base, ctx);
52
86
 
53
87
  expect(chart.matchingInstalledApps).toHaveLength(0);
54
88
  });
@@ -57,8 +91,9 @@ describe('class Chart', () => {
57
91
  const installedApp = makeInstalledApp();
58
92
 
59
93
  installedApp.spec.chart.metadata.annotations[CATALOG_ANNOTATIONS.SOURCE_REPO_NAME] = 'different-repo';
94
+ ctx.rootGetters['cluster/all'] = () => [installedApp];
60
95
 
61
- const chart = new Chart(base, { rootGetters: { 'cluster/all': () => [installedApp] } });
96
+ const chart = new Chart(base, ctx);
62
97
 
63
98
  expect(chart.matchingInstalledApps).toHaveLength(0);
64
99
  });
@@ -66,9 +101,10 @@ describe('class Chart', () => {
66
101
  it('matches by version+home when not latest', () => {
67
102
  const installedApp = makeInstalledApp();
68
103
 
69
- installedApp.spec.chart.metadata.version = '1.2.3'; // not the latest in base
104
+ installedApp.spec.chart.metadata.version = '1.2.3';
105
+ ctx.rootGetters['cluster/all'] = () => [installedApp];
70
106
 
71
- const chart = new Chart(base, { rootGetters: { 'cluster/all': () => [installedApp] } });
107
+ const chart = new Chart(base, ctx);
72
108
 
73
109
  expect(chart.matchingInstalledApps).toHaveLength(1);
74
110
  });
@@ -76,10 +112,11 @@ describe('class Chart', () => {
76
112
  it('can use fallback repo from metadata labels', () => {
77
113
  const installedApp = makeInstalledApp();
78
114
 
79
- installedApp.spec.chart.metadata.annotations = {}; // remove SOURCE_REPO_NAME
115
+ installedApp.spec.chart.metadata.annotations = {};
80
116
  installedApp.metadata = { labels: { [CATALOG_ANNOTATIONS.CLUSTER_REPO_NAME]: 'my-repo' } };
117
+ ctx.rootGetters['cluster/all'] = () => [installedApp];
81
118
 
82
- const chart = new Chart(base, { rootGetters: { 'cluster/all': () => [installedApp] } });
119
+ const chart = new Chart(base, ctx);
83
120
 
84
121
  expect(chart.matchingInstalledApps).toHaveLength(1);
85
122
  });
@@ -87,45 +124,29 @@ describe('class Chart', () => {
87
124
 
88
125
  describe('isInstalled', () => {
89
126
  it('is true when one app matches', () => {
90
- const installedApp = {
91
- spec: {
92
- chart: {
93
- metadata: {
94
- name: 'my-app',
95
- version: '1.2.3',
96
- home: 'https://example.com',
97
- annotations: { [CATALOG_ANNOTATIONS.SOURCE_REPO_NAME]: 'my-repo' }
98
- }
99
- }
100
- }
101
- };
127
+ const installedApp = makeInstalledApp();
128
+
129
+ installedApp.spec.chart.metadata.version = '1.2.3';
130
+ ctx.rootGetters['cluster/all'] = () => [installedApp];
102
131
 
103
- const chart = new Chart(base, { rootGetters: { 'cluster/all': () => [installedApp] } });
132
+ const chart = new Chart(base, ctx);
104
133
 
105
134
  expect(chart.isInstalled).toBe(true);
106
135
  });
107
136
 
108
137
  it('is false when no apps match', () => {
109
- const chart = new Chart(base, { rootGetters: { 'cluster/all': () => [] } });
138
+ const chart = new Chart(base, ctx);
110
139
 
111
140
  expect(chart.isInstalled).toBe(false);
112
141
  });
113
142
 
114
143
  it('is false when multiple apps match', () => {
115
- const appTemplate = {
116
- spec: {
117
- chart: {
118
- metadata: {
119
- name: 'my-app',
120
- version: '1.2.3',
121
- home: 'https://example.com',
122
- annotations: { [CATALOG_ANNOTATIONS.SOURCE_REPO_NAME]: 'my-repo' }
123
- }
124
- }
125
- }
126
- };
144
+ const app = makeInstalledApp();
127
145
 
128
- const chart = new Chart(base, { rootGetters: { 'cluster/all': () => [appTemplate, appTemplate] } });
146
+ app.spec.chart.metadata.version = '1.2.3';
147
+ ctx.rootGetters['cluster/all'] = () => [app, app];
148
+
149
+ const chart = new Chart(base, ctx);
129
150
 
130
151
  expect(chart.isInstalled).toBe(false);
131
152
  });
@@ -133,47 +154,29 @@ describe('class Chart', () => {
133
154
 
134
155
  describe('upgradeable', () => {
135
156
  it('is true when installed and upgradeAvailable is SINGLE_UPGRADE', () => {
136
- const installedApp = {
137
- spec: {
138
- chart: {
139
- metadata: {
140
- name: 'my-app',
141
- version: '1.2.3',
142
- home: 'https://example.com',
143
- annotations: { [CATALOG_ANNOTATIONS.SOURCE_REPO_NAME]: 'my-repo' }
144
- }
145
- }
146
- },
147
- upgradeAvailable: APP_UPGRADE_STATUS.SINGLE_UPGRADE
148
- };
157
+ const installedApp = makeInstalledApp(APP_UPGRADE_STATUS.SINGLE_UPGRADE);
158
+
159
+ installedApp.spec.chart.metadata.version = '1.2.3';
160
+ ctx.rootGetters['cluster/all'] = () => [installedApp];
149
161
 
150
- const chart = new Chart(base, { rootGetters: { 'cluster/all': () => [installedApp] } });
162
+ const chart = new Chart(base, ctx);
151
163
 
152
164
  expect(chart.upgradeable).toBe(true);
153
165
  });
154
166
 
155
167
  it('is false if upgradeAvailable is different', () => {
156
- const installedApp = {
157
- spec: {
158
- chart: {
159
- metadata: {
160
- name: 'my-app',
161
- version: '1.2.3',
162
- home: 'https://example.com',
163
- annotations: { [CATALOG_ANNOTATIONS.SOURCE_REPO_NAME]: 'my-repo' }
164
- }
165
- }
166
- },
167
- upgradeAvailable: APP_UPGRADE_STATUS.NO_UPGRADE
168
- };
168
+ const installedApp = makeInstalledApp(APP_UPGRADE_STATUS.NO_UPGRADE);
169
+
170
+ installedApp.spec.chart.metadata.version = '1.2.3';
171
+ ctx.rootGetters['cluster/all'] = () => [installedApp];
169
172
 
170
- const chart = new Chart(base, { rootGetters: { 'cluster/all': () => [installedApp] } });
173
+ const chart = new Chart(base, ctx);
171
174
 
172
175
  expect(chart.upgradeable).toBe(false);
173
176
  });
174
177
 
175
178
  it('is false when not installed', () => {
176
- const chart = new Chart(base, { rootGetters: { 'cluster/all': () => [] } });
179
+ const chart = new Chart(base, ctx);
177
180
 
178
181
  expect(chart.upgradeable).toBe(false);
179
182
  });
@@ -181,9 +184,9 @@ describe('class Chart', () => {
181
184
 
182
185
  describe('cardContent', () => {
183
186
  it('includes correct subHeader and footer info', () => {
184
- const chart = new Chart(base, { rootGetters: { 'cluster/all': () => [] } });
187
+ const chart = new Chart(base, ctx);
185
188
 
186
- const result = chart.cardContent;
189
+ const result = chart.cardContent as CardContent;
187
190
 
188
191
  expect(result.subHeaderItems).toHaveLength(2);
189
192
  expect(result.subHeaderItems[0].label).toBe('1.3.0');
@@ -200,66 +203,72 @@ describe('class Chart', () => {
200
203
  ...base,
201
204
  categories: ['database'],
202
205
  tags: ['linux', 'experimentl']
203
- }, { rootGetters: { 'cluster/all': () => [] } });
206
+ }, ctx);
204
207
 
205
- const result = chart.cardContent;
208
+ const result = chart.cardContent as CardContent;
206
209
 
207
210
  expect(result.footerItems).toHaveLength(3);
208
211
 
209
212
  const categoryItem = result.footerItems.find((i) => i.icon === 'icon-category-alt');
210
213
 
211
214
  expect(categoryItem).toBeDefined();
212
- expect(categoryItem.labels).toContain('database');
215
+ expect(categoryItem?.labels).toContain('database');
213
216
 
214
217
  const tagItem = result.footerItems.find((i) => i.icon === 'icon-tag-alt');
215
218
 
216
219
  expect(tagItem).toBeDefined();
217
- expect(tagItem.labels).toStrictEqual(expect.arrayContaining(['linux', 'experimentl']));
220
+ expect(tagItem?.labels).toStrictEqual(expect.arrayContaining(['linux', 'experimentl']));
218
221
  });
219
222
 
220
223
  it('includes deprecated status when deprecated is true', () => {
221
- const chart = new Chart({ ...base, deprecated: true }, { rootGetters: { 'cluster/all': () => [] } });
224
+ const chart = new Chart({ ...base, deprecated: true }, ctx);
222
225
 
223
- const result = chart.cardContent;
226
+ const result = chart.cardContent as CardContent;
224
227
 
225
228
  const deprecatedStatus = result.statuses.find((s) => s.tooltip.key === 'generic.deprecated');
226
229
 
227
230
  expect(deprecatedStatus).toBeDefined();
228
- expect(deprecatedStatus.color).toBe('error');
231
+ expect(deprecatedStatus?.color).toBe('error');
229
232
  });
230
233
 
231
234
  it('includes installed status when app is installed', () => {
232
235
  const installedApp = makeInstalledApp();
233
236
 
234
- const chart = new Chart(base, { rootGetters: { 'cluster/all': () => [installedApp] } });
237
+ ctx.rootGetters['cluster/all'] = () => [installedApp];
235
238
 
236
- const result = chart.cardContent;
239
+ const chart = new Chart(base, ctx);
240
+
241
+ const result = chart.cardContent as CardContent;
237
242
 
238
243
  const installedStatus = result.statuses.find((s) => s.tooltip.key === 'generic.installed');
239
244
 
240
245
  expect(installedStatus).toBeDefined();
241
- expect(installedStatus.color).toBe('success');
246
+ expect(installedStatus?.color).toBe('success');
242
247
  });
243
248
 
244
249
  it('includes upgradeable status when upgrade is available', () => {
245
250
  const installedApp = makeInstalledApp(APP_UPGRADE_STATUS.SINGLE_UPGRADE);
246
251
 
247
- const chart = new Chart(base, { rootGetters: { 'cluster/all': () => [installedApp] } });
252
+ ctx.rootGetters['cluster/all'] = () => [installedApp];
248
253
 
249
- const result = chart.cardContent;
254
+ const chart = new Chart(base, ctx);
255
+
256
+ const result = chart.cardContent as CardContent;
250
257
 
251
258
  const upgradeableStatus = result.statuses.find((s) => s.tooltip.key === 'generic.upgradeable');
252
259
 
253
260
  expect(upgradeableStatus).toBeDefined();
254
- expect(upgradeableStatus.color).toBe('info');
261
+ expect(upgradeableStatus?.color).toBe('info');
255
262
  });
256
263
 
257
264
  it('shows all statuses together when all conditions are met', () => {
258
265
  const installedApp = makeInstalledApp(APP_UPGRADE_STATUS.SINGLE_UPGRADE);
259
266
 
260
- const chart = new Chart({ ...base, deprecated: true }, { rootGetters: { 'cluster/all': () => [installedApp] } });
267
+ ctx.rootGetters['cluster/all'] = () => [installedApp];
261
268
 
262
- const result = chart.cardContent;
269
+ const chart = new Chart({ ...base, deprecated: true }, ctx);
270
+
271
+ const result = chart.cardContent as CardContent;
263
272
 
264
273
  const keys = result.statuses.map((s) => s.tooltip.key);
265
274
 
@@ -269,5 +278,27 @@ describe('class Chart', () => {
269
278
  'generic.installed'
270
279
  ]));
271
280
  });
281
+
282
+ it('handles zero time for last updated date', () => {
283
+ const chartWithZeroTime = {
284
+ ...base,
285
+ versions: [{
286
+ ...base.versions[0],
287
+ created: ZERO_TIME,
288
+ }]
289
+ };
290
+ const chart = new Chart(chartWithZeroTime, {
291
+ rootGetters: {
292
+ 'cluster/all': () => [],
293
+ 'i18n/t': (key) => key
294
+ },
295
+ });
296
+
297
+ const result = chart.cardContent;
298
+ const lastUpdatedItem = result.subHeaderItems[1];
299
+
300
+ expect(lastUpdatedItem.label).toBe('generic.na');
301
+ expect(lastUpdatedItem.labelTooltip).toBe('catalog.charts.appChartCard.subHeaderItem.missingVersionDate');
302
+ });
272
303
  });
273
304
  });
@@ -0,0 +1,224 @@
1
+ import HelmOp from '@shell/models/fleet.cattle.io.helmop.js';
2
+
3
+ describe('class HelmOp', () => {
4
+ let instance;
5
+
6
+ describe('source getter', () => {
7
+ it('should return correct source for SOURCE_TYPE.REPO (HTTPS)', () => {
8
+ instance = new HelmOp({
9
+ spec: {
10
+ helm: {
11
+ repo: 'https://charts.rancher.io/fleet',
12
+ chart: 'fleet-agent'
13
+ }
14
+ }
15
+ });
16
+
17
+ const source = instance.source;
18
+
19
+ expect(source.value).toBe('https://charts.rancher.io/fleet');
20
+ expect(source.display).toBe('charts.rancher.io/fleet');
21
+ expect(source.icon).toBe('icon icon-application');
22
+ expect(source.showLink).toBe(true);
23
+ });
24
+
25
+ it('should return correct source for SOURCE_TYPE.REPO (GitHub HTTPS .git)', () => {
26
+ instance = new HelmOp({
27
+ spec: {
28
+ helm: {
29
+ repo: 'https://github.com/rancher/fleet.git',
30
+ chart: 'fleet'
31
+ }
32
+ }
33
+ });
34
+
35
+ const source = instance.source;
36
+
37
+ expect(source.value).toBe('https://github.com/rancher/fleet.git');
38
+ expect(source.display).toBe('rancher/fleet');
39
+ expect(source.icon).toBe('icon icon-application');
40
+ expect(source.showLink).toBe(true);
41
+ });
42
+
43
+ it('should return correct source for SOURCE_TYPE.REPO (GitHub SSH)', () => {
44
+ instance = new HelmOp({
45
+ spec: {
46
+ helm: {
47
+ repo: 'git@github.com:rancher/fleet.git',
48
+ chart: 'fleet'
49
+ }
50
+ }
51
+ });
52
+
53
+ const source = instance.source;
54
+
55
+ expect(source.value).toBe('https://github.com/rancher/fleet');
56
+ expect(source.display).toBe('rancher/fleet');
57
+ expect(source.icon).toBe('icon icon-application');
58
+ expect(source.showLink).toBe(true);
59
+ });
60
+
61
+ it('should return correct source for SOURCE_TYPE.OCI', () => {
62
+ instance = new HelmOp({ spec: { helm: { repo: 'oci://ghcr.io/rancher/some-chart' } } });
63
+
64
+ const source = instance.source;
65
+
66
+ expect(source.value).toBe('oci://ghcr.io');
67
+ expect(source.display).toBe('oci://ghcr.io');
68
+ expect(source.icon).toBe('icon icon-application');
69
+ expect(source.showLink).toBe(false);
70
+ });
71
+
72
+ it('should return correct source for SOURCE_TYPE.TARBALL', () => {
73
+ instance = new HelmOp({ spec: { helm: { chart: 'https://github.com/rancher/fleet-helm-charts/releases/download/fleet-0.12.1-beta.2/fleet-0.12.1-beta.2.tgz' } } });
74
+
75
+ const source = instance.source;
76
+
77
+ expect(source.value).toBe('https://github.com/rancher/fleet-helm-charts/releases/download/fleet-0.12.1-beta.2/fleet-0.12.1-beta.2.tgz');
78
+ expect(source.display).toBe('rancher/fleet-helm-charts/releases/download/fleet-0.12.1-beta.2/fleet-0.12.1-beta.2.tgz');
79
+ expect(source.icon).toBe('icon icon-application');
80
+ expect(source.showLink).toBe(true);
81
+ });
82
+
83
+ it('should handle missing helm spec gracefully', () => {
84
+ instance = new HelmOp({ spec: {} });
85
+ const source = instance.source;
86
+
87
+ expect(source.value).toBe('');
88
+ expect(source.display).toBeNull();
89
+ expect(source.icon).toBe('icon icon-application');
90
+ expect(source.showLink).toBe(false);
91
+ });
92
+ });
93
+
94
+ describe('sourceSub getter', () => {
95
+ it('should display chart name and desired version when both are present (SOURCE_TYPE.REPO)', () => {
96
+ instance = new HelmOp({
97
+ spec: {
98
+ helm: {
99
+ repo: 'https://charts.rancher.io/fleet',
100
+ chart: 'fleet-agent',
101
+ version: '0.12.x'
102
+ }
103
+ }
104
+ });
105
+
106
+ const sourceSub = instance.sourceSub;
107
+
108
+ expect(sourceSub.value).toBe('fleet-agent : 0.12.x');
109
+ expect(sourceSub.display).toBe('fleet-agent : 0.12.x');
110
+ });
111
+
112
+ it('should display chart name and desired version when both are present (SOURCE_TYPE.OCI)', () => {
113
+ instance = new HelmOp({
114
+ spec: {
115
+ helm: {
116
+ repo: 'oci://ghcr.io/rancher/some-chart',
117
+ version: '1.0.0'
118
+ }
119
+ }
120
+ });
121
+
122
+ const sourceSub = instance.sourceSub;
123
+
124
+ expect(sourceSub.value).toBe('rancher/some-chart : 1.0.0');
125
+ expect(sourceSub.display).toBe('rancher/some-chart : 1.0.0');
126
+ });
127
+
128
+ it('should display only installed version when only it is present', () => {
129
+ instance = new HelmOp({
130
+ status: { version: '0.12.3' },
131
+ spec: { helm: {} }
132
+ });
133
+
134
+ const sourceSub = instance.sourceSub;
135
+
136
+ expect(sourceSub.value).toBe('0.12.3');
137
+ expect(sourceSub.display).toBe('0.12.3');
138
+ });
139
+
140
+ it('should display semantic version when installed version is missing', () => {
141
+ instance = new HelmOp({
142
+ spec: {
143
+ helm: {
144
+ version: '0.12.x',
145
+ chart: 'test-chart'
146
+ }
147
+ }
148
+ });
149
+
150
+ const sourceSub = instance.sourceSub;
151
+
152
+ expect(sourceSub.value).toBe('0.12.x');
153
+ expect(sourceSub.display).toBe('0.12.x');
154
+ });
155
+
156
+ it('should display "semantic -> installed" when both versions are present (no chart)', () => {
157
+ instance = new HelmOp({
158
+ spec: { helm: { version: '0.12.x' } },
159
+ status: { version: '0.12.5' }
160
+ });
161
+
162
+ const sourceSub = instance.sourceSub;
163
+
164
+ expect(sourceSub.value).toBe('0.12.x -> 0.12.5');
165
+ expect(sourceSub.display).toBe('0.12.x -> 0.12.5');
166
+ });
167
+
168
+ it('should display chart and "semantic -> installed" when all are present', () => {
169
+ instance = new HelmOp({
170
+ spec: {
171
+ helm: {
172
+ repo: 'https://charts.rancher.io/fleet',
173
+ chart: 'fleet-agent',
174
+ version: '0.12.x'
175
+ }
176
+ },
177
+ status: { version: '0.12.5' }
178
+ });
179
+
180
+ const sourceSub = instance.sourceSub;
181
+
182
+ expect(sourceSub.value).toBe('fleet-agent : 0.12.x -> 0.12.5');
183
+ expect(sourceSub.display).toBe('fleet-agent : 0.12.x -> 0.12.5');
184
+ });
185
+
186
+ it('should display chart and only semantic version when all are present but semantic version is equal to installed version (no duplicate info)', () => {
187
+ instance = new HelmOp({
188
+ spec: {
189
+ helm: {
190
+ repo: 'https://charts.rancher.io/fleet',
191
+ chart: 'fleet-agent',
192
+ version: '0.12.3'
193
+ }
194
+ },
195
+ status: { version: '0.12.3' }
196
+ });
197
+
198
+ const sourceSub = instance.sourceSub;
199
+
200
+ expect(sourceSub.value).toBe('fleet-agent : 0.12.3');
201
+ expect(sourceSub.display).toBe('fleet-agent : 0.12.3');
202
+ });
203
+
204
+ it('should return empty string when no version or chart information is available', () => {
205
+ instance = new HelmOp({
206
+ spec: { helm: {} },
207
+ status: {}
208
+ });
209
+
210
+ const sourceSub = instance.sourceSub;
211
+
212
+ expect(sourceSub.value).toBe('');
213
+ expect(sourceSub.display).toBe('');
214
+ });
215
+
216
+ it('should correctly handle missing helm spec', () => {
217
+ instance = new HelmOp({ spec: {}, status: {} });
218
+ const sourceSub = instance.sourceSub;
219
+
220
+ expect(sourceSub.value).toBe('');
221
+ expect(sourceSub.display).toBe('');
222
+ });
223
+ });
224
+ });