@rancher/shell 3.0.8-rc.1 → 3.0.8-rc.3

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 (129) hide show
  1. package/assets/brand/suse/dark/rancher-logo.svg +64 -1
  2. package/assets/brand/suse/rancher-logo.svg +1 -1
  3. package/assets/styles/global/_cards.scss +0 -3
  4. package/assets/styles/global/_layout.scss +21 -35
  5. package/assets/styles/themes/_modern.scss +9 -1
  6. package/assets/styles/themes/_suse.scss +81 -24
  7. package/assets/translations/en-us.yaml +79 -9
  8. package/components/AutoscalerCard.vue +113 -0
  9. package/components/AutoscalerTab.vue +94 -0
  10. package/components/ClusterIconMenu.vue +1 -1
  11. package/components/ClusterProviderIcon.vue +1 -1
  12. package/components/EmberPage.vue +1 -1
  13. package/components/IconOrSvg.vue +2 -2
  14. package/components/PopoverCard.vue +192 -0
  15. package/components/Resource/Detail/CopyToClipboard.vue +1 -1
  16. package/components/Resource/Detail/FetchLoader/composables.ts +18 -4
  17. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +1 -1
  18. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +4 -0
  19. package/components/Resource/Detail/ResourcePopover/ResourcePopoverCard.vue +2 -19
  20. package/components/Resource/Detail/ResourcePopover/__tests__/ResourcePopoverCard.test.ts +0 -29
  21. package/components/Resource/Detail/ResourcePopover/__tests__/index.test.ts +132 -150
  22. package/components/Resource/Detail/ResourcePopover/index.vue +54 -159
  23. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +0 -2
  24. package/components/Resource/Detail/TitleBar/index.vue +10 -6
  25. package/components/ResourceDetail/Masthead/latest.vue +29 -0
  26. package/components/ResourceDetail/index.vue +3 -0
  27. package/components/ResourceList/Masthead.vue +1 -1
  28. package/components/SortableTable/index.vue +1 -0
  29. package/components/{nav/WindowManager → Window}/ContainerLogs.vue +1 -1
  30. package/components/{nav/WindowManager → Window}/ContainerLogsActions.vue +1 -0
  31. package/components/{nav/WindowManager → Window}/__tests__/ContainerLogs.test.ts +1 -1
  32. package/components/{nav/WindowManager → Window}/__tests__/ContainerShell.test.ts +2 -2
  33. package/components/__tests__/AutoscalerCard.test.ts +154 -0
  34. package/components/__tests__/AutoscalerTab.test.ts +125 -0
  35. package/components/__tests__/PopoverCard.test.ts +204 -0
  36. package/components/formatter/Autoscaler.vue +97 -0
  37. package/components/formatter/InternalExternalIP.vue +195 -24
  38. package/components/formatter/__tests__/Autoscaler.test.ts +156 -0
  39. package/components/formatter/__tests__/InternalExternalIP.test.ts +133 -0
  40. package/components/nav/Group.vue +12 -3
  41. package/components/nav/Header.vue +33 -13
  42. package/components/nav/TopLevelMenu.vue +2 -2
  43. package/components/{DraggableZone.vue → nav/WindowManager/PinArea.vue} +47 -80
  44. package/components/nav/WindowManager/composables/useComponentsMount.ts +70 -0
  45. package/components/nav/WindowManager/composables/useDimensionsHandler.ts +105 -0
  46. package/components/nav/WindowManager/composables/useDragHandler.ts +99 -0
  47. package/components/nav/WindowManager/composables/usePanelHandler.ts +72 -0
  48. package/components/nav/WindowManager/composables/usePanelsHandler.ts +14 -0
  49. package/components/nav/WindowManager/composables/useResizeHandler.ts +167 -0
  50. package/components/nav/WindowManager/composables/useTabsHandler.ts +51 -0
  51. package/components/nav/WindowManager/constants.ts +23 -0
  52. package/components/nav/WindowManager/index.vue +61 -575
  53. package/components/nav/WindowManager/panels/HorizontalPanel.vue +265 -0
  54. package/components/nav/WindowManager/panels/TabBodyContainer.vue +39 -0
  55. package/components/nav/WindowManager/panels/VerticalPanel.vue +308 -0
  56. package/components/templates/default.vue +4 -40
  57. package/components/templates/home.vue +31 -5
  58. package/composables/useInterval.ts +15 -0
  59. package/config/labels-annotations.js +8 -1
  60. package/config/product/manager.js +20 -9
  61. package/config/router/routes.js +4 -0
  62. package/config/settings.ts +2 -1
  63. package/config/store.js +4 -2
  64. package/config/table-headers.js +8 -0
  65. package/config/types.js +2 -0
  66. package/core/types-provisioning.ts +3 -0
  67. package/detail/pod.vue +1 -0
  68. package/detail/provisioning.cattle.io.cluster.vue +12 -1
  69. package/directives/ui-context.ts +103 -0
  70. package/edit/auth/github.vue +5 -0
  71. package/edit/cloudcredential.vue +1 -1
  72. package/edit/fleet.cattle.io.gitrepo.vue +0 -10
  73. package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +32 -5
  74. package/edit/provisioning.cattle.io.cluster/__tests__/CustomCommand.test.ts +35 -0
  75. package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +132 -0
  76. package/edit/provisioning.cattle.io.cluster/index.vue +18 -12
  77. package/edit/provisioning.cattle.io.cluster/rke2.vue +39 -8
  78. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +107 -5
  79. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +90 -3
  80. package/initialize/install-directives.js +2 -0
  81. package/initialize/install-plugins.js +3 -1
  82. package/list/provisioning.cattle.io.cluster.vue +15 -2
  83. package/machine-config/amazonec2.vue +36 -135
  84. package/machine-config/components/EC2Networking.vue +474 -0
  85. package/machine-config/components/__tests__/EC2Networking.test.ts +94 -0
  86. package/machine-config/components/__tests__/utils/vpcSubnetMockData.js +294 -0
  87. package/machine-config/digitalocean.vue +11 -0
  88. package/models/cluster/node.js +13 -6
  89. package/models/cluster.x-k8s.io.machine.js +10 -20
  90. package/models/cluster.x-k8s.io.machinedeployment.js +5 -1
  91. package/models/management.cattle.io.kontainerdriver.js +1 -0
  92. package/models/provisioning.cattle.io.cluster.js +223 -2
  93. package/package.json +2 -2
  94. package/pages/c/_cluster/apps/charts/install.vue +1 -1
  95. package/pages/c/_cluster/manager/hostedprovider/index.vue +209 -0
  96. package/plugins/dynamic-content.js +13 -0
  97. package/rancher-components/Form/Checkbox/Checkbox.vue +1 -1
  98. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +8 -0
  99. package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +5 -1
  100. package/store/features.js +1 -0
  101. package/store/notifications.ts +32 -1
  102. package/store/plugins.js +7 -3
  103. package/store/prefs.js +1 -0
  104. package/store/ui-context.ts +86 -0
  105. package/store/wm.ts +244 -0
  106. package/types/notifications/index.ts +24 -3
  107. package/types/shell/index.d.ts +26 -1
  108. package/types/window-manager.ts +22 -0
  109. package/utils/__tests__/object.test.ts +19 -0
  110. package/utils/autoscaler-utils.ts +7 -0
  111. package/utils/dynamic-content/__tests__/announcement.test.ts +498 -0
  112. package/utils/dynamic-content/announcement.ts +112 -0
  113. package/utils/dynamic-content/example.json +40 -0
  114. package/utils/dynamic-content/index.ts +6 -2
  115. package/utils/dynamic-content/new-release.ts +1 -1
  116. package/utils/dynamic-content/notification-handler.ts +48 -0
  117. package/utils/dynamic-content/types.d.ts +33 -1
  118. package/utils/dynamic-importer.js +2 -2
  119. package/utils/object.js +20 -2
  120. package/utils/scroll.js +7 -0
  121. package/utils/settings.ts +15 -0
  122. package/utils/validators/machine-pool.ts +13 -3
  123. package/assets/images/icons/document.svg +0 -3
  124. package/store/wm.js +0 -95
  125. /package/components/{nav/WindowManager → Window}/ChartReadme.vue +0 -0
  126. /package/components/{nav/WindowManager → Window}/ContainerShell.vue +0 -0
  127. /package/components/{nav/WindowManager → Window}/KubectlShell.vue +0 -0
  128. /package/components/{nav/WindowManager → Window}/MachineSsh.vue +0 -0
  129. /package/components/{nav/WindowManager → Window}/Window.vue +0 -0
@@ -0,0 +1,474 @@
1
+ <script>
2
+
3
+ import { Checkbox } from '@components/Form/Checkbox';
4
+ import LabeledSelect from '@shell/components/form/LabeledSelect';
5
+ import { sortBy } from '@shell/utils/sort';
6
+ import { addObject, addObjects, findBy } from '@shell/utils/array';
7
+ import { isEmpty } from '@shell/utils/object';
8
+ import { LabeledInput } from '@components/Form/LabeledInput';
9
+ import { Banner } from '@components/Banner';
10
+ import { scrollToBottom } from '@shell/utils/scroll';
11
+ import { _CREATE } from '@shell/config/query-params';
12
+
13
+ export default {
14
+ name: 'Ec2MachinePoolNetworking',
15
+
16
+ components: {
17
+ Checkbox, LabeledSelect, LabeledInput, Banner
18
+ },
19
+
20
+ emits: ['update:enablePrimaryIpv6', 'update:ipv6AddressCount', 'update:ipv6AddressOnly', 'update:httpProtocolIpv6', 'update:vpcId', 'update:subnetId', 'update:hasIpv6', 'validationChanged'],
21
+
22
+ props: {
23
+ mode: {
24
+ type: String,
25
+ default: _CREATE
26
+ },
27
+
28
+ disabled: {
29
+ type: Boolean,
30
+ default: true
31
+ },
32
+
33
+ region: {
34
+ type: String,
35
+ default: ''
36
+ },
37
+
38
+ zone: {
39
+ type: String,
40
+ default: ''
41
+ },
42
+
43
+ vpcInfo: {
44
+ type: Object,
45
+ default: () => {
46
+ return {};
47
+ }
48
+ },
49
+
50
+ subnetInfo: {
51
+ type: Object,
52
+ default: () => {
53
+ return {};
54
+ }
55
+ },
56
+
57
+ enablePrimaryIpv6: {
58
+ type: Boolean,
59
+ default: false
60
+ },
61
+
62
+ ipv6AddressCount: {
63
+ type: String,
64
+ default: '0'
65
+ },
66
+
67
+ ipv6AddressOnly: {
68
+ type: Boolean,
69
+ default: false
70
+ },
71
+
72
+ httpProtocolIpv6: {
73
+ type: String,
74
+ default: 'disabled'
75
+ },
76
+
77
+ vpcId: {
78
+ type: String,
79
+ default: ''
80
+ },
81
+
82
+ subnetId: {
83
+ type: String,
84
+ default: ''
85
+ },
86
+
87
+ hasIpv6: {
88
+ type: Boolean,
89
+ default: false
90
+ },
91
+
92
+ machinePools: {
93
+ type: Array,
94
+ default: () => []
95
+ }
96
+ },
97
+
98
+ created() {
99
+ const subnets = this.subnetInfo?.Subnets || [];
100
+ const vpcs = this.vpcInfo?.Vpcs || [];
101
+
102
+ this.selectedNetwork = this.subnetId || this.vpcId;
103
+ if (this.subnetId) {
104
+ const subnetObj = subnets.find((sn) => sn.SubnetId === this.subnetId);
105
+
106
+ this.enableIpv6 = !!subnetObj?.Ipv6CidrBlockAssociationSet && !isEmpty(subnetObj?.Ipv6CidrBlockAssociationSet);
107
+ } else if (this.vpcId) {
108
+ this.enableIpv6 = !!vpcs.find((vpc) => vpc.VpcId === this.vpcId && vpc.Ipv6CidrBlockAssociationSet && !isEmpty(vpc.Ipv6CidrBlockAssociationSet));
109
+ }
110
+ },
111
+
112
+ data() {
113
+ return { selectedNetwork: {}, enableIpv6: false };
114
+ },
115
+
116
+ watch: {
117
+ region() {
118
+ if (this.isCreate) {
119
+ this.updateNetwork();
120
+ }
121
+ },
122
+
123
+ enableIpv6(neu) {
124
+ if (this.isCreate) {
125
+ this.updateNetwork();
126
+ }
127
+ this.$emit('update:hasIpv6', neu);
128
+
129
+ if (neu) {
130
+ this.$emit('update:ipv6AddressCount', '1');
131
+ } else {
132
+ this.$emit('update:ipv6AddressCount', '0');
133
+ this.$emit('update:ipv6AddressOnly', false);
134
+ this.$emit('update:enablePrimaryIpv6', false);
135
+ this.$emit('update:httpProtocolIpv6', 'disabled');
136
+ }
137
+ },
138
+
139
+ ipv6Selected(neu) {
140
+ if (neu) {
141
+ this.$emit('update:ipv6AddressOnly', neu);
142
+ } else if (!this.dualStackSelected) {
143
+ this.$emit('update:ipv6AddressOnly', false);
144
+ }
145
+ },
146
+
147
+ allValid(neu) {
148
+ this.$emit('validationChanged', neu);
149
+ }
150
+ },
151
+
152
+ computed: {
153
+ isCreate() {
154
+ return this.mode === _CREATE;
155
+ },
156
+
157
+ allNetworkOptions() {
158
+ if ( !this.vpcInfo || !this.subnetInfo ) {
159
+ return [];
160
+ }
161
+ let vpcs = [];
162
+ const subnetsByVpc = {};
163
+
164
+ for ( const obj of this.vpcInfo.Vpcs ) {
165
+ const name = obj.Tags && obj.Tags?.length ? obj.Tags.find((t) => t.Key === 'Name')?.Value : null;
166
+ const hasIpv6 = !!obj.Ipv6CidrBlockAssociationSet && !isEmpty(obj.Ipv6CidrBlockAssociationSet);
167
+ const hasIpv4 = !!obj.CidrBlock;
168
+
169
+ vpcs.push({
170
+ label: name || obj.VpcId,
171
+ subLabel: name ? obj.VpcId : obj.CidrBlock,
172
+ isDefault: obj.IsDefault || false,
173
+ kind: 'vpc',
174
+ value: obj.VpcId,
175
+ disabled: this.enableIpv6 !== hasIpv6,
176
+ hasIpv6,
177
+ hasIpv4
178
+ });
179
+ }
180
+
181
+ vpcs = sortBy(vpcs, ['isDefault:desc', 'label']);
182
+
183
+ for ( const obj of this.subnetInfo.Subnets ) {
184
+ if ( obj.AvailabilityZone !== `${ this.region }${ this.zone }` ) {
185
+ continue;
186
+ }
187
+
188
+ const hasIpv6 = !!obj.Ipv6CidrBlockAssociationSet && !isEmpty(obj.Ipv6CidrBlockAssociationSet);
189
+ const hasIpv4 = !!obj.CidrBlock;
190
+
191
+ if (this.enableIpv6 !== hasIpv6) {
192
+ continue;
193
+ }
194
+
195
+ let entry = subnetsByVpc[obj.VpcId];
196
+
197
+ if ( !entry ) {
198
+ entry = [];
199
+ subnetsByVpc[obj.VpcId] = entry;
200
+ }
201
+
202
+ const name = obj.Tags && obj.Tags?.length ? obj.Tags.find((t) => t.Key === 'Name')?.Value : null;
203
+
204
+ entry.push({
205
+ label: name || obj.SubnetId,
206
+ subLabel: name ? obj.SubnetId : obj.CidrBlock,
207
+ kind: 'subnet',
208
+ isDefault: obj.DefaultForAz || false,
209
+ value: obj.SubnetId,
210
+ vpcId: obj.VpcId,
211
+ hasIpv6,
212
+ hasIpv4
213
+ });
214
+ }
215
+
216
+ const out = [];
217
+
218
+ for ( const obj of vpcs ) {
219
+ if (!obj.disabled || subnetsByVpc[obj.value]) {
220
+ addObject(out, obj);
221
+ }
222
+
223
+ if ( subnetsByVpc[obj.value] ) {
224
+ addObjects(out, sortBy(subnetsByVpc[obj.value], ['isDefault:desc', 'label']));
225
+ }
226
+ }
227
+
228
+ return out;
229
+ },
230
+
231
+ // ipv4-only subnets and vpcs
232
+ ipv4OnlyNetworkOptions() {
233
+ return this.allNetworkOptions.reduce((opts, opt) => {
234
+ if (opt.kind === 'vpc') {
235
+ opts.push({ ...opt, disabled: opt.hasIpv6 });
236
+ } else if (!opt.hasIpv6) {
237
+ opts.push(opt);
238
+ }
239
+
240
+ return opts;
241
+ }, []);
242
+ },
243
+
244
+ // ipv6-enabled subnets and vpcs - some of these may be both ipv4 and ipv6 (dual-stack)
245
+ ipv6NetworkOptions() {
246
+ return this.allNetworkOptions.filter((opt) => {
247
+ return opt.hasIpv6;
248
+ });
249
+ },
250
+
251
+ networkOptions() {
252
+ return this.enableIpv6 ? this.ipv6NetworkOptions : this.ipv4OnlyNetworkOptions;
253
+ },
254
+
255
+ // ipv4 and ipv6 subnet or vpc selected
256
+ dualStackSelected() {
257
+ const opt = this.allNetworkOptions.find((o) => o.value === this.selectedNetwork);
258
+
259
+ return opt?.hasIpv4 && opt?.hasIpv6;
260
+ },
261
+
262
+ // ipv6-only subnet selected (vpc are never ipv6-only)
263
+ ipv6Selected() {
264
+ const opt = this.allNetworkOptions.find((o) => o.value === this.selectedNetwork);
265
+
266
+ return opt && opt.hasIpv6 && !opt.hasIpv4;
267
+ },
268
+
269
+ poolsInvalid() {
270
+ const hasIpv6 = !!this.machinePools.find((p) => {
271
+ return p.hasIpv6;
272
+ });
273
+
274
+ const hasNotIpv6 = !!this.machinePools.find((p) => !p.hasIpv6);
275
+
276
+ return hasIpv6 && hasNotIpv6;
277
+ },
278
+
279
+ addressCountInvalid() {
280
+ return !!this.ipv6AddressCountValidator(this.ipv6AddressCount);
281
+ },
282
+
283
+ allValid() {
284
+ return !this.poolsInvalid && !this.addressCountInvalid;
285
+ }
286
+ },
287
+
288
+ methods: {
289
+ updateNetwork(value) {
290
+ let obj;
291
+
292
+ if ( value ) {
293
+ obj = findBy(this.allNetworkOptions, 'value', value);
294
+ }
295
+
296
+ if ( obj?.kind === 'subnet' ) {
297
+ this.$emit('update:subnetId', value);
298
+ this.$emit('update:vpcId', obj.vpcId);
299
+ this.selectedNetwork = value;
300
+ } else if ( obj ) {
301
+ this.$emit('update:subnetId', null);
302
+ this.$emit('update:vpcId', value);
303
+ this.selectedNetwork = value;
304
+ } else {
305
+ this.$emit('update:subnetId', null);
306
+ this.$emit('update:vpcId', null);
307
+ this.selectedNetwork = null;
308
+ }
309
+ },
310
+
311
+ ipv6AddressCountValidator(val = '0') {
312
+ const value = parseInt(val);
313
+ // 0 or undefined are both acceptable if ipv6 is disabled
314
+ const isValid = this.enableIpv6 ? value > 0 : !value;
315
+
316
+ return isValid ? null : this.t('cluster.machineConfig.amazonEc2.ipv6AddressCount.error');
317
+ },
318
+
319
+ scrollDown(e) {
320
+ if (e?.target?.localName === 'a') {
321
+ setTimeout(() => {
322
+ scrollToBottom();
323
+ }, 3); // timeout allows the networking tab to render, changing the length of the page, before scrolling
324
+ }
325
+ }
326
+ }
327
+ };
328
+ </script>
329
+
330
+ <template>
331
+ <Banner
332
+ v-if="poolsInvalid"
333
+ color="error"
334
+ :label="t('cluster.machineConfig.amazonEc2.ipv6ValidationWarning')"
335
+ data-testid="amazonEc2__ipv6Warning"
336
+ />
337
+ <div
338
+ v-if="isCreate || enableIpv6"
339
+ class="row mb-20"
340
+ >
341
+ <div
342
+
343
+ class="col span-6"
344
+ >
345
+ <Checkbox
346
+ v-model:value="enableIpv6"
347
+ :disabled="!isCreate"
348
+ :label="t('cluster.machineConfig.amazonEc2.enableIpv6.label')"
349
+ data-testid="amazonEc2__enableIpv6"
350
+ :mode="mode"
351
+ />
352
+ <div
353
+ class="text-muted"
354
+ @click="scrollDown"
355
+ >
356
+ <t
357
+ raw
358
+ k="cluster.machineConfig.amazonEc2.enableIpv6.description"
359
+ />
360
+ </div>
361
+ </div>
362
+ </div>
363
+ <div class="row mb-20 ipv6-row">
364
+ <div class="col span-6">
365
+ <LabeledSelect
366
+ :mode="mode"
367
+ :value="selectedNetwork"
368
+ :options="allNetworkOptions"
369
+ :searchable="true"
370
+ :required="true"
371
+ :disabled="disabled"
372
+ :placeholder="t('cluster.machineConfig.amazonEc2.selectedNetwork.placeholder')"
373
+ :label="t('cluster.machineConfig.amazonEc2.selectedNetwork.label')"
374
+ data-testid="amazonEc2__selectedNetwork"
375
+ option-key="value"
376
+ @update:value="updateNetwork($event)"
377
+ >
378
+ <template v-slot:option="opt">
379
+ <div :class="{'vpc': opt.kind === 'vpc', 'vpc-subnet': opt.kind !== 'vpc'}">
380
+ <span class="vpc-name">{{ opt.label }}</span><span class="vpc-info">{{ opt.subLabel }}</span>
381
+ </div>
382
+ </template>
383
+ </LabeledSelect>
384
+ </div>
385
+ <div
386
+ v-if="enableIpv6"
387
+ class="col span-3"
388
+ >
389
+ <Checkbox
390
+ :disabled="!isCreate || !dualStackSelected"
391
+ :value="ipv6AddressOnly"
392
+ :label="t('cluster.machineConfig.amazonEc2.ipv6AddressOnly.label')"
393
+ :mode="mode"
394
+ data-testid="amazonEc2__ipv6AddressOnly"
395
+ @update:value="e=>$emit('update:ipv6AddressOnly', e)"
396
+ />
397
+ </div>
398
+ </div>
399
+ <div
400
+ v-if="enableIpv6"
401
+ class="row mb-10 ipv6-row"
402
+ >
403
+ <div class="col span-6">
404
+ <Checkbox
405
+ :value="httpProtocolIpv6==='enabled'"
406
+ :disabled="!isCreate"
407
+ :label="t('cluster.machineConfig.amazonEc2.httpProtocolIpv6.label')"
408
+ data-testid="amazonEc2__enableIpv6"
409
+ :mode="mode"
410
+ @update:value="e=>$emit('update:httpProtocolIpv6', e ? 'enabled' : 'disabled')"
411
+ />
412
+ </div>
413
+ </div>
414
+ <div
415
+ v-if="enableIpv6"
416
+ class="row mb-10 ipv6-row"
417
+ >
418
+ <div class="col span-3">
419
+ <LabeledInput
420
+ :disabled="!isCreate"
421
+ min="1"
422
+ :mode="mode"
423
+ :value="ipv6AddressCount"
424
+ label-key="cluster.machineConfig.amazonEc2.ipv6AddressCount.label"
425
+ data-testid="amazonEc2__ipv6AddressCount"
426
+ :rules="[ipv6AddressCountValidator]"
427
+ @update:value="e=>$emit('update:ipv6AddressCount', e)"
428
+ />
429
+ </div>
430
+ <div class="col span-9">
431
+ <Checkbox
432
+ :disabled="!isCreate"
433
+ :value="enablePrimaryIpv6"
434
+ :label="t('cluster.machineConfig.amazonEc2.enablePrimaryIpv6.label')"
435
+ :mode="mode"
436
+ data-testid="amazonEc2__enablePrimaryIpv6"
437
+ @update:value="e=>$emit('update:enablePrimaryIpv6', e)"
438
+ />
439
+ <div class="text-muted">
440
+ <t
441
+
442
+ k="cluster.machineConfig.amazonEc2.enablePrimaryIpv6.description"
443
+ />
444
+ </div>
445
+ </div>
446
+ </div>
447
+ </template>
448
+
449
+ <style scoped lang="scss">
450
+ .vpc, .vpc-subnet {
451
+ display: flex;
452
+ line-height: 30px;
453
+
454
+ .vpc-name {
455
+ font-weight: bold;
456
+ flex: 1;
457
+ }
458
+
459
+ .vpc-info {
460
+ font-size: 12px;
461
+ opacity: 0.7;
462
+ }
463
+ }
464
+
465
+ .vpc-subnet .vpc-name {
466
+ font-weight: normal;
467
+ padding-left: 15px;
468
+ }
469
+
470
+ .ipv6-row {
471
+ display: flex;
472
+ align-items: center;
473
+ }
474
+ </style>
@@ -0,0 +1,94 @@
1
+ import { mount, shallowMount } from '@vue/test-utils';
2
+ import EC2Networking from '@shell/machine-config/components/EC2Networking.vue';
3
+
4
+ import { vpcInfo, subnetInfo } from './utils/vpcSubnetMockData';
5
+
6
+ describe('component: EC2Networking', () => {
7
+ const defaultGetters = { 'i18n/t': jest.fn().mockImplementation((key: string) => key) };
8
+ const baseSetup = {
9
+ propsData: {
10
+ region: '',
11
+ zone: 'us-west-2a',
12
+ machinePools: [],
13
+ vpcInfo,
14
+ subnetInfo
15
+
16
+ },
17
+ global: {
18
+ mocks: {
19
+ $fetchState: { pending: false },
20
+ $store: { getters: defaultGetters },
21
+ },
22
+ stubs: { CodeMirror: true },
23
+ }
24
+ };
25
+ const defaultCreateSetup = {
26
+ ...baseSetup,
27
+ propsData: {
28
+ ...baseSetup.propsData,
29
+ mode: 'create',
30
+ }
31
+ };
32
+
33
+ it('should render a dropdown containing ipv4-only subnets and vpcs', () => {
34
+ const wrapper = shallowMount(EC2Networking, defaultCreateSetup);
35
+ const expectedValues = ['vpc-1234', 'vpc-123'];
36
+ const networkOptionValues = wrapper.vm.networkOptions.map((o:any) => o.value);
37
+
38
+ expect(networkOptionValues).toStrictEqual(expectedValues);
39
+ });
40
+
41
+ it('should update the network dropdown to contain ipv6 and dual stack vpcs/subnets when the ipv6 checkbox is checked', async() => {
42
+ const wrapper = shallowMount(EC2Networking, defaultCreateSetup);
43
+
44
+ const ipv6Checkbox = wrapper.findComponent('[data-testid="amazonEc2__enableIpv6"]');
45
+
46
+ ipv6Checkbox.vm.$emit('update:value', true);
47
+ await wrapper.vm.$nextTick();
48
+ const expectedValues = ['vpc-12345', 'subnet-4321'];
49
+ const networkOptionValues = wrapper.vm.networkOptions.map((o:any) => o.value);
50
+
51
+ expect(networkOptionValues).toStrictEqual(expectedValues);
52
+ });
53
+
54
+ it('should render the ipv6 address count input when the enable ipv6 checkbox is checked', async() => {
55
+ const wrapper = shallowMount(EC2Networking, defaultCreateSetup);
56
+ const ipv6Checkbox = wrapper.findComponent('[data-testid="amazonEc2__enableIpv6"]');
57
+
58
+ ipv6Checkbox.vm.$emit('update:value', true);
59
+ await wrapper.vm.$nextTick();
60
+
61
+ const ipv6AddressCountInput = wrapper.findComponent('[data-testid="amazonEc2__ipv6AddressCount"]');
62
+
63
+ expect(wrapper.vm.enableIpv6).toBe(true);
64
+ expect(ipv6AddressCountInput.exists()).toBe(true);
65
+ });
66
+
67
+ it('should render the ipv6 only checkbox when the selected network is dual-stack', async() => {
68
+ const wrapper = mount(EC2Networking, defaultCreateSetup);
69
+ const ipv6Checkbox = wrapper.findComponent('[data-testid="amazonEc2__enableIpv6"]');
70
+
71
+ ipv6Checkbox.vm.$emit('update:value', true);
72
+ await wrapper.vm.$nextTick();
73
+
74
+ const networkDropdown = wrapper.findComponent('[data-testid="amazonEc2__selectedNetwork"]');
75
+
76
+ networkDropdown.vm.$emit('selecting', 'subnet-321');
77
+ await wrapper.vm.$nextTick();
78
+
79
+ const ipv6OnlyCheckbox = wrapper.findComponent('[data-testid="amazonEc2__ipv6AddressOnly"]');
80
+
81
+ expect(ipv6OnlyCheckbox.exists()).toBe(true);
82
+ });
83
+
84
+ it.each([
85
+ [[{ hasIpv6: true }, { hasIpv6: false }], true],
86
+ [[{ hasIpv6: false }, { hasIpv6: false }], false],
87
+ [[{ hasIpv6: true }, { hasIpv6: true }], false],
88
+ ])('should show an error banner if pools do not either all have ipv6 or all have ipv4', (pools, shouldShowError) => {
89
+ const wrapper = shallowMount(EC2Networking, { ...defaultCreateSetup, propsData: { ...defaultCreateSetup.propsData, machinePools: pools } });
90
+ const ipv6Warning = wrapper.findComponent('[data-testid="amazonEc2__ipv6Warning"]');
91
+
92
+ expect(ipv6Warning.exists()).toBe(shouldShowError);
93
+ });
94
+ });