@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
@@ -71,6 +71,11 @@ export default {
71
71
  default: 'auth-',
72
72
  },
73
73
 
74
+ clientGeneratedName: {
75
+ type: String,
76
+ default: null,
77
+ },
78
+
74
79
  allowNone: {
75
80
  type: Boolean,
76
81
  default: true,
@@ -150,6 +155,42 @@ export default {
150
155
  default: false,
151
156
  },
152
157
 
158
+ /**
159
+ * Used specifically to fix the HTTP BASIC auth to generate specific authentication
160
+ * This is used to clear up the SELECT to make sure it only has HTTP BASIC with some special conditions
161
+ */
162
+ fixedHttpBasicAuth: {
163
+ type: Boolean,
164
+ default: false,
165
+ },
166
+
167
+ /** Used together with fixedHttpBasicAuth
168
+ * It will filter all the cases to use this specific label at the start.
169
+ */
170
+ filterBasicAuth: {
171
+ type: String,
172
+ default: '',
173
+ },
174
+
175
+ /**
176
+ * This works similar to other allow* but this makes specific to create as ImagePullSecret since it is specific for one page that only uses it
177
+ * To avoid using it in other places.
178
+ */
179
+ fixedImagePullSecret: {
180
+ type: Boolean,
181
+ default: false,
182
+ },
183
+ /**
184
+ * Whenever the fixedImagePullSecret is setup the dockerJsonUrlConfig needs to be passed that will be used to create the DockerJsonConfig file
185
+ */
186
+ imagePullSecretDockerJsonUrlConfig: {
187
+ type: String,
188
+ default: '',
189
+ },
190
+
191
+ /**
192
+ * Specific property to change labels if it is Github.com repository
193
+ */
153
194
  isGithubDotComRepository: {
154
195
  type: Boolean,
155
196
  default: false,
@@ -157,7 +198,7 @@ export default {
157
198
  },
158
199
 
159
200
  async fetch() {
160
- if ( (this.allowSsh || this.allowBasic || this.allowRke) && this.$store.getters[`${ this.inStore }/schemaFor`](SECRET) ) {
201
+ if ( (this.allowSsh || this.allowBasic || this.allowRke || this.fixedImagePullSecret) && this.$store.getters[`${ this.inStore }/schemaFor`](SECRET) ) {
161
202
  if (this.$store.getters[`${ this.inStore }/paginationEnabled`](SECRET)) {
162
203
  // Filter results via api (because we shouldn't be fetching them all...)
163
204
  this.filteredSecrets = await this.filterSecretsByApi();
@@ -200,6 +241,8 @@ export default {
200
241
 
201
242
  selected: null,
202
243
 
244
+ previousValue: null,
245
+
203
246
  filterByNamespace: this.namespace && this.limitToNamespace,
204
247
 
205
248
  publicKey: '',
@@ -207,10 +250,11 @@ export default {
207
250
  sshKnownHosts: '',
208
251
  uniqueId: new Date().getTime(), // Allows form state to be individually tracked if the form is in a list
209
252
 
210
- SSH: AUTH_TYPE._SSH,
211
- BASIC: AUTH_TYPE._BASIC,
212
- S3: AUTH_TYPE._S3,
213
- RKE: AUTH_TYPE._RKE,
253
+ SSH: AUTH_TYPE._SSH,
254
+ BASIC: AUTH_TYPE._BASIC,
255
+ IMAGE_PULL_SECRET: AUTH_TYPE._IMAGE_PULL_SECRET,
256
+ S3: AUTH_TYPE._S3,
257
+ RKE: AUTH_TYPE._RKE,
214
258
  };
215
259
  },
216
260
 
@@ -218,6 +262,12 @@ export default {
218
262
  secretTypes() {
219
263
  const types = [];
220
264
 
265
+ if ( this.fixedImagePullSecret ) {
266
+ types.push(SECRET_TYPES.DOCKER_JSON);
267
+
268
+ return types;
269
+ }
270
+
221
271
  if ( this.allowSsh ) {
222
272
  types.push(SECRET_TYPES.SSH);
223
273
  }
@@ -325,7 +375,7 @@ export default {
325
375
  });
326
376
  }
327
377
 
328
- if (this.allowSsh || this.allowS3 || this.allowBasic || this.allowRke) {
378
+ if (this.allowSsh || this.allowS3 || this.allowBasic || this.allowRke || this.fixedImagePullSecret) {
329
379
  out.unshift({
330
380
  label: 'divider',
331
381
  disabled: true,
@@ -366,6 +416,18 @@ export default {
366
416
  });
367
417
  }
368
418
 
419
+ if ( this.fixedImagePullSecret ) {
420
+ out.unshift({
421
+ label: this.t('selectOrCreateAuthSecret.createImagePullSecret'),
422
+ value: AUTH_TYPE._IMAGE_PULL_SECRET,
423
+ kind: 'highlighted'
424
+ });
425
+ }
426
+
427
+ if (this.fixedHttpBasicAuth) {
428
+ out = out.filter((o) => o.label.search(this.filterBasicAuth) === 0 || ['title', 'divider'].includes(o.kind) || o.value === AUTH_TYPE._BASIC);
429
+ }
430
+
369
431
  return out;
370
432
  },
371
433
 
@@ -374,7 +436,7 @@ export default {
374
436
  return '';
375
437
  }
376
438
 
377
- if ( this.selected === AUTH_TYPE._SSH || this.selected === AUTH_TYPE._BASIC || this.selected === AUTH_TYPE._RKE || this.selected === AUTH_TYPE._S3 ) {
439
+ if ( this.selected === AUTH_TYPE._SSH || this.selected === AUTH_TYPE._BASIC || this.selected === AUTH_TYPE._RKE || this.selected === AUTH_TYPE._S3 || this.selected === AUTH_TYPE._IMAGE_PULL_SECRET ) {
378
440
  return 'col span-4';
379
441
  }
380
442
 
@@ -395,6 +457,7 @@ export default {
395
457
  publicKey: 'updateKeyVal',
396
458
  privateKey: 'updateKeyVal',
397
459
  sshKnownHosts: 'updateKeyVal',
460
+ preSelect: 'updateSelectedFromValue',
398
461
  value: 'updateSelectedFromValue',
399
462
 
400
463
  async namespace(ns) {
@@ -478,7 +541,7 @@ export default {
478
541
  },
479
542
 
480
543
  updateKeyVal() {
481
- if ( ![AUTH_TYPE._SSH, AUTH_TYPE._BASIC, AUTH_TYPE._S3, AUTH_TYPE._RKE].includes(this.selected) ) {
544
+ if ( ![AUTH_TYPE._SSH, AUTH_TYPE._BASIC, AUTH_TYPE._S3, AUTH_TYPE._RKE, AUTH_TYPE._IMAGE_PULL_SECRET].includes(this.selected) ) {
482
545
  this.privateKey = '';
483
546
  this.publicKey = '';
484
547
  this.sshKnownHosts = '';
@@ -498,9 +561,9 @@ export default {
498
561
  },
499
562
 
500
563
  update() {
501
- if ( (!this.selected || [AUTH_TYPE._SSH, AUTH_TYPE._BASIC, AUTH_TYPE._S3, AUTH_TYPE._RKE, AUTH_TYPE._NONE].includes(this.selected))) {
564
+ if ( (!this.selected || [AUTH_TYPE._SSH, AUTH_TYPE._BASIC, AUTH_TYPE._S3, AUTH_TYPE._RKE, AUTH_TYPE._NONE, AUTH_TYPE._IMAGE_PULL_SECRET].includes(this.selected))) {
502
565
  this.$emit('update:value', null);
503
- } else if ( this.selected.includes(':') ) {
566
+ } else if ( this.selected.includes(':')) {
504
567
  // Cloud creds
505
568
  this.$emit('update:value', this.selected);
506
569
  } else {
@@ -522,7 +585,7 @@ export default {
522
585
  },
523
586
 
524
587
  async doCreate() {
525
- if ( ![AUTH_TYPE._SSH, AUTH_TYPE._BASIC, AUTH_TYPE._S3, AUTH_TYPE._RKE].includes(this.selected) || this.delegateCreateToParent ) {
588
+ if ( ![AUTH_TYPE._SSH, AUTH_TYPE._BASIC, AUTH_TYPE._S3, AUTH_TYPE._RKE, AUTH_TYPE._IMAGE_PULL_SECRET].includes(this.selected) || this.delegateCreateToParent ) {
526
589
  return;
527
590
  }
528
591
 
@@ -537,12 +600,17 @@ export default {
537
600
  },
538
601
  });
539
602
  } else {
603
+ const metadata = { namespace: this.namespace };
604
+
605
+ if (this.clientGeneratedName) {
606
+ metadata.name = this.clientGeneratedName;
607
+ } else {
608
+ metadata.generateName = this.generateName;
609
+ }
610
+
540
611
  secret = await this.$store.dispatch(`${ this.inStore }/create`, {
541
- type: SECRET,
542
- metadata: {
543
- namespace: this.namespace,
544
- generateName: this.generateName
545
- },
612
+ type: SECRET,
613
+ metadata,
546
614
  });
547
615
 
548
616
  let type, publicField, privateField;
@@ -558,6 +626,11 @@ export default {
558
626
  publicField = 'username';
559
627
  privateField = 'password';
560
628
  break;
629
+ case AUTH_TYPE._IMAGE_PULL_SECRET:
630
+ type = SECRET_TYPES.DOCKER_JSON;
631
+ publicField = 'username';
632
+ privateField = 'password';
633
+ break;
561
634
  case AUTH_TYPE._RKE:
562
635
  type = SECRET_TYPES.RKE_AUTH_CONFIG;
563
636
  // Set the 'auth' key to be the base64 of the username and password concatenated with a ':' character
@@ -581,6 +654,22 @@ export default {
581
654
  if ((this.selected === AUTH_TYPE._SSH) && this.showSshKnownHosts) {
582
655
  secret.data.known_hosts = base64Encode(this.sshKnownHosts || '');
583
656
  }
657
+
658
+ if (this.selected === AUTH_TYPE._IMAGE_PULL_SECRET && this.imagePullSecretDockerJsonUrlConfig) {
659
+ const registryHost = this.imagePullSecretDockerJsonUrlConfig ? new URL(this.imagePullSecretDockerJsonUrlConfig).host : '';
660
+
661
+ const config = {
662
+ auths: {
663
+ [registryHost]: {
664
+ [publicField]: this.publicKey,
665
+ [privateField]: this.privateKey,
666
+ }
667
+ }
668
+ };
669
+ const json = JSON.stringify(config);
670
+
671
+ secret.setData('.dockerconfigjson', json);
672
+ }
584
673
  }
585
674
  }
586
675
 
@@ -609,7 +698,7 @@ export default {
609
698
  v-model:value="selected"
610
699
  data-testid="auth-secret-select"
611
700
  :mode="mode"
612
- :label-key="labelKey"
701
+ :label-key="fixedImagePullSecret ? 'selectOrCreateAuthSecret.imagePullSecret' : labelKey"
613
702
  :loading="$fetchState.pending"
614
703
  :options="options"
615
704
  :selectable="option => !option.disabled"
@@ -645,7 +734,7 @@ export default {
645
734
  />
646
735
  </div>
647
736
  </template>
648
- <template v-else-if="selected === BASIC || selected === RKE">
737
+ <template v-else-if="selected === BASIC || selected === RKE || selected === IMAGE_PULL_SECRET">
649
738
  <Banner
650
739
  v-if="selected === RKE"
651
740
  color="info"
@@ -6,12 +6,17 @@ const requiredSetup = () => {
6
6
  return {
7
7
  global: {
8
8
  mocks: {
9
- $store: {
9
+ $fetchState: { pending: false },
10
+ $store: {
10
11
  getters: {
11
- currentProduct: { inStore: 'cluster' },
12
- 'i18n/t': (text: string) => text,
13
- t: (text: string) => text,
14
- }
12
+ currentStore: () => 'cluster',
13
+ currentProduct: { inStore: 'cluster' },
14
+ 'i18n/t': (text: string) => text,
15
+ t: (text: string) => text,
16
+ 'cluster/paginationEnabled': () => false,
17
+ 'cluster/all': () => ['node-0', 'node-1'],
18
+ },
19
+ dispatch: jest.fn(),
15
20
  }
16
21
  },
17
22
  }
@@ -21,8 +26,6 @@ const requiredSetup = () => {
21
26
  describe('component: NodeScheduling', () => {
22
27
  const value = { nodeName: 'node-1' };
23
28
 
24
- const nodes = ['node-0', 'node-1'];
25
-
26
29
  it.each([
27
30
  _VIEW,
28
31
  _CREATE,
@@ -32,13 +35,13 @@ describe('component: NodeScheduling', () => {
32
35
  NodeScheduling,
33
36
  {
34
37
  props: {
35
- mode, loading: false, value, nodes
38
+ mode, loading: false, value
36
39
  },
37
40
  ...requiredSetup(),
38
41
  }
39
42
  );
40
43
 
41
44
  expect(wrapper.find('[data-testid="node-scheduling-selectNode"]').exists()).toBeTruthy();
42
- expect(wrapper.find('[data-testid="node-scheduling-nodeSelector"]').element.textContent).toContain(value.nodeName);
45
+ expect(wrapper.find('[data-testid="node-scheduling-nodeSelector"] .vs__selected').text()).toBe(value.nodeName);
43
46
  });
44
47
  });
@@ -3,6 +3,23 @@ import { mount } from '@vue/test-utils';
3
3
  import PodAffinity from '@shell/components/form/PodAffinity.vue';
4
4
  import { _CREATE } from '@shell/config/query-params';
5
5
 
6
+ const requiredSetup = () => {
7
+ return {
8
+ global: {
9
+ mocks: {
10
+ $store: {
11
+ getters: {
12
+ currentStore: () => 'cluster',
13
+ 'i18n/t': (text: string) => text,
14
+ 'cluster/schemaFor': jest.fn(),
15
+ 'cluster/canList': jest.fn(),
16
+ }
17
+ }
18
+ }
19
+ }
20
+ };
21
+ };
22
+
6
23
  describe('component: PodAffinity', () => {
7
24
  it('should display the weight input when the priority is preferred', () => {
8
25
  const podAffinity = {
@@ -15,7 +32,8 @@ describe('component: PodAffinity', () => {
15
32
  const wrapper = mount(PodAffinity, {
16
33
  props: {
17
34
  mode: _CREATE, field: 'overrideAffinity', value: { overrideAffinity: { podAffinity } }
18
- }
35
+ },
36
+ ...requiredSetup()
19
37
  });
20
38
 
21
39
  expect(wrapper.find('[data-testid="pod-affinity-weight-index0"]').exists()).toBeTruthy();
@@ -33,7 +51,8 @@ describe('component: PodAffinity', () => {
33
51
  const wrapper = mount(PodAffinity, {
34
52
  props: {
35
53
  mode: _CREATE, field: 'overrideAffinity', value: { overrideAffinity: { podAffinity } }
36
- }
54
+ },
55
+ ...requiredSetup()
37
56
  });
38
57
 
39
58
  const weightInput = wrapper.find('[data-testid="pod-affinity-weight-index0"]');
@@ -0,0 +1,240 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import { nextTick } from 'vue';
3
+ import SchedulingCustomization from '@shell/components/form/SchedulingCustomization.vue';
4
+ import { AGENT_CONFIGURATION_TYPES } from '@shell/config/settings';
5
+ import { _CREATE, _EDIT } from '@shell/config/query-params';
6
+
7
+ const mockStore = { getters: { 'i18n/t': jest.fn().mockImplementation((key: string) => key) } };
8
+
9
+ const createWrapper = (propsData: any = {}) => {
10
+ return mount(SchedulingCustomization, {
11
+ propsData: {
12
+ type: AGENT_CONFIGURATION_TYPES.CLUSTER,
13
+ feature: true,
14
+ mode: _CREATE,
15
+ defaultPC: { value: 100, preemptionPolicy: 'PreemptLowerPriority' },
16
+ defaultPDB: { maxUnavailable: 1 },
17
+ ...propsData
18
+ },
19
+ global: {
20
+ mocks: {
21
+ $store: mockStore,
22
+ t: jest.fn().mockImplementation((key: string) => key)
23
+ }
24
+ }
25
+ });
26
+ };
27
+
28
+ describe('component: SchedulingCustomization - Agent Type Support', () => {
29
+ let wrapper: any;
30
+
31
+ beforeEach(() => {
32
+ jest.clearAllMocks();
33
+ });
34
+
35
+ afterEach(() => {
36
+ if (wrapper) {
37
+ wrapper.unmount();
38
+ }
39
+ });
40
+
41
+ describe('agent Type Prop', () => {
42
+ it('should accept and use CLUSTER agent type', () => {
43
+ wrapper = createWrapper({ type: AGENT_CONFIGURATION_TYPES.CLUSTER });
44
+
45
+ expect(wrapper.vm.type).toBe(AGENT_CONFIGURATION_TYPES.CLUSTER);
46
+ });
47
+
48
+ it('should accept and use FLEET agent type', () => {
49
+ wrapper = createWrapper({ type: AGENT_CONFIGURATION_TYPES.FLEET });
50
+
51
+ expect(wrapper.vm.type).toBe(AGENT_CONFIGURATION_TYPES.FLEET);
52
+ });
53
+
54
+ it('should emit scheduling-customization-changed event with correct agent type for CLUSTER', async() => {
55
+ wrapper = createWrapper({ type: AGENT_CONFIGURATION_TYPES.CLUSTER });
56
+
57
+ const checkbox = wrapper.findComponent('[data-testid="scheduling-customization-checkbox"]');
58
+
59
+ await checkbox.vm.$emit('update:value', true);
60
+
61
+ expect(wrapper.emitted('scheduling-customization-changed')).toBeTruthy();
62
+ expect(wrapper.emitted('scheduling-customization-changed')[0][0]).toStrictEqual({
63
+ event: true,
64
+ agentType: AGENT_CONFIGURATION_TYPES.CLUSTER
65
+ });
66
+ });
67
+
68
+ it('should emit scheduling-customization-changed event with correct agent type for FLEET', async() => {
69
+ wrapper = createWrapper({ type: AGENT_CONFIGURATION_TYPES.FLEET });
70
+
71
+ const checkbox = wrapper.findComponent('[data-testid="scheduling-customization-checkbox"]');
72
+
73
+ await checkbox.vm.$emit('update:value', false);
74
+
75
+ expect(wrapper.emitted('scheduling-customization-changed')).toBeTruthy();
76
+ expect(wrapper.emitted('scheduling-customization-changed')[0][0]).toStrictEqual({
77
+ event: false,
78
+ agentType: AGENT_CONFIGURATION_TYPES.FLEET
79
+ });
80
+ });
81
+ });
82
+
83
+ describe('settings Mismatch Detection', () => {
84
+ it('should detect priority class value mismatch', () => {
85
+ const value = {
86
+ priorityClass: { value: 200, preemptionPolicy: 'PreemptLowerPriority' },
87
+ podDisruptionBudget: { maxUnavailable: 1 }
88
+ };
89
+
90
+ wrapper = createWrapper({
91
+ value,
92
+ defaultPC: { value: 100, preemptionPolicy: 'PreemptLowerPriority' },
93
+ mode: _EDIT
94
+ });
95
+
96
+ expect(wrapper.vm.settingMissmatch).toBe(true);
97
+ });
98
+
99
+ it('should detect priority class preemption policy mismatch', () => {
100
+ const value = {
101
+ priorityClass: { value: 100, preemptionPolicy: 'Never' },
102
+ podDisruptionBudget: { maxUnavailable: 1 }
103
+ };
104
+
105
+ wrapper = createWrapper({
106
+ value,
107
+ defaultPC: { value: 100, preemptionPolicy: 'PreemptLowerPriority' },
108
+ mode: _EDIT
109
+ });
110
+
111
+ expect(wrapper.vm.settingMissmatch).toBe(true);
112
+ });
113
+
114
+ it('should detect pod disruption budget maxUnavailable mismatch', () => {
115
+ const value = {
116
+ priorityClass: { value: 100, preemptionPolicy: 'PreemptLowerPriority' },
117
+ podDisruptionBudget: { maxUnavailable: 2 }
118
+ };
119
+
120
+ wrapper = createWrapper({
121
+ value,
122
+ defaultPDB: { maxUnavailable: 1 },
123
+ mode: _EDIT
124
+ });
125
+
126
+ expect(wrapper.vm.settingMissmatch).toBe(true);
127
+ });
128
+
129
+ it('should detect pod disruption budget minAvailable mismatch', () => {
130
+ const value = {
131
+ priorityClass: { value: 100, preemptionPolicy: 'PreemptLowerPriority' },
132
+ podDisruptionBudget: { minAvailable: 2 }
133
+ };
134
+
135
+ wrapper = createWrapper({
136
+ value,
137
+ defaultPDB: { minAvailable: 1 },
138
+ mode: _EDIT
139
+ });
140
+
141
+ expect(wrapper.vm.settingMissmatch).toBe(true);
142
+ });
143
+
144
+ it('should not detect mismatch when values match defaults', () => {
145
+ const value = {
146
+ priorityClass: { value: 100, preemptionPolicy: 'PreemptLowerPriority' },
147
+ podDisruptionBudget: { maxUnavailable: 1 }
148
+ };
149
+
150
+ wrapper = createWrapper({
151
+ value,
152
+ defaultPC: { value: 100, preemptionPolicy: 'PreemptLowerPriority' },
153
+ defaultPDB: { maxUnavailable: 1 },
154
+ mode: _EDIT
155
+ });
156
+
157
+ expect(wrapper.vm.settingMissmatch).toBe(false);
158
+ });
159
+
160
+ it('should not detect mismatch when no value is provided', () => {
161
+ wrapper = createWrapper({
162
+ value: undefined,
163
+ mode: _EDIT
164
+ });
165
+
166
+ expect(wrapper.vm.settingMissmatch).toBe(false);
167
+ });
168
+ });
169
+
170
+ describe('component State', () => {
171
+ it('should be enabled when value is provided', () => {
172
+ const value = {
173
+ priorityClass: { value: 100 },
174
+ podDisruptionBudget: { maxUnavailable: 1 }
175
+ };
176
+
177
+ wrapper = createWrapper({ value });
178
+
179
+ expect(wrapper.vm.enabled).toBe(true);
180
+ });
181
+
182
+ it('should be disabled when no value is provided', () => {
183
+ wrapper = createWrapper({ value: undefined });
184
+
185
+ expect(wrapper.vm.enabled).toBe(false);
186
+ });
187
+
188
+ it('should detect edit mode correctly', () => {
189
+ wrapper = createWrapper({ mode: _EDIT });
190
+
191
+ expect(wrapper.vm.isEdit).toBe(true);
192
+ });
193
+
194
+ it('should detect create mode correctly', () => {
195
+ wrapper = createWrapper({ mode: _CREATE });
196
+
197
+ expect(wrapper.vm.isEdit).toBe(false);
198
+ });
199
+ });
200
+
201
+ describe('banner Display in Edit Mode', () => {
202
+ it('should show banner when in edit mode and settings mismatch', async() => {
203
+ const value = {
204
+ priorityClass: { value: 200, preemptionPolicy: 'PreemptLowerPriority' },
205
+ podDisruptionBudget: { maxUnavailable: 1 }
206
+ };
207
+
208
+ wrapper = createWrapper({
209
+ value,
210
+ defaultPC: { value: 100, preemptionPolicy: 'PreemptLowerPriority' },
211
+ mode: _EDIT,
212
+ feature: true
213
+ });
214
+
215
+ await nextTick();
216
+
217
+ // The banner should be available in the template when conditions are met
218
+ expect(wrapper.vm.settingMissmatch).toBe(true);
219
+ expect(wrapper.vm.isEdit).toBe(true);
220
+ expect(wrapper.vm.feature).toBe(true);
221
+ });
222
+
223
+ it('should not show banner when not in edit mode', () => {
224
+ const value = {
225
+ priorityClass: { value: 200, preemptionPolicy: 'PreemptLowerPriority' },
226
+ podDisruptionBudget: { maxUnavailable: 1 }
227
+ };
228
+
229
+ wrapper = createWrapper({
230
+ value,
231
+ defaultPC: { value: 100, preemptionPolicy: 'PreemptLowerPriority' },
232
+ mode: _CREATE,
233
+ feature: true
234
+ });
235
+
236
+ expect(wrapper.vm.isEdit).toBe(false);
237
+ // Banner should not show because not in edit mode
238
+ });
239
+ });
240
+ });
@@ -15,6 +15,10 @@ export default {
15
15
  reference: {
16
16
  type: String,
17
17
  default: null,
18
+ },
19
+ getCustomDetailLink: {
20
+ type: Function,
21
+ default: null
18
22
  }
19
23
  },
20
24
  computed: {
@@ -23,6 +27,10 @@ export default {
23
27
  return get(this.row, this.reference);
24
28
  }
25
29
 
30
+ if (this.getCustomDetailLink) {
31
+ return this.getCustomDetailLink(this.row);
32
+ }
33
+
26
34
  return this.row?.detailLocation;
27
35
  },
28
36
 
@@ -0,0 +1,79 @@
1
+ <script>
2
+ import { MANAGEMENT } from '@shell/config/types';
3
+ import { STORE } from '@shell/store/store-types';
4
+ import { mapGetters } from 'vuex';
5
+
6
+ export default {
7
+ props: {
8
+ row: {
9
+ type: Object,
10
+ required: true
11
+ },
12
+ },
13
+ async fetch() {
14
+ if (this.row.isProjectScoped && this.currentCluster?.isLocal) {
15
+ const id = this.row.projectScopedClusterId;
16
+
17
+ if (id && !this.$store.getters[`${ STORE.MANAGEMENT }/byId`](MANAGEMENT.CLUSTER, id)) {
18
+ try {
19
+ await this.$store.dispatch(`${ STORE.MANAGEMENT }/find`, { type: MANAGEMENT.CLUSTER, id });
20
+ } catch (e) {
21
+ // Ignore error
22
+ }
23
+ }
24
+ }
25
+ },
26
+ computed: {
27
+ ...mapGetters(['currentCluster']),
28
+ originText() {
29
+ if (this.row.isProjectScoped) {
30
+ return this.t('secret.projectScoped.origin.source');
31
+ } else if (this.row.isProjectSecretCopy) {
32
+ return this.t('secret.projectScoped.origin.copy');
33
+ }
34
+
35
+ return '';
36
+ },
37
+ tooltip() {
38
+ if (this.row.isProjectScoped) {
39
+ const projectName = this.row.project?.nameDisplay || this.row.projectScopedProjectId;
40
+ const clusterName = this.row.projectCluster?.nameDisplay || this.row.projectScopedClusterId;
41
+
42
+ return this.t('secret.projectScoped.tooltip.source', { project: projectName, cluster: clusterName });
43
+ } else if (this.row.isProjectSecretCopy) {
44
+ const projectID = this.row.projectScopedProjectId;
45
+ const clusterId = this.currentCluster?.id;
46
+
47
+ // Try to fetch the project.
48
+ // Note: The management store might not have the project loaded if we haven't visited the cluster list or project list.
49
+ // However, if we are in the dashboard, we usually have projects loaded.
50
+ const project = this.$store.getters[`${ STORE.MANAGEMENT }/byId`](MANAGEMENT.PROJECT, `${ clusterId }/${ projectID }`);
51
+ const projectName = project?.nameDisplay || projectID;
52
+
53
+ return this.t('secret.projectScoped.tooltip.copy', { secret: this.row.nameDisplay, project: projectName });
54
+ }
55
+
56
+ return null;
57
+ }
58
+ }
59
+ };
60
+ </script>
61
+
62
+ <template>
63
+ <div
64
+ v-if="originText"
65
+ v-clean-tooltip="tooltip"
66
+ class="secret-origin"
67
+ >
68
+ {{ originText }}
69
+ </div>
70
+ <div v-else>
71
+
72
+ </div>
73
+ </template>
74
+
75
+ <style lang="scss" scoped>
76
+ .secret-origin {
77
+ white-space: nowrap;
78
+ }
79
+ </style>