@rancher/shell 3.0.12-rc.3 → 3.0.12-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 (315) hide show
  1. package/assets/styles/global/_button.scss +1 -1
  2. package/assets/styles/global/_layout.scss +4 -0
  3. package/assets/translations/en-us.yaml +183 -51
  4. package/assets/translations/zh-hans.yaml +1 -7
  5. package/chart/monitoring/ClusterSelector.vue +0 -21
  6. package/chart/monitoring/prometheus/index.vue +6 -3
  7. package/components/ActionDropdownShell.vue +5 -3
  8. package/components/ButtonGroup.vue +26 -1
  9. package/components/CruResource.vue +212 -16
  10. package/components/ExplorerMembers.vue +8 -4
  11. package/components/ExplorerProjectsNamespaces.vue +10 -6
  12. package/components/GrowlManager.vue +4 -0
  13. package/components/MgmtNodeList.vue +184 -0
  14. package/components/PromptRestore.vue +93 -32
  15. package/components/Questions/index.vue +1 -0
  16. package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +90 -1
  17. package/components/Resource/Detail/Card/StateCard/composables.ts +57 -87
  18. package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +61 -0
  19. package/components/Resource/Detail/Card/StatusCard/index.vue +61 -15
  20. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +2 -0
  21. package/components/Resource/Detail/Metadata/KeyValue.vue +5 -2
  22. package/components/Resource/Detail/Metadata/KeyValueRow.vue +2 -6
  23. package/components/ResourceDetail/index.vue +1 -1
  24. package/components/ResourceList/Masthead.vue +7 -1
  25. package/components/ResourceList/index.vue +82 -1
  26. package/components/ResourceTable.vue +1 -0
  27. package/components/RichTranslation.vue +5 -2
  28. package/components/Setting.vue +1 -0
  29. package/components/SortableTable/index.vue +4 -3
  30. package/components/SubtleLink.vue +31 -6
  31. package/components/Tabbed/Tab.vue +29 -3
  32. package/components/Tabbed/index.vue +25 -3
  33. package/components/TableOfContents/TableOfContents.vue +109 -0
  34. package/components/TableOfContents/composables.ts +258 -0
  35. package/components/Window/ContainerShell.vue +21 -11
  36. package/components/Window/__tests__/ContainerShell.test.ts +107 -37
  37. package/components/Wizard.vue +23 -5
  38. package/components/__tests__/ButtonGroup.test.ts +56 -0
  39. package/components/__tests__/PromptRestore.test.ts +169 -19
  40. package/components/fleet/AppCoChartGrid.vue +401 -0
  41. package/components/fleet/AppCoEmptyState.vue +127 -0
  42. package/components/fleet/AppCoPageHeader.vue +119 -0
  43. package/components/fleet/AppCoVersionSelect.vue +70 -0
  44. package/components/fleet/FleetClusterTargets/ClusterSelectionFields.vue +217 -0
  45. package/components/fleet/FleetClusterTargets/TargetsList.vue +123 -35
  46. package/components/fleet/FleetClusterTargets/index.vue +189 -146
  47. package/components/fleet/FleetIntro.vue +7 -3
  48. package/components/fleet/FleetNoWorkspaces.vue +7 -3
  49. package/components/fleet/FleetSecretSelector.vue +5 -3
  50. package/components/fleet/FleetValuesFrom.vue +8 -2
  51. package/components/fleet/GitRepoAdvancedTab.vue +1 -0
  52. package/components/fleet/GitRepoMetadataTab.vue +5 -0
  53. package/components/fleet/GitRepoTargetTab.vue +0 -2
  54. package/components/fleet/HelmOpAdvancedTab.vue +19 -53
  55. package/components/fleet/HelmOpAppCoConfigTab.vue +597 -0
  56. package/components/fleet/HelmOpAppCoResourcesSection.vue +162 -0
  57. package/components/fleet/HelmOpMetadataTab.vue +5 -0
  58. package/components/fleet/HelmOpResourcesSection.vue +82 -0
  59. package/components/fleet/HelmOpTargetOptionsSection.vue +89 -0
  60. package/components/fleet/HelmOpTargetTab.vue +64 -60
  61. package/components/fleet/HelmOpValuesTab.vue +129 -105
  62. package/components/fleet/__tests__/AppCoEmptyState.test.ts +71 -0
  63. package/components/fleet/__tests__/AppCoVersionSelect.test.ts +36 -0
  64. package/components/fleet/__tests__/ClusterSelectionFields.test.ts +62 -0
  65. package/components/fleet/__tests__/FleetClusterTargets.test.ts +253 -0
  66. package/components/fleet/__tests__/FleetSecretSelector.test.ts +16 -0
  67. package/components/fleet/__tests__/FleetValuesFrom.test.ts +44 -0
  68. package/components/fleet/__tests__/HelmOpAppCoConfigTab.test.ts +59 -0
  69. package/components/fleet/__tests__/HelmOpAppCoResourcesSection.test.ts +62 -0
  70. package/components/fleet/__tests__/HelmOpResourcesSection.test.ts +43 -0
  71. package/components/fleet/__tests__/HelmOpTargetOptionsSection.test.ts +34 -0
  72. package/components/fleet/__tests__/HelmOpValuesTab.test.ts +39 -0
  73. package/components/fleet/__tests__/__snapshots__/AppCoEmptyState.test.ts.snap +97 -0
  74. package/components/fleet/__tests__/__snapshots__/AppCoVersionSelect.test.ts.snap +30 -0
  75. package/components/fleet/__tests__/__snapshots__/ClusterSelectionFields.test.ts.snap +209 -0
  76. package/components/fleet/__tests__/__snapshots__/HelmOpTargetOptionsSection.test.ts.snap +140 -0
  77. package/components/fleet/dashboard/Empty.vue +8 -4
  78. package/components/fleet/dashboard/ResourceCard.vue +28 -0
  79. package/components/fleet/dashboard/ResourceDetails.vue +28 -0
  80. package/components/fleet/dashboard/__tests__/ResourceCard.test.ts +87 -0
  81. package/components/form/ArrayList.vue +61 -4
  82. package/components/form/FileSelector.vue +39 -1
  83. package/components/form/KeyValue.vue +23 -2
  84. package/components/form/LabeledSelect.vue +39 -1
  85. package/components/form/Labels.vue +22 -3
  86. package/components/form/NameNsDescription.vue +13 -5
  87. package/components/form/PrivateRegistry.constants.ts +7 -0
  88. package/components/form/PrivateRegistry.vue +253 -18
  89. package/components/form/ResourceTabs/index.vue +1 -0
  90. package/components/form/SelectOrCreateAuthSecret.vue +140 -17
  91. package/components/form/__tests__/FileSelector.test.ts +23 -0
  92. package/components/form/__tests__/NameNsDescription.test.ts +75 -0
  93. package/components/form/__tests__/PrivateRegistry.test.ts +463 -73
  94. package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +122 -0
  95. package/components/formatter/EtcdSnapshotName.vue +73 -0
  96. package/components/formatter/InternalExternalIP.vue +10 -4
  97. package/components/formatter/ServiceTargets.vue +26 -7
  98. package/components/formatter/__tests__/InternalExternalIP.test.ts +132 -0
  99. package/components/formatter/__tests__/ServiceTargets.test.ts +412 -0
  100. package/components/nav/Header.vue +12 -1
  101. package/components/nav/TopLevelMenu.vue +7 -2
  102. package/components/nav/__tests__/Header.test.ts +15 -0
  103. package/components/nav/__tests__/TopLevelMenu.test.ts +120 -2
  104. package/components/templates/default.vue +16 -4
  105. package/components/templates/home.vue +9 -4
  106. package/components/templates/plain.vue +9 -4
  107. package/composables/useHelmOpResources.test.ts +56 -0
  108. package/composables/useHelmOpResources.ts +32 -0
  109. package/composables/useStateColor.test.ts +325 -0
  110. package/composables/useStateColor.ts +128 -0
  111. package/config/features.js +1 -0
  112. package/config/home-links.js +1 -1
  113. package/config/labels-annotations.js +3 -0
  114. package/config/product/explorer.js +17 -4
  115. package/config/product/manager.js +8 -0
  116. package/config/router/index.js +16 -0
  117. package/config/router/navigation-guards/__tests__/authentication.test.ts +130 -0
  118. package/config/router/navigation-guards/authentication.js +10 -4
  119. package/config/router/routes.js +20 -6
  120. package/config/secret.ts +10 -0
  121. package/config/settings.ts +6 -4
  122. package/config/table-headers.js +3 -4
  123. package/config/types.js +16 -0
  124. package/core/plugin-products-base.ts +3 -3
  125. package/core/plugin-types.ts +83 -30
  126. package/core/plugin.ts +3 -0
  127. package/core/types-provisioning.ts +34 -1
  128. package/core/types.ts +15 -2
  129. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +114 -0
  130. package/detail/__tests__/workload.test.ts +3 -152
  131. package/detail/catalog.cattle.io.clusterrepo.vue +1 -1
  132. package/detail/provisioning.cattle.io.cluster.vue +109 -7
  133. package/detail/workload/index.vue +12 -55
  134. package/dialog/RotateEncryptionKeyDialog.vue +33 -9
  135. package/dialog/__tests__/RotateEncryptionKeyDialog.test.ts +78 -0
  136. package/edit/__tests__/catalog.cattle.io.clusterrepo.test.ts +248 -0
  137. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +92 -0
  138. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +206 -0
  139. package/edit/__tests__/management.cattle.io.setting.test.ts +2 -1
  140. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
  141. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +1 -0
  142. package/edit/auth/__tests__/azuread.test.ts +34 -9
  143. package/edit/auth/__tests__/github.test.ts +234 -0
  144. package/edit/auth/__tests__/oidc.test.ts +26 -6
  145. package/edit/auth/__tests__/saml.test.ts +196 -0
  146. package/edit/auth/azuread.vue +128 -95
  147. package/edit/auth/github.vue +72 -13
  148. package/edit/auth/ldap/__tests__/index.test.ts +206 -0
  149. package/edit/auth/ldap/config.vue +8 -0
  150. package/edit/auth/ldap/index.vue +75 -1
  151. package/edit/auth/oidc.vue +119 -73
  152. package/edit/auth/saml.vue +76 -12
  153. package/edit/catalog.cattle.io.clusterrepo.vue +140 -32
  154. package/edit/compliance.cattle.io.clusterscanprofile.vue +39 -41
  155. package/edit/fleet.cattle.io.gitrepo.vue +70 -16
  156. package/edit/fleet.cattle.io.helmop.vue +542 -141
  157. package/edit/helm.cattle.io.projecthelmchart.vue +1 -0
  158. package/edit/{management.cattle.io.setting.vue → management.cattle.io.setting/index.vue} +32 -9
  159. package/edit/management.cattle.io.setting/system-default-registry-pull-secrets.vue +81 -0
  160. package/edit/management.cattle.io.user.vue +5 -2
  161. package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +3 -12
  162. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +18 -0
  163. package/edit/provisioning.cattle.io.cluster/rke2.vue +89 -11
  164. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
  165. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +0 -1
  166. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +14 -55
  167. package/list/group.principal.vue +5 -4
  168. package/list/harvesterhci.io.management.cluster.vue +8 -9
  169. package/list/management.cattle.io.user.vue +12 -9
  170. package/list/provisioning.cattle.io.cluster.vue +16 -10
  171. package/mixins/__tests__/auth-config.test.ts +90 -0
  172. package/mixins/__tests__/chart.test.ts +94 -0
  173. package/mixins/__tests__/resource-fetch-api-pagination.test.ts +48 -0
  174. package/mixins/auth-config.js +7 -0
  175. package/mixins/chart.js +11 -2
  176. package/mixins/child-hook.js +12 -6
  177. package/mixins/create-edit-view/impl.js +5 -3
  178. package/mixins/resource-fetch-api-pagination.js +21 -1
  179. package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +57 -0
  180. package/models/__tests__/compliance.cattle.io.clusterscan.test.ts +144 -0
  181. package/models/__tests__/fleet-application.test.ts +175 -0
  182. package/models/__tests__/fleet.cattle.io.bundle.test.ts +169 -0
  183. package/models/__tests__/fleet.cattle.io.helmop.test.ts +84 -0
  184. package/models/__tests__/management.cattle.io.node.ts +22 -0
  185. package/models/__tests__/namespace.test.ts +36 -0
  186. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +205 -0
  187. package/models/__tests__/secret.test.ts +68 -1
  188. package/models/__tests__/workload.test.ts +401 -26
  189. package/models/catalog.cattle.io.clusterrepo.js +28 -4
  190. package/models/compliance.cattle.io.clusterscan.js +39 -4
  191. package/models/fleet-application.js +4 -0
  192. package/models/fleet.cattle.io.helmop.js +20 -1
  193. package/models/management.cattle.io.cluster.js +39 -5
  194. package/models/management.cattle.io.node.js +44 -3
  195. package/models/namespace.js +1 -1
  196. package/models/pod.js +46 -3
  197. package/models/provisioning.cattle.io.cluster.js +64 -14
  198. package/models/rke.cattle.io.etcdsnapshot.js +17 -9
  199. package/models/secret.js +19 -0
  200. package/models/workload.js +120 -20
  201. package/models/workload.service.js +5 -0
  202. package/package.json +14 -13
  203. package/pages/about.vue +5 -6
  204. package/pages/auth/login.vue +0 -35
  205. package/pages/auth/setup.vue +11 -0
  206. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +2 -2
  207. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +10 -1
  208. package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +93 -0
  209. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +485 -107
  210. package/pages/c/_cluster/apps/charts/chart.vue +2 -1
  211. package/pages/c/_cluster/apps/charts/index.vue +48 -10
  212. package/pages/c/_cluster/apps/charts/install.vue +236 -144
  213. package/pages/c/_cluster/auth/roles/index.vue +5 -4
  214. package/pages/c/_cluster/explorer/workload-dashboard/ByNamespaceSection.vue +31 -0
  215. package/pages/c/_cluster/explorer/workload-dashboard/ByStateSection.vue +138 -0
  216. package/pages/c/_cluster/explorer/workload-dashboard/ByTypeSection.vue +30 -0
  217. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadCard.vue +155 -0
  218. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadNamespaceCard.vue +142 -0
  219. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadTypeCard.vue +159 -0
  220. package/pages/c/_cluster/explorer/workload-dashboard/__tests__/composable.test.ts +561 -0
  221. package/pages/c/_cluster/explorer/workload-dashboard/composable.ts +440 -0
  222. package/pages/c/_cluster/explorer/workload-dashboard/index.vue +187 -0
  223. package/pages/c/_cluster/explorer/workload-dashboard/types.ts +80 -0
  224. package/pages/c/_cluster/fleet/application/create.vue +187 -136
  225. package/pages/c/_cluster/fleet/application/index.vue +5 -3
  226. package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailBody.vue +338 -0
  227. package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailHeader.vue +121 -0
  228. package/pages/c/_cluster/fleet/application/suse-app-collection/chart.vue +369 -0
  229. package/pages/c/_cluster/fleet/application/suse-app-collection/charts.vue +248 -0
  230. package/pages/c/_cluster/fleet/application/suse-app-collection/credentials.vue +310 -0
  231. package/pages/c/_cluster/fleet/index.vue +2 -2
  232. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +96 -0
  233. package/pages/c/_cluster/uiplugins/index.vue +15 -0
  234. package/pages/fail-whale.vue +16 -11
  235. package/pages/home.vue +16 -46
  236. package/pkg/require-asset.lib.js +25 -0
  237. package/pkg/vue.config.js +7 -0
  238. package/plugins/clean-html.d.ts +9 -0
  239. package/plugins/dashboard-store/__tests__/resource-class.test.ts +177 -0
  240. package/plugins/dashboard-store/getters.js +0 -1
  241. package/plugins/dashboard-store/resource-class.js +114 -19
  242. package/plugins/steve/__tests__/actions.test.ts +212 -0
  243. package/plugins/steve/actions.js +96 -0
  244. package/plugins/steve/steve-pagination-utils.ts +1 -1
  245. package/rancher-components/Accordion/Accordion.vue +53 -9
  246. package/rancher-components/Form/Checkbox/Checkbox.vue +14 -0
  247. package/rancher-components/Form/Radio/RadioButton.vue +17 -1
  248. package/rancher-components/Form/Radio/RadioGroup.vue +10 -0
  249. package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +30 -0
  250. package/rancher-components/Form/TextArea/__tests__/TextAreaAutoGrow.test.ts +95 -0
  251. package/rancher-components/Pill/RcTag/RcTag.vue +3 -2
  252. package/rancher-components/RcButton/RcButton.test.ts +103 -0
  253. package/rancher-components/RcButton/RcButton.vue +94 -15
  254. package/rancher-components/RcButton/index.ts +1 -1
  255. package/rancher-components/RcButton/types.ts +3 -0
  256. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +6 -1
  257. package/rancher-components/RcItemCard/RcItemCard.test.ts +18 -0
  258. package/rancher-components/RcItemCard/RcItemCard.vue +2 -2
  259. package/rancher-components/RcSection/RcSection.vue +28 -3
  260. package/scripts/extension/helm/package/Dockerfile +1 -1
  261. package/scripts/test-plugins-build.sh +2 -1
  262. package/store/__tests__/features.test.ts +131 -0
  263. package/store/__tests__/growl.test.ts +374 -0
  264. package/store/__tests__/modal.test.ts +131 -0
  265. package/store/__tests__/notifications.test.ts +434 -0
  266. package/store/__tests__/slideInPanel.test.ts +88 -0
  267. package/store/__tests__/type-map.utils.test.ts +433 -0
  268. package/store/catalog.js +57 -0
  269. package/store/features.js +4 -0
  270. package/store/plugins.js +7 -4
  271. package/types/components/buttonGroup.ts +5 -0
  272. package/types/shell/index.d.ts +166 -70
  273. package/utils/__tests__/auth.test.ts +273 -0
  274. package/utils/__tests__/computed.test.ts +193 -0
  275. package/utils/__tests__/cspAdaptor.test.ts +163 -0
  276. package/utils/__tests__/dom.test.ts +81 -0
  277. package/utils/__tests__/duration.test.ts +37 -1
  278. package/utils/__tests__/dynamic-importer.test.ts +102 -0
  279. package/utils/__tests__/fleet-appco.test.ts +312 -0
  280. package/utils/__tests__/monitoring.test.ts +130 -0
  281. package/utils/__tests__/object.test.ts +22 -0
  282. package/utils/__tests__/operation-cr.test.ts +34 -0
  283. package/utils/__tests__/platform.test.ts +91 -0
  284. package/utils/__tests__/position.test.ts +237 -0
  285. package/utils/__tests__/provider.test.ts +51 -1
  286. package/utils/__tests__/queue.test.ts +232 -0
  287. package/utils/__tests__/release-notes.test.ts +221 -0
  288. package/utils/__tests__/router.test.js +254 -1
  289. package/utils/__tests__/select.test.ts +208 -0
  290. package/utils/__tests__/time.test.ts +265 -1
  291. package/utils/__tests__/title.test.ts +47 -0
  292. package/utils/__tests__/width.test.ts +53 -0
  293. package/utils/__tests__/window.test.ts +158 -0
  294. package/utils/__tests__/xccdf.test.ts +126 -6
  295. package/utils/crypto/__tests__/browserHashUtils.test.ts +98 -0
  296. package/utils/crypto/__tests__/index.test.ts +144 -0
  297. package/utils/duration.ts +104 -0
  298. package/utils/dynamic-content/__tests__/notification-handler.test.ts +196 -0
  299. package/utils/dynamic-content/info.ts +2 -1
  300. package/utils/error.js +13 -0
  301. package/utils/fleet-appco.ts +323 -0
  302. package/utils/object.js +22 -2
  303. package/utils/operation-cr.js +19 -0
  304. package/utils/provider.ts +12 -0
  305. package/utils/require-asset.ts +7 -0
  306. package/utils/validators/__tests__/container-images.test.ts +104 -0
  307. package/utils/validators/__tests__/flow-output.test.ts +91 -0
  308. package/utils/validators/__tests__/logging-outputs.test.ts +58 -0
  309. package/utils/validators/__tests__/monitoring-route.test.ts +119 -0
  310. package/utils/validators/__tests__/private-registry.test.ts +27 -15
  311. package/utils/validators/private-registry.ts +15 -4
  312. package/utils/xccdf.ts +39 -42
  313. package/vue.config.js +1 -1
  314. package/pages/support/index.vue +0 -264
  315. package/utils/duration.js +0 -43
@@ -1,6 +1,6 @@
1
1
  import Workload from '@shell/models/workload.js';
2
2
  import { steveClassJunkObject } from '@shell/plugins/steve/__tests__/utils/steve-mocks';
3
- import { WORKLOAD_TYPES, SERVICE } from '@shell/config/types';
3
+ import { WORKLOAD_TYPES, SERVICE, INGRESS } from '@shell/config/types';
4
4
 
5
5
  describe('class: Workload', () => {
6
6
  describe('given custom workload keys', () => {
@@ -160,12 +160,7 @@ describe('class: Workload', () => {
160
160
  });
161
161
 
162
162
  describe('getter: relatedServices', () => {
163
- it('should return services that match workload pods', () => {
164
- const mockPod = {
165
- metadata: {
166
- name: 'pod-1', namespace: 'default', labels: { app: 'my-app' }
167
- }
168
- };
163
+ it('should return services that match workload pod template labels', () => {
169
164
  const mockService = {
170
165
  metadata: { name: 'my-service', namespace: 'default' },
171
166
  spec: { selector: { app: 'my-app' } }
@@ -173,7 +168,7 @@ describe('class: Workload', () => {
173
168
  const workload = new Workload({
174
169
  type: WORKLOAD_TYPES.DEPLOYMENT,
175
170
  metadata: { name: 'test', namespace: 'default' },
176
- spec: {}
171
+ spec: { template: { metadata: { labels: { app: 'my-app' } } } }
177
172
  }, {
178
173
  getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
179
174
  dispatch: jest.fn(),
@@ -183,20 +178,12 @@ describe('class: Workload', () => {
183
178
  },
184
179
  });
185
180
 
186
- // Mock pods getter
187
- Object.defineProperty(workload, 'pods', { get: () => [mockPod] });
188
-
189
181
  const related = workload.relatedServices;
190
182
 
191
183
  expect(related).toContain(mockService);
192
184
  });
193
185
 
194
186
  it('should not return services from different namespace', () => {
195
- const mockPod = {
196
- metadata: {
197
- name: 'pod-1', namespace: 'default', labels: { app: 'my-app' }
198
- }
199
- };
200
187
  const mockService = {
201
188
  metadata: { name: 'my-service', namespace: 'other-namespace' },
202
189
  spec: { selector: { app: 'my-app' } }
@@ -204,7 +191,7 @@ describe('class: Workload', () => {
204
191
  const workload = new Workload({
205
192
  type: WORKLOAD_TYPES.DEPLOYMENT,
206
193
  metadata: { name: 'test', namespace: 'default' },
207
- spec: {}
194
+ spec: { template: { metadata: { labels: { app: 'my-app' } } } }
208
195
  }, {
209
196
  getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
210
197
  dispatch: jest.fn(),
@@ -214,19 +201,12 @@ describe('class: Workload', () => {
214
201
  },
215
202
  });
216
203
 
217
- Object.defineProperty(workload, 'pods', { get: () => [mockPod] });
218
-
219
204
  const related = workload.relatedServices;
220
205
 
221
206
  expect(related).toHaveLength(0);
222
207
  });
223
208
 
224
209
  it('should not return services with non-matching selectors', () => {
225
- const mockPod = {
226
- metadata: {
227
- name: 'pod-1', namespace: 'default', labels: { app: 'my-app' }
228
- }
229
- };
230
210
  const mockService = {
231
211
  metadata: { name: 'my-service', namespace: 'default' },
232
212
  spec: { selector: { app: 'different-app' } }
@@ -234,7 +214,7 @@ describe('class: Workload', () => {
234
214
  const workload = new Workload({
235
215
  type: WORKLOAD_TYPES.DEPLOYMENT,
236
216
  metadata: { name: 'test', namespace: 'default' },
237
- spec: {}
217
+ spec: { template: { metadata: { labels: { app: 'my-app' } } } }
238
218
  }, {
239
219
  getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
240
220
  dispatch: jest.fn(),
@@ -244,7 +224,28 @@ describe('class: Workload', () => {
244
224
  },
245
225
  });
246
226
 
247
- Object.defineProperty(workload, 'pods', { get: () => [mockPod] });
227
+ const related = workload.relatedServices;
228
+
229
+ expect(related).toHaveLength(0);
230
+ });
231
+
232
+ it('should return empty array when pod template has no labels', () => {
233
+ const mockService = {
234
+ metadata: { name: 'my-service', namespace: 'default' },
235
+ spec: { selector: { app: 'my-app' } }
236
+ };
237
+ const workload = new Workload({
238
+ type: WORKLOAD_TYPES.DEPLOYMENT,
239
+ metadata: { name: 'test', namespace: 'default' },
240
+ spec: { template: { metadata: {} } }
241
+ }, {
242
+ getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
243
+ dispatch: jest.fn(),
244
+ rootGetters: {
245
+ 'i18n/t': jest.fn(),
246
+ 'cluster/all': (type: string) => (type === SERVICE ? [mockService] : [])
247
+ },
248
+ });
248
249
 
249
250
  const related = workload.relatedServices;
250
251
 
@@ -506,4 +507,378 @@ describe('class: Workload', () => {
506
507
  expect(jobsCard).toBeDefined();
507
508
  });
508
509
  });
510
+
511
+ describe('getter: matchingIngresses', () => {
512
+ const makeWorkload = (services: any[], ingresses: any[], pods: any[] = []) => {
513
+ const workload = new Workload({
514
+ type: WORKLOAD_TYPES.DEPLOYMENT,
515
+ metadata: { name: 'test', namespace: 'default' },
516
+ spec: { template: { metadata: { labels: { app: 'my-app' } } } }
517
+ }, {
518
+ getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
519
+ dispatch: jest.fn(),
520
+ rootGetters: {
521
+ 'i18n/t': jest.fn(),
522
+ 'cluster/all': (type: string) => {
523
+ if (type === SERVICE) {
524
+ return services;
525
+ }
526
+ if (type === INGRESS) {
527
+ return ingresses;
528
+ }
529
+
530
+ return [];
531
+ }
532
+ },
533
+ });
534
+
535
+ Object.defineProperty(workload, 'pods', { get: () => pods });
536
+
537
+ return workload;
538
+ };
539
+
540
+ it('should return empty array when no related services', () => {
541
+ const workload = makeWorkload([], [
542
+ {
543
+ metadata: { namespace: 'default' },
544
+ spec: { rules: [{ http: { paths: [{ backend: { service: { name: 'svc1' } } }] } }] }
545
+ }
546
+ ]);
547
+
548
+ expect(workload.matchingIngresses).toStrictEqual([]);
549
+ });
550
+
551
+ it('should find matching ingresses', () => {
552
+ const mockPod = {
553
+ metadata: {
554
+ name: 'pod-1', namespace: 'default', labels: { app: 'my-app' }
555
+ }
556
+ };
557
+ const mockService = {
558
+ metadata: { name: 'svc1', namespace: 'default' },
559
+ spec: { selector: { app: 'my-app' } }
560
+ };
561
+ const mockIngress = {
562
+ metadata: { namespace: 'default' },
563
+ spec: { rules: [{ http: { paths: [{ backend: { service: { name: 'svc1' } } }] } }] }
564
+ };
565
+
566
+ const workload = makeWorkload([mockService], [mockIngress], [mockPod]);
567
+
568
+ expect(workload.matchingIngresses).toHaveLength(1);
569
+ expect(workload.matchingIngresses[0]).toStrictEqual(mockIngress);
570
+ });
571
+
572
+ it('should not match ingresses from other namespaces', () => {
573
+ const mockPod = {
574
+ metadata: {
575
+ name: 'pod-1', namespace: 'default', labels: { app: 'my-app' }
576
+ }
577
+ };
578
+ const mockService = {
579
+ metadata: { name: 'svc1', namespace: 'default' },
580
+ spec: { selector: { app: 'my-app' } }
581
+ };
582
+ const mockIngress = {
583
+ metadata: { namespace: 'other' },
584
+ spec: { rules: [{ http: { paths: [{ backend: { service: { name: 'svc1' } } }] } }] }
585
+ };
586
+
587
+ const workload = makeWorkload([mockService], [mockIngress], [mockPod]);
588
+
589
+ expect(workload.matchingIngresses).toHaveLength(0);
590
+ });
591
+
592
+ it('should not match ingresses pointing to other services', () => {
593
+ const mockPod = {
594
+ metadata: {
595
+ name: 'pod-1', namespace: 'default', labels: { app: 'my-app' }
596
+ }
597
+ };
598
+ const mockService = {
599
+ metadata: { name: 'svc1', namespace: 'default' },
600
+ spec: { selector: { app: 'my-app' } }
601
+ };
602
+ const mockIngress = {
603
+ metadata: { namespace: 'default' },
604
+ spec: { rules: [{ http: { paths: [{ backend: { service: { name: 'svc2' } } }] } }] }
605
+ };
606
+
607
+ const workload = makeWorkload([mockService], [mockIngress], [mockPod]);
608
+
609
+ expect(workload.matchingIngresses).toHaveLength(0);
610
+ });
611
+
612
+ it('should handle ingresses with no rules', () => {
613
+ const mockPod = {
614
+ metadata: {
615
+ name: 'pod-1', namespace: 'default', labels: { app: 'my-app' }
616
+ }
617
+ };
618
+ const mockService = {
619
+ metadata: { name: 'svc1', namespace: 'default' },
620
+ spec: { selector: { app: 'my-app' } }
621
+ };
622
+ const mockIngress = {
623
+ metadata: { namespace: 'default' },
624
+ spec: {}
625
+ };
626
+
627
+ const workload = makeWorkload([mockService], [mockIngress], [mockPod]);
628
+
629
+ expect(workload.matchingIngresses).toHaveLength(0);
630
+ });
631
+
632
+ it('should handle ingress rules with no paths', () => {
633
+ const mockPod = {
634
+ metadata: {
635
+ name: 'pod-1', namespace: 'default', labels: { app: 'my-app' }
636
+ }
637
+ };
638
+ const mockService = {
639
+ metadata: { name: 'svc1', namespace: 'default' },
640
+ spec: { selector: { app: 'my-app' } }
641
+ };
642
+ const mockIngress = {
643
+ metadata: { namespace: 'default' },
644
+ spec: { rules: [{ http: {} }] }
645
+ };
646
+
647
+ const workload = makeWorkload([mockService], [mockIngress], [mockPod]);
648
+
649
+ expect(workload.matchingIngresses).toHaveLength(0);
650
+ });
651
+
652
+ it('should handle ingress paths with no backend service', () => {
653
+ const mockPod = {
654
+ metadata: {
655
+ name: 'pod-1', namespace: 'default', labels: { app: 'my-app' }
656
+ }
657
+ };
658
+ const mockService = {
659
+ metadata: { name: 'svc1', namespace: 'default' },
660
+ spec: { selector: { app: 'my-app' } }
661
+ };
662
+ const mockIngress = {
663
+ metadata: { namespace: 'default' },
664
+ spec: { rules: [{ http: { paths: [{ backend: {} }] } }] }
665
+ };
666
+
667
+ const workload = makeWorkload([mockService], [mockIngress], [mockPod]);
668
+
669
+ expect(workload.matchingIngresses).toHaveLength(0);
670
+ });
671
+
672
+ it('should find one of many ingresses', () => {
673
+ const mockPod = {
674
+ metadata: {
675
+ name: 'pod-1', namespace: 'default', labels: { app: 'my-app' }
676
+ }
677
+ };
678
+ const mockService = {
679
+ metadata: { name: 'svc1', namespace: 'default' },
680
+ spec: { selector: { app: 'my-app' } }
681
+ };
682
+ const ingresses = [
683
+ {
684
+ metadata: { namespace: 'other' },
685
+ spec: { rules: [{ http: { paths: [{ backend: { service: { name: 'svc1' } } }] } }] }
686
+ },
687
+ {
688
+ metadata: { namespace: 'default' },
689
+ spec: { rules: [{ http: { paths: [{ backend: { service: { name: 'svc1' } } }] } }] }
690
+ },
691
+ {
692
+ metadata: { namespace: 'default' },
693
+ spec: { rules: [{ http: { paths: [{ backend: { service: { name: 'svc2' } } }] } }] }
694
+ }
695
+ ];
696
+
697
+ const workload = makeWorkload([mockService], ingresses, [mockPod]);
698
+
699
+ expect(workload.matchingIngresses).toHaveLength(1);
700
+ expect(workload.matchingIngresses[0]).toStrictEqual(ingresses[1]);
701
+ });
702
+ });
703
+
704
+ describe('getter: resourcesCardRows', () => {
705
+ it('should include services row when related services exist', () => {
706
+ const mockPod = {
707
+ metadata: {
708
+ name: 'pod-1', namespace: 'default', labels: { app: 'my-app' }
709
+ }
710
+ };
711
+ const mockService = {
712
+ metadata: { name: 'svc1', namespace: 'default' },
713
+ spec: { selector: { app: 'my-app' } },
714
+ stateDisplay: 'Active',
715
+ stateSimpleColor: 'success'
716
+ };
717
+
718
+ const workload = new Workload({
719
+ type: WORKLOAD_TYPES.DEPLOYMENT,
720
+ metadata: { name: 'test', namespace: 'default' },
721
+ spec: { template: { metadata: { labels: { app: 'my-app' } } } }
722
+ }, {
723
+ getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
724
+ dispatch: jest.fn(),
725
+ rootGetters: {
726
+ 'i18n/t': (key: string) => key,
727
+ 'cluster/all': (type: string) => {
728
+ if (type === SERVICE) {
729
+ return [mockService];
730
+ }
731
+
732
+ return [];
733
+ }
734
+ },
735
+ });
736
+
737
+ Object.defineProperty(workload, 'pods', { get: () => [mockPod] });
738
+
739
+ const rows = workload.resourcesCardRows;
740
+ const servicesRow = rows.find((r: any) => r.label === 'component.resource.detail.card.resourcesCard.rows.services');
741
+
742
+ expect(servicesRow).toBeDefined();
743
+ });
744
+
745
+ it('should include ingresses row when matching ingresses exist', () => {
746
+ const mockPod = {
747
+ metadata: {
748
+ name: 'pod-1', namespace: 'default', labels: { app: 'my-app' }
749
+ }
750
+ };
751
+ const mockService = {
752
+ metadata: { name: 'svc1', namespace: 'default' },
753
+ spec: { selector: { app: 'my-app' } },
754
+ stateDisplay: 'Active',
755
+ stateSimpleColor: 'success'
756
+ };
757
+ const mockIngress = {
758
+ metadata: { namespace: 'default' },
759
+ spec: { rules: [{ http: { paths: [{ backend: { service: { name: 'svc1' } } }] } }] },
760
+ stateDisplay: 'Active',
761
+ stateSimpleColor: 'success'
762
+ };
763
+
764
+ const workload = new Workload({
765
+ type: WORKLOAD_TYPES.DEPLOYMENT,
766
+ metadata: { name: 'test', namespace: 'default' },
767
+ spec: { template: { metadata: { labels: { app: 'my-app' } } } }
768
+ }, {
769
+ getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
770
+ dispatch: jest.fn(),
771
+ rootGetters: {
772
+ 'i18n/t': (key: string) => key,
773
+ 'cluster/all': (type: string) => {
774
+ if (type === SERVICE) {
775
+ return [mockService];
776
+ }
777
+ if (type === INGRESS) {
778
+ return [mockIngress];
779
+ }
780
+
781
+ return [];
782
+ }
783
+ },
784
+ });
785
+
786
+ Object.defineProperty(workload, 'pods', { get: () => [mockPod] });
787
+
788
+ const rows = workload.resourcesCardRows;
789
+ const ingressesRow = rows.find((r: any) => r.label === 'component.resource.detail.card.resourcesCard.rows.ingresses');
790
+
791
+ expect(ingressesRow).toBeDefined();
792
+ expect(ingressesRow.to).toBe('#ingresses');
793
+ });
794
+
795
+ it('should order services before ingresses', () => {
796
+ const mockPod = {
797
+ metadata: {
798
+ name: 'pod-1', namespace: 'default', labels: { app: 'my-app' }
799
+ }
800
+ };
801
+ const mockService = {
802
+ metadata: { name: 'svc1', namespace: 'default' },
803
+ spec: { selector: { app: 'my-app' } },
804
+ stateDisplay: 'Active',
805
+ stateSimpleColor: 'success'
806
+ };
807
+ const mockIngress = {
808
+ metadata: { namespace: 'default' },
809
+ spec: { rules: [{ http: { paths: [{ backend: { service: { name: 'svc1' } } }] } }] },
810
+ stateDisplay: 'Active',
811
+ stateSimpleColor: 'success'
812
+ };
813
+
814
+ const workload = new Workload({
815
+ type: WORKLOAD_TYPES.DEPLOYMENT,
816
+ metadata: { name: 'test', namespace: 'default' },
817
+ spec: { template: { metadata: { labels: { app: 'my-app' } } } }
818
+ }, {
819
+ getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
820
+ dispatch: jest.fn(),
821
+ rootGetters: {
822
+ 'i18n/t': (key: string) => key,
823
+ 'cluster/all': (type: string) => {
824
+ if (type === SERVICE) {
825
+ return [mockService];
826
+ }
827
+ if (type === INGRESS) {
828
+ return [mockIngress];
829
+ }
830
+
831
+ return [];
832
+ }
833
+ },
834
+ });
835
+
836
+ Object.defineProperty(workload, 'pods', { get: () => [mockPod] });
837
+
838
+ const rows = workload.resourcesCardRows;
839
+
840
+ expect(rows[0].label).toBe('component.resource.detail.card.resourcesCard.rows.services');
841
+ expect(rows[1].label).toBe('component.resource.detail.card.resourcesCard.rows.ingresses');
842
+ });
843
+
844
+ it('should not include ingresses row when no matching ingresses', () => {
845
+ const mockPod = {
846
+ metadata: {
847
+ name: 'pod-1', namespace: 'default', labels: { app: 'my-app' }
848
+ }
849
+ };
850
+ const mockService = {
851
+ metadata: { name: 'svc1', namespace: 'default' },
852
+ spec: { selector: { app: 'my-app' } },
853
+ stateDisplay: 'Active',
854
+ stateSimpleColor: 'success'
855
+ };
856
+
857
+ const workload = new Workload({
858
+ type: WORKLOAD_TYPES.DEPLOYMENT,
859
+ metadata: { name: 'test', namespace: 'default' },
860
+ spec: { template: { metadata: { labels: { app: 'my-app' } } } }
861
+ }, {
862
+ getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
863
+ dispatch: jest.fn(),
864
+ rootGetters: {
865
+ 'i18n/t': (key: string) => key,
866
+ 'cluster/all': (type: string) => {
867
+ if (type === SERVICE) {
868
+ return [mockService];
869
+ }
870
+
871
+ return [];
872
+ }
873
+ },
874
+ });
875
+
876
+ Object.defineProperty(workload, 'pods', { get: () => [mockPod] });
877
+
878
+ const rows = workload.resourcesCardRows;
879
+ const ingressesRow = rows.find((r: any) => r.label === 'component.resource.detail.card.resourcesCard.rows.ingresses');
880
+
881
+ expect(ingressesRow).toBeUndefined();
882
+ });
883
+ });
509
884
  });
@@ -4,8 +4,10 @@ import { insertAt } from '@shell/utils/array';
4
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
  import { _CREATE } from '@shell/config/query-params';
7
+ import { formatDuration } from '@shell/utils/duration';
7
8
 
8
9
  import SteveModel from '@shell/plugins/steve/steve-class';
10
+ import { SUSE_APPCO_DISPLAY_NAME } from '@shell/utils/fleet-appco';
9
11
 
10
12
  export default class ClusterRepo extends SteveModel {
11
13
  applyDefaults() {
@@ -183,7 +185,7 @@ export default class ClusterRepo extends SteveModel {
183
185
 
184
186
  get typeDisplay() {
185
187
  if (this.isSuseAppCollectionFromUI) {
186
- return 'SUSE AppCo';
188
+ return SUSE_APPCO_DISPLAY_NAME;
187
189
  }
188
190
  if ( this.spec.gitRepo ) {
189
191
  return 'git';
@@ -218,15 +220,37 @@ export default class ClusterRepo extends SteveModel {
218
220
  return this.spec?.gitBranch || '(default)';
219
221
  }
220
222
 
223
+ get defaultRefreshIntervalHours() {
224
+ return this.isOciType ? 24 : 1;
225
+ }
226
+
227
+ get defaultRefreshInterval() {
228
+ return 60 * 60 * this.defaultRefreshIntervalHours;
229
+ }
230
+
231
+ get refreshIntervalDisplay() {
232
+ const val = this.spec?.refreshInterval;
233
+
234
+ if (val < 0) {
235
+ return this.t('generic.disabled');
236
+ }
237
+
238
+ return formatDuration(val ?? this.defaultRefreshInterval);
239
+ }
240
+
221
241
  get details() {
222
242
  return [
223
243
  {
224
- label: 'Type',
244
+ label: this.t('generic.type'),
225
245
  content: this.typeDisplay,
226
246
  },
227
247
  {
228
- label: 'Downloaded',
229
- content: this.status.downloadTime,
248
+ label: this.t('catalog.repo.refreshInterval.label'),
249
+ content: this.refreshIntervalDisplay,
250
+ },
251
+ {
252
+ label: this.t('catalog.repo.downloaded.label'),
253
+ content: this.status?.downloadTime,
230
254
  formatter: 'LiveDate',
231
255
  formatterOpts: { addSuffix: true },
232
256
  },
@@ -190,20 +190,54 @@ export default class ClusterScan extends SteveModel {
190
190
  return this.$dispatch('find', { type: COMPLIANCE.BENCHMARK, id: benchmarkId });
191
191
  }
192
192
 
193
+ // Fetches the kube-bench profile ConfigMap referenced by the benchmark and
194
+ // parses its metadata.yaml data key. Top-level fields become the XCCDF
195
+ // benchmark metadata; the checks: map becomes per-check decorations
196
+ // (framework-agnostic; STIG, CIS, BSI, etc. all populate the same shape).
197
+ async _resolveExportMetadata(benchmark) {
198
+ const name = benchmark?.spec?.customBenchmarkConfigMapName;
199
+ const namespace = benchmark?.spec?.customBenchmarkConfigMapNamespace;
200
+ const empty = { metadata: {}, decorations: {} };
201
+
202
+ if (!name || !namespace) {
203
+ return empty;
204
+ }
205
+
206
+ try {
207
+ const cm = await this.$dispatch('find', { type: 'configmap', id: `${ namespace }/${ name }` });
208
+ const raw = cm?.data?.['metadata.yaml'];
209
+
210
+ if (!raw) {
211
+ return empty;
212
+ }
213
+
214
+ const jsyaml = await import(/* webpackChunkName: "js-yaml" */'js-yaml');
215
+ const parsed = jsyaml.load(raw) || {};
216
+ const { checks = {}, ...rest } = parsed;
217
+
218
+ return { metadata: rest, decorations: checks };
219
+ } catch (e) {
220
+ console.error('[_resolveExportMetadata] failed to fetch or parse benchmark ConfigMap:', e); // eslint-disable-line no-console
221
+
222
+ return empty;
223
+ }
224
+ }
225
+
193
226
  async downloadLatestReportXCCDF() {
194
227
  const reports = await this.getReports() || [];
195
228
  const report = sortBy(reports, 'metadata.creationTimestamp', true)[0];
196
229
 
197
230
  try {
198
231
  const benchmark = await this._resolveBenchmark();
232
+ const { metadata, decorations } = await this._resolveExportMetadata(benchmark);
199
233
  const { generateXCCDFPerNode } = await import(/* webpackChunkName: "xccdf" */'@shell/utils/xccdf');
200
234
 
201
235
  const parsed = report.parsedReport || {};
202
236
  const common = {
203
237
  report: parsed,
204
238
  benchmarkVersion: parsed.version || benchmark?.spec?.benchmarkVersion || '',
205
- metadata: benchmark?.spec?.benchmarkMetadata || {},
206
- stigChecks: benchmark?.spec?.stigChecks || {},
239
+ metadata,
240
+ decorations,
207
241
  };
208
242
 
209
243
  const toZip = {};
@@ -238,6 +272,7 @@ export default class ClusterScan extends SteveModel {
238
272
 
239
273
  try {
240
274
  const benchmark = await this._resolveBenchmark();
275
+ const { metadata, decorations } = await this._resolveExportMetadata(benchmark);
241
276
  const { generateXCCDFPerNode } = await import(/* webpackChunkName: "xccdf" */'@shell/utils/xccdf');
242
277
 
243
278
  const hasParsedNodes = reports.some((report) => Object.entries(report.parsedReport?.nodes || {}).length);
@@ -251,8 +286,8 @@ export default class ClusterScan extends SteveModel {
251
286
  const common = {
252
287
  report: parsed,
253
288
  benchmarkVersion: parsed.version || benchmark?.spec?.benchmarkVersion || '',
254
- metadata: benchmark?.spec?.benchmarkMetadata || {},
255
- stigChecks: benchmark?.spec?.stigChecks || {},
289
+ metadata,
290
+ decorations,
256
291
  };
257
292
  const folder = labelFor(report);
258
293
 
@@ -30,6 +30,10 @@ function normalizeStateCounts(data) {
30
30
  }
31
31
 
32
32
  export default class FleetApplication extends SteveModel {
33
+ get applicationType() {
34
+ return this.kind;
35
+ }
36
+
33
37
  async getCurrentUser() {
34
38
  const user = this.$rootGetters['auth/user'];
35
39
 
@@ -4,10 +4,29 @@ import { set } from '@shell/utils/object';
4
4
  import { SOURCE_TYPE } from '@shell/config/product/fleet';
5
5
  import FleetUtils from '@shell/utils/fleet';
6
6
  import { FLEET } from '@shell/config/types';
7
- import { FLEET as FLEET_ANNOTATIONS } from '@shell/config/labels-annotations';
7
+ import { CATALOG, FLEET as FLEET_ANNOTATIONS } from '@shell/config/labels-annotations';
8
8
  import FleetApplication from '@shell/models/fleet-application';
9
+ import { SUSE_APP_COLLECTION_REPO_URL, SUSE_APPCO_DISPLAY_NAME } from '@shell/utils/fleet-appco';
9
10
 
10
11
  export default class HelmOp extends FleetApplication {
12
+ get isSuseAppCollectionFromUI() {
13
+ return !!this.metadata?.annotations?.[CATALOG.SUSE_APP_COLLECTION];
14
+ }
15
+
16
+ get isSuseAppCollection() {
17
+ // Annotation set by the UI on create, or fallback to URL check for older resources
18
+ return this.isSuseAppCollectionFromUI ||
19
+ (this.spec?.helm?.repo || '').startsWith(SUSE_APP_COLLECTION_REPO_URL);
20
+ }
21
+
22
+ get applicationType() {
23
+ if (this.isSuseAppCollectionFromUI) {
24
+ return SUSE_APPCO_DISPLAY_NAME;
25
+ }
26
+
27
+ return this.kind;
28
+ }
29
+
11
30
  applyDefaults() {
12
31
  const spec = this.spec || {};
13
32
  const meta = this.metadata || {};