@rancher/shell 3.0.8 → 3.0.9-rc.2

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 (192) hide show
  1. package/apis/intf/modal.ts +38 -0
  2. package/apis/intf/slide-in.ts +3 -1
  3. package/apis/shell/__tests__/slide-in.test.ts +36 -0
  4. package/apis/shell/slide-in.ts +5 -1
  5. package/assets/styles/base/_color.scss +1 -0
  6. package/assets/styles/base/_typography.scss +14 -5
  7. package/assets/styles/themes/_light.scss +1 -1
  8. package/assets/styles/themes/_modern.scss +1 -1
  9. package/assets/translations/en-us.yaml +94 -33
  10. package/assets/translations/zh-hans.yaml +0 -2
  11. package/components/ActionMenuShell.vue +4 -4
  12. package/components/CodeMirror.vue +4 -3
  13. package/components/DetailText.vue +54 -7
  14. package/components/Drawer/Chrome.vue +11 -4
  15. package/components/Drawer/DrawerCard.vue +19 -0
  16. package/components/Drawer/ResourceDetailDrawer/ConfigTab.vue +3 -11
  17. package/components/Drawer/ResourceDetailDrawer/__tests__/ConfigTab.test.ts +2 -2
  18. package/components/Drawer/ResourceDetailDrawer/index.vue +3 -20
  19. package/components/Drawer/types.ts +1 -0
  20. package/components/DynamicContent/DynamicContentCloseButton.vue +2 -2
  21. package/components/LocaleSelector.vue +1 -1
  22. package/components/Markdown.vue +1 -1
  23. package/components/PopoverCard.vue +3 -3
  24. package/components/Resource/Detail/Card/ExtrasCard.vue +39 -0
  25. package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +142 -0
  26. package/components/Resource/Detail/Card/StateCard/composables.ts +41 -11
  27. package/components/Resource/Detail/Card/StateCard/index.vue +3 -9
  28. package/components/Resource/Detail/Card/StateCard/types.ts +6 -0
  29. package/components/Resource/Detail/Card/{PodsCard → StatusCard}/index.vue +11 -10
  30. package/components/Resource/Detail/Card/__tests__/PodsCard.test.ts +24 -25
  31. package/components/Resource/Detail/Cards.vue +27 -0
  32. package/components/Resource/Detail/Masthead/__tests__/index.test.ts +70 -0
  33. package/components/Resource/Detail/Masthead/index.vue +5 -0
  34. package/components/Resource/Detail/Metadata/KeyValueRow.vue +4 -2
  35. package/components/Resource/Detail/ResourcePopover/ResourcePopoverCard.vue +2 -2
  36. package/components/Resource/Detail/ResourceRow.types.ts +14 -0
  37. package/components/Resource/Detail/ResourceRow.vue +23 -35
  38. package/components/Resource/Detail/StatusRow.vue +5 -2
  39. package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +38 -7
  40. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +106 -2
  41. package/components/Resource/Detail/TitleBar/composables.ts +2 -1
  42. package/components/Resource/Detail/TitleBar/index.vue +41 -6
  43. package/components/ResourceDetail/Masthead/__tests__/index.test.ts +49 -1
  44. package/components/ResourceDetail/Masthead/__tests__/latest.test.ts +85 -0
  45. package/components/ResourceDetail/Masthead/index.vue +1 -0
  46. package/components/ResourceDetail/Masthead/latest.vue +8 -1
  47. package/components/ResourceDetail/Masthead/legacy.vue +1 -1
  48. package/components/Setting.vue +1 -1
  49. package/components/SortableTable/index.vue +25 -0
  50. package/components/SortableTable/selection.js +25 -12
  51. package/components/SortableTable/sorting.js +1 -1
  52. package/components/Tabbed/Tab.vue +1 -0
  53. package/components/Tabbed/index.vue +29 -6
  54. package/components/Window/ContainerShell.vue +10 -13
  55. package/components/fleet/FleetClusterTargets/TargetsList.vue +47 -29
  56. package/components/fleet/FleetClusterTargets/index.vue +82 -29
  57. package/components/fleet/FleetClusters.vue +26 -12
  58. package/components/fleet/FleetGitRepoPaths.vue +2 -2
  59. package/components/fleet/FleetResources.vue +14 -0
  60. package/components/fleet/FleetValuesFrom.vue +2 -2
  61. package/components/fleet/__tests__/FleetClusterTargets.test.ts +531 -0
  62. package/components/fleet/__tests__/FleetClusters.test.ts +576 -0
  63. package/components/fleet/dashboard/ResourceDetails.vue +96 -123
  64. package/components/form/Conditions.vue +1 -15
  65. package/components/form/HookOption.vue +5 -0
  66. package/components/form/LabeledSelect.vue +1 -1
  67. package/components/form/LifecycleHooks.vue +2 -6
  68. package/components/form/ResourceLabeledSelect.vue +12 -1
  69. package/components/form/SeccompProfile.vue +113 -0
  70. package/components/form/Security.vue +244 -133
  71. package/components/form/__tests__/LabeledSelect.test.ts +1 -1
  72. package/components/form/__tests__/SeccompProfile.test.js +124 -0
  73. package/components/form/__tests__/Security.test.ts +125 -37
  74. package/components/formatter/Autoscaler.vue +2 -2
  75. package/components/formatter/FleetSummaryGraph.vue +4 -1
  76. package/components/nav/Group.vue +5 -0
  77. package/components/nav/Header.vue +3 -3
  78. package/components/nav/HeaderPageActionMenu.vue +1 -1
  79. package/components/nav/NamespaceFilter.vue +6 -6
  80. package/components/nav/NotificationCenter/index.vue +1 -1
  81. package/components/nav/TopLevelMenu.helper.ts +41 -16
  82. package/components/nav/TopLevelMenu.vue +45 -25
  83. package/components/nav/WorkspaceSwitcher.vue +1 -1
  84. package/components/nav/__tests__/TopLevelMenu.helper.test.ts +277 -0
  85. package/components/nav/__tests__/TopLevelMenu.test.ts +160 -4
  86. package/components/templates/default.vue +0 -3
  87. package/components/templates/home.vue +0 -3
  88. package/components/templates/plain.vue +0 -3
  89. package/composables/useClickOutside.ts +1 -1
  90. package/config/product/explorer.js +1 -2
  91. package/config/types.js +41 -8
  92. package/detail/__tests__/workload.test.ts +8 -16
  93. package/detail/catalog.cattle.io.app.vue +6 -0
  94. package/detail/fleet.cattle.io.cluster.vue +6 -0
  95. package/detail/workload/index.vue +7 -109
  96. package/edit/__tests__/projectsecret.test.ts +42 -0
  97. package/edit/auth/__tests__/oidc.test.ts +50 -0
  98. package/edit/auth/oidc.vue +68 -44
  99. package/edit/autoscaling.horizontalpodautoscaler/index.vue +140 -59
  100. package/edit/autoscaling.horizontalpodautoscaler/metrics-row.vue +41 -5
  101. package/edit/projectsecret.vue +29 -0
  102. package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +89 -200
  103. package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +58 -17
  104. package/edit/provisioning.cattle.io.cluster/rke2.vue +11 -0
  105. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +3 -63
  106. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +82 -14
  107. package/edit/workload/__tests__/index.test.ts +122 -85
  108. package/edit/workload/index.vue +48 -29
  109. package/edit/workload/mixins/workload.js +85 -32
  110. package/list/catalog.cattle.io.clusterrepo.vue +1 -1
  111. package/list/projectsecret.vue +2 -2
  112. package/machine-config/__tests__/vmwarevsphere.test.ts +64 -0
  113. package/machine-config/amazonec2.vue +2 -2
  114. package/machine-config/vmwarevsphere.vue +58 -4
  115. package/mixins/__tests__/brand.spec.ts +18 -13
  116. package/mixins/__tests__/chart.test.ts +63 -0
  117. package/mixins/chart.js +56 -51
  118. package/models/__tests__/catalog.cattle.io.app.test.ts +33 -0
  119. package/models/__tests__/workload.test.ts +333 -0
  120. package/models/catalog.cattle.io.app.js +8 -0
  121. package/models/pod.js +14 -0
  122. package/models/secret.js +1 -1
  123. package/models/workload.js +93 -27
  124. package/package.json +4 -4
  125. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +91 -0
  126. package/pages/c/_cluster/apps/charts/install.vue +4 -4
  127. package/pages/c/_cluster/explorer/EventsTable.vue +2 -2
  128. package/pages/c/_cluster/fleet/index.vue +18 -12
  129. package/pages/c/_cluster/manager/hostedprovider/index.vue +1 -19
  130. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -1
  131. package/pages/c/_cluster/uiplugins/index.vue +1 -1
  132. package/plugins/dashboard-store/__tests__/resource-class.test.ts +234 -0
  133. package/plugins/dashboard-store/actions.js +9 -8
  134. package/plugins/dashboard-store/resource-class.js +97 -1
  135. package/plugins/steve/__tests__/revision.test.ts +84 -0
  136. package/plugins/steve/__tests__/steve-pagination-utils.test.ts +30 -0
  137. package/plugins/steve/__tests__/subscribe.spec.ts +134 -0
  138. package/plugins/steve/mutations.js +9 -0
  139. package/plugins/steve/revision.ts +26 -0
  140. package/plugins/steve/steve-pagination-utils.ts +6 -5
  141. package/plugins/steve/subscribe.js +211 -51
  142. package/plugins/subscribe-events.ts +2 -2
  143. package/rancher-components/Form/Checkbox/Checkbox.vue +13 -0
  144. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +1 -1
  145. package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +1 -1
  146. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +3 -1
  147. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +3 -1
  148. package/rancher-components/Pill/RcTag/RcTag.vue +1 -1
  149. package/rancher-components/Pill/index.ts +4 -0
  150. package/rancher-components/RcButton/RcButton.test.ts +53 -9
  151. package/rancher-components/RcButton/RcButton.vue +217 -25
  152. package/rancher-components/RcButton/types.ts +27 -1
  153. package/rancher-components/RcDropdown/RcDropdownMenu.vue +4 -4
  154. package/rancher-components/RcDropdown/types.ts +3 -3
  155. package/rancher-components/RcIcon/RcIcon.test.ts +42 -0
  156. package/rancher-components/RcIcon/RcIcon.vue +9 -6
  157. package/rancher-components/RcIcon/types.ts +13 -9
  158. package/rancher-components/utils/status.test.ts +10 -15
  159. package/rancher-components/utils/status.ts +5 -6
  160. package/store/aws.js +18 -12
  161. package/store/index.js +4 -8
  162. package/store/type-map.utils.ts +1 -1
  163. package/types/kube/kube-api.ts +29 -3
  164. package/types/rancher/steve.api.ts +40 -0
  165. package/types/shell/index.d.ts +99 -0
  166. package/types/store/dashboard-store.types.ts +29 -7
  167. package/types/store/pagination.types.ts +1 -0
  168. package/types/store/subscribe-events.types.ts +1 -0
  169. package/utils/__tests__/azure.test.ts +56 -0
  170. package/utils/__tests__/back-off.test.ts +364 -245
  171. package/utils/__tests__/error.test.ts +44 -0
  172. package/utils/__tests__/fleet.test.ts +8 -1
  173. package/utils/__tests__/pagination-wrapper.test.ts +167 -0
  174. package/utils/__tests__/version.test.ts +55 -1
  175. package/utils/azure.js +12 -0
  176. package/utils/back-off.ts +302 -69
  177. package/utils/cspAdaptor.ts +32 -14
  178. package/utils/dynamic-content/__tests__/index.test.ts +1 -1
  179. package/utils/dynamic-content/__tests__/new-release.test.ts +48 -7
  180. package/utils/dynamic-content/__tests__/support-notice.test.ts +1 -4
  181. package/utils/dynamic-content/index.ts +1 -6
  182. package/utils/dynamic-content/new-release.ts +5 -3
  183. package/utils/dynamic-content/types.d.ts +0 -1
  184. package/utils/error.js +9 -0
  185. package/utils/fleet.ts +2 -2
  186. package/utils/inactivity.ts +2 -3
  187. package/utils/pagination-wrapper.ts +101 -17
  188. package/utils/validators/formRules/index.ts +3 -0
  189. package/utils/version.js +38 -0
  190. package/components/auth/AzureWarning.vue +0 -77
  191. /package/components/Resource/Detail/{Card/PodsCard/Bubble.vue → Bubble.vue} +0 -0
  192. /package/components/Resource/Detail/Card/{PodsCard → StatusCard}/composable.ts +0 -0
@@ -204,6 +204,12 @@ export default {
204
204
  const spec = this.value.spec;
205
205
  let podTemplateSpec = type === WORKLOAD_TYPES.CRON_JOB ? spec.jobTemplate.spec.template.spec : spec?.template?.spec;
206
206
 
207
+ // Area to add default value to securityContext on POD CREATE
208
+ // Check if the securityContext has been setup, for None should be {} when cloning, for new should be empty.
209
+ if (this.mode === _CREATE && !podTemplateSpec.securityContext) {
210
+ podTemplateSpec.securityContext = { seccompProfile: { type: 'RuntimeDefault' } };
211
+ }
212
+
207
213
  let containers = podTemplateSpec.containers || [];
208
214
  let container;
209
215
 
@@ -258,6 +264,7 @@ export default {
258
264
  secondaryResourceData: this.secondaryResourceDataConfig(),
259
265
  namespacedConfigMaps: [],
260
266
  allNodes: null,
267
+ workerNodes: null,
261
268
  allNodeObjects: [],
262
269
  namespacedSecrets: [],
263
270
  imagePullNamespacedSecrets: [],
@@ -276,20 +283,46 @@ export default {
276
283
  container,
277
284
  containerChange: 0,
278
285
  tabChange: 0,
279
- podFsGroup: podTemplateSpec.securityContext?.fsGroup,
280
286
  savePvcHookName: 'savePvcHook',
281
287
  tabWeightMap: TAB_WEIGHT_MAP,
282
- fvFormRuleSets: [],
283
- fvReportedValidationPaths: ['spec'],
284
- isNamespaceNew: false,
285
- idKey: ID_KEY
288
+ // [fvFormRulesets][localhostProfile] added as default not required, needs to be on the INDEX 0
289
+ fvFormRuleSets: [{
290
+ path: 'podTemplateSpec.securityContext.seccompProfile.localhostProfile',
291
+ rules: [''],
292
+ rootObject: this,
293
+ translationKey: 'workload.container.security.localhostProfile.label'
294
+ }],
295
+ fvReportedValidationPaths: ['spec'],
296
+ isNamespaceNew: false,
297
+ idKey: ID_KEY
286
298
  };
287
299
  },
288
300
 
289
301
  computed: {
290
302
  ...mapGetters(['currentCluster']),
303
+ seccompProfileTypes() {
304
+ return [
305
+ {
306
+ value: 'None',
307
+ label: this.t('workload.container.security.seccompProfile.types.none.label')
308
+ },
309
+ {
310
+ value: 'RuntimeDefault',
311
+ label: this.t('workload.container.security.seccompProfile.types.runtimeDefault.label')
312
+ },
313
+ {
314
+ value: 'Localhost',
315
+ label: this.t('workload.container.security.seccompProfile.types.localhost.label')
316
+ }, {
317
+ value: 'Unconfined',
318
+ label: this.t('workload.container.security.seccompProfile.types.unconfined.label')
319
+ }];
320
+ },
321
+
291
322
  tabErrors() {
292
- return { general: this.fvGetPathErrors(['image'])?.length > 0 };
323
+ const tabErrors = { podSecurityContext: this.fvGetPathErrors(['podTemplateSpec.securityContext.seccompProfile.localhostProfile'])?.length > 0 };
324
+
325
+ return tabErrors;
293
326
  },
294
327
 
295
328
  defaultWorkloadTab() {
@@ -422,8 +455,15 @@ export default {
422
455
  }),
423
456
  ].map((container) => {
424
457
  const containerImageRule = formRulesGenerator(this.$store.getters['i18n/t'], { name: container.name }).containerImage;
458
+ const localhostProfileRule = formRulesGenerator(this.$store.getters['i18n/t'], { name: container.name }).localhostProfile;
425
459
 
426
- container.error = containerImageRule(container);
460
+ const imageError = containerImageRule(container);
461
+ const localhostProfileError = localhostProfileRule(container);
462
+
463
+ container.error = {
464
+ general: imageError,
465
+ localhostProfile: localhostProfileError
466
+ };
427
467
 
428
468
  return container;
429
469
  });
@@ -560,6 +600,26 @@ export default {
560
600
  this.servicesOwned = await this.value.getServicesOwned();
561
601
  },
562
602
 
603
+ 'podTemplateSpec.securityContext.seccompProfile.type'(neu) {
604
+ if (neu === 'Localhost') {
605
+ // [fvFormRulesets][localhostProfile] added here, should be required if Localhost selected
606
+ this.fvFormRuleSets[0] = {
607
+ path: 'podTemplateSpec.securityContext.seccompProfile.localhostProfile',
608
+ rules: ['required'],
609
+ rootObject: this,
610
+ translationKey: 'workload.container.security.localhostProfile.label'
611
+ };
612
+ } else {
613
+ // [fvFormRulesets][localhostProfile] added here, should not be required if Localhost selected
614
+ this.fvFormRuleSets[0] = {
615
+ path: 'podTemplateSpec.securityContext.seccompProfile.localhostProfile',
616
+ rules: [''],
617
+ rootObject: this,
618
+ translationKey: 'workload.container.security.localhostProfile.label'
619
+ };
620
+ }
621
+ },
622
+
563
623
  isNamespaceNew(neu, old) {
564
624
  if (!old && neu) {
565
625
  // As the namespace is new any resource that's been fetched with a namespace is now invalid
@@ -602,15 +662,6 @@ export default {
602
662
  this.value['type'] = neu;
603
663
  delete this.value.apiVersion;
604
664
  },
605
-
606
- container: {
607
- handler(c) {
608
- this.fvFormRuleSets = [{
609
- path: 'image', rootObject: c, rules: ['required'], translationKey: 'workload.container.image'
610
- }];
611
- },
612
- immediate: true
613
- }
614
665
  },
615
666
 
616
667
  created() {
@@ -647,7 +698,24 @@ export default {
647
698
  parsingFunc: (data) => {
648
699
  return data.map((node) => node.id);
649
700
  }
650
- }
701
+ },
702
+ {
703
+ var: 'workerNodes',
704
+ parsingFunc: (data) => {
705
+ const keys = [
706
+ `node-role.kubernetes.io/control-plane`,
707
+ `node-role.kubernetes.io/etcd`
708
+ ];
709
+
710
+ return data
711
+ .filter((node) => {
712
+ const taints = node?.spec?.taints || [];
713
+
714
+ return taints.every((taint) => !keys.includes(taint.key));
715
+ })
716
+ .map((node) => node.id);
717
+ }
718
+ },
651
719
  ]
652
720
  },
653
721
  [SERVICE]: {
@@ -806,7 +874,6 @@ export default {
806
874
  }
807
875
 
808
876
  this.fixPodAffinity(podAntiAffinity);
809
- this.fixPodSecurityContext(this.podTemplateSpec);
810
877
 
811
878
  template.metadata.namespace = this.value.metadata.namespace;
812
879
 
@@ -903,20 +970,6 @@ export default {
903
970
  return podAffinity;
904
971
  },
905
972
 
906
- fixPodSecurityContext(podTempSpec) {
907
- if (this.podFsGroup) {
908
- podTempSpec.securityContext = podTempSpec.securityContext || {};
909
- podTempSpec.securityContext.fsGroup = this.podFsGroup;
910
- } else {
911
- if (podTempSpec.securityContext?.fsGroup) {
912
- delete podTempSpec.securityContext.fsGroup;
913
- }
914
- if (Object.keys(podTempSpec.securityContext || {}).length === 0) {
915
- delete podTempSpec.securityContext;
916
- }
917
- }
918
- },
919
-
920
973
  selectType(type) {
921
974
  if (!this.type && type) {
922
975
  this.$router.replace({ params: { resource: type } });
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import PaginatedResourceTable from '@shell/components/PaginatedResourceTable.vue';
3
3
  import { CATALOG as CATALOG_ANNOTATIONS } from '@shell/config/labels-annotations';
4
- import { RancherKubeMetadata } from '@shell/types/kube/kube-api';
4
+ import { RancherKubeMetadata } from '@shell/types/rancher/steve.api';
5
5
  import { PaginationArgs, PaginationParamFilter } from '@shell/types/store/pagination.types';
6
6
  import { defineComponent } from 'vue';
7
7
 
@@ -10,7 +10,7 @@ import { TableColumn } from '@shell/types/store/type-map';
10
10
  import { mapGetters } from 'vuex';
11
11
  import { GROUP_RESOURCES, mapPref } from '@shell/store/prefs';
12
12
  import { DEFAULT_PROJECT, SYSTEM_PROJECT, UI_PROJECT_SECRET, UI_PROJECT_SECRET_COPY } from '@shell/config/labels-annotations';
13
- import { RancherKubeMetadata } from '@shell/types/kube/kube-api';
13
+ import { RancherKubeMetadata } from '@shell/types/rancher/steve.api';
14
14
  import {
15
15
  AGE, SECRET_DATA, STATE, SUB_TYPE, NAME as NAME_COL,
16
16
  } from '@shell/config/table-headers';
@@ -170,7 +170,7 @@ export default {
170
170
  createLocation() {
171
171
  return {
172
172
  name: 'c-cluster-product-resource-create',
173
- params: { resource: SECRET },
173
+ params: { resource: VIRTUAL_TYPES.PROJECT_SECRETS },
174
174
  query: { [SECRET_SCOPE]: SECRET_QUERY_PARAMS.PROJECT_SCOPED }
175
175
  };
176
176
  },
@@ -57,6 +57,7 @@ describe('component: vmwarevsphere', () => {
57
57
 
58
58
  const dataCenterElement = wrapper.find(`[data-testid="datacenter"]`).element;
59
59
  const resourcePoolElement = wrapper.find(`[data-testid="resourcePool"]`).element;
60
+ const storageTypeElement = wrapper.find(`[data-testid="storageType"]`).element;
60
61
  const dataStoreElement = wrapper.find(`[data-testid="dataStore"]`).element;
61
62
  const folderElement = wrapper.find(`[data-testid="folder"]`).element;
62
63
  const hostElement = wrapper.find(`[data-testid="host"]`).element;
@@ -64,6 +65,7 @@ describe('component: vmwarevsphere', () => {
64
65
 
65
66
  expect(dataCenterElement).toBeDefined();
66
67
  expect(resourcePoolElement).toBeDefined();
68
+ expect(storageTypeElement).toBeDefined();
67
69
  expect(dataStoreElement).toBeDefined();
68
70
  expect(folderElement).toBeDefined();
69
71
  expect(hostElement).toBeDefined();
@@ -297,4 +299,66 @@ describe('component: vmwarevsphere', () => {
297
299
  expect(wrapper.vm.value.network).toStrictEqual([legacyName]);
298
300
  });
299
301
  });
302
+
303
+ describe('storage type toggling', () => {
304
+ it('should toggle storage type to datastore-cluster and clear datastore value', async() => {
305
+ const wrapper = mount(vmwarevsphere, defaultCreateSetup);
306
+
307
+ wrapper.vm.value.datastore = 'some-datastore';
308
+ await wrapper.setData({ storageType: 'datastore-cluster' });
309
+
310
+ expect(wrapper.vm.storageType).toBe('datastore-cluster');
311
+ expect(wrapper.vm.value.datastore).toBe('');
312
+ });
313
+
314
+ it('should toggle storage type to datastore and clear datastoreCluster value', async() => {
315
+ const wrapper = mount(vmwarevsphere, defaultCreateSetup);
316
+
317
+ // Initialize with datastore-cluster selected
318
+ await wrapper.setData({ storageType: 'datastore-cluster' });
319
+ wrapper.vm.value.datastoreCluster = 'some-cluster';
320
+
321
+ // Switch back to datastore
322
+ await wrapper.setData({ storageType: 'datastore' });
323
+
324
+ expect(wrapper.vm.storageType).toBe('datastore');
325
+ expect(wrapper.vm.value.datastoreCluster).toBe('');
326
+ });
327
+
328
+ it('should initialize storage type to datastore-cluster if value has datastoreCluster set', () => {
329
+ const setup = {
330
+ ...defaultEditSetup,
331
+ propsData: {
332
+ ...defaultEditSetup.propsData,
333
+ value: {
334
+ ...defaultEditSetup.propsData.value,
335
+ datastoreCluster: 'existing-cluster',
336
+ datastore: ''
337
+ }
338
+ }
339
+ };
340
+ const wrapper = mount(vmwarevsphere, setup);
341
+
342
+ expect(wrapper.vm.storageType).toBe('datastore-cluster');
343
+ });
344
+
345
+ it('should correctly toggle the visibility of datastore and datastore cluster selects', async() => {
346
+ const wrapper = mount(vmwarevsphere, defaultCreateSetup);
347
+ const dataStoreContainer = wrapper.find('[data-testid="dataStore"]');
348
+
349
+ // Default state: datastore
350
+ let select = dataStoreContainer.findComponent({ name: 'LabeledSelect' });
351
+
352
+ expect(select.exists()).toBe(true);
353
+ expect(select.props('label')).toBe('%cluster.machineConfig.vsphere.scheduling.dataStore%');
354
+
355
+ // Toggle to datastore-cluster
356
+ await wrapper.setData({ storageType: 'datastore-cluster' });
357
+
358
+ select = dataStoreContainer.findComponent({ name: 'LabeledSelect' });
359
+
360
+ expect(select.exists()).toBe(true);
361
+ expect(select.props('label')).toBe('%cluster.machineConfig.vsphere.scheduling.dataStoreCluster%');
362
+ });
363
+ });
300
364
  });
@@ -12,7 +12,7 @@ import { NORMAN } from '@shell/config/types';
12
12
  import { allHash } from '@shell/utils/promise';
13
13
  import { convertStringToKV, convertKVToString } from '@shell/utils/object';
14
14
  import { sortBy } from '@shell/utils/sort';
15
- import { stringify, exceptionToErrorsArray } from '@shell/utils/error';
15
+ import { stringify, exceptionToErrorsArray, formatAWSError } from '@shell/utils/error';
16
16
  import EC2Networking from './components/EC2Networking.vue';
17
17
 
18
18
  const DEFAULT_GROUP = 'rancher-nodes';
@@ -139,7 +139,7 @@ export default {
139
139
 
140
140
  this.loadedRegionalFor = region;
141
141
  } catch (e) {
142
- this.errors = exceptionToErrorsArray(e);
142
+ this.errors = exceptionToErrorsArray(formatAWSError(e));
143
143
  }
144
144
  },
145
145
 
@@ -244,7 +244,17 @@ export default {
244
244
  haveTemplates: null,
245
245
  vAppOptions,
246
246
  vappMode: VAPP_MODE.DISABLED,
247
-
247
+ storageType: this.value.datastoreCluster ? 'datastore-cluster' : 'datastore',
248
+ storageOptions: [
249
+ {
250
+ label: this.t('cluster.machineConfig.vsphere.scheduling.dataStore'),
251
+ value: 'datastore'
252
+ },
253
+ {
254
+ label: this.t('cluster.machineConfig.vsphere.scheduling.dataStoreCluster'),
255
+ value: 'datastore-cluster'
256
+ }
257
+ ],
248
258
  osOptions: OS_OPTIONS,
249
259
  validationErrors: {},
250
260
  };
@@ -381,6 +391,19 @@ export default {
381
391
  'value.contentLibrary'() {
382
392
  this.loadLibraryTemplates();
383
393
  },
394
+ storageType(val) {
395
+ if (val === 'datastore-cluster') {
396
+ set(this.value, 'datastore', '');
397
+ if (!this.dataStoreClustersResults) {
398
+ this.loadDataStoreClusters();
399
+ }
400
+ } else {
401
+ set(this.value, 'datastoreCluster', '');
402
+ if (!this.dataStoresResults) {
403
+ this.loadDataStores();
404
+ }
405
+ }
406
+ },
384
407
  'value.creationType'(value) {
385
408
  set(this.value, 'cloneFrom', '');
386
409
  const boot2dockerUrl = value === CREATION_METHOD.LEGACY ? BOOT_2_DOCKER_URL : '';
@@ -447,7 +470,7 @@ export default {
447
470
  if (this.mode === _CREATE || this.poolCreateMode) {
448
471
  set(this.value, 'datacenter', options[0]);
449
472
  set(this.value, 'cloneFrom', undefined);
450
- set(this.value, 'useDataStoreCluster', false);
473
+ this.storageType = 'datastore';
451
474
  }
452
475
 
453
476
  if ([_EDIT, _VIEW].includes(this.mode) && !this.poolCreateMode) {
@@ -530,7 +553,7 @@ export default {
530
553
  },
531
554
 
532
555
  async loadDataStoreClusters() {
533
- set(this, 'dataStoreResults', null);
556
+ set(this, 'dataStoreClustersResults', null);
534
557
 
535
558
  const options = await this.requestOptions('data-store-clusters', this.value.datacenter);
536
559
  const content = this.mapPathOptionsToContent(options);
@@ -633,7 +656,13 @@ export default {
633
656
 
634
657
  loadAllDatacenterResources() {
635
658
  this.loadResourcePools();
636
- this.loadDataStores();
659
+ if (this.storageType === 'datastore') {
660
+ this.loadDataStores();
661
+ this.dataStoreClustersResults = null;
662
+ } else {
663
+ this.loadDataStoreClusters();
664
+ this.dataStoresResults = null;
665
+ }
637
666
  this.loadFolders();
638
667
  this.loadHosts();
639
668
  this.loadTemplates();
@@ -831,12 +860,27 @@ export default {
831
860
  />
832
861
  </div>
833
862
  </div>
863
+ <div class="row mt-10">
864
+ <div
865
+ class="col span-12"
866
+ data-testid="storageType"
867
+ >
868
+ <RadioGroup
869
+ v-model:value="storageType"
870
+ name="storageType"
871
+ :options="storageOptions"
872
+ :disabled="isDisabled"
873
+ :row="true"
874
+ />
875
+ </div>
876
+ </div>
834
877
  <div class="row mt-10">
835
878
  <div
836
879
  class="col span-6"
837
880
  data-testid="dataStore"
838
881
  >
839
882
  <LabeledSelect
883
+ v-if="storageType === 'datastore'"
840
884
  v-model:value="value.datastore"
841
885
  :loading="dataStoresLoading"
842
886
  :mode="mode"
@@ -845,6 +889,16 @@ export default {
845
889
  :disabled="isDisabled"
846
890
  :tooltip="value.datastore"
847
891
  />
892
+ <LabeledSelect
893
+ v-else
894
+ v-model:value="value.datastoreCluster"
895
+ :loading="dataStoreClustersLoading"
896
+ :mode="mode"
897
+ :options="dataStoreClusters"
898
+ :label="t('cluster.machineConfig.vsphere.scheduling.dataStoreCluster')"
899
+ :disabled="isDisabled"
900
+ :tooltip="value.datastoreCluster"
901
+ />
848
902
  </div>
849
903
  <div
850
904
  class="col span-6"
@@ -1,6 +1,7 @@
1
1
  import { mount } from '@vue/test-utils';
2
2
  import { CATALOG, MANAGEMENT } from '@shell/config/types';
3
3
  import Brand from '@shell/mixins/brand';
4
+ import CspAdapterUtils from '@shell/utils/cspAdaptor';
4
5
 
5
6
  describe('brandMixin', () => {
6
7
  const createWrapper = (vaiOn = false) => {
@@ -54,45 +55,47 @@ describe('brandMixin', () => {
54
55
  data: () => data,
55
56
  global: { mocks: { $store: store } }
56
57
  });
57
- const spyManagementFindAll = jest.spyOn(store, 'dispatch');
58
+ const spyManagementDispatch = jest.spyOn(store, 'dispatch');
59
+
60
+ CspAdapterUtils.resetState();
58
61
 
59
62
  return {
60
63
  wrapper,
61
64
  store,
62
- spyManagementFindAll,
65
+ spyManagementDispatch,
63
66
  };
64
67
  };
65
68
 
66
69
  describe('should make correct requests', () => {
67
70
  it('vai off', async() => {
68
- const { wrapper, spyManagementFindAll } = createWrapper(false);
71
+ const { wrapper, spyManagementDispatch } = createWrapper(false);
69
72
 
70
73
  // NOTE - wrapper.vm.$options.fetch() doesn't work
71
74
  await wrapper.vm.$options.fetch.apply(wrapper.vm);
72
75
 
73
76
  // wrapper.vm.$nextTick();
74
- expect(spyManagementFindAll).toHaveBeenNthCalledWith(1, 'management/findAll', {
77
+ expect(spyManagementDispatch).toHaveBeenNthCalledWith(1, 'management/findAll', {
75
78
  type: MANAGEMENT.SETTING,
76
79
  opt: {
77
80
  load: 'multi', redirectUnauthorized: false, url: `/v1/${ MANAGEMENT.SETTING }s`
78
81
  }
79
82
  });
80
- expect(spyManagementFindAll).toHaveBeenNthCalledWith(2, 'management/findAll', { type: CATALOG.APP });
83
+ expect(spyManagementDispatch).toHaveBeenNthCalledWith(2, 'management/findAll', { type: CATALOG.APP });
81
84
  });
82
85
 
83
86
  it('vai on', async() => {
84
- const { wrapper, spyManagementFindAll } = createWrapper(true);
87
+ const { wrapper, spyManagementDispatch } = createWrapper(true);
85
88
 
86
89
  // NOTE - wrapper.vm.$options.fetch() doesn't work
87
90
  await wrapper.vm.$options.fetch.apply(wrapper.vm);
88
91
 
89
- expect(spyManagementFindAll).toHaveBeenNthCalledWith(1, 'management/findAll', {
92
+ expect(spyManagementDispatch).toHaveBeenNthCalledWith(1, 'management/findAll', {
90
93
  type: MANAGEMENT.SETTING,
91
94
  opt: {
92
95
  load: 'multi', url: `/v1/${ MANAGEMENT.SETTING }s`, redirectUnauthorized: false
93
96
  }
94
97
  });
95
- expect(spyManagementFindAll).toHaveBeenNthCalledWith(2, 'management/findPage', {
98
+ expect(spyManagementDispatch).toHaveBeenNthCalledWith(2, 'management/findPage', {
96
99
  type: CATALOG.APP,
97
100
  opt: {
98
101
  pagination: {
@@ -110,7 +113,9 @@ describe('brandMixin', () => {
110
113
  pageSize: null,
111
114
  projectsOrNamespaces: [],
112
115
  sort: []
113
- }
116
+ },
117
+ transient: true,
118
+ watch: false
114
119
  }
115
120
  });
116
121
  });
@@ -120,7 +125,7 @@ describe('brandMixin', () => {
120
125
  it('should have correct csp values (off)', async() => {
121
126
  const { wrapper, store } = createWrapper();
122
127
 
123
- const spyManagementFindAll = jest.spyOn(store, 'dispatch').mockImplementation((_, options) => {
128
+ const spyManagementDispatch = jest.spyOn(store, 'dispatch').mockImplementation((_, options) => {
124
129
  const { type } = options as any;
125
130
 
126
131
  if (type === MANAGEMENT.SETTING) {
@@ -136,7 +141,7 @@ describe('brandMixin', () => {
136
141
  // NOTE - wrapper.vm.$options.fetch() doesn't work
137
142
  await wrapper.vm.$options.fetch.apply(wrapper.vm, []);
138
143
 
139
- expect(spyManagementFindAll).toHaveBeenCalledTimes(2);
144
+ expect(spyManagementDispatch).toHaveBeenCalledTimes(2);
140
145
 
141
146
  expect(wrapper.vm.canCalcCspAdapter).toBeTruthy();
142
147
  expect(wrapper.vm.cspAdapter).toBeFalsy();
@@ -145,7 +150,7 @@ describe('brandMixin', () => {
145
150
  it.each(['rancher-csp-adapter', 'rancher-csp-billing-adapter'])('should have correct csp values (on - %p )', async(chartName) => {
146
151
  const { wrapper, store } = createWrapper();
147
152
 
148
- const spyManagementFindAll = jest.spyOn(store, 'dispatch').mockImplementation((_, options) => {
153
+ const spyManagementDispatch = jest.spyOn(store, 'dispatch').mockImplementation((_, options) => {
149
154
  const { type } = options as any;
150
155
 
151
156
  if (type === MANAGEMENT.SETTING) {
@@ -161,7 +166,7 @@ describe('brandMixin', () => {
161
166
  // NOTE - wrapper.vm.$options.fetch() doesn't work
162
167
  await wrapper.vm.$options.fetch.apply(wrapper.vm, []);
163
168
 
164
- expect(spyManagementFindAll).toHaveBeenCalledTimes(2);
169
+ expect(spyManagementDispatch).toHaveBeenCalledTimes(2);
165
170
 
166
171
  expect(wrapper.vm.canCalcCspAdapter).toBeTruthy();
167
172
  expect(wrapper.vm.cspAdapter).toBeTruthy();
@@ -2,6 +2,8 @@ import ChartMixin from '@shell/mixins/chart';
2
2
  import { OPA_GATE_KEEPER_ID } from '@shell/pages/c/_cluster/gatekeeper/index.vue';
3
3
  import { mount } from '@vue/test-utils';
4
4
  import { APP_UPGRADE_STATUS } from '@shell/store/catalog';
5
+ import { CATALOG as CATALOG_ANNOTATIONS } from '@shell/config/labels-annotations';
6
+ import { CATALOG } from '@shell/config/types';
5
7
 
6
8
  describe('chartMixin', () => {
7
9
  const testCases = {
@@ -151,6 +153,67 @@ describe('chartMixin', () => {
151
153
 
152
154
  expect(mockStore.getters['catalog/version']).toHaveBeenCalledWith(expect.objectContaining({ showDeprecated: true }));
153
155
  });
156
+
157
+ it('should find existing app using version annotations', async() => {
158
+ const mockStore = {
159
+ dispatch: jest.fn((action, payload) => {
160
+ if (action === 'cluster/find') {
161
+ return Promise.resolve({ id: payload.id, fetchValues: jest.fn() });
162
+ }
163
+
164
+ return Promise.resolve();
165
+ }),
166
+ getters: {
167
+ currentCluster: () => {},
168
+ isRancher: () => true,
169
+ 'catalog/repo': () => {
170
+ return () => 'repo';
171
+ },
172
+ 'catalog/chart': () => {
173
+ return {
174
+ id: 'chart-id', versions: [{ version: '1.0.0' }], matchingInstalledApps: []
175
+ };
176
+ },
177
+ 'catalog/version': () => ({
178
+ version: '1.0.0',
179
+ annotations: {
180
+ [CATALOG_ANNOTATIONS.NAMESPACE]: 'custom-ns',
181
+ [CATALOG_ANNOTATIONS.RELEASE_NAME]: 'custom-name',
182
+ }
183
+ }),
184
+ 'prefs/get': () => {},
185
+ 'i18n/t': () => jest.fn()
186
+ }
187
+ };
188
+
189
+ const DummyComponent = {
190
+ mixins: [ChartMixin],
191
+ template: '<div></div>',
192
+ };
193
+
194
+ const wrapper = mount(
195
+ DummyComponent,
196
+ {
197
+ global: {
198
+ mocks: {
199
+ $store: mockStore,
200
+ $route: {
201
+ query: {
202
+ chart: 'chart_name',
203
+ versionName: '1.0.0'
204
+ }
205
+ }
206
+ }
207
+ }
208
+ });
209
+
210
+ await wrapper.vm.fetchChart();
211
+
212
+ expect(mockStore.dispatch).toHaveBeenCalledWith('cluster/find', {
213
+ type: CATALOG.APP,
214
+ id: 'custom-ns/custom-name'
215
+ });
216
+ });
154
217
  });
155
218
 
156
219
  describe('action', () => {