@rancher/shell 3.0.12-rc.3 → 3.0.12-rc.4

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 (258) hide show
  1. package/assets/styles/global/_layout.scss +4 -0
  2. package/assets/translations/en-us.yaml +144 -41
  3. package/assets/translations/zh-hans.yaml +1 -7
  4. package/chart/monitoring/ClusterSelector.vue +0 -21
  5. package/chart/monitoring/prometheus/index.vue +6 -3
  6. package/components/CruResource.vue +161 -14
  7. package/components/ExplorerMembers.vue +8 -4
  8. package/components/ExplorerProjectsNamespaces.vue +10 -6
  9. package/components/GrowlManager.vue +4 -0
  10. package/components/MgmtNodeList.vue +184 -0
  11. package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +90 -1
  12. package/components/Resource/Detail/Card/StateCard/composables.ts +57 -87
  13. package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +61 -0
  14. package/components/Resource/Detail/Card/StatusCard/index.vue +61 -15
  15. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +2 -0
  16. package/components/Resource/Detail/Metadata/KeyValue.vue +5 -2
  17. package/components/Resource/Detail/Metadata/KeyValueRow.vue +2 -6
  18. package/components/ResourceDetail/index.vue +1 -1
  19. package/components/ResourceList/Masthead.vue +7 -1
  20. package/components/ResourceList/index.vue +82 -1
  21. package/components/RichTranslation.vue +5 -2
  22. package/components/Setting.vue +1 -0
  23. package/components/SubtleLink.vue +31 -6
  24. package/components/Tabbed/Tab.vue +29 -3
  25. package/components/Tabbed/index.vue +25 -3
  26. package/components/TableOfContents/TableOfContents.vue +109 -0
  27. package/components/TableOfContents/composables.ts +258 -0
  28. package/components/Window/ContainerShell.vue +21 -11
  29. package/components/Window/__tests__/ContainerShell.test.ts +107 -37
  30. package/components/Wizard.vue +9 -4
  31. package/components/fleet/AppCoChartGrid.vue +401 -0
  32. package/components/fleet/AppCoEmptyState.vue +127 -0
  33. package/components/fleet/AppCoPageHeader.vue +119 -0
  34. package/components/fleet/AppCoVersionSelect.vue +70 -0
  35. package/components/fleet/FleetClusterTargets/ClusterSelectionFields.vue +217 -0
  36. package/components/fleet/FleetClusterTargets/TargetsList.vue +123 -35
  37. package/components/fleet/FleetClusterTargets/index.vue +189 -146
  38. package/components/fleet/FleetIntro.vue +7 -3
  39. package/components/fleet/FleetNoWorkspaces.vue +7 -3
  40. package/components/fleet/FleetSecretSelector.vue +5 -3
  41. package/components/fleet/FleetValuesFrom.vue +8 -2
  42. package/components/fleet/GitRepoTargetTab.vue +0 -2
  43. package/components/fleet/HelmOpAdvancedTab.vue +19 -53
  44. package/components/fleet/HelmOpAppCoConfigTab.vue +593 -0
  45. package/components/fleet/HelmOpAppCoResourcesSection.vue +162 -0
  46. package/components/fleet/HelmOpResourcesSection.vue +82 -0
  47. package/components/fleet/HelmOpTargetOptionsSection.vue +89 -0
  48. package/components/fleet/HelmOpTargetTab.vue +64 -60
  49. package/components/fleet/HelmOpValuesTab.vue +129 -105
  50. package/components/fleet/__tests__/AppCoEmptyState.test.ts +71 -0
  51. package/components/fleet/__tests__/AppCoVersionSelect.test.ts +36 -0
  52. package/components/fleet/__tests__/ClusterSelectionFields.test.ts +62 -0
  53. package/components/fleet/__tests__/FleetClusterTargets.test.ts +253 -0
  54. package/components/fleet/__tests__/FleetSecretSelector.test.ts +16 -0
  55. package/components/fleet/__tests__/FleetValuesFrom.test.ts +44 -0
  56. package/components/fleet/__tests__/HelmOpAppCoConfigTab.test.ts +59 -0
  57. package/components/fleet/__tests__/HelmOpAppCoResourcesSection.test.ts +62 -0
  58. package/components/fleet/__tests__/HelmOpResourcesSection.test.ts +43 -0
  59. package/components/fleet/__tests__/HelmOpTargetOptionsSection.test.ts +34 -0
  60. package/components/fleet/__tests__/HelmOpValuesTab.test.ts +39 -0
  61. package/components/fleet/__tests__/__snapshots__/AppCoEmptyState.test.ts.snap +97 -0
  62. package/components/fleet/__tests__/__snapshots__/AppCoVersionSelect.test.ts.snap +30 -0
  63. package/components/fleet/__tests__/__snapshots__/ClusterSelectionFields.test.ts.snap +209 -0
  64. package/components/fleet/__tests__/__snapshots__/HelmOpTargetOptionsSection.test.ts.snap +140 -0
  65. package/components/fleet/dashboard/Empty.vue +8 -4
  66. package/components/fleet/dashboard/ResourceCard.vue +28 -0
  67. package/components/fleet/dashboard/ResourceDetails.vue +28 -0
  68. package/components/fleet/dashboard/__tests__/ResourceCard.test.ts +87 -0
  69. package/components/form/ArrayList.vue +61 -4
  70. package/components/form/KeyValue.vue +23 -2
  71. package/components/form/LabeledSelect.vue +39 -1
  72. package/components/form/Labels.vue +22 -3
  73. package/components/form/NameNsDescription.vue +13 -5
  74. package/components/form/ResourceTabs/index.vue +1 -0
  75. package/components/form/__tests__/NameNsDescription.test.ts +75 -0
  76. package/components/formatter/InternalExternalIP.vue +10 -4
  77. package/components/formatter/ServiceTargets.vue +26 -7
  78. package/components/formatter/__tests__/InternalExternalIP.test.ts +132 -0
  79. package/components/formatter/__tests__/ServiceTargets.test.ts +412 -0
  80. package/components/nav/Header.vue +4 -0
  81. package/components/nav/TopLevelMenu.vue +7 -2
  82. package/components/nav/__tests__/Header.test.ts +15 -0
  83. package/components/nav/__tests__/TopLevelMenu.test.ts +120 -2
  84. package/components/templates/default.vue +9 -4
  85. package/components/templates/home.vue +9 -4
  86. package/components/templates/plain.vue +9 -4
  87. package/composables/useHelmOpResources.test.ts +56 -0
  88. package/composables/useHelmOpResources.ts +32 -0
  89. package/composables/useStateColor.test.ts +325 -0
  90. package/composables/useStateColor.ts +128 -0
  91. package/config/home-links.js +1 -1
  92. package/config/labels-annotations.js +1 -0
  93. package/config/product/explorer.js +17 -4
  94. package/config/product/manager.js +2 -0
  95. package/config/router/index.js +16 -0
  96. package/config/router/navigation-guards/__tests__/authentication.test.ts +130 -0
  97. package/config/router/navigation-guards/authentication.js +10 -4
  98. package/config/router/routes.js +20 -6
  99. package/config/settings.ts +0 -2
  100. package/config/table-headers.js +3 -4
  101. package/config/types.js +9 -0
  102. package/core/plugin-products-base.ts +3 -3
  103. package/core/plugin-types.ts +83 -30
  104. package/core/plugin.ts +3 -0
  105. package/core/types-provisioning.ts +34 -1
  106. package/core/types.ts +15 -2
  107. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +114 -0
  108. package/detail/__tests__/workload.test.ts +3 -152
  109. package/detail/catalog.cattle.io.clusterrepo.vue +1 -1
  110. package/detail/provisioning.cattle.io.cluster.vue +30 -4
  111. package/detail/workload/index.vue +12 -55
  112. package/edit/__tests__/catalog.cattle.io.clusterrepo.test.ts +248 -0
  113. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +105 -0
  114. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
  115. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +1 -0
  116. package/edit/auth/__tests__/azuread.test.ts +34 -9
  117. package/edit/auth/__tests__/github.test.ts +234 -0
  118. package/edit/auth/__tests__/oidc.test.ts +26 -6
  119. package/edit/auth/__tests__/saml.test.ts +196 -0
  120. package/edit/auth/azuread.vue +128 -95
  121. package/edit/auth/github.vue +72 -13
  122. package/edit/auth/ldap/__tests__/index.test.ts +206 -0
  123. package/edit/auth/ldap/config.vue +8 -0
  124. package/edit/auth/ldap/index.vue +75 -1
  125. package/edit/auth/oidc.vue +119 -73
  126. package/edit/auth/saml.vue +76 -12
  127. package/edit/catalog.cattle.io.clusterrepo.vue +140 -32
  128. package/edit/fleet.cattle.io.helmop.vue +491 -136
  129. package/edit/management.cattle.io.user.vue +5 -2
  130. package/edit/provisioning.cattle.io.cluster/rke2.vue +84 -10
  131. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
  132. package/list/group.principal.vue +5 -4
  133. package/list/harvesterhci.io.management.cluster.vue +8 -9
  134. package/list/management.cattle.io.user.vue +12 -9
  135. package/list/provisioning.cattle.io.cluster.vue +16 -10
  136. package/mixins/__tests__/auth-config.test.ts +90 -0
  137. package/mixins/__tests__/chart.test.ts +94 -0
  138. package/mixins/__tests__/resource-fetch-api-pagination.test.ts +48 -0
  139. package/mixins/auth-config.js +7 -0
  140. package/mixins/chart.js +11 -2
  141. package/mixins/child-hook.js +12 -6
  142. package/mixins/create-edit-view/impl.js +5 -3
  143. package/mixins/resource-fetch-api-pagination.js +21 -1
  144. package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +57 -0
  145. package/models/__tests__/compliance.cattle.io.clusterscan.test.ts +144 -0
  146. package/models/__tests__/fleet-application.test.ts +175 -0
  147. package/models/__tests__/fleet.cattle.io.bundle.test.ts +169 -0
  148. package/models/__tests__/fleet.cattle.io.helmop.test.ts +84 -0
  149. package/models/__tests__/management.cattle.io.node.ts +22 -0
  150. package/models/__tests__/namespace.test.ts +36 -0
  151. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +49 -0
  152. package/models/__tests__/workload.test.ts +401 -26
  153. package/models/catalog.cattle.io.clusterrepo.js +28 -4
  154. package/models/compliance.cattle.io.clusterscan.js +39 -4
  155. package/models/fleet-application.js +4 -0
  156. package/models/fleet.cattle.io.helmop.js +20 -1
  157. package/models/management.cattle.io.cluster.js +18 -2
  158. package/models/management.cattle.io.node.js +44 -3
  159. package/models/namespace.js +1 -1
  160. package/models/pod.js +33 -1
  161. package/models/provisioning.cattle.io.cluster.js +5 -5
  162. package/models/workload.js +108 -13
  163. package/models/workload.service.js +5 -0
  164. package/package.json +14 -13
  165. package/pages/about.vue +5 -6
  166. package/pages/auth/login.vue +0 -35
  167. package/pages/auth/setup.vue +11 -0
  168. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +2 -2
  169. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +10 -1
  170. package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +93 -0
  171. package/pages/c/_cluster/apps/charts/chart.vue +2 -1
  172. package/pages/c/_cluster/apps/charts/index.vue +48 -10
  173. package/pages/c/_cluster/apps/charts/install.vue +122 -116
  174. package/pages/c/_cluster/auth/roles/index.vue +5 -4
  175. package/pages/c/_cluster/explorer/workload-dashboard/ByNamespaceSection.vue +31 -0
  176. package/pages/c/_cluster/explorer/workload-dashboard/ByStateSection.vue +138 -0
  177. package/pages/c/_cluster/explorer/workload-dashboard/ByTypeSection.vue +30 -0
  178. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadCard.vue +155 -0
  179. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadNamespaceCard.vue +142 -0
  180. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadTypeCard.vue +159 -0
  181. package/pages/c/_cluster/explorer/workload-dashboard/__tests__/composable.test.ts +561 -0
  182. package/pages/c/_cluster/explorer/workload-dashboard/composable.ts +440 -0
  183. package/pages/c/_cluster/explorer/workload-dashboard/index.vue +187 -0
  184. package/pages/c/_cluster/explorer/workload-dashboard/types.ts +80 -0
  185. package/pages/c/_cluster/fleet/application/create.vue +187 -136
  186. package/pages/c/_cluster/fleet/application/index.vue +5 -3
  187. package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailBody.vue +338 -0
  188. package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailHeader.vue +121 -0
  189. package/pages/c/_cluster/fleet/application/suse-app-collection/chart.vue +369 -0
  190. package/pages/c/_cluster/fleet/application/suse-app-collection/charts.vue +248 -0
  191. package/pages/c/_cluster/fleet/application/suse-app-collection/credentials.vue +310 -0
  192. package/pages/c/_cluster/fleet/index.vue +2 -2
  193. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +96 -0
  194. package/pages/c/_cluster/uiplugins/index.vue +15 -0
  195. package/pages/fail-whale.vue +16 -11
  196. package/pages/home.vue +16 -46
  197. package/plugins/clean-html.d.ts +9 -0
  198. package/plugins/dashboard-store/__tests__/resource-class.test.ts +93 -0
  199. package/plugins/dashboard-store/resource-class.js +62 -7
  200. package/plugins/steve/__tests__/actions.test.ts +212 -0
  201. package/plugins/steve/actions.js +96 -0
  202. package/plugins/steve/steve-pagination-utils.ts +1 -1
  203. package/rancher-components/Accordion/Accordion.vue +53 -9
  204. package/rancher-components/Form/Checkbox/Checkbox.vue +14 -0
  205. package/rancher-components/Form/Radio/RadioButton.vue +17 -1
  206. package/rancher-components/Form/Radio/RadioGroup.vue +10 -0
  207. package/rancher-components/Pill/RcTag/RcTag.vue +3 -2
  208. package/rancher-components/RcButton/RcButton.test.ts +103 -0
  209. package/rancher-components/RcButton/RcButton.vue +94 -15
  210. package/rancher-components/RcButton/types.ts +3 -0
  211. package/rancher-components/RcItemCard/RcItemCard.test.ts +18 -0
  212. package/rancher-components/RcItemCard/RcItemCard.vue +2 -2
  213. package/rancher-components/RcSection/RcSection.vue +28 -3
  214. package/scripts/extension/helm/package/Dockerfile +1 -1
  215. package/scripts/test-plugins-build.sh +2 -1
  216. package/store/__tests__/notifications.test.ts +434 -0
  217. package/store/catalog.js +57 -0
  218. package/store/plugins.js +7 -4
  219. package/types/components/buttonGroup.ts +5 -0
  220. package/types/shell/index.d.ts +104 -70
  221. package/utils/__tests__/auth.test.ts +273 -0
  222. package/utils/__tests__/computed.test.ts +193 -0
  223. package/utils/__tests__/cspAdaptor.test.ts +163 -0
  224. package/utils/__tests__/dom.test.ts +81 -0
  225. package/utils/__tests__/duration.test.ts +37 -1
  226. package/utils/__tests__/dynamic-importer.test.ts +102 -0
  227. package/utils/__tests__/fleet-appco.test.ts +312 -0
  228. package/utils/__tests__/monitoring.test.ts +130 -0
  229. package/utils/__tests__/object.test.ts +22 -0
  230. package/utils/__tests__/platform.test.ts +91 -0
  231. package/utils/__tests__/position.test.ts +237 -0
  232. package/utils/__tests__/provider.test.ts +51 -1
  233. package/utils/__tests__/queue.test.ts +232 -0
  234. package/utils/__tests__/release-notes.test.ts +221 -0
  235. package/utils/__tests__/router.test.js +254 -1
  236. package/utils/__tests__/select.test.ts +208 -0
  237. package/utils/__tests__/time.test.ts +265 -1
  238. package/utils/__tests__/title.test.ts +47 -0
  239. package/utils/__tests__/width.test.ts +53 -0
  240. package/utils/__tests__/window.test.ts +158 -0
  241. package/utils/__tests__/xccdf.test.ts +126 -6
  242. package/utils/crypto/__tests__/browserHashUtils.test.ts +98 -0
  243. package/utils/crypto/__tests__/index.test.ts +144 -0
  244. package/utils/duration.ts +104 -0
  245. package/utils/dynamic-content/__tests__/notification-handler.test.ts +196 -0
  246. package/utils/dynamic-content/info.ts +2 -1
  247. package/utils/error.js +13 -0
  248. package/utils/fleet-appco.ts +323 -0
  249. package/utils/object.js +22 -2
  250. package/utils/provider.ts +12 -0
  251. package/utils/validators/__tests__/container-images.test.ts +104 -0
  252. package/utils/validators/__tests__/flow-output.test.ts +91 -0
  253. package/utils/validators/__tests__/logging-outputs.test.ts +58 -0
  254. package/utils/validators/__tests__/monitoring-route.test.ts +119 -0
  255. package/utils/xccdf.ts +39 -42
  256. package/vue.config.js +1 -1
  257. package/pages/support/index.vue +0 -264
  258. package/utils/duration.js +0 -43
@@ -36,7 +36,8 @@ export default {
36
36
  nodes: fetchNodesForServiceTargets({
37
37
  $store: this.$store,
38
38
  inStore: this.$store.getters['currentStore']()
39
- })
39
+ }),
40
+ summaries: this.value.fetchSummaries()
40
41
  };
41
42
 
42
43
  if (this.podSchema) {
@@ -71,7 +72,6 @@ export default {
71
72
  this.showProjectMetrics = await allDashboardsExist(this.$store, this.currentCluster.id, [this.WORKLOAD_PROJECT_METRICS_DETAIL_URL, this.WORKLOAD_PROJECT_METRICS_SUMMARY_URL], 'cluster', projectId);
72
73
  }
73
74
  }
74
- this.findMatchingIngresses();
75
75
  },
76
76
 
77
77
  async unmounted() {
@@ -82,8 +82,6 @@ export default {
82
82
 
83
83
  data() {
84
84
  return {
85
- allIngresses: [],
86
- matchingIngresses: [],
87
85
  WORKLOAD_METRICS_DETAIL_URL,
88
86
  WORKLOAD_METRICS_SUMMARY_URL,
89
87
  POD_PROJECT_METRICS_DETAIL_URL: '',
@@ -124,6 +122,10 @@ export default {
124
122
  return this.$store.getters['cluster/schemaFor'](SERVICE);
125
123
  },
126
124
 
125
+ relatedServices() {
126
+ return this.value.relatedServices;
127
+ },
128
+
127
129
  podTemplateSpec() {
128
130
  if ( this.value.type === WORKLOAD_TYPES.CRON_JOB ) {
129
131
  return this.value.spec.jobTemplate.spec.template.spec;
@@ -208,51 +210,6 @@ export default {
208
210
  }
209
211
  },
210
212
 
211
- methods: {
212
- findMatchingIngresses() {
213
- if (!this.ingressSchema) {
214
- return [];
215
- }
216
-
217
- // Find Ingresses that forward traffic to Services
218
- // that select this workload.
219
- const matchingIngresses = this.allIngresses.filter((ingress) => {
220
- try {
221
- const rules = ingress.spec.rules;
222
-
223
- if (!rules || !Array.isArray(rules)) return false;
224
-
225
- for (let i = 0; i < rules.length; i++) {
226
- const paths = rules[i]?.http?.paths;
227
-
228
- if (!paths || !Array.isArray(paths)) continue;
229
- // For each Ingress, check if any Services that match
230
- // this workload are also target backends for the Ingress.
231
- for (let j = 0; j < paths.length; j++) {
232
- const pathData = paths[j];
233
- const targetServiceName = pathData?.backend?.service?.name;
234
-
235
- if (!targetServiceName) continue;
236
-
237
- for (let k = 0; k < this.value.relatedServices.length; k++) {
238
- const service = this.value.relatedServices[k];
239
- const matchingServiceName = service?.metadata?.name;
240
-
241
- if (ingress.metadata?.namespace === this.value.metadata?.namespace && matchingServiceName === targetServiceName) {
242
- return true;
243
- }
244
- }
245
- }
246
- }
247
- } catch (err) {
248
- return false;
249
- }
250
- });
251
-
252
- this.matchingIngresses = matchingIngresses;
253
- }
254
- },
255
-
256
213
  watch: {
257
214
  async 'value.jobRelationships.length'(neu, old) {
258
215
  // If there are MORE jobs ensure we go out and fetch them (changes and removals are tracked by watches)
@@ -348,7 +305,7 @@ export default {
348
305
  {{ t('workload.detail.cannotViewServices') }}
349
306
  </p>
350
307
  <p
351
- v-else-if="value.relatedServices.length === 0"
308
+ v-else-if="relatedServices.length === 0"
352
309
  class="caption"
353
310
  >
354
311
  {{ t('workload.detail.cannotFindServices') }}
@@ -360,8 +317,8 @@ export default {
360
317
  {{ t('workload.detail.serviceListCaption') }}
361
318
  </p>
362
319
  <ResourceTable
363
- v-if="serviceSchema && value.relatedServices.length > 0"
364
- :rows="value.relatedServices"
320
+ v-if="serviceSchema && relatedServices.length > 0"
321
+ :rows="relatedServices"
365
322
  :headers="serviceHeaders"
366
323
  key-field="id"
367
324
  :schema="serviceSchema"
@@ -390,7 +347,7 @@ export default {
390
347
  {{ t('workload.detail.cannotViewIngresses') }}
391
348
  </p>
392
349
  <p
393
- v-else-if="matchingIngresses.length === 0"
350
+ v-else-if="value.matchingIngresses.length === 0"
394
351
  class="caption"
395
352
  >
396
353
  {{ t('workload.detail.cannotFindIngresses') }}
@@ -402,8 +359,8 @@ export default {
402
359
  {{ t('workload.detail.ingressListCaption') }}
403
360
  </p>
404
361
  <ResourceTable
405
- v-if="ingressSchema && matchingIngresses.length > 0"
406
- :rows="matchingIngresses"
362
+ v-if="ingressSchema && value.matchingIngresses.length > 0"
363
+ :rows="value.matchingIngresses"
407
364
  :headers="ingressHeaders"
408
365
  key-field="id"
409
366
  :schema="ingressSchema"
@@ -0,0 +1,248 @@
1
+ import { shallowMount, VueWrapper } from '@vue/test-utils';
2
+ import CruCatalogRepo from '@shell/edit/catalog.cattle.io.clusterrepo.vue';
3
+ import { _CREATE, _EDIT } from '@shell/config/query-params';
4
+
5
+ const createEditViewMock = {
6
+ props: {
7
+ value: {
8
+ type: Object,
9
+ default: () => ({}),
10
+ },
11
+ realMode: {
12
+ type: String,
13
+ default: _CREATE,
14
+ },
15
+ },
16
+ data() {
17
+ return { errors: [] };
18
+ },
19
+ computed: {
20
+ isCreate: () => false,
21
+ isEdit: () => true,
22
+ isView: () => false,
23
+ schema: () => ({}),
24
+ isNamespaced: () => false,
25
+ labels: {
26
+ get: jest.fn(() => ({})),
27
+ set: jest.fn(),
28
+ },
29
+ annotations: {
30
+ get: jest.fn(() => ({})),
31
+ set: jest.fn(),
32
+ },
33
+ doneRoute: () => 'mockedRoute',
34
+ doneParams: () => ({}),
35
+ },
36
+ methods: {
37
+ done: jest.fn(),
38
+ conflict: jest.fn(() => Promise.resolve([])),
39
+ save: jest.fn(() => Promise.resolve()),
40
+ actuallySave: jest.fn(() => Promise.resolve()),
41
+ setErrors: jest.fn(),
42
+ registerBeforeHook: jest.fn(),
43
+ }
44
+ };
45
+
46
+ jest.mock('@shell/config/version', () => ({ getVersionData: () => ({ RancherPrime: 'false' }) }));
47
+ jest.mock('@shell/utils/require-asset', () => ({ requireAsset: (path: string) => path }));
48
+
49
+ const defaultGlobalMocks = {
50
+ $store: {
51
+ getters: {
52
+ 'i18n/t': (key: string) => key,
53
+ currentProduct: { inStore: 'cluster' },
54
+ 'cluster/byId': () => null,
55
+ 'cluster/all': () => [],
56
+ },
57
+ dispatch: jest.fn(),
58
+ },
59
+ $route: {
60
+ name: 'test-route',
61
+ query: {},
62
+ },
63
+ $fetchState: { pending: false },
64
+ };
65
+
66
+ const createWrapper = (specOverrides = {}, mode = _EDIT): VueWrapper<any> => {
67
+ return shallowMount(CruCatalogRepo, {
68
+ props: {
69
+ value: {
70
+ spec: { url: 'https://test.com', ...specOverrides },
71
+ isOciType: false,
72
+ isSuseAppCollectionFromUI: false,
73
+ metadata: { name: 'test-repo', annotations: {} },
74
+ },
75
+ realMode: mode,
76
+ },
77
+ mixins: [createEditViewMock],
78
+ global: {
79
+ mocks: defaultGlobalMocks,
80
+ stubs: {
81
+ AsyncButton: true,
82
+ Footer: true,
83
+ NameNsDescription: true,
84
+ Labels: true,
85
+ SelectOrCreateAuthSecret: true,
86
+ Banner: true,
87
+ RcItemCard: true,
88
+ UnitInput: true,
89
+ },
90
+ },
91
+ });
92
+ };
93
+
94
+ describe('CruCatalogRepo - refresh interval', () => {
95
+ describe('initial state', () => {
96
+ it('defaults to enabled with null display value when no refreshInterval is set', () => {
97
+ const wrapper = createWrapper();
98
+
99
+ expect(wrapper.vm.refreshEnabled).toStrictEqual(true);
100
+ expect(wrapper.vm.refreshDisplayValue).toStrictEqual(null);
101
+ expect(wrapper.vm.refreshUnit).toStrictEqual(3600);
102
+ });
103
+
104
+ it('parses existing refreshInterval into display value and unit', () => {
105
+ const wrapper = createWrapper({ refreshInterval: 7200 });
106
+
107
+ expect(wrapper.vm.refreshEnabled).toStrictEqual(true);
108
+ expect(wrapper.vm.refreshDisplayValue).toStrictEqual(2);
109
+ expect(wrapper.vm.refreshUnit).toStrictEqual(3600);
110
+ });
111
+
112
+ it('parses refreshInterval that maps to minutes', () => {
113
+ const wrapper = createWrapper({ refreshInterval: 300 });
114
+
115
+ expect(wrapper.vm.refreshEnabled).toStrictEqual(true);
116
+ expect(wrapper.vm.refreshDisplayValue).toStrictEqual(5);
117
+ expect(wrapper.vm.refreshUnit).toStrictEqual(60);
118
+ });
119
+
120
+ it('parses refreshInterval that maps to days', () => {
121
+ const wrapper = createWrapper({ refreshInterval: 172800 });
122
+
123
+ expect(wrapper.vm.refreshEnabled).toStrictEqual(true);
124
+ expect(wrapper.vm.refreshDisplayValue).toStrictEqual(2);
125
+ expect(wrapper.vm.refreshUnit).toStrictEqual(86400);
126
+ });
127
+
128
+ it('parses refreshInterval that maps to seconds', () => {
129
+ const wrapper = createWrapper({ refreshInterval: 45 });
130
+
131
+ expect(wrapper.vm.refreshEnabled).toStrictEqual(true);
132
+ expect(wrapper.vm.refreshDisplayValue).toStrictEqual(45);
133
+ expect(wrapper.vm.refreshUnit).toStrictEqual(1);
134
+ });
135
+
136
+ it('sets refreshEnabled to false when refreshInterval is -1', () => {
137
+ const wrapper = createWrapper({ refreshInterval: -1 });
138
+
139
+ expect(wrapper.vm.refreshEnabled).toStrictEqual(false);
140
+ expect(wrapper.vm.refreshDisplayValue).toStrictEqual(null);
141
+ });
142
+ });
143
+
144
+ describe('syncRefreshIntervalToSpec', () => {
145
+ it('sets spec.refreshInterval to -1 when disabled', () => {
146
+ const wrapper = createWrapper();
147
+
148
+ wrapper.vm.onRefreshEnabledChange(false);
149
+
150
+ expect(wrapper.vm.value.spec.refreshInterval).toStrictEqual(-1);
151
+ });
152
+
153
+ it('deletes spec.refreshInterval when input is empty', () => {
154
+ const wrapper = createWrapper({ refreshInterval: 3600 });
155
+
156
+ wrapper.vm.onRefreshValueChange('');
157
+
158
+ expect(wrapper.vm.value.spec.refreshInterval).toStrictEqual(undefined);
159
+ });
160
+
161
+ it('deletes spec.refreshInterval when input is 0', () => {
162
+ const wrapper = createWrapper({ refreshInterval: 3600 });
163
+
164
+ wrapper.vm.onRefreshValueChange('0');
165
+
166
+ expect(wrapper.vm.value.spec.refreshInterval).toStrictEqual(undefined);
167
+ });
168
+
169
+ it('converts display value and unit to seconds on spec', () => {
170
+ const wrapper = createWrapper();
171
+
172
+ wrapper.vm.onRefreshValueChange('5');
173
+
174
+ expect(wrapper.vm.value.spec.refreshInterval).toStrictEqual(5 * 3600);
175
+ });
176
+
177
+ it('recalculates when unit changes', () => {
178
+ const wrapper = createWrapper();
179
+
180
+ wrapper.vm.onRefreshValueChange('2');
181
+ wrapper.vm.onRefreshUnitChange(60);
182
+
183
+ expect(wrapper.vm.value.spec.refreshInterval).toStrictEqual(120);
184
+ });
185
+
186
+ it('recalculates when re-enabled', () => {
187
+ const wrapper = createWrapper();
188
+
189
+ wrapper.vm.onRefreshValueChange('10');
190
+ wrapper.vm.onRefreshEnabledChange(false);
191
+
192
+ expect(wrapper.vm.value.spec.refreshInterval).toStrictEqual(-1);
193
+
194
+ wrapper.vm.onRefreshEnabledChange(true);
195
+
196
+ expect(wrapper.vm.value.spec.refreshInterval).toStrictEqual(10 * 3600);
197
+ });
198
+ });
199
+
200
+ describe('onRefreshValueChange', () => {
201
+ it('normalizes empty string to null', () => {
202
+ const wrapper = createWrapper();
203
+
204
+ wrapper.vm.onRefreshValueChange('');
205
+
206
+ expect(wrapper.vm.refreshDisplayValue).toStrictEqual(null);
207
+ });
208
+
209
+ it('normalizes null to null', () => {
210
+ const wrapper = createWrapper();
211
+
212
+ wrapper.vm.onRefreshValueChange(null);
213
+
214
+ expect(wrapper.vm.refreshDisplayValue).toStrictEqual(null);
215
+ });
216
+
217
+ it('coerces string value to number', () => {
218
+ const wrapper = createWrapper();
219
+
220
+ wrapper.vm.onRefreshValueChange('42');
221
+
222
+ expect(wrapper.vm.refreshDisplayValue).toStrictEqual(42);
223
+ });
224
+ });
225
+
226
+ describe('validation', () => {
227
+ it('returns error for negative values', () => {
228
+ const wrapper = createWrapper();
229
+ const rule = wrapper.vm.refreshIntervalRules[0];
230
+
231
+ expect(rule(-1)).toBeDefined();
232
+ });
233
+
234
+ it('returns undefined for zero', () => {
235
+ const wrapper = createWrapper();
236
+ const rule = wrapper.vm.refreshIntervalRules[0];
237
+
238
+ expect(rule(0)).toStrictEqual(undefined);
239
+ });
240
+
241
+ it('returns undefined for positive values', () => {
242
+ const wrapper = createWrapper();
243
+ const rule = wrapper.vm.refreshIntervalRules[0];
244
+
245
+ expect(rule(5)).toStrictEqual(undefined);
246
+ });
247
+ });
248
+ });
@@ -28,12 +28,17 @@ const mocks = {
28
28
  $fetchState: { pending: false },
29
29
  $route: {
30
30
  query: { AS: '' },
31
+ hash: '',
31
32
  name: {
32
33
  endsWith: () => {
33
34
  return false;
34
35
  }
35
36
  }
36
37
  },
38
+ $router: {
39
+ currentRoute: { _value: { hash: '' } },
40
+ replace: jest.fn(),
41
+ },
37
42
  };
38
43
 
39
44
  const mockComputed = {
@@ -152,6 +157,106 @@ describe('helmOp component lifecycle', () => {
152
157
  });
153
158
  });
154
159
 
160
+ describe('onCancel', () => {
161
+ it('should navigate back to the AppCo chart page with version when isSuseAppCollection is true and mode is CREATE', () => {
162
+ const routerPush = jest.fn();
163
+ const appCoMocks = {
164
+ ...mocks,
165
+ $route: {
166
+ ...mocks.$route,
167
+ query: {
168
+ AS: '',
169
+ type: 'suse-application-collection',
170
+ chart: 'alertmanager',
171
+ version: '1.37.0',
172
+ secret: 'fleet-appco-auth-2n9px',
173
+ },
174
+ params: { cluster: 'local' },
175
+ },
176
+ $router: {
177
+ ...mocks.$router,
178
+ push: routerPush,
179
+ },
180
+ };
181
+
182
+ const wrapper = mount(HelmOpComponent, {
183
+ ...initHelmOp({ mode: _CREATE, realMode: _CREATE }),
184
+ computed: {
185
+ ...mockComputed,
186
+ isSuseAppCollection: () => true,
187
+ },
188
+ global: { mocks: appCoMocks },
189
+ });
190
+
191
+ wrapper.vm.onCancel();
192
+
193
+ expect(routerPush).toHaveBeenCalledWith({
194
+ name: 'c-cluster-fleet-application-appco-chart',
195
+ params: { cluster: 'local' },
196
+ query: {
197
+ 'repo-type': 'cluster',
198
+ repo: 'fleet-appco-repo-2n9px',
199
+ chart: 'alertmanager',
200
+ version: '1.37.0',
201
+ secret: 'fleet-appco-auth-2n9px',
202
+ },
203
+ });
204
+ });
205
+
206
+ it('should navigate back to the AppCo chart page without version when version is not in query', () => {
207
+ const routerPush = jest.fn();
208
+ const appCoMocks = {
209
+ ...mocks,
210
+ $route: {
211
+ ...mocks.$route,
212
+ query: {
213
+ AS: '',
214
+ type: 'suse-application-collection',
215
+ chart: 'alertmanager',
216
+ secret: 'fleet-appco-auth-2n9px',
217
+ },
218
+ params: { cluster: 'local' },
219
+ },
220
+ $router: {
221
+ ...mocks.$router,
222
+ push: routerPush,
223
+ },
224
+ };
225
+
226
+ const wrapper = mount(HelmOpComponent, {
227
+ ...initHelmOp({ mode: _CREATE, realMode: _CREATE }),
228
+ computed: {
229
+ ...mockComputed,
230
+ isSuseAppCollection: () => true,
231
+ },
232
+ global: { mocks: appCoMocks },
233
+ });
234
+
235
+ wrapper.vm.onCancel();
236
+
237
+ expect(routerPush).toHaveBeenCalledWith({
238
+ name: 'c-cluster-fleet-application-appco-chart',
239
+ params: { cluster: 'local' },
240
+ query: {
241
+ 'repo-type': 'cluster',
242
+ repo: 'fleet-appco-repo-2n9px',
243
+ chart: 'alertmanager',
244
+ version: undefined,
245
+ secret: 'fleet-appco-auth-2n9px',
246
+ },
247
+ });
248
+ });
249
+
250
+ it('should call done() when not a SuseAppCollection', () => {
251
+ const wrapper = mount(HelmOpComponent, initHelmOp({ mode: _CREATE, realMode: _CREATE }));
252
+
253
+ jest.spyOn(wrapper.vm, 'done').mockImplementation(jest.fn());
254
+ wrapper.vm.onCancel();
255
+
256
+ expect(wrapper.vm.done).toHaveBeenCalledWith();
257
+ });
258
+ });
259
+
155
260
  describe.each([
156
261
  _CREATE,
157
262
  _EDIT,
@@ -21,6 +21,7 @@ exports[`component: General rendering & initial state should render with default
21
21
  labelkey="auditPolicy.general.enabled.checkbox"
22
22
  mode="create"
23
23
  primary="false"
24
+ usebodytextcolor="false"
24
25
  value="false"
25
26
  valuewhentrue="true"
26
27
  />
@@ -71,6 +72,7 @@ exports[`component: General rendering & initial state should render with default
71
72
  label="auditPolicy.general.verbosity.level.label"
72
73
  loading="false"
73
74
  localizedlabel="false"
75
+ lockedoptions=""
74
76
  mode="create"
75
77
  nooptionslabelkey="labelSelect.noOptions.empty"
76
78
  optionlabel="label"
@@ -112,6 +114,7 @@ exports[`component: General rendering & initial state should render with default
112
114
  label="auditPolicy.general.verbosity.request.requestHeaders"
113
115
  mode="create"
114
116
  primary="false"
117
+ usebodytextcolor="false"
115
118
  value="false"
116
119
  valuewhentrue="true"
117
120
  />
@@ -127,6 +130,7 @@ exports[`component: General rendering & initial state should render with default
127
130
  label="auditPolicy.general.verbosity.request.requestBody"
128
131
  mode="create"
129
132
  primary="false"
133
+ usebodytextcolor="false"
130
134
  value="false"
131
135
  valuewhentrue="true"
132
136
  />
@@ -152,6 +156,7 @@ exports[`component: General rendering & initial state should render with default
152
156
  label="auditPolicy.general.verbosity.response.responseHeaders"
153
157
  mode="create"
154
158
  primary="false"
159
+ usebodytextcolor="false"
155
160
  value="false"
156
161
  valuewhentrue="true"
157
162
  />
@@ -167,6 +172,7 @@ exports[`component: General rendering & initial state should render with default
167
172
  label="auditPolicy.general.verbosity.response.responseBody"
168
173
  mode="create"
169
174
  primary="false"
175
+ usebodytextcolor="false"
170
176
  value="false"
171
177
  valuewhentrue="true"
172
178
  />
@@ -19,6 +19,7 @@ exports[`component: CRUAuditPolicy (index) rendering & initial state should rend
19
19
  prevententersubmit="false"
20
20
  resource="[object Object]"
21
21
  showcancel="true"
22
+ showtoc="false"
22
23
  steps=""
23
24
  stepsoptions="[object Object]"
24
25
  subtypes=""
@@ -1,6 +1,6 @@
1
1
  import { nextTick } from 'vue';
2
2
  /* eslint-disable jest/no-hooks */
3
- import { mount } from '@vue/test-utils';
3
+ import { mount, flushPromises } from '@vue/test-utils';
4
4
  import AzureAD from '@shell/edit/auth/azuread.vue';
5
5
  import { _EDIT } from '@shell/config/query-params';
6
6
  import { SLO_OPTION_VALUES } from '@shell/mixins/auth-config';
@@ -65,11 +65,36 @@ const requiredSetup = (modelOverrides = {}) => ({
65
65
  },
66
66
  });
67
67
 
68
+ describe('edit: azureAD accessMode default', () => {
69
+ let wrapper: any;
70
+
71
+ afterEach(() => {
72
+ wrapper.unmount();
73
+ });
74
+
75
+ it('should default accessMode to required when not set', async() => {
76
+ wrapper = mount(AzureAD, { ...requiredSetup({ accessMode: '' }) });
77
+ wrapper.setData({ model: { tenantId: 'trigger-watcher' } });
78
+ await nextTick();
79
+
80
+ expect(wrapper.vm.model.accessMode).toStrictEqual('unrestricted');
81
+ });
82
+
83
+ it('should not override accessMode when already set', async() => {
84
+ wrapper = mount(AzureAD, { ...requiredSetup({ accessMode: 'required' }) });
85
+ wrapper.setData({ model: { tenantId: 'trigger-watcher' } });
86
+ await nextTick();
87
+
88
+ expect(wrapper.vm.model.accessMode).toStrictEqual('required');
89
+ });
90
+ });
91
+
68
92
  describe('edit: azureAD should', () => {
69
93
  let wrapper: any;
70
94
 
71
- beforeEach(() => {
95
+ beforeEach(async() => {
72
96
  wrapper = mount(AzureAD, { ...requiredSetup() });
97
+ await flushPromises();
73
98
  });
74
99
  afterEach(() => {
75
100
  wrapper.unmount();
@@ -121,7 +146,7 @@ describe('edit: azureAD should', () => {
121
146
  tenantIdInputField.setValue(testCase.tenantId);
122
147
  applicationIdInputField.setValue(testCase.applicationId);
123
148
  applicationSecretInputField.setValue(testCase.applicationSecret);
124
- await nextTick();
149
+ await flushPromises();
125
150
 
126
151
  expect(saveButton.disabled).toBe(testCase.result);
127
152
  });
@@ -217,11 +242,11 @@ describe('edit: azureAD should', () => {
217
242
  tenantIdInputField.setValue(validTenantId);
218
243
  applicationIdInputField.setValue(validApplicationId);
219
244
  applicationSecretInputField.setValue(validAppSecret);
220
- await nextTick();
245
+ await flushPromises();
221
246
 
222
247
  expect(saveButton.disabled).toBe(false);
223
248
  customButton.trigger('click');
224
- await nextTick();
249
+ await flushPromises();
225
250
  expect(saveButton.disabled).toBe(true);
226
251
 
227
252
  const endpointInputField = wrapper.find('[data-testid="input-azureAD-endpoint"]');
@@ -233,7 +258,7 @@ describe('edit: azureAD should', () => {
233
258
  graphEndpointInputField.setValue(testCase.graphEndpoint);
234
259
  tokenEndpointInputField.setValue(testCase.tokenEndpoint);
235
260
  authEndpointInputField.setValue(testCase.authEndpoint);
236
- await nextTick();
261
+ await flushPromises();
237
262
 
238
263
  expect(saveButton.disabled).toBe(testCase.result);
239
264
  });
@@ -287,7 +312,7 @@ describe('edit: azureAD SSO logout should', () => {
287
312
  };
288
313
  },
289
314
  });
290
- await nextTick();
315
+ await flushPromises();
291
316
  const endSessionEndpointField = wrapper.find('[data-testid="azuread-endSessionEndpoint"]');
292
317
 
293
318
  expect(endSessionEndpointField.exists()).toBe(true);
@@ -303,7 +328,7 @@ describe('edit: azureAD SSO logout should', () => {
303
328
  };
304
329
  },
305
330
  });
306
- await nextTick();
331
+ await flushPromises();
307
332
  const endSessionEndpointField = wrapper.find('[data-testid="azuread-endSessionEndpoint"]');
308
333
 
309
334
  expect(endSessionEndpointField.exists()).toBe(true);
@@ -358,7 +383,7 @@ describe('edit: azureAD SSO logout should', () => {
358
383
  };
359
384
  },
360
385
  });
361
- await nextTick();
386
+ await flushPromises();
362
387
  const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
363
388
 
364
389
  expect(saveButton.disabled).toBe(testCase.disabled);