@rancher/shell 3.0.9-rc.5 → 3.0.9

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 (172) hide show
  1. package/assets/images/providers/oci-open-containers.svg +22 -0
  2. package/assets/images/providers/traefik.png +0 -0
  3. package/assets/styles/themes/_dark.scss +2 -0
  4. package/assets/styles/themes/_light.scss +2 -0
  5. package/assets/styles/themes/_modern.scss +6 -0
  6. package/assets/translations/en-us.yaml +129 -25
  7. package/components/CruResource.vue +3 -1
  8. package/components/ExplorerProjectsNamespaces.vue +12 -12
  9. package/components/IconOrSvg.vue +61 -42
  10. package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +109 -0
  11. package/components/Resource/Detail/Card/StatusCard/index.vue +21 -4
  12. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +19 -2
  13. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +19 -11
  14. package/components/Resource/Detail/ResourcePopover/__tests__/index.test.ts +12 -0
  15. package/components/Resource/Detail/ResourcePopover/index.vue +2 -0
  16. package/components/Resource/Detail/ResourceRow.vue +2 -2
  17. package/components/ResourceList/index.vue +7 -4
  18. package/components/SortableTable/index.vue +2 -2
  19. package/components/Window/ContainerLogs.vue +48 -37
  20. package/components/fleet/FleetClusterTargets/TargetsList.vue +2 -2
  21. package/components/fleet/FleetClusterTargets/index.vue +6 -1
  22. package/components/fleet/GitRepoAdvancedTab.vue +333 -0
  23. package/components/fleet/GitRepoMetadataTab.vue +43 -0
  24. package/components/fleet/GitRepoRepositoryTab.vue +101 -0
  25. package/components/fleet/GitRepoTargetTab.vue +77 -0
  26. package/components/fleet/HelmOpAdvancedTab.vue +247 -0
  27. package/components/fleet/HelmOpChartTab.vue +158 -0
  28. package/components/fleet/HelmOpMetadataTab.vue +46 -0
  29. package/components/fleet/HelmOpTargetTab.vue +84 -0
  30. package/components/fleet/HelmOpValuesTab.vue +147 -0
  31. package/components/fleet/__tests__/FleetClusterTargets.test.ts +119 -70
  32. package/components/form/BannerSettings.vue +2 -2
  33. package/components/form/NodeScheduling.vue +81 -7
  34. package/components/form/NotificationSettings.vue +2 -2
  35. package/components/form/PodAffinity.vue +1 -36
  36. package/components/form/ResourceLabeledSelect.vue +8 -4
  37. package/components/form/ResourceQuota/Namespace.vue +30 -9
  38. package/components/form/ResourceQuota/NamespaceRow.vue +25 -7
  39. package/components/form/ResourceQuota/Project.vue +140 -82
  40. package/components/form/ResourceQuota/ResourceQuotaEntry.vue +145 -0
  41. package/components/form/ResourceQuota/__tests__/Namespace.test.ts +307 -0
  42. package/components/form/ResourceQuota/__tests__/NamespaceRow.test.ts +281 -0
  43. package/components/form/ResourceQuota/__tests__/Project.test.ts +274 -27
  44. package/components/form/ResourceQuota/__tests__/ResourceQuotaEntry.test.ts +215 -0
  45. package/components/form/SchedulingCustomization.vue +14 -6
  46. package/components/form/SelectOrCreateAuthSecret.vue +107 -18
  47. package/components/form/__tests__/NodeScheduling.test.ts +12 -9
  48. package/components/form/__tests__/PodAffinity.test.ts +21 -2
  49. package/components/form/__tests__/SchedulingCustomization.test.ts +240 -0
  50. package/components/formatter/ClusterLink.vue +8 -0
  51. package/components/formatter/SecretOrigin.vue +79 -0
  52. package/config/labels-annotations.js +7 -6
  53. package/config/pagination-table-headers.js +6 -4
  54. package/config/product/explorer.js +1 -11
  55. package/config/product/manager.js +0 -1
  56. package/config/query-params.js +3 -0
  57. package/config/settings.ts +15 -2
  58. package/config/table-headers.js +21 -17
  59. package/config/types.js +23 -8
  60. package/detail/fleet.cattle.io.cluster.vue +1 -1
  61. package/detail/workload/index.vue +11 -16
  62. package/dialog/DeactivateDriverDialog.vue +1 -1
  63. package/dialog/FeatureFlagListDialog.vue +1 -1
  64. package/dialog/Ipv6NetworkingDialog.vue +156 -0
  65. package/dialog/ScalePoolDownDialog.vue +2 -2
  66. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -1
  67. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +1 -0
  68. package/edit/__tests__/management.cattle.io.project.test.js +56 -128
  69. package/edit/auth/oidc.vue +1 -1
  70. package/edit/catalog.cattle.io.clusterrepo.vue +155 -25
  71. package/edit/fleet.cattle.io.gitrepo.vue +153 -283
  72. package/edit/fleet.cattle.io.helmop.vue +190 -332
  73. package/edit/management.cattle.io.project.vue +5 -42
  74. package/edit/management.cattle.io.setting.vue +6 -0
  75. package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/auth.spec.ts +145 -0
  76. package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/index.test.ts +202 -0
  77. package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/tls.spec.ts +226 -0
  78. package/edit/monitoring.coreos.com.alertmanagerconfig/auth.vue +24 -21
  79. package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/opsgenie.spec.ts +157 -0
  80. package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/pagerduty.spec.ts +132 -0
  81. package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/slack.spec.ts +108 -0
  82. package/edit/monitoring.coreos.com.alertmanagerconfig/types/pagerduty.vue +2 -1
  83. package/edit/monitoring.coreos.com.receiver/__tests__/auth.spec.ts +165 -0
  84. package/edit/monitoring.coreos.com.receiver/__tests__/index.test.ts +153 -0
  85. package/edit/monitoring.coreos.com.receiver/__tests__/tls.spec.ts +115 -0
  86. package/edit/monitoring.coreos.com.receiver/types/__tests__/email.spec.ts +86 -0
  87. package/edit/monitoring.coreos.com.receiver/types/__tests__/opsgenie.spec.ts +209 -0
  88. package/edit/monitoring.coreos.com.receiver/types/__tests__/pagerduty.spec.ts +105 -0
  89. package/edit/monitoring.coreos.com.receiver/types/__tests__/slack.spec.ts +92 -0
  90. package/edit/monitoring.coreos.com.receiver/types/__tests__/webhook.spec.ts +131 -0
  91. package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +55 -24
  92. package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +1 -103
  93. package/edit/provisioning.cattle.io.cluster/__tests__/index.test.ts +13 -1
  94. package/edit/provisioning.cattle.io.cluster/__tests__/rke2-fleet-cluster-agent.test.ts +283 -0
  95. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +65 -49
  96. package/edit/provisioning.cattle.io.cluster/ingress/IngressCards.vue +114 -0
  97. package/edit/provisioning.cattle.io.cluster/ingress/IngressConfiguration.vue +158 -0
  98. package/edit/provisioning.cattle.io.cluster/rke2.vue +167 -69
  99. package/edit/provisioning.cattle.io.cluster/shared.ts +36 -1
  100. package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +2 -1
  101. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +70 -7
  102. package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +343 -0
  103. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +2 -1
  104. package/edit/provisioning.cattle.io.cluster/tabs/etcd/__tests__/S3Config.test.ts +13 -1
  105. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +10 -44
  106. package/edit/secret/index.vue +1 -1
  107. package/edit/token.vue +68 -29
  108. package/edit/workload/__tests__/index.test.ts +2 -37
  109. package/edit/workload/index.vue +6 -2
  110. package/edit/workload/mixins/workload.js +0 -32
  111. package/list/__tests__/management.cattle.io.setting.test.ts +198 -0
  112. package/list/management.cattle.io.setting.vue +13 -0
  113. package/list/provisioning.cattle.io.cluster.vue +50 -1
  114. package/list/secret.vue +4 -9
  115. package/list/service.vue +6 -8
  116. package/machine-config/amazonec2.vue +11 -4
  117. package/machine-config/components/EC2Networking.vue +46 -30
  118. package/machine-config/components/__tests__/EC2Networking.test.ts +7 -7
  119. package/machine-config/components/__tests__/utils/vpcSubnetMockData.js +0 -9
  120. package/machine-config/digitalocean.vue +3 -3
  121. package/models/__tests__/chart.test.ts +2 -2
  122. package/models/__tests__/namespace.test.ts +11 -0
  123. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +96 -0
  124. package/models/__tests__/workload.test.ts +42 -1
  125. package/models/catalog.cattle.io.clusterrepo.js +30 -4
  126. package/models/chart.js +3 -3
  127. package/models/ext.cattle.io.token.js +48 -0
  128. package/models/kontainerdriver.js +2 -2
  129. package/models/namespace.js +7 -1
  130. package/models/nodedriver.js +2 -2
  131. package/models/provisioning.cattle.io.cluster.js +28 -7
  132. package/models/secret.js +0 -17
  133. package/models/service.js +44 -1
  134. package/models/token.js +4 -0
  135. package/models/workload.js +12 -6
  136. package/package.json +1 -1
  137. package/pages/account/index.vue +96 -67
  138. package/pages/auth/setup.vue +5 -14
  139. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +45 -18
  140. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +4 -1
  141. package/pages/c/_cluster/apps/charts/index.vue +82 -3
  142. package/pages/c/_cluster/apps/charts/install.vue +317 -42
  143. package/pages/c/_cluster/explorer/tools/index.vue +1 -1
  144. package/pages/c/_cluster/manager/cloudCredential/index.vue +1 -1
  145. package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -4
  146. package/pages/c/_cluster/settings/index.vue +3 -1
  147. package/pages/c/_cluster/uiplugins/index.vue +1 -1
  148. package/plugins/dashboard-store/__tests__/getters.test.ts +108 -0
  149. package/plugins/dashboard-store/__tests__/resource-class.test.ts +27 -0
  150. package/plugins/dashboard-store/actions.js +3 -8
  151. package/plugins/dashboard-store/getters.js +7 -5
  152. package/plugins/dashboard-store/mutations.js +4 -1
  153. package/plugins/dashboard-store/resource-class.js +3 -3
  154. package/plugins/steve/__tests__/steve-class.test.ts +102 -141
  155. package/plugins/steve/steve-class.js +12 -3
  156. package/plugins/steve/steve-pagination-utils.ts +6 -2
  157. package/rancher-components/RcIcon/types.ts +2 -0
  158. package/rancher-components/RcItemCard/RcItemCard.vue +72 -20
  159. package/store/prefs.js +3 -0
  160. package/types/aws-sdk.d.ts +121 -0
  161. package/types/resources/node.ts +15 -0
  162. package/types/shell/index.d.ts +537 -506
  163. package/types/store/pagination.types.ts +5 -5
  164. package/utils/__tests__/array.test.ts +1 -29
  165. package/utils/__tests__/cluster-agent-configuration.test.ts +203 -0
  166. package/utils/array.ts +0 -11
  167. package/utils/aws.ts +21 -0
  168. package/utils/cluster.js +22 -2
  169. package/utils/selector-typed.ts +1 -1
  170. package/utils/svg-filter.js +4 -3
  171. package/components/__tests__/ProjectRow.test.ts +0 -206
  172. package/components/form/ResourceQuota/ProjectRow.vue +0 -277
@@ -25,7 +25,10 @@ import Wizard from '@shell/components/Wizard';
25
25
  import TypeDescription from '@shell/components/TypeDescription';
26
26
  import ChartMixin from '@shell/mixins/chart';
27
27
  import ChildHook, { BEFORE_SAVE_HOOKS, AFTER_SAVE_HOOKS } from '@shell/mixins/child-hook';
28
- import { CATALOG, MANAGEMENT, DEFAULT_WORKSPACE, CAPI } from '@shell/config/types';
28
+ import {
29
+ CLUSTER_REPO_APPCO_AUTH_GENERATE_NAME, CATALOG, MANAGEMENT, DEFAULT_WORKSPACE, CAPI, SECRET,
30
+ AUTH_TYPE, NAMESPACE as NAMESPACE_TYPE
31
+ } from '@shell/config/types';
29
32
  import {
30
33
  CHART, FROM_CLUSTER, FROM_TOOLS, HIDE_SIDE_NAV, NAMESPACE, REPO, REPO_TYPE, VERSION, _FLAGGED
31
34
  } from '@shell/config/query-params';
@@ -40,6 +43,8 @@ import { findBy, insertAt } from '@shell/utils/array';
40
43
  import { saferDump } from '@shell/utils/create-yaml';
41
44
  import { LINUX, WINDOWS } from '@shell/store/catalog';
42
45
  import { SETTING } from '@shell/config/settings';
46
+ import SelectOrCreateAuthSecret from '@shell/components/form/SelectOrCreateAuthSecret.vue';
47
+ import { generateRandomAlphaString } from '@shell/utils/string';
43
48
 
44
49
  const VALUES_STATE = {
45
50
  FORM: 'FORM',
@@ -91,7 +96,8 @@ export default {
91
96
  UnitInput,
92
97
  YamlEditor,
93
98
  Wizard,
94
- TypeDescription
99
+ TypeDescription,
100
+ SelectOrCreateAuthSecret
95
101
  },
96
102
 
97
103
  mixins: [
@@ -321,6 +327,8 @@ export default {
321
327
  userValues = {};
322
328
  }
323
329
 
330
+ this.userValues = userValues;
331
+
324
332
  /*
325
333
  Remove global values if they are identical to
326
334
  the currently available information about the cluster
@@ -370,49 +378,84 @@ export default {
370
378
 
371
379
  /* Look for annotation to say this app is a legacy migrated app (we look in either place for now) */
372
380
  this.migratedApp = (this.existing?.spec?.chart?.metadata?.annotations?.[CATALOG_ANNOTATIONS.MIGRATED] === 'true');
381
+
382
+ if (this.repo.isSuseAppCollection) {
383
+ let defaultSelectedSecret = await this.$store.getters['cluster/byId'](SECRET, `cattle-system/${ this.repo.spec.clientSecret.name }`);
384
+
385
+ if (!defaultSelectedSecret) {
386
+ try {
387
+ defaultSelectedSecret = (await this.$store.dispatch('cluster/find', { type: SECRET, id: `cattle-system/${ this.repo.spec.clientSecret.name }` }));
388
+ } catch (e) {
389
+ // If cannot get the secret for any reason, permission or doesn't exist
390
+ // We can fallback to use the name only and with that name move forward.
391
+ // On only other required data is the DecodedData but not having it will only trigger a different flow.
392
+ defaultSelectedSecret = { name: this.repo.spec.clientSecret.name };
393
+ }
394
+ }
395
+
396
+ this.selectedSecret = defaultSelectedSecret;
397
+ this.defaultGeneratedNameForImagePullSecret = `${ this.selectedSecret.name }-image-pull-secret`;
398
+ this.generatedNameForImagePullSecret = `${ this.selectedSecret.name }-image-pull-secret-${ generateRandomAlphaString(5) }`;
399
+ this.appCoDataFetched = true;
400
+ await this.initializeDataForNamespaceChanges();
401
+ await this.setImagePullSecretData();
402
+ }
373
403
  },
374
404
 
375
405
  data() {
376
406
  return {
377
- defaultRegistrySetting: '',
378
- customRegistrySetting: '',
379
- serverUrlSetting: null,
380
- chartValues: null,
381
- clusterRegistry: '',
382
- originalYamlValues: null,
383
- previousYamlValues: null,
384
- errors: null,
385
- existing: null,
386
- globalRegistry: '',
387
- forceNamespace: null,
388
- loadedVersion: null,
389
- loadedVersionValues: null,
390
- legacyApp: null,
391
- mcapp: null,
392
- mode: null,
393
- value: null,
394
- valuesComponent: null,
395
- valuesYaml: '',
396
- project: null,
397
- migratedApp: false,
407
+ defaultRegistrySetting: '',
408
+ customRegistrySetting: '',
409
+ serverUrlSetting: null,
410
+ chartValues: null,
411
+ clusterRegistry: '',
412
+ originalYamlValues: null,
413
+ previousYamlValues: null,
414
+ errors: null,
415
+ existing: null,
416
+ globalRegistry: '',
417
+ forceNamespace: null,
418
+ loadedVersion: null,
419
+ loadedVersionValues: null,
420
+ legacyApp: null,
421
+ mcapp: null,
422
+ mode: null,
423
+ value: null,
424
+ valuesComponent: null,
425
+ valuesYaml: '',
426
+ project: null,
427
+ migratedApp: false,
398
428
  defaultCmdOpts,
399
- customCmdOpts: { ...defaultCmdOpts },
400
- autoInstallInfo: [],
401
-
402
- nameDisabled: false,
403
-
404
- preFormYamlOption: VALUES_STATE.YAML,
405
- formYamlOption: VALUES_STATE.YAML,
406
- showDiff: false,
407
- showValuesComponent: true,
408
- showQuestions: true,
409
- showSlideIn: false,
410
- shownReadmeWindows: [],
411
- showCommandStep: false,
412
- showCustomRegistryInput: false,
413
- isNamespaceNew: false,
414
-
415
- stepBasic: {
429
+ customCmdOpts: { ...defaultCmdOpts },
430
+ autoInstallInfo: [],
431
+ nameDisabled: false,
432
+ preFormYamlOption: VALUES_STATE.YAML,
433
+ formYamlOption: VALUES_STATE.YAML,
434
+ showDiff: false,
435
+ showValuesComponent: true,
436
+ showQuestions: true,
437
+ showSlideIn: false,
438
+ shownReadmeWindows: [],
439
+ showCommandStep: false,
440
+ showCustomRegistryInput: false,
441
+ isNamespaceNew: false,
442
+ selectedSecret: null,
443
+ secrets: [],
444
+ secretsView: [],
445
+ appCoSecretsView: [],
446
+ selectedImagePullSecret: null,
447
+ appCoImagePullSecretView: [],
448
+ generatedNameForImagePullSecret: null,
449
+ defaultGeneratedNameForImagePullSecret: null,
450
+ defaultImagePullSecret: null,
451
+ clientSecret: null,
452
+ showCreateAuthSecret: false,
453
+ dontUseDefaultOption: null,
454
+ disabledCheckbox: false,
455
+ appCoDataFetched: false,
456
+ AUTH_TYPE,
457
+ CLUSTER_REPO_APPCO_AUTH_GENERATE_NAME,
458
+ stepBasic: {
416
459
  name: 'basics',
417
460
  label: this.t('catalog.install.steps.basics.label'),
418
461
  subtext: this.t('catalog.install.steps.basics.subtext'),
@@ -465,6 +508,11 @@ export default {
465
508
  return ignoreVariables(this.versionInfo);
466
509
  },
467
510
 
511
+ hasDecodedDataAvailable() {
512
+ // Will return false if doesn't have access to neither the decodedData or the selectedSecret, or if the decodedData is empty
513
+ return this.selectedSecret?.decodedData;
514
+ },
515
+
468
516
  namespaceIsNew() {
469
517
  const all = this.$store.getters['cluster/all'](NAMESPACE);
470
518
  const want = this.value?.metadata?.namespace;
@@ -480,6 +528,22 @@ export default {
480
528
  return this.isRancher && !this.existing && this.forceNamespace;
481
529
  },
482
530
 
531
+ selectedRepoAuthBanner() {
532
+ if (!this.selectedSecret) {
533
+ return '';
534
+ }
535
+
536
+ if (!this.dontUseDefaultOption && !this.selectedImagePullSecret) {
537
+ return `${ this.t('catalog.install.steps.basics.generatedImagePullSecretBannerFromPreviousAuth', { imagePullSecretName: this.defaultGeneratedNameForImagePullSecret, repoAuthenticationName: this.selectedSecret.name }, {}, true) }`;
538
+ } else if (!this.selectedImagePullSecret) {
539
+ return `${ this.t('catalog.install.steps.basics.generatedNewImagePullSecret', { imagePullSecretName: this.generatedNameForImagePullSecret }, {}, true) }`;
540
+ } else if (this.selectedImagePullSecret === this.defaultImagePullSecret?.name) {
541
+ return `${ this.t('catalog.install.steps.basics.usePreviouslyGeneratedImagePullSecretBanner', { imagePullSecretName: this.selectedImagePullSecret, repoAuthenticationName: this.selectedSecret.name }, {}, true) }`;
542
+ }
543
+
544
+ return '';
545
+ },
546
+
483
547
  projectOpts() {
484
548
  const cluster = this.currentCluster;
485
549
  const projects = this.$store.getters['management/all'](MANAGEMENT.PROJECT);
@@ -729,6 +793,13 @@ export default {
729
793
  return global.systemDefaultRegistry !== undefined || global.cattle?.systemDefaultRegistry !== undefined;
730
794
  },
731
795
 
796
+ setImagePullSecretDataTrigger() {
797
+ return `
798
+ ${ this.defaultImagePullSecret?.name }
799
+ ${ this.dontUseDefaultOption }
800
+ ${ this.selectedImagePullSecret }`;
801
+ }
802
+
732
803
  },
733
804
 
734
805
  watch: {
@@ -741,7 +812,7 @@ export default {
741
812
  }
742
813
  },
743
814
 
744
- 'value.metadata.namespace'(neu, old) {
815
+ async 'value.metadata.namespace'(neu, old) {
745
816
  if (neu) {
746
817
  const ns = this.$store.getters['cluster/byId'](NAMESPACE, this.value.metadata.namespace);
747
818
 
@@ -751,6 +822,14 @@ export default {
751
822
  this.project = project.replace(':', '/');
752
823
  }
753
824
  }
825
+
826
+ if (this.repo.isSuseAppCollection) {
827
+ await this.initializeDataForNamespaceChanges();
828
+ }
829
+ },
830
+
831
+ async setImagePullSecretDataTrigger() {
832
+ await this.setImagePullSecretData();
754
833
  },
755
834
 
756
835
  preFormYamlOption(neu, old) {
@@ -818,6 +897,9 @@ export default {
818
897
  window.scrollTop = 0;
819
898
 
820
899
  this.preFormYamlOption = this.valuesComponent || this.hasQuestions ? VALUES_STATE.FORM : VALUES_STATE.YAML;
900
+
901
+ // Register the image pull secret creation hook with lower priority (runs after SelectOrCreateAuthSecret at 99)
902
+ this.registerBeforeHook(this.createImagePullSecret, 'createImagePullSecret', 150);
821
903
  },
822
904
 
823
905
  beforeUnmount() {
@@ -854,6 +936,54 @@ export default {
854
936
  }
855
937
  },
856
938
 
939
+ async initializeDataForNamespaceChanges() {
940
+ // Skip the flow if the data still not fetched, it will trigger after fetching manually
941
+ if (!this.appCoDataFetched) {
942
+ try {
943
+ this.defaultImagePullSecret = await this.$store.dispatch('cluster/find', { type: SECRET, id: `${ this.targetNamespace }/${ this.repo.spec.clientSecret.name }-image-pull-secret` });
944
+ } catch (e) {
945
+ // If the secret doesn't exist, that's fine, we'll just create a new one later
946
+ this.defaultImagePullSecret = null;
947
+ }
948
+
949
+ // Reset if doesnt have the defaultImagePullSecret and doesn't have decoded data
950
+ // Disable the checkbox
951
+ const previousDontUseDefaultOption = this.dontUseDefaultOption;
952
+ let dontUseDefaultOption = false;
953
+
954
+ if (!this.hasDecodedDataAvailable && !this.defaultImagePullSecret) {
955
+ dontUseDefaultOption = true;
956
+ this.disabledCheckbox = true;
957
+ } else {
958
+ dontUseDefaultOption = false;
959
+ this.disabledCheckbox = false;
960
+ }
961
+
962
+ // On upgrade mode you cannot change namespace so this works as a full setup
963
+ if (!!this.existing) {
964
+ if (this.userValues?.global?.imagePullSecrets?.[0]) {
965
+ this.selectedImagePullSecret = this.userValues?.global?.imagePullSecrets[0];
966
+ }
967
+ this.dontUseDefaultOption = true;
968
+
969
+ return;
970
+ }
971
+
972
+ this.dontUseDefaultOption = dontUseDefaultOption;
973
+ // Setting default values if changing to avoid duplicated trigger
974
+ if (this.dontUseDefaultOption !== previousDontUseDefaultOption) {
975
+ if (this.defaultImagePullSecret) {
976
+ // If the default option is used and the default secret already exists, use it
977
+ this.selectedImagePullSecret = this.defaultImagePullSecret.name;
978
+ this.chartValues.global.imagePullSecrets = [this.selectedImagePullSecret];
979
+ } else if (!this.defaultImagePullSecret) {
980
+ // Create new option with default generated name if the default option is selected
981
+ this.selectedImagePullSecret = null;
982
+ this.chartValues.global.imagePullSecrets = [this.defaultGeneratedNameForImagePullSecret];
983
+ }
984
+ }
985
+ }
986
+ },
857
987
  async getClusterRegistry() {
858
988
  const hasPermissionToSeeProvCluster = this.$store.getters[`management/schemaFor`](CAPI.RANCHER_CLUSTER);
859
989
 
@@ -897,6 +1027,30 @@ export default {
897
1027
  }
898
1028
  },
899
1029
 
1030
+ async setImagePullSecretData() {
1031
+ if (this.selectedSecret && this.repo.isSuseAppCollection && this.dontUseDefaultOption !== null) {
1032
+ if (!this.dontUseDefaultOption && this.defaultImagePullSecret) {
1033
+ // If the default option is used and the default secret already exists, use it
1034
+ this.selectedImagePullSecret = this.defaultImagePullSecret.name;
1035
+ this.chartValues.global.imagePullSecrets = [this.selectedImagePullSecret];
1036
+ } else if (!this.dontUseDefaultOption && !this.defaultImagePullSecret) {
1037
+ // Create new option with default generated name if the default option is selected
1038
+ this.selectedImagePullSecret = null;
1039
+ this.chartValues.global.imagePullSecrets = [this.defaultGeneratedNameForImagePullSecret];
1040
+ } else if (this.dontUseDefaultOption) {
1041
+ // If doesn't have the dontUseDefaultOption selected, it will respect the SELECT
1042
+ // New one with new username and password
1043
+ if (!this.selectedImagePullSecret) {
1044
+ this.chartValues.global.imagePullSecrets = [this.generatedNameForImagePullSecret];
1045
+ } else {
1046
+ this.chartValues.global.imagePullSecrets = [this.selectedImagePullSecret];
1047
+ }
1048
+ }
1049
+
1050
+ this.valuesYaml = saferDump(this.chartValues);
1051
+ }
1052
+ },
1053
+
900
1054
  async getGlobalRegistry() {
901
1055
  // Use the global registry as a fallback.
902
1056
  // If it is an empty string, the container
@@ -973,12 +1127,42 @@ export default {
973
1127
  }
974
1128
  },
975
1129
 
1130
+ async createNamespaceIfNeeded() {
1131
+ const namespace = this.targetNamespace;
1132
+
1133
+ // Check if namespace already exists
1134
+ try {
1135
+ await this.$store.dispatch('cluster/find', {
1136
+ type: NAMESPACE_TYPE,
1137
+ id: namespace
1138
+ });
1139
+
1140
+ // Namespace exists, no need to create it
1141
+ return;
1142
+ } catch (e) {
1143
+ // Namespace doesn't exist, create it
1144
+ }
1145
+
1146
+ // Create the namespace
1147
+ const nsResource = await this.$store.dispatch('cluster/createNamespace', { name: namespace });
1148
+
1149
+ // Apply any defaults and save
1150
+ nsResource.applyDefaults();
1151
+ await nsResource.save();
1152
+ },
1153
+
976
1154
  async finish(btnCb) {
977
1155
  try {
978
1156
  const isUpgrade = !!this.existing;
979
1157
 
980
1158
  this.errors = [];
981
1159
 
1160
+ // Create namespace if it doesn't exist (before hooks run)
1161
+ // And only if it is SUSE APP Collection, overall should just do the same flow
1162
+ if (!isUpgrade && this.isNamespaceNew && this.repo.isSuseAppCollection) {
1163
+ await this.createNamespaceIfNeeded();
1164
+ }
1165
+
982
1166
  await this.applyHooks(BEFORE_SAVE_HOOKS);
983
1167
 
984
1168
  const { errors, input } = this.actionInput(isUpgrade);
@@ -1170,7 +1354,8 @@ export default {
1170
1354
  annotations: {
1171
1355
  ...migratedAnnotations,
1172
1356
  [CATALOG_ANNOTATIONS.SOURCE_REPO_TYPE]: this.chart.repoType,
1173
- [CATALOG_ANNOTATIONS.SOURCE_REPO_NAME]: this.chart.repoName
1357
+ [CATALOG_ANNOTATIONS.SOURCE_REPO_NAME]: this.chart.repoName,
1358
+ ...(this.repo.isSuseAppCollection ? { [CATALOG_ANNOTATIONS.SUSE_APP_COLLECTION]: 'true' } : {}),
1174
1359
  },
1175
1360
  values,
1176
1361
  };
@@ -1309,6 +1494,49 @@ export default {
1309
1494
  step[prop] = update[prop];
1310
1495
  }
1311
1496
  }
1497
+ },
1498
+
1499
+ async createImagePullSecret() {
1500
+ if (!this.repo.isSuseAppCollection) {
1501
+ return;
1502
+ }
1503
+
1504
+ let imagePullSecretName = '';
1505
+
1506
+ // If wants to use the default one, it will create the Image Pull Secret
1507
+ if (!this.dontUseDefaultOption && !this.selectedImagePullSecret && this.hasDecodedDataAvailable) {
1508
+ // Create the secret for app collections
1509
+ const secret = await this.$store.dispatch('cluster/create', {
1510
+ type: SECRET,
1511
+ _type: 'kubernetes.io/dockerconfigjson',
1512
+ metadata: {
1513
+ name: this.defaultGeneratedNameForImagePullSecret,
1514
+ namespace: this.targetNamespace,
1515
+ },
1516
+ data: {}
1517
+ });
1518
+
1519
+ const registryHost = this.repo?.spec?.url ? new URL(this.repo.spec.url).host : '';
1520
+ const config = { auths: { [registryHost]: this.selectedSecret?.decodedData } };
1521
+ const json = JSON.stringify(config);
1522
+
1523
+ secret.setData('.dockerconfigjson', json);
1524
+
1525
+ const result = await secret.save();
1526
+
1527
+ // Now use the secret to add to the input
1528
+ imagePullSecretName = result.id.split('/')[1];
1529
+
1530
+ // Now if it is not default, and there is a selectedImagePullSecret, ideally we should use it
1531
+ } else if (this.dontUseDefaultOption && !this.selectedImagePullSecret) {
1532
+ imagePullSecretName = this.selectedImagePullSecret;
1533
+ }
1534
+ // If the dontUseDefaultOption is false, than there is no need to change the data
1535
+
1536
+ // Finally add the imagePullSecrets to the input, if it is already a DOCKER_JSON, it can use what has been setup on the page
1537
+ if (imagePullSecretName) {
1538
+ this.chartValues.global.imagePullSecrets = [imagePullSecretName];
1539
+ }
1312
1540
  }
1313
1541
  },
1314
1542
  };
@@ -1427,6 +1655,7 @@ export default {
1427
1655
  </div>
1428
1656
  </div>
1429
1657
  <NameNsDescription
1658
+ v-if="showNameEditor"
1430
1659
  v-model:value="value"
1431
1660
  :description-hidden="true"
1432
1661
  :mode="mode"
@@ -1456,6 +1685,52 @@ export default {
1456
1685
  />
1457
1686
  </template>
1458
1687
  </NameNsDescription>
1688
+ <div
1689
+ v-if="repo.isSuseAppCollection"
1690
+ class="mb-20"
1691
+ >
1692
+ <Banner
1693
+ color="info"
1694
+ class="mt-10"
1695
+ label-key="catalog.install.steps.basics.requiresImagePullSecret"
1696
+ />
1697
+ <Checkbox
1698
+ v-model:value="dontUseDefaultOption"
1699
+ label-key="catalog.install.steps.basics.dontUseDefaultImagePullSecret"
1700
+ :disabled="disabledCheckbox"
1701
+ />
1702
+
1703
+ <div v-if="dontUseDefaultOption">
1704
+ <SelectOrCreateAuthSecret
1705
+ v-model:value="selectedImagePullSecret"
1706
+ :mode="mode"
1707
+ data-testid="clusterrepo-auth-secret"
1708
+ :register-before-hook="registerBeforeHook"
1709
+ :namespace="value.namespace"
1710
+ :pre-select="{ selected: AUTH_TYPE._IMAGE_PULL_SECRET }"
1711
+ :limit-to-namespace="true"
1712
+ :in-store="inStore"
1713
+ :allow-ssh="false"
1714
+ :allow-none="false"
1715
+ :allow-basic="false"
1716
+ :generate-name="`${CLUSTER_REPO_APPCO_AUTH_GENERATE_NAME}image-pull-secret-`"
1717
+ :cache-secrets="true"
1718
+ :fixed-image-pull-secret="true"
1719
+ :client-generated-name="generatedNameForImagePullSecret"
1720
+ :image-pull-secret-docker-json-url-config="repo?.spec?.url"
1721
+ />
1722
+ </div>
1723
+ <Banner
1724
+ v-if="selectedRepoAuthBanner"
1725
+ color="info"
1726
+ class="mt-10"
1727
+ >
1728
+ <span
1729
+ v-clean-html="selectedRepoAuthBanner"
1730
+ />
1731
+ </Banner>
1732
+ </div>
1733
+
1459
1734
  <Checkbox
1460
1735
  v-model:value="showCommandStep"
1461
1736
  class="mb-20"
@@ -98,7 +98,7 @@ export default {
98
98
  },
99
99
  subHeaderItems: chart.cardContent.subHeaderItems,
100
100
  footerItems: chart.deploysOnWindows ? [{
101
- icon: 'icon-tag-alt',
101
+ icon: 'tag-alt',
102
102
  iconTooltip: { key: 'generic.tags' },
103
103
  labels: [this.t('catalog.charts.deploysOnWindows')],
104
104
  }] : [],
@@ -9,7 +9,7 @@ import {
9
9
  ID_UNLINKED,
10
10
  NAME_UNLINKED,
11
11
  } from '@shell/config/table-headers';
12
- import { allHash } from 'utils/promise';
12
+ import { allHash } from '@shell/utils/promise';
13
13
 
14
14
  export default {
15
15
  components: {
@@ -37,14 +37,15 @@ export default {
37
37
  async refreshK8sMetadata(buttonDone) {
38
38
  try {
39
39
  await this.$store.dispatch('rancher/request', {
40
- url: '/v3/kontainerdrivers?action=refresh',
41
- method: 'post',
42
- data: { },
40
+ url: '/v3/kontainerdrivers?action=refresh',
41
+ method: 'post',
42
+ data: { },
43
+ timeout: 15000,
43
44
  });
44
45
 
45
46
  buttonDone(true);
46
47
  } catch (err) {
47
- this.$store.dispatch('growl/fromError', { title: 'Error refreshing kontainer drivers', err }, { root: true });
48
+ this.$store.dispatch('growl/fromError', { title: this.t('drivers.kontainer.refreshError', { error: err }) }, { root: true });
48
49
  buttonDone(false);
49
50
  }
50
51
  }
@@ -18,7 +18,9 @@ export default {
18
18
  product: SETTINGS,
19
19
  // Will have one or t'other
20
20
  resource: hasSettings ? MANAGEMENT.SETTING : MANAGEMENT.FEATURE,
21
- }
21
+ },
22
+ // Used to keep the route on the redirected page when coming from a link with a hash
23
+ hash: this.$route.hash
22
24
  });
23
25
  }
24
26
  };
@@ -873,7 +873,7 @@ export default {
873
873
  }
874
874
 
875
875
  return labels.length ? [{
876
- icon: 'icon-tag-alt',
876
+ icon: 'tag-alt',
877
877
  iconTooltip: { key: 'generic.tags' },
878
878
  labels,
879
879
  }] : [];
@@ -47,6 +47,7 @@ describe('dashboard-store: getters', () => {
47
47
  expect(urlForGetter('typeFoo', 'idBar')).toBe('protocol/urlFoo/idBar');
48
48
  });
49
49
  });
50
+
50
51
  describe('dashboard-store > getters > urlOptions', () => {
51
52
  // we're not testing function output based off of state or getter inputs here since they are dependencies
52
53
  const state = { config: { baseUrl: 'protocol' } };
@@ -97,4 +98,111 @@ describe('dashboard-store: getters', () => {
97
98
  expect(urlOptionsGetter('foo', { sortBy: 'bar', sortOrder: 'baz' })).toBe('foo');
98
99
  });
99
100
  });
101
+
102
+ describe('dashboard-store > getters > matchingLabelSelector', () => {
103
+ const { matchingLabelSelector } = getters;
104
+ const labelSelector = { matchLabels: { foo: 'bar' } };
105
+ const selectorString = 'foo=bar';
106
+ const type = 'pod';
107
+ const namespace = 'default';
108
+ const allResources = [{ id: '1' }];
109
+ const matchingResources = [{ id: '1' }];
110
+
111
+ it('returns all resources if store has a VAI page matching the selector', () => {
112
+ const state = {};
113
+ const rootState = {};
114
+ const gettersMock = {
115
+ normalizeType: jest.fn((t) => t),
116
+ havePage: jest.fn().mockReturnValue({
117
+ request: {
118
+ namespace,
119
+ pagination: {
120
+ filters: [],
121
+ labelSelector
122
+ }
123
+ }
124
+ }),
125
+ all: jest.fn().mockReturnValue(allResources),
126
+ haveSelector: jest.fn(),
127
+ haveAll: jest.fn(),
128
+ matching: jest.fn(),
129
+ };
130
+
131
+ const result = matchingLabelSelector(state, gettersMock, rootState)(type, labelSelector, namespace);
132
+
133
+ expect(result).toStrictEqual(allResources);
134
+ expect(gettersMock.all).toHaveBeenCalledWith(type);
135
+ });
136
+
137
+ it('returns all resources if store has the specific selector cached', () => {
138
+ const state = {};
139
+ const rootState = {};
140
+ const gettersMock = {
141
+ normalizeType: jest.fn((t) => t),
142
+ havePage: jest.fn().mockReturnValue(null),
143
+ all: jest.fn().mockReturnValue(allResources),
144
+ haveSelector: jest.fn().mockReturnValue(true),
145
+ haveAll: jest.fn(),
146
+ matching: jest.fn(),
147
+ };
148
+
149
+ const result = matchingLabelSelector(state, gettersMock, rootState)(type, labelSelector, namespace);
150
+
151
+ expect(result).toStrictEqual(allResources);
152
+ expect(gettersMock.haveSelector).toHaveBeenCalledWith(type, selectorString);
153
+ });
154
+
155
+ it('returns matching resources if store has a page (subset)', () => {
156
+ const state = {};
157
+ const rootState = {};
158
+ const gettersMock = {
159
+ normalizeType: jest.fn((t) => t),
160
+ havePage: jest.fn().mockReturnValue({ request: {} }), // Truthy, but doesn't match first if
161
+ all: jest.fn(),
162
+ haveSelector: jest.fn().mockReturnValue(false),
163
+ haveAll: jest.fn(),
164
+ matching: jest.fn().mockReturnValue(matchingResources),
165
+ };
166
+
167
+ const result = matchingLabelSelector(state, gettersMock, rootState)(type, labelSelector, namespace);
168
+
169
+ expect(result).toStrictEqual(matchingResources);
170
+ expect(gettersMock.matching).toHaveBeenCalledWith(type, selectorString, namespace);
171
+ });
172
+
173
+ it('returns matching resources if store has all resources', () => {
174
+ const state = {};
175
+ const rootState = {};
176
+ const gettersMock = {
177
+ normalizeType: jest.fn((t) => t),
178
+ havePage: jest.fn().mockReturnValue(null),
179
+ all: jest.fn(),
180
+ haveSelector: jest.fn().mockReturnValue(false),
181
+ haveAll: jest.fn().mockReturnValue(true),
182
+ matching: jest.fn().mockReturnValue(matchingResources),
183
+ };
184
+
185
+ const result = matchingLabelSelector(state, gettersMock, rootState)(type, labelSelector, namespace);
186
+
187
+ expect(result).toStrictEqual(matchingResources);
188
+ expect(gettersMock.matching).toHaveBeenCalledWith(type, selectorString, namespace);
189
+ });
190
+
191
+ it('returns empty array if no conditions met', () => {
192
+ const state = {};
193
+ const rootState = {};
194
+ const gettersMock = {
195
+ normalizeType: jest.fn((t) => t),
196
+ havePage: jest.fn().mockReturnValue(null),
197
+ all: jest.fn(),
198
+ haveSelector: jest.fn().mockReturnValue(false),
199
+ haveAll: jest.fn().mockReturnValue(false),
200
+ matching: jest.fn(),
201
+ };
202
+
203
+ const result = matchingLabelSelector(state, gettersMock, rootState)(type, labelSelector, namespace);
204
+
205
+ expect(result).toStrictEqual([]);
206
+ });
207
+ });
100
208
  });