@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
package/list/service.vue CHANGED
@@ -1,6 +1,6 @@
1
1
  <script>
2
2
  import PaginatedResourceTable from '@shell/components/PaginatedResourceTable';
3
- import { NODE } from '@shell/config/types';
3
+ import { fetchNodesForServiceTargets } from '@shell/models/service';
4
4
 
5
5
  export default {
6
6
  name: 'ListService',
@@ -27,13 +27,11 @@ export default {
27
27
  * of type PagTableFetchSecondaryResources
28
28
  */
29
29
  async fetchSecondaryResources(opts) {
30
- const inStore = this.$store.getters['currentStore']();
31
-
32
- if (this.$store.getters[`${ inStore }/schemaFor`](NODE)) {
33
- // fetch nodes before loading this page, as they may be referenced in the Target table column
34
- // shell/components/formatter/ServiceTargets.vue --> shell/components/formatter/Endpoints.vue --> Picks the first one that has a model's externalIp
35
- await this.$store.dispatch(`${ inStore }/findAll`, { type: NODE });
36
- }
30
+ // Nodes should be fetched because they may be referenced in the target column of a service list item.
31
+ await fetchNodesForServiceTargets({
32
+ $store: this.$store,
33
+ inStore: this.$store.getters['currentStore']()
34
+ });
37
35
  }
38
36
  }
39
37
  };
@@ -24,7 +24,7 @@ export default {
24
24
 
25
25
  mixins: [CreateEditView],
26
26
 
27
- emits: ['validationChanged', 'update:hasIpv6'],
27
+ emits: ['validationChanged', 'update:isIpv6', 'update:isDualStack'],
28
28
 
29
29
  props: {
30
30
  uuid: {
@@ -47,7 +47,12 @@ export default {
47
47
  default: false
48
48
  },
49
49
 
50
- hasIpv6: {
50
+ isIpv6: {
51
+ type: Boolean,
52
+ default: false
53
+ },
54
+
55
+ isDualStack: {
51
56
  type: Boolean,
52
57
  default: false
53
58
  },
@@ -386,10 +391,12 @@ export default {
386
391
  :zone="value.zone"
387
392
  :region="value.region"
388
393
  :machine-pools="machinePools"
389
- :has-ipv6="hasIpv6"
394
+ :is-ipv6="isIpv6"
395
+ :is-dual-stack="isDualStack"
390
396
  :disabled="disabled"
391
397
  :is-new="poolCreateMode"
392
- @update:has-ipv6="e=>$emit('update:hasIpv6', e)"
398
+ @update:is-ipv6="e=>$emit('update:isIpv6', e)"
399
+ @update:is-dual-stack="e=>$emit('update:isDualStack', e)"
393
400
  @validation-changed="e=>$emit('validationChanged',e)"
394
401
  />
395
402
 
@@ -9,6 +9,7 @@ import { LabeledInput } from '@components/Form/LabeledInput';
9
9
  import { Banner } from '@components/Banner';
10
10
  import { scrollToBottom } from '@shell/utils/scroll';
11
11
  import { _CREATE } from '@shell/config/query-params';
12
+ import { getSubnetDisplayName, getVpcDisplayName, isIpv4Network, isIpv6Network } from '@shell/utils/aws';
12
13
 
13
14
  export default {
14
15
  name: 'Ec2MachinePoolNetworking',
@@ -17,7 +18,7 @@ export default {
17
18
  Checkbox, LabeledSelect, LabeledInput, Banner
18
19
  },
19
20
 
20
- emits: ['update:enablePrimaryIpv6', 'update:ipv6AddressCount', 'update:ipv6AddressOnly', 'update:httpProtocolIpv6', 'update:vpcId', 'update:subnetId', 'update:hasIpv6', 'validationChanged'],
21
+ emits: ['update:enablePrimaryIpv6', 'update:ipv6AddressCount', 'update:ipv6AddressOnly', 'update:httpProtocolIpv6', 'update:vpcId', 'update:subnetId', 'update:isIpv6', 'update:isDualStack', 'validationChanged'],
21
22
 
22
23
  props: {
23
24
  mode: {
@@ -84,7 +85,12 @@ export default {
84
85
  default: ''
85
86
  },
86
87
 
87
- hasIpv6: {
88
+ isIpv6: {
89
+ type: Boolean,
90
+ default: false
91
+ },
92
+
93
+ isDualStack: {
88
94
  type: Boolean,
89
95
  default: false
90
96
  },
@@ -113,7 +119,7 @@ export default {
113
119
  this.enableIpv6 = !!vpcs.find((vpc) => vpc.VpcId === this.vpcId && vpc.Ipv6CidrBlockAssociationSet && !isEmpty(vpc.Ipv6CidrBlockAssociationSet));
114
120
  }
115
121
 
116
- if (this.isNew && this.somePoolHasIpv6OrDual) {
122
+ if (this.isNew && this.somePoolisIpv6OrDual) {
117
123
  this.enableIpv6 = true;
118
124
  }
119
125
  },
@@ -133,7 +139,7 @@ export default {
133
139
  if (this.isNew) {
134
140
  this.updateNetwork();
135
141
  }
136
- this.$emit('update:hasIpv6', neu);
142
+ this.$emit('update:isDualStack', neu);
137
143
 
138
144
  if (neu) {
139
145
  this.$emit('update:ipv6AddressCount', '1');
@@ -154,7 +160,12 @@ export default {
154
160
  },
155
161
 
156
162
  ipv6AddressOnly(neu) {
157
- this.$emit('update:hasIpv6', neu);
163
+ this.$emit('update:isIpv6', neu);
164
+ if (neu) {
165
+ this.$emit('update:isDualStack', false);
166
+ } else if (this.enableIpv6) {
167
+ this.$emit('update:isDualStack', true);
168
+ }
158
169
  },
159
170
 
160
171
  allValid: {
@@ -174,13 +185,13 @@ export default {
174
185
  const subnetsByVpc = {};
175
186
 
176
187
  for ( const obj of this.vpcInfo.Vpcs ) {
177
- const name = obj.Tags && obj.Tags?.length ? obj.Tags.find((t) => t.Key === 'Name')?.Value : null;
178
- const hasIpv6 = !!obj.Ipv6CidrBlockAssociationSet && !isEmpty(obj.Ipv6CidrBlockAssociationSet);
179
- const hasIpv4 = !!obj.CidrBlock;
188
+ const name = getVpcDisplayName(obj);
189
+ const hasIpv6 = isIpv6Network(obj);
190
+ const hasIpv4 = isIpv4Network(obj);
180
191
 
181
192
  vpcs.push({
182
- label: name || obj.VpcId,
183
- subLabel: name ? obj.VpcId : obj.CidrBlock,
193
+ label: name,
194
+ subLabel: obj.CidrBlock,
184
195
  isDefault: obj.IsDefault || false,
185
196
  kind: 'vpc',
186
197
  value: obj.VpcId,
@@ -197,8 +208,8 @@ export default {
197
208
  continue;
198
209
  }
199
210
 
200
- const hasIpv6 = !!obj.Ipv6CidrBlockAssociationSet && !isEmpty(obj.Ipv6CidrBlockAssociationSet);
201
- const hasIpv4 = !!obj.CidrBlock;
211
+ const hasIpv6 = isIpv6Network(obj);
212
+ const hasIpv4 = isIpv4Network(obj);
202
213
 
203
214
  if (this.enableIpv6 !== hasIpv6) {
204
215
  continue;
@@ -211,11 +222,11 @@ export default {
211
222
  subnetsByVpc[obj.VpcId] = entry;
212
223
  }
213
224
 
214
- const name = obj.Tags && obj.Tags?.length ? obj.Tags.find((t) => t.Key === 'Name')?.Value : null;
225
+ const name = getSubnetDisplayName(obj);
215
226
 
216
227
  entry.push({
217
- label: name || obj.SubnetId,
218
- subLabel: name ? obj.SubnetId : obj.CidrBlock,
228
+ label: name,
229
+ subLabel: obj.CidrBlock,
219
230
  kind: 'subnet',
220
231
  isDefault: obj.DefaultForAz || false,
221
232
  value: obj.SubnetId,
@@ -278,18 +289,18 @@ export default {
278
289
  return opt && opt.hasIpv6 && !opt.hasIpv4;
279
290
  },
280
291
 
281
- somePoolHasIpv6OrDual() {
282
- return !!this.machinePools.find((p) => p.hasIpv6);
292
+ somePoolisIpv6OrDual() {
293
+ return !!this.machinePools.find((p) => p.isIpv6 || p.isDualStack);
283
294
  },
284
295
 
285
296
  showIpv6Options() {
286
- return this.mode === _CREATE || (this.isNew && this.somePoolHasIpv6OrDual) || this.enableIpv6;
297
+ return this.mode === _CREATE || (this.isNew && this.somePoolisIpv6OrDual) || this.enableIpv6;
287
298
  },
288
299
 
289
300
  poolsInvalid() {
290
- const somePoolHasIpv4 = !!this.machinePools.find((p) => !p.hasIpv6);
301
+ const somePoolHasIpv4 = !!this.machinePools.find((p) => !p.isIpv6 && !p.isDualStack);
291
302
 
292
- return this.somePoolHasIpv6OrDual && somePoolHasIpv4;
303
+ return this.somePoolisIpv6OrDual && somePoolHasIpv4;
293
304
  },
294
305
 
295
306
  addressCountInvalid() {
@@ -398,9 +409,14 @@ export default {
398
409
  </template>
399
410
  </LabeledSelect>
400
411
  </div>
412
+ </div>
413
+ <div
414
+ v-if="enableIpv6"
415
+ class="row mb-10 ipv6-row"
416
+ >
401
417
  <div
402
418
  v-if="enableIpv6"
403
- class="col span-3"
419
+ class="col span-2"
404
420
  >
405
421
  <Checkbox
406
422
  :disabled="!isNew || !dualStackSelected"
@@ -411,17 +427,12 @@ export default {
411
427
  @update:value="e=>$emit('update:ipv6AddressOnly', e)"
412
428
  />
413
429
  </div>
414
- </div>
415
- <div
416
- v-if="enableIpv6"
417
- class="row mb-10 ipv6-row"
418
- >
419
- <div class="col span-6">
430
+ <div class="col span-4">
420
431
  <Checkbox
421
432
  :value="httpProtocolIpv6==='enabled'"
422
433
  :disabled="!isNew"
423
434
  :label="t('cluster.machineConfig.amazonEc2.httpProtocolIpv6.label')"
424
- data-testid="amazonEc2__enableIpv6"
435
+ data-testid="amazonEc2__httpProtocolIpv6"
425
436
  :mode="mode"
426
437
  @update:value="e=>$emit('update:httpProtocolIpv6', e ? 'enabled' : 'disabled')"
427
438
  />
@@ -431,7 +442,7 @@ export default {
431
442
  v-if="enableIpv6"
432
443
  class="row mb-10 ipv6-row"
433
444
  >
434
- <div class="col span-3">
445
+ <div class="col span-6">
435
446
  <LabeledInput
436
447
  :disabled="!isNew"
437
448
  min="1"
@@ -443,7 +454,12 @@ export default {
443
454
  @update:value="e=>$emit('update:ipv6AddressCount', e)"
444
455
  />
445
456
  </div>
446
- <div class="col span-9">
457
+ </div>
458
+ <div
459
+ v-if="enableIpv6"
460
+ class="row mb-10 ipv6-row"
461
+ >
462
+ <div class="col span-6">
447
463
  <Checkbox
448
464
  :disabled="!isNew"
449
465
  :value="enablePrimaryIpv6"
@@ -82,9 +82,9 @@ describe('component: EC2Networking', () => {
82
82
  });
83
83
 
84
84
  it.each([
85
- [[{ hasIpv6: true }, { hasIpv6: false }], true],
86
- [[{ hasIpv6: false }, { hasIpv6: false }], false],
87
- [[{ hasIpv6: true }, { hasIpv6: true }], false],
85
+ [[{ isIpv6: true }, { isIpv6: false }], true],
86
+ [[{ isIpv6: false }, { isIpv6: false }], false],
87
+ [[{ isIpv6: true }, { isIpv6: true }], false],
88
88
  ])('should show an error banner if pools do not either all have ipv6 or all have ipv4', (pools, shouldShowError) => {
89
89
  const wrapper = shallowMount(EC2Networking, { ...defaultCreateSetup, propsData: { ...defaultCreateSetup.propsData, machinePools: pools } });
90
90
  const ipv6Warning = wrapper.findComponent('[data-testid="amazonEc2__ipv6Warning"]');
@@ -97,7 +97,7 @@ describe('component: EC2Networking', () => {
97
97
  ...defaultCreateSetup,
98
98
  propsData: {
99
99
  ...defaultCreateSetup.propsData,
100
- machinePools: [{ hasIpv6: true }],
100
+ machinePools: [{ isIpv6: true }],
101
101
  },
102
102
  });
103
103
 
@@ -112,7 +112,7 @@ describe('component: EC2Networking', () => {
112
112
  ...defaultCreateSetup,
113
113
  propsData: {
114
114
  ...defaultCreateSetup.propsData,
115
- machinePools: [{ hasIpv6: false }],
115
+ machinePools: [{ isIpv6: false }],
116
116
  },
117
117
  });
118
118
 
@@ -127,7 +127,7 @@ describe('component: EC2Networking', () => {
127
127
  ...defaultCreateSetup,
128
128
  propsData: {
129
129
  ...defaultCreateSetup.propsData,
130
- machinePools: [{ hasIpv6: true }, { hasIpv6: false }],
130
+ machinePools: [{ isIpv6: true }, { isIpv6: false }],
131
131
  },
132
132
  });
133
133
 
@@ -139,7 +139,7 @@ describe('component: EC2Networking', () => {
139
139
  ...defaultCreateSetup,
140
140
  propsData: {
141
141
  ...defaultCreateSetup.propsData,
142
- machinePools: [{ hasIpv6: true }, { hasIpv6: true }],
142
+ machinePools: [{ isIpv6: true }, { isIpv6: true }],
143
143
  },
144
144
  });
145
145
 
@@ -25,10 +25,6 @@ export const vpcInfo = {
25
25
  {
26
26
  Key: 'displayName',
27
27
  Value: 'test'
28
- },
29
- {
30
- Key: 'Name',
31
- Value: 'test-eks-vpc-VPC'
32
28
  }
33
29
  ]
34
30
  },
@@ -107,11 +103,6 @@ export const subnetInfo = {
107
103
  OwnerId: '1234',
108
104
  AssignIpv6AddressOnCreation: false,
109
105
  Tags: [
110
- {
111
- Key: 'Name',
112
- Value: 'aws-controltower-PrivateSubnet2A'
113
- },
114
-
115
106
  {
116
107
  Key: 'Network',
117
108
  Value: 'Private'
@@ -16,7 +16,7 @@ export default {
16
16
 
17
17
  mixins: [CreateEditView],
18
18
 
19
- emits: ['update:hasIpv6'],
19
+ emits: ['update:isDualStack'],
20
20
 
21
21
  props: {
22
22
  credentialId: {
@@ -29,7 +29,7 @@ export default {
29
29
  default: false
30
30
  },
31
31
 
32
- hasIpv6: {
32
+ isDualStack: {
33
33
  type: Boolean,
34
34
  default: false
35
35
  },
@@ -119,7 +119,7 @@ export default {
119
119
  'value.image': 'updateUsername',
120
120
 
121
121
  'value.ipv6'(neu) {
122
- this.$emit('update:hasIpv6', neu);
122
+ this.$emit('update:isDualStack', neu);
123
123
  }
124
124
  },
125
125
 
@@ -236,12 +236,12 @@ describe('class Chart', () => {
236
236
 
237
237
  expect(result.footerItems).toHaveLength(3);
238
238
 
239
- const categoryItem = result.footerItems.find((i) => i.icon === 'icon-category-alt');
239
+ const categoryItem = result.footerItems.find((i) => i.icon === 'category-alt');
240
240
 
241
241
  expect(categoryItem).toBeDefined();
242
242
  expect(categoryItem?.labels).toContain('database');
243
243
 
244
- const tagItem = result.footerItems.find((i) => i.icon === 'icon-tag-alt');
244
+ const tagItem = result.footerItems.find((i) => i.icon === 'tag-alt');
245
245
 
246
246
  expect(tagItem).toBeDefined();
247
247
  expect(tagItem?.labels).toStrictEqual(expect.arrayContaining(['linux', 'experimentl']));
@@ -187,6 +187,17 @@ describe('class Namespace', () => {
187
187
  it.todo('should set the resourceQuota as reactive Vue property');
188
188
  it.todo('should reset project with cleanForNew');
189
189
 
190
+ describe('hideDetailLocation', () => {
191
+ it('should not throw when currentProduct is undefined', () => {
192
+ const namespace = new Namespace({});
193
+
194
+ jest.spyOn(namespace, '$rootGetters', 'get').mockReturnValue({ currentProduct: undefined });
195
+
196
+ expect(() => namespace.hideDetailLocation).not.toThrow();
197
+ expect(namespace.hideDetailLocation).toBe(true);
198
+ });
199
+ });
200
+
190
201
  describe('glance', () => {
191
202
  it('should return projectGlance instead of namespace when namespace is in a project', () => {
192
203
  const t = jest.fn((key) => key);
@@ -275,4 +275,100 @@ describe('class ProvCluster', () => {
275
275
  }
276
276
  );
277
277
  });
278
+
279
+ describe('supportsWindows', () => {
280
+ const testCases = [
281
+ {
282
+ description: 'should return false for k3s',
283
+ clusterData: { isK3s: true },
284
+ expected: false
285
+ },
286
+ {
287
+ description: 'should return false for imported k3s',
288
+ clusterData: { isImportedK3s: true },
289
+ expected: false
290
+ },
291
+ {
292
+ description: 'should return windowsPreferedCluster for rke1',
293
+ clusterData: { isRke1: true, mgmt: { spec: { windowsPreferedCluster: true } } },
294
+ expected: true
295
+ },
296
+ {
297
+ description: 'should return false for rke1 if windowsPreferedCluster is false/missing',
298
+ clusterData: { isRke1: true, mgmt: { spec: { windowsPreferedCluster: false } } },
299
+ expected: false
300
+ },
301
+ {
302
+ description: 'should return false if not rke2 (and not rke1 or k3s)',
303
+ clusterData: { isRke2: false },
304
+ expected: false
305
+ },
306
+ {
307
+ description: 'should return false if kubernetesVersion is missing',
308
+ clusterData: { isRke2: true, kubernetesVersion: undefined },
309
+ expected: false
310
+ },
311
+ {
312
+ description: 'should return false if kubernetesVersion is less than v1.21.0',
313
+ clusterData: { isRke2: true, kubernetesVersion: 'v1.20.9' },
314
+ expected: false
315
+ },
316
+ {
317
+ description: 'should return false if cni is not calico or flannel',
318
+ clusterData: {
319
+ isRke2: true, kubernetesVersion: 'v1.34.0', spec: { rkeConfig: { machineGlobalConfig: { cni: 'cilium' } } }
320
+ },
321
+ expected: false
322
+ },
323
+ {
324
+ description: 'should return true if cni is calico',
325
+ clusterData: {
326
+ isRke2: true, kubernetesVersion: 'v1.34.0', spec: { rkeConfig: { machineGlobalConfig: { cni: 'calico' } } }
327
+ },
328
+ expected: true
329
+ },
330
+ {
331
+ description: 'should return false if cni is flannel and kubernetesVersion is less than v1.29.2 (e.g. v1.29.1)',
332
+ clusterData: {
333
+ isRke2: true, kubernetesVersion: 'v1.29.1', spec: { rkeConfig: { machineGlobalConfig: { cni: 'flannel' } } }
334
+ },
335
+ expected: false
336
+ },
337
+ {
338
+ description: 'should return true if cni is flannel and kubernetesVersion is exactly v1.29.2',
339
+ clusterData: {
340
+ isRke2: true, kubernetesVersion: 'v1.29.2', spec: { rkeConfig: { machineGlobalConfig: { cni: 'flannel' } } }
341
+ },
342
+ expected: true
343
+ },
344
+ {
345
+ description: 'should return true if cni is flannel and kubernetesVersion is >= v1.29.2 (e.g. v1.35.0)',
346
+ clusterData: {
347
+ isRke2: true, kubernetesVersion: 'v1.35.0', spec: { rkeConfig: { machineGlobalConfig: { cni: 'flannel' } } }
348
+ },
349
+ expected: true
350
+ },
351
+ {
352
+ description: 'should return true if cni is empty/undefined',
353
+ clusterData: {
354
+ isRke2: true, kubernetesVersion: 'v1.34.0', spec: { rkeConfig: { machineGlobalConfig: {} } }
355
+ },
356
+ expected: true
357
+ },
358
+ ];
359
+
360
+ it.each(testCases)('$description', ({ clusterData, expected }) => {
361
+ const cluster = new ProvCluster({ spec: clusterData.spec });
362
+
363
+ jest.spyOn(cluster, 'mgmt', 'get').mockReturnValue(clusterData.mgmt);
364
+ jest.spyOn(cluster, 'isK3s', 'get').mockReturnValue(clusterData.isK3s || false);
365
+ jest.spyOn(cluster, 'isImportedK3s', 'get').mockReturnValue(clusterData.isImportedK3s || false);
366
+ jest.spyOn(cluster, 'isRke1', 'get').mockReturnValue(clusterData.isRke1 || false);
367
+ jest.spyOn(cluster, 'isRke2', 'get').mockReturnValue(clusterData.isRke2 || false);
368
+ jest.spyOn(cluster, 'kubernetesVersion', 'get').mockReturnValue(clusterData.kubernetesVersion);
369
+
370
+ expect(cluster.supportsWindows).toBe(expected);
371
+ jest.clearAllMocks();
372
+ });
373
+ });
278
374
  });
@@ -274,6 +274,7 @@ describe('class: Workload', () => {
274
274
  expect(card).not.toBeNull();
275
275
  expect(card.props.title).toBe('component.resource.detail.card.podsCard.title');
276
276
  expect(card.props.showScaling).toBe(true);
277
+ expect(card.props.noResourcesMessage).toBe('component.resource.detail.card.podsCard.noPods');
277
278
  });
278
279
 
279
280
  it('should return card for DaemonSet type without scaling', () => {
@@ -312,7 +313,7 @@ describe('class: Workload', () => {
312
313
  expect(card).toBeNull();
313
314
  });
314
315
 
315
- it('should return null when pods array is empty', () => {
316
+ it('should return card when pods array is empty (scaled to 0)', () => {
316
317
  const workload = new Workload({
317
318
  type: WORKLOAD_TYPES.DEPLOYMENT,
318
319
  metadata: { name: 'test', namespace: 'default' },
@@ -324,6 +325,46 @@ describe('class: Workload', () => {
324
325
  });
325
326
 
326
327
  Object.defineProperty(workload, 'pods', { get: () => [] });
328
+ Object.defineProperty(workload, 'canUpdate', { get: () => true });
329
+
330
+ const card = workload.podsCard;
331
+
332
+ expect(card).not.toBeNull();
333
+ expect(card.props.resources).toStrictEqual([]);
334
+ expect(card.props.noResourcesMessage).toBe('component.resource.detail.card.podsCard.noPods');
335
+ });
336
+
337
+ it('should return null for non-scalable type with empty pods', () => {
338
+ const workload = new Workload({
339
+ type: WORKLOAD_TYPES.DAEMON_SET,
340
+ metadata: { name: 'test', namespace: 'default' },
341
+ spec: {}
342
+ }, {
343
+ getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
344
+ dispatch: jest.fn(),
345
+ rootGetters: { 'i18n/t': (key: string) => key },
346
+ });
347
+
348
+ Object.defineProperty(workload, 'pods', { get: () => [] });
349
+ Object.defineProperty(workload, 'canUpdate', { get: () => true });
350
+
351
+ const card = workload.podsCard;
352
+
353
+ expect(card).toBeNull();
354
+ });
355
+
356
+ it('should return null when pods is undefined', () => {
357
+ const workload = new Workload({
358
+ type: WORKLOAD_TYPES.DEPLOYMENT,
359
+ metadata: { name: 'test', namespace: 'default' },
360
+ spec: {}
361
+ }, {
362
+ getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
363
+ dispatch: jest.fn(),
364
+ rootGetters: { 'i18n/t': (key: string) => key },
365
+ });
366
+
367
+ Object.defineProperty(workload, 'pods', { get: () => undefined });
327
368
 
328
369
  const card = workload.podsCard;
329
370
 
@@ -1,7 +1,7 @@
1
1
  import { parse } from '@shell/utils/url';
2
2
  import { CATALOG } from '@shell/config/labels-annotations';
3
3
  import { insertAt } from '@shell/utils/array';
4
- import { CATALOG as CATALOG_TYPE } from '@shell/config/types';
4
+ import { CLUSTER_REPO_APPCO_AUTH_GENERATE_NAME, CATALOG as CATALOG_TYPE } from '@shell/config/types';
5
5
  import { colorForState, stateDisplay } from '@shell/plugins/dashboard-store/resource-class';
6
6
 
7
7
  import SteveModel from '@shell/plugins/steve/steve-class';
@@ -143,14 +143,27 @@ export default class ClusterRepo extends SteveModel {
143
143
  return this.metadata?.state?.name === 'active';
144
144
  }
145
145
 
146
+ get isSuseAppCollectionFromUI() {
147
+ return this.metadata?.annotations?.[CATALOG.SUSE_APP_COLLECTION];
148
+ }
149
+
150
+ get isSuseAppCollection() {
151
+ // Check annotation set by the UI or if the URL points to the SUSE App Collection registry
152
+ return this.isSuseAppCollectionFromUI || this.spec?.url?.startsWith('oci://dp.apps.rancher.io/charts');
153
+ }
154
+
146
155
  get typeDisplay() {
156
+ if (this.isSuseAppCollectionFromUI) {
157
+ return 'SUSE AppCo';
158
+ }
147
159
  if ( this.spec.gitRepo ) {
148
160
  return 'git';
149
- } else if ( this.spec.url ) {
161
+ }
162
+ if ( this.spec.url ) {
150
163
  return this.isOciType ? 'oci' : 'http';
151
- } else {
152
- return '?';
153
164
  }
165
+
166
+ return '?';
154
167
  }
155
168
 
156
169
  get nameDisplay() {
@@ -220,4 +233,17 @@ export default class ClusterRepo extends SteveModel {
220
233
  });
221
234
  }, `catalog operation fetch`, timeout, interval);
222
235
  }
236
+
237
+ async save() {
238
+ // Add annotation only if the type is SUSE_APP_COLLECTION
239
+ if (this.spec.clientSecret?.name?.search(CLUSTER_REPO_APPCO_AUTH_GENERATE_NAME) === 0) {
240
+ if (!this.metadata.annotations) {
241
+ this.metadata.annotations = {};
242
+ }
243
+ this.metadata.annotations[CATALOG.SUSE_APP_COLLECTION] = 'true';
244
+ }
245
+
246
+ // Call the parent save method
247
+ return super.save();
248
+ }
223
249
  }
package/models/chart.js CHANGED
@@ -164,7 +164,7 @@ export default class Chart extends SteveModel {
164
164
  const footerItems = [
165
165
  {
166
166
  type: REPO,
167
- icon: 'icon-repository-alt',
167
+ icon: 'repository-alt',
168
168
  iconTooltip: { key: 'tableHeaders.repoName' },
169
169
  labels: [this.repoNameDisplay],
170
170
  labelTooltip: this.t('catalog.charts.findSimilar.message', { type: this.t('catalog.charts.findSimilar.types.repo') }, true)
@@ -174,7 +174,7 @@ export default class Chart extends SteveModel {
174
174
  if (this.categories.length) {
175
175
  footerItems.push( {
176
176
  type: CATEGORY,
177
- icon: 'icon-category-alt',
177
+ icon: 'category-alt',
178
178
  iconTooltip: { key: 'generic.category' },
179
179
  labels: this.categories,
180
180
  labelTooltip: this.t('catalog.charts.findSimilar.message', { type: this.t('catalog.charts.findSimilar.types.category') }, true)
@@ -184,7 +184,7 @@ export default class Chart extends SteveModel {
184
184
  if (this.tags.length) {
185
185
  footerItems.push({
186
186
  type: TAG,
187
- icon: 'icon-tag-alt',
187
+ icon: 'tag-alt',
188
188
  iconTooltip: { key: 'generic.tags' },
189
189
  labels: this.tags,
190
190
  labelTooltip: this.t('catalog.charts.findSimilar.message', { type: this.t('catalog.charts.findSimilar.types.tag') }, true)