@openshift-assisted/ui-lib 2.56.0 → 2.56.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/build/@types/common/config/constants.d.ts.map +1 -1
  2. package/build/@types/common/config/docs_links.d.ts +1 -0
  3. package/build/@types/common/config/docs_links.d.ts.map +1 -1
  4. package/build/@types/ocm/components/wizard/NewClusterWizard.d.ts.map +1 -1
  5. package/build/@types/ocm/components/wizard/clusterWizardContext/ClusterWizardContextProvider.d.ts +1 -1
  6. package/build/@types/ocm/components/wizard/clusterWizardContext/ClusterWizardContextProvider.d.ts.map +1 -1
  7. package/build/@types/ocm/components/wizard/clusterWizardContext/utils.d.ts +7 -0
  8. package/build/@types/ocm/components/wizard/clusterWizardContext/utils.d.ts.map +1 -0
  9. package/build/@types/ocm/components/wizard/steps/clusterDetails/ClusterDetailsForm.d.ts.map +1 -1
  10. package/build/@types/ocm/components/wizard/steps/disconnected/BasicStep.d.ts +2 -1
  11. package/build/@types/ocm/components/wizard/steps/disconnected/BasicStep.d.ts.map +1 -1
  12. package/build/@types/ocm/components/wizard/steps/disconnected/DisconnectedReviewStep.d.ts.map +1 -1
  13. package/build/@types/ocm/components/wizard/steps/disconnected/DisconnectedStaticIp.d.ts +4 -0
  14. package/build/@types/ocm/components/wizard/steps/disconnected/DisconnectedStaticIp.d.ts.map +1 -0
  15. package/build/@types/ocm/components/wizard/steps/disconnected/InstallDisconnectedSwitch.d.ts.map +1 -1
  16. package/build/@types/ocm/components/wizard/steps/disconnected/OptionalConfigurationsStep.d.ts.map +1 -1
  17. package/build/@types/ocm/components/wizard/steps/disconnected/index.d.ts +1 -0
  18. package/build/@types/ocm/components/wizard/steps/disconnected/index.d.ts.map +1 -1
  19. package/build/@types/ocm/components/wizard/steps/staticIp/StaticIp.d.ts +1 -1
  20. package/build/@types/ocm/components/wizard/steps/staticIp/StaticIp.d.ts.map +1 -1
  21. package/build/@types/ocm/components/wizard/steps/staticIp/commonValidationSchemas.d.ts +6 -1
  22. package/build/@types/ocm/components/wizard/steps/staticIp/commonValidationSchemas.d.ts.map +1 -1
  23. package/build/@types/ocm/components/wizard/steps/staticIp/components/FormViewNetworkWide/FormViewNetworkWide.d.ts.map +1 -1
  24. package/build/@types/ocm/components/wizard/steps/staticIp/components/FormViewNetworkWide/FormViewNetworkWideFields.d.ts.map +1 -1
  25. package/build/@types/ocm/components/wizard/steps/staticIp/components/FormViewNetworkWide/disconnectedNetworkWideValidationSchema.d.ts +24 -0
  26. package/build/@types/ocm/components/wizard/steps/staticIp/components/FormViewNetworkWide/disconnectedNetworkWideValidationSchema.d.ts.map +1 -0
  27. package/build/@types/ocm/components/wizard/steps/staticIp/components/FormViewNetworkWide/formViewNetworkWideValidationSchema.d.ts +46 -1
  28. package/build/@types/ocm/components/wizard/steps/staticIp/components/FormViewNetworkWide/formViewNetworkWideValidationSchema.d.ts.map +1 -1
  29. package/build/@types/ocm/components/wizard/steps/staticIp/components/StaticIpPage.d.ts.map +1 -1
  30. package/build/@types/ocm/components/wizard/steps/staticIp/data/dataTypes.d.ts +1 -1
  31. package/build/@types/ocm/components/wizard/steps/staticIp/data/dataTypes.d.ts.map +1 -1
  32. package/build/@types/ocm/components/wizard/steps/staticIp/data/disconnectedNetworkWide.d.ts +8 -0
  33. package/build/@types/ocm/components/wizard/steps/staticIp/data/disconnectedNetworkWide.d.ts.map +1 -0
  34. package/build/@types/ocm/components/wizard/steps/staticIp/data/formDataFromInfraEnvField.d.ts.map +1 -1
  35. package/build/@types/ocm/components/wizard/steps/staticIp/data/fromInfraEnv.d.ts +2 -0
  36. package/build/@types/ocm/components/wizard/steps/staticIp/data/fromInfraEnv.d.ts.map +1 -1
  37. package/build/@types/ocm/components/wizard/steps/staticIp/data/index.d.ts +1 -0
  38. package/build/@types/ocm/components/wizard/steps/staticIp/data/index.d.ts.map +1 -1
  39. package/build/@types/ocm/components/wizard/steps/staticIp/data/nmstateYaml.d.ts +1 -0
  40. package/build/@types/ocm/components/wizard/steps/staticIp/data/nmstateYaml.d.ts.map +1 -1
  41. package/build/@types/ocm/components/wizard/steps/staticIp/data/protocolVersion.d.ts +2 -2
  42. package/build/@types/ocm/components/wizard/steps/staticIp/data/protocolVersion.d.ts.map +1 -1
  43. package/build/@types/ocm/components/wizard/wizardComponents/ClusterWizardNavigation.d.ts +7 -0
  44. package/build/@types/ocm/components/wizard/wizardComponents/ClusterWizardNavigation.d.ts.map +1 -1
  45. package/build/cjs/common/components/clusterWizard/ClusterWizardStep.css +9 -0
  46. package/build/cjs/common/config/constants.js +1 -0
  47. package/build/cjs/common/config/constants.js.map +1 -1
  48. package/build/cjs/common/config/docs_links.js +3 -1
  49. package/build/cjs/common/config/docs_links.js.map +1 -1
  50. package/build/cjs/common/validationSchemas/addressValidation.js +2 -2
  51. package/build/cjs/common/validationSchemas/addressValidation.js.map +1 -1
  52. package/build/cjs/ocm/components/wizard/NewClusterWizard.js +4 -0
  53. package/build/cjs/ocm/components/wizard/NewClusterWizard.js.map +1 -1
  54. package/build/cjs/ocm/components/wizard/clusterWizardContext/ClusterWizardContextProvider.js +62 -62
  55. package/build/cjs/ocm/components/wizard/clusterWizardContext/ClusterWizardContextProvider.js.map +1 -1
  56. package/build/cjs/ocm/components/wizard/clusterWizardContext/utils.js +61 -0
  57. package/build/cjs/ocm/components/wizard/clusterWizardContext/utils.js.map +1 -0
  58. package/build/cjs/ocm/components/wizard/steps/clusterDetails/ClusterDetailsForm.js +1 -2
  59. package/build/cjs/ocm/components/wizard/steps/clusterDetails/ClusterDetailsForm.js.map +1 -1
  60. package/build/cjs/ocm/components/wizard/steps/disconnected/BasicStep.js +95 -7
  61. package/build/cjs/ocm/components/wizard/steps/disconnected/BasicStep.js.map +1 -1
  62. package/build/cjs/ocm/components/wizard/steps/disconnected/DisconnectedReviewStep.js +54 -44
  63. package/build/cjs/ocm/components/wizard/steps/disconnected/DisconnectedReviewStep.js.map +1 -1
  64. package/build/cjs/ocm/components/wizard/steps/disconnected/DisconnectedStaticIp.js +26 -0
  65. package/build/cjs/ocm/components/wizard/steps/disconnected/DisconnectedStaticIp.js.map +1 -0
  66. package/build/cjs/ocm/components/wizard/steps/disconnected/InstallDisconnectedSwitch.js +1 -1
  67. package/build/cjs/ocm/components/wizard/steps/disconnected/InstallDisconnectedSwitch.js.map +1 -1
  68. package/build/cjs/ocm/components/wizard/steps/disconnected/OptionalConfigurationsStep.js +87 -56
  69. package/build/cjs/ocm/components/wizard/steps/disconnected/OptionalConfigurationsStep.js.map +1 -1
  70. package/build/cjs/ocm/components/wizard/steps/disconnected/index.js +1 -0
  71. package/build/cjs/ocm/components/wizard/steps/disconnected/index.js.map +1 -1
  72. package/build/cjs/ocm/components/wizard/steps/staticIp/StaticIp.js.map +1 -1
  73. package/build/cjs/ocm/components/wizard/steps/staticIp/commonValidationSchemas.js +49 -12
  74. package/build/cjs/ocm/components/wizard/steps/staticIp/commonValidationSchemas.js.map +1 -1
  75. package/build/cjs/ocm/components/wizard/steps/staticIp/components/FormViewNetworkWide/FormViewNetworkWide.js +13 -4
  76. package/build/cjs/ocm/components/wizard/steps/staticIp/components/FormViewNetworkWide/FormViewNetworkWide.js.map +1 -1
  77. package/build/cjs/ocm/components/wizard/steps/staticIp/components/FormViewNetworkWide/FormViewNetworkWideFields.js +96 -4
  78. package/build/cjs/ocm/components/wizard/steps/staticIp/components/FormViewNetworkWide/FormViewNetworkWideFields.js.map +1 -1
  79. package/build/cjs/ocm/components/wizard/steps/staticIp/components/FormViewNetworkWide/disconnectedNetworkWideValidationSchema.js +70 -0
  80. package/build/cjs/ocm/components/wizard/steps/staticIp/components/FormViewNetworkWide/disconnectedNetworkWideValidationSchema.js.map +1 -0
  81. package/build/cjs/ocm/components/wizard/steps/staticIp/components/FormViewNetworkWide/formViewNetworkWideValidationSchema.js +19 -9
  82. package/build/cjs/ocm/components/wizard/steps/staticIp/components/FormViewNetworkWide/formViewNetworkWideValidationSchema.js.map +1 -1
  83. package/build/cjs/ocm/components/wizard/steps/staticIp/components/StaticIpPage.js +8 -2
  84. package/build/cjs/ocm/components/wizard/steps/staticIp/components/StaticIpPage.js.map +1 -1
  85. package/build/cjs/ocm/components/wizard/steps/staticIp/data/disconnectedNetworkWide.js +55 -0
  86. package/build/cjs/ocm/components/wizard/steps/staticIp/data/disconnectedNetworkWide.js.map +1 -0
  87. package/build/cjs/ocm/components/wizard/steps/staticIp/data/formDataFromInfraEnvField.js +5 -1
  88. package/build/cjs/ocm/components/wizard/steps/staticIp/data/formDataFromInfraEnvField.js.map +1 -1
  89. package/build/cjs/ocm/components/wizard/steps/staticIp/data/fromInfraEnv.js +48 -1
  90. package/build/cjs/ocm/components/wizard/steps/staticIp/data/fromInfraEnv.js.map +1 -1
  91. package/build/cjs/ocm/components/wizard/steps/staticIp/data/index.js +1 -0
  92. package/build/cjs/ocm/components/wizard/steps/staticIp/data/index.js.map +1 -1
  93. package/build/cjs/ocm/components/wizard/steps/staticIp/data/nmstateYaml.js +12 -1
  94. package/build/cjs/ocm/components/wizard/steps/staticIp/data/nmstateYaml.js.map +1 -1
  95. package/build/cjs/ocm/components/wizard/steps/staticIp/data/protocolVersion.js +9 -7
  96. package/build/cjs/ocm/components/wizard/steps/staticIp/data/protocolVersion.js.map +1 -1
  97. package/build/cjs/ocm/components/wizard/wizardComponents/ClusterWizardNavigation.js +31 -20
  98. package/build/cjs/ocm/components/wizard/wizardComponents/ClusterWizardNavigation.js.map +1 -1
  99. package/lib/common/components/clusterWizard/ClusterWizardStep.css +9 -0
  100. package/lib/common/config/constants.ts +1 -0
  101. package/lib/common/config/docs_links.ts +3 -0
  102. package/lib/common/validationSchemas/_validationSchemas.test.ts +1 -1
  103. package/lib/common/validationSchemas/addressValidation.tsx +2 -2
  104. package/lib/ocm/components/wizard/NewClusterWizard.tsx +5 -0
  105. package/lib/ocm/components/wizard/clusterWizardContext/ClusterWizardContextProvider.tsx +63 -85
  106. package/lib/ocm/components/wizard/clusterWizardContext/utils.ts +73 -0
  107. package/lib/ocm/components/wizard/steps/clusterDetails/ClusterDetailsForm.tsx +0 -2
  108. package/lib/ocm/components/wizard/steps/disconnected/BasicStep.tsx +147 -7
  109. package/lib/ocm/components/wizard/steps/disconnected/DisconnectedReviewStep.tsx +101 -72
  110. package/lib/ocm/components/wizard/steps/disconnected/DisconnectedStaticIp.tsx +29 -0
  111. package/lib/ocm/components/wizard/steps/disconnected/InstallDisconnectedSwitch.tsx +1 -0
  112. package/lib/ocm/components/wizard/steps/disconnected/OptionalConfigurationsStep.tsx +137 -69
  113. package/lib/ocm/components/wizard/steps/disconnected/index.tsx +1 -0
  114. package/lib/ocm/components/wizard/steps/staticIp/StaticIp.tsx +1 -1
  115. package/lib/ocm/components/wizard/steps/staticIp/commonValidationSchemas.tsx +42 -3
  116. package/lib/ocm/components/wizard/steps/staticIp/components/FormViewNetworkWide/FormViewNetworkWide.tsx +15 -4
  117. package/lib/ocm/components/wizard/steps/staticIp/components/FormViewNetworkWide/FormViewNetworkWideFields.tsx +232 -14
  118. package/lib/ocm/components/wizard/steps/staticIp/components/FormViewNetworkWide/disconnectedNetworkWideValidationSchema.tsx +98 -0
  119. package/lib/ocm/components/wizard/steps/staticIp/components/FormViewNetworkWide/formViewNetworkWideValidationSchema.tsx +14 -5
  120. package/lib/ocm/components/wizard/steps/staticIp/components/StaticIpPage.tsx +8 -3
  121. package/lib/ocm/components/wizard/steps/staticIp/data/dataTypes.ts +1 -1
  122. package/lib/ocm/components/wizard/steps/staticIp/data/disconnectedNetworkWide.ts +81 -0
  123. package/lib/ocm/components/wizard/steps/staticIp/data/formDataFromInfraEnvField.ts +6 -2
  124. package/lib/ocm/components/wizard/steps/staticIp/data/fromInfraEnv.ts +57 -2
  125. package/lib/ocm/components/wizard/steps/staticIp/data/index.tsx +1 -0
  126. package/lib/ocm/components/wizard/steps/staticIp/data/nmstateYaml.ts +16 -0
  127. package/lib/ocm/components/wizard/steps/staticIp/data/protocolVersion.ts +9 -7
  128. package/lib/ocm/components/wizard/utils/_wizardTransition.test.ts +60 -1
  129. package/lib/ocm/components/wizard/wizardComponents/ClusterWizardNavigation.tsx +39 -16
  130. package/package.json +3 -3
@@ -23,7 +23,12 @@ import {
23
23
  PopoverIcon,
24
24
  useFieldErrorMsg,
25
25
  } from '../../../../../../../common';
26
- import { OcmCheckboxField, OcmInputField, OcmRadioField } from '../../../../../ui/OcmFormFields';
26
+ import {
27
+ OcmCheckboxField,
28
+ OcmInputField,
29
+ OcmRadio,
30
+ OcmRadioField,
31
+ } from '../../../../../ui/OcmFormFields';
27
32
  import {
28
33
  getAddressObject,
29
34
  getProtocolVersionLabel,
@@ -36,13 +41,16 @@ import {
36
41
  ProtocolVersion,
37
42
  StaticProtocolType,
38
43
  getMachineNetworkCidr,
44
+ DISCONNECTED_SINGLE_STACK_FIELD,
39
45
  } from '../../data';
46
+ import { detectProtocolVersionFromIp } from '../../commonValidationSchemas';
40
47
  import {
41
48
  MIN_PREFIX_LENGTH,
42
49
  MAX_PREFIX_LENGTH,
43
50
  MAX_VLAN_ID,
44
51
  MIN_VLAN_ID,
45
52
  } from './formViewNetworkWideValidationSchema';
53
+ import { useClusterWizardContext } from '../../../../clusterWizardContext';
46
54
 
47
55
  import '../staticIp.css';
48
56
 
@@ -127,6 +135,92 @@ const MachineNetwork: React.FC<{ fieldName: string; protocolVersion: ProtocolVer
127
135
  );
128
136
  };
129
137
 
138
+ const SingleStackMachineNetwork: React.FC<{ fieldName: string }> = ({ fieldName }) => {
139
+ const [{ value }] = useField<Cidr>(fieldName);
140
+ const ipFieldName = `${fieldName}.ip`;
141
+ const prefixLengthFieldName = `${fieldName}.prefixLength`;
142
+ const ipErrorMessage = useFieldErrorMsg({ name: ipFieldName });
143
+ const prefixLengthErrorMessage = useFieldErrorMsg({ name: prefixLengthFieldName });
144
+ const errorMessage = ipErrorMessage || prefixLengthErrorMessage;
145
+ const protocolVersion = detectProtocolVersionFromIp(value.ip) ?? ProtocolVersion.ipv4;
146
+ const machineNetworkHelptext = React.useMemo(() => {
147
+ if (errorMessage) {
148
+ return '';
149
+ }
150
+ const cidr = getMachineNetworkCidr(value);
151
+ return getHumanizedSubnetRange(getAddressObject(cidr, protocolVersion));
152
+ }, [value, protocolVersion, errorMessage]);
153
+ const fieldId = getFieldId(`${fieldName}`, 'input');
154
+ return (
155
+ <FormGroup
156
+ labelHelp={
157
+ <PopoverIcon noVerticalAlign bodyContent="The range of IP addresses of the hosts." />
158
+ }
159
+ label="Subnet"
160
+ fieldId={fieldId}
161
+ isRequired
162
+ className="subnet"
163
+ >
164
+ <Flex>
165
+ <FlexItem spacer={{ default: 'spacerSm' }}>
166
+ <OcmInputField
167
+ name={`${fieldName}.ip`}
168
+ isRequired={true}
169
+ data-testid="single-stack-machine-network-ip"
170
+ showErrorMessage={false}
171
+ />
172
+ </FlexItem>
173
+ <FlexItem spacer={{ default: 'spacerSm' }}>{'/'}</FlexItem>
174
+ <FlexItem>
175
+ <OcmInputField
176
+ name={`${fieldName}.prefixLength`}
177
+ isRequired={true}
178
+ data-testid="single-stack-machine-network-prefix-length"
179
+ type={TextInputTypes.number}
180
+ showErrorMessage={false}
181
+ min={MIN_PREFIX_LENGTH}
182
+ max={MAX_PREFIX_LENGTH.ipv6}
183
+ />
184
+ </FlexItem>
185
+ </Flex>
186
+ {(errorMessage || machineNetworkHelptext) && (
187
+ <FormHelperText>
188
+ <HelperText>
189
+ <HelperTextItem
190
+ icon={errorMessage ? <ExclamationCircleIcon /> : null}
191
+ variant={errorMessage ? 'error' : 'default'}
192
+ id={errorMessage ? `${fieldId}-helper-error` : `${fieldId}-helper`}
193
+ data-testid={`input-machine-network-${fieldId}-helper-text`}
194
+ >
195
+ {errorMessage ? errorMessage : machineNetworkHelptext}
196
+ </HelperTextItem>
197
+ </HelperText>
198
+ </FormHelperText>
199
+ )}
200
+ </FormGroup>
201
+ );
202
+ };
203
+
204
+ const SingleStackIpConfigFields: React.FC<{ fieldName: string }> = ({ fieldName }) => {
205
+ return (
206
+ <Grid hasGutter>
207
+ <SingleStackMachineNetwork fieldName={`${fieldName}.machineNetwork`} />
208
+ <OcmInputField
209
+ isRequired
210
+ label="Default gateway"
211
+ labelIcon={
212
+ <PopoverIcon
213
+ noVerticalAlign
214
+ bodyContent="An IP address to where any IP packet should be forwarded in case there is no other routing rule configured for a destination IP."
215
+ />
216
+ }
217
+ name={`${fieldName}.gateway`}
218
+ data-testid="single-stack-gateway"
219
+ />
220
+ </Grid>
221
+ );
222
+ };
223
+
130
224
  const IpConfigFields: React.FC<{
131
225
  fieldName: string;
132
226
  protocolVersion: ProtocolVersion;
@@ -155,7 +249,7 @@ const ipv6ValuesEmpty = (values: FormViewNetworkWideValues) =>
155
249
  values.ipConfigs.ipv6.machineNetwork.ip === '' &&
156
250
  values.ipConfigs.ipv6.machineNetwork.prefixLength === '';
157
251
 
158
- export const ProtocolTypeSelect = () => {
252
+ const ConnectedProtocolTypeSelect = () => {
159
253
  const selectFieldName = 'protocolType';
160
254
  const [{ value: protocolType }, , { setValue: setProtocolType }] =
161
255
  useField<StaticProtocolType>(selectFieldName);
@@ -171,7 +265,6 @@ export const ProtocolTypeSelect = () => {
171
265
  if (newProtocolType === 'ipv4' && !ipv6ValuesEmpty(values)) {
172
266
  setConfirmModal(true);
173
267
  } else {
174
- //no need to empty ipv4, when switching back dual stack ipv4 remains as is
175
268
  setIpv6(getEmptyIpConfig());
176
269
  setProtocolType(newProtocolType);
177
270
  }
@@ -239,6 +332,99 @@ export const ProtocolTypeSelect = () => {
239
332
  }}
240
333
  onConfirm={() => {
241
334
  setConfirmModal(false);
335
+ setIpv6(getEmptyIpConfig());
336
+ setProtocolType('ipv4');
337
+ }}
338
+ />
339
+ )}
340
+ </>
341
+ );
342
+ };
343
+
344
+ const DisconnectedProtocolTypeSelect = () => {
345
+ const selectFieldName = 'protocolType';
346
+ const [{ value: protocolType }, , { setValue: setProtocolType }] =
347
+ useField<StaticProtocolType>(selectFieldName);
348
+ const [, , { setValue: setIpv6 }] = useField<IpConfig>(`ipConfigs.ipv6`);
349
+ const [openConfirmModal, setConfirmModal] = React.useState(false);
350
+ const { values } = useFormikContext<FormViewNetworkWideValues>();
351
+
352
+ const isDualStack = protocolType === 'dualStack';
353
+ const singleStackRadioId = getFieldId(selectFieldName, 'radio', 'singleStack');
354
+ const dualStackRadioId = getFieldId(selectFieldName, 'radio', 'dualStack');
355
+
356
+ const switchToSingleStack = () => {
357
+ if (protocolType !== 'dualStack') {
358
+ return;
359
+ }
360
+ if (!ipv6ValuesEmpty(values)) {
361
+ setConfirmModal(true);
362
+ return;
363
+ }
364
+ setIpv6(getEmptyIpConfig());
365
+ setProtocolType('ipv4');
366
+ };
367
+
368
+ const switchToDualStack = () => {
369
+ if (protocolType === 'dualStack') {
370
+ return;
371
+ }
372
+ setProtocolType('dualStack');
373
+ };
374
+
375
+ return (
376
+ <>
377
+ <FormGroup
378
+ fieldId={getFieldId(selectFieldName, 'radio')}
379
+ isInline
380
+ label="Networking stack type"
381
+ isRequired
382
+ >
383
+ <OcmRadio
384
+ id={singleStackRadioId}
385
+ name={`${selectFieldName}-stack-level`}
386
+ data-testid="select-single-stack"
387
+ label={
388
+ <>
389
+ {'Single stack\u00A0'}
390
+ <PopoverIcon
391
+ noVerticalAlign
392
+ bodyContent="Use either IPv4 or IPv6 for all network-wide and host addresses. The address family is determined from the subnet you enter below."
393
+ />
394
+ </>
395
+ }
396
+ isChecked={!isDualStack}
397
+ onChange={() => switchToSingleStack()}
398
+ />
399
+ <OcmRadio
400
+ id={dualStackRadioId}
401
+ name={`${selectFieldName}-stack-level`}
402
+ data-testid="select-dual-stack"
403
+ label={
404
+ <>
405
+ {'Dual-stack'}{' '}
406
+ <PopoverIcon
407
+ noVerticalAlign
408
+ bodyContent="Select dual-stack when your hosts are using IPV4 together with IPV6."
409
+ />
410
+ </>
411
+ }
412
+ isChecked={isDualStack}
413
+ onChange={() => switchToDualStack()}
414
+ />
415
+ </FormGroup>
416
+
417
+ {openConfirmModal && (
418
+ <ConfirmationModal
419
+ title={'Change networking stack type?'}
420
+ titleIconVariant={'warning'}
421
+ confirmationButtonText={'Change'}
422
+ confirmationButtonVariant={ButtonVariant.primary}
423
+ content={<p>All data and configuration done for &apos;Dual-stack&apos; will be lost.</p>}
424
+ onClose={() => setConfirmModal(false)}
425
+ onConfirm={() => {
426
+ setConfirmModal(false);
427
+ setIpv6(getEmptyIpConfig());
242
428
  setProtocolType('ipv4');
243
429
  }}
244
430
  />
@@ -246,8 +432,30 @@ export const ProtocolTypeSelect = () => {
246
432
  </>
247
433
  );
248
434
  };
435
+
436
+ export const ProtocolTypeSelect = () => {
437
+ const { installDisconnected } = useClusterWizardContext();
438
+ if (installDisconnected) {
439
+ return <DisconnectedProtocolTypeSelect />;
440
+ }
441
+ return <ConnectedProtocolTypeSelect />;
442
+ };
443
+ const getIpConfigSectionLabel = (
444
+ protocolVersion: ProtocolVersion,
445
+ protocolType: StaticProtocolType,
446
+ installDisconnected: boolean,
447
+ ): string => {
448
+ if (installDisconnected && protocolType !== 'dualStack') {
449
+ return 'Single stack';
450
+ }
451
+ return getProtocolVersionLabel(protocolVersion);
452
+ };
453
+
249
454
  export const FormViewNetworkWideFields = ({ hosts }: { hosts: FormViewHost[] }) => {
455
+ const { installDisconnected } = useClusterWizardContext();
250
456
  const { values, setFieldValue } = useFormikContext<FormViewNetworkWideValues>();
457
+ const isDisconnectedSingleStack = installDisconnected && values.protocolType !== 'dualStack';
458
+
251
459
  return (
252
460
  <>
253
461
  <Content>
@@ -297,18 +505,28 @@ export const FormViewNetworkWideFields = ({ hosts }: { hosts: FormViewHost[] })
297
505
  helperText={'List of your DNS server addresses, separated by commas.'}
298
506
  />
299
507
 
300
- {getShownProtocolVersions(values.protocolType).map((protocolVersion) => (
301
- <FormGroup
302
- key={protocolVersion}
303
- label={getProtocolVersionLabel(protocolVersion)}
304
- fieldId={getFieldId(`ip-configs-${protocolVersion}`, 'input')}
305
- >
306
- <IpConfigFields
307
- fieldName={`ipConfigs.${protocolVersion}`}
308
- protocolVersion={protocolVersion}
309
- />
508
+ {isDisconnectedSingleStack ? (
509
+ <FormGroup label="Single stack" fieldId={getFieldId('ip-configs-single-stack', 'input')}>
510
+ <SingleStackIpConfigFields fieldName={`ipConfigs.${DISCONNECTED_SINGLE_STACK_FIELD}`} />
310
511
  </FormGroup>
311
- ))}
512
+ ) : (
513
+ getShownProtocolVersions(values.protocolType).map((protocolVersion) => (
514
+ <FormGroup
515
+ key={protocolVersion}
516
+ label={getIpConfigSectionLabel(
517
+ protocolVersion,
518
+ values.protocolType,
519
+ installDisconnected,
520
+ )}
521
+ fieldId={getFieldId(`ip-configs-${protocolVersion}`, 'input')}
522
+ >
523
+ <IpConfigFields
524
+ fieldName={`ipConfigs.${protocolVersion}`}
525
+ protocolVersion={protocolVersion}
526
+ />
527
+ </FormGroup>
528
+ ))
529
+ )}
312
530
  </>
313
531
  );
314
532
  };
@@ -0,0 +1,98 @@
1
+ import * as Yup from 'yup';
2
+ import { IpConfig, FormViewNetworkWideValues, Cidr } from '../../data';
3
+ import {
4
+ detectProtocolVersionFromIp,
5
+ getDnsMatchingSubnetFamilyValidationSchema,
6
+ getFamilyAgnosticIpValidationSchema,
7
+ isNotReservedHostIPAddress,
8
+ } from '../../commonValidationSchemas';
9
+ import {
10
+ buildNetworkWideValidationSchema,
11
+ getInMachineNetworkValidationSchema,
12
+ getIsNotNetworkOrBroadcastAddressSchema,
13
+ getIPValidationSchema,
14
+ MAX_PREFIX_LENGTH,
15
+ MIN_PREFIX_LENGTH,
16
+ validateNumber,
17
+ MAX_VLAN_ID,
18
+ MUST_BE_A_NUMBER,
19
+ } from './formViewNetworkWideValidationSchema';
20
+
21
+ const REQUIRED_MESSAGE = 'A value is required';
22
+
23
+ const transformNumber = (originalValue: number) => {
24
+ return isNaN(originalValue) ? null : originalValue;
25
+ };
26
+
27
+ const getDisconnectedSingleStackMachineNetworkSchema = (subnetIp: string) =>
28
+ Yup.object<Cidr>().shape({
29
+ ip: getFamilyAgnosticIpValidationSchema().required(REQUIRED_MESSAGE),
30
+ prefixLength: Yup.number()
31
+ .required('Prefix length is required')
32
+ .min(MIN_PREFIX_LENGTH, `Prefix length must be more than or equal to ${MIN_PREFIX_LENGTH}`)
33
+ .test(
34
+ 'prefix-length-family',
35
+ `Prefix length must be less than or equal to ${MAX_PREFIX_LENGTH.ipv6}`,
36
+ (prefixLength) => {
37
+ if (prefixLength === undefined || prefixLength === null) {
38
+ return true;
39
+ }
40
+ const family = detectProtocolVersionFromIp(subnetIp);
41
+ const max = family ? MAX_PREFIX_LENGTH[family] : MAX_PREFIX_LENGTH.ipv6;
42
+ return prefixLength <= max;
43
+ },
44
+ )
45
+ .transform(transformNumber) as Yup.NumberSchema,
46
+ });
47
+
48
+ const getDisconnectedSingleStackIpConfigSchema = (ipConfig: IpConfig) => {
49
+ const subnetIp = ipConfig.machineNetwork.ip;
50
+ const family = detectProtocolVersionFromIp(subnetIp);
51
+
52
+ const gatewaySchema = family
53
+ ? getIPValidationSchema(family)
54
+ .concat(getInMachineNetworkValidationSchema(family, ipConfig.machineNetwork))
55
+ .concat(getIsNotNetworkOrBroadcastAddressSchema(family, ipConfig.machineNetwork))
56
+ : getFamilyAgnosticIpValidationSchema()
57
+ .required(REQUIRED_MESSAGE)
58
+ .concat(isNotReservedHostIPAddress());
59
+
60
+ return Yup.object({
61
+ machineNetwork: getDisconnectedSingleStackMachineNetworkSchema(subnetIp),
62
+ gateway: gatewaySchema,
63
+ });
64
+ };
65
+
66
+ const buildDisconnectedSingleStackValidationSchema = (values: FormViewNetworkWideValues) => {
67
+ const subnetIp = values.ipConfigs.ipv4.machineNetwork.ip;
68
+
69
+ return Yup.object({
70
+ useVlan: Yup.boolean(),
71
+ vlanId: Yup.mixed().when('useVlan', {
72
+ is: (useVlan: boolean) => useVlan,
73
+ then: () =>
74
+ Yup.number()
75
+ .required(MUST_BE_A_NUMBER)
76
+ .min(1, `Must be more than or equal to 1`)
77
+ .max(MAX_VLAN_ID, `Must be less than or equal to ${MAX_VLAN_ID}`)
78
+ .test('not-number', MUST_BE_A_NUMBER, () => validateNumber(values.vlanId))
79
+ .nullable()
80
+ .transform(transformNumber) as Yup.NumberSchema,
81
+ }),
82
+ protocolType: Yup.string(),
83
+ dns: getDnsMatchingSubnetFamilyValidationSchema(subnetIp),
84
+ ipConfigs: Yup.object({
85
+ ipv4: getDisconnectedSingleStackIpConfigSchema(values.ipConfigs.ipv4),
86
+ ipv6: Yup.object<IpConfig>(),
87
+ }),
88
+ });
89
+ };
90
+
91
+ export const disconnectedNetworkWideValidationSchema = Yup.lazy(
92
+ (values: FormViewNetworkWideValues) => {
93
+ if (values.protocolType === 'dualStack') {
94
+ return buildNetworkWideValidationSchema(values);
95
+ }
96
+ return buildDisconnectedSingleStackValidationSchema(values);
97
+ },
98
+ );
@@ -19,7 +19,7 @@ import {
19
19
  } from '../../commonValidationSchemas';
20
20
 
21
21
  const REQUIRED_MESSAGE = 'A value is required';
22
- const MUST_BE_A_NUMBER = 'Must be a number';
22
+ export const MUST_BE_A_NUMBER = 'Must be a number';
23
23
  const ONLY_DIGITS_REGEX = /^\d+$/;
24
24
 
25
25
  export const MIN_PREFIX_LENGTH = 1;
@@ -68,7 +68,7 @@ const getMachineNetworkValidationSchema = (protocolVersion: ProtocolVersion) =>
68
68
  .transform(transformNumber) as Yup.NumberSchema, //add casting to not get typescript error caused by nullable
69
69
  });
70
70
 
71
- const getIPValidationSchema = (protocolVersion: ProtocolVersion) => {
71
+ export const getIPValidationSchema = (protocolVersion: ProtocolVersion) => {
72
72
  return getIpAddressValidationSchema(protocolVersion)
73
73
  .required(REQUIRED_MESSAGE)
74
74
  .concat(isNotReservedHostIPAddress(protocolVersion));
@@ -80,6 +80,11 @@ const getDNSValidationSchema = (protocolType: StaticProtocolType) => {
80
80
  .required(REQUIRED_MESSAGE)
81
81
  .concat(isNotReservedHostDNSAddress());
82
82
  }
83
+ if (protocolType === 'ipv6') {
84
+ return getMultipleIpAddressValidationSchema(ProtocolVersion.ipv6)
85
+ .required(REQUIRED_MESSAGE)
86
+ .concat(isNotReservedHostDNSAddress(ProtocolVersion.ipv6));
87
+ }
83
88
  return getMultipleIpAddressValidationSchema(ProtocolVersion.ipv4)
84
89
  .required(REQUIRED_MESSAGE)
85
90
  .concat(isNotReservedHostDNSAddress(ProtocolVersion.ipv4));
@@ -94,9 +99,11 @@ const getAddressDataValidationSchema = (protocolVersion: ProtocolVersion, ipConf
94
99
  });
95
100
  };
96
101
 
97
- export const networkWideValidationSchema = Yup.lazy((values: FormViewNetworkWideValues) => {
102
+ export const buildNetworkWideValidationSchema = (values: FormViewNetworkWideValues) => {
98
103
  const ipConfigsValidationSchemas = Yup.object({
99
- ipv4: getAddressDataValidationSchema(ProtocolVersion.ipv4, values.ipConfigs.ipv4),
104
+ ipv4: showProtocolVersion(values.protocolType, ProtocolVersion.ipv4)
105
+ ? getAddressDataValidationSchema(ProtocolVersion.ipv4, values.ipConfigs.ipv4)
106
+ : Yup.object<IpConfig>(),
100
107
  ipv6: showProtocolVersion(values.protocolType, ProtocolVersion.ipv6)
101
108
  ? getAddressDataValidationSchema(ProtocolVersion.ipv6, values.ipConfigs.ipv6)
102
109
  : Yup.object<IpConfig>(),
@@ -119,7 +126,9 @@ export const networkWideValidationSchema = Yup.lazy((values: FormViewNetworkWide
119
126
  dns: getDNSValidationSchema(values.protocolType),
120
127
  ipConfigs: ipConfigsValidationSchemas,
121
128
  });
122
- });
129
+ };
130
+
131
+ export const networkWideValidationSchema = Yup.lazy(buildNetworkWideValidationSchema);
123
132
 
124
133
  export const validateNumber = (vlanId: FormViewNetworkWideValues['vlanId']) => {
125
134
  //We need to validate that value is a number(without letters) and is not an exponential number (ex: 1e2)
@@ -95,8 +95,10 @@ export const StaticIpPage: React.FC<StaticIpPageProps> = ({
95
95
  <GridItem>
96
96
  <ClusterWizardStepHeader>Static network configurations</ClusterWizardStepHeader>
97
97
  <Content component={ContentVariants.small}>
98
- Network configuration can be done using either the form view or YAML view. Configurations
99
- done in this step are for discovering hosts.
98
+ Network configuration can be done using either the form view or YAML view.
99
+ {clusterWizardContext.installDisconnected
100
+ ? ' Configurations done in this step are for hosts.'
101
+ : ' Configurations done in this step are for discovering hosts.'}
100
102
  </Content>
101
103
  </GridItem>
102
104
 
@@ -108,7 +110,10 @@ export const StaticIpPage: React.FC<StaticIpPageProps> = ({
108
110
  onChangeView={onChangeView}
109
111
  />
110
112
  )}
111
- {initialStaticIpInfo.isDataComplete && isoRegenerationAlert}
113
+ {/* Disconnected flow has no Host discovery step; the ISO is generated at the end */}
114
+ {!clusterWizardContext.installDisconnected &&
115
+ initialStaticIpInfo.isDataComplete &&
116
+ isoRegenerationAlert}
112
117
  {content}
113
118
  </Grid>
114
119
  );
@@ -10,7 +10,7 @@ export enum ProtocolVersion {
10
10
  ipv6 = 'ipv6',
11
11
  }
12
12
 
13
- export type StaticProtocolType = 'ipv4' | 'dualStack';
13
+ export type StaticProtocolType = 'ipv4' | 'ipv6' | 'dualStack';
14
14
 
15
15
  export type Cidr = {
16
16
  ip: string;
@@ -0,0 +1,81 @@
1
+ import { Address4, Address6 } from 'ip-address';
2
+ import { InfraEnv } from '@openshift-assisted/types/assisted-installer-service';
3
+ import { FormViewNetworkWideValues, ProtocolVersion } from './dataTypes';
4
+ import { getEmptyIpConfig } from './emptyData';
5
+ import { getFormViewNetworkWideValues } from './fromInfraEnv';
6
+ import { networkWideToInfraEnvField } from './formDataToInfraEnvField';
7
+
8
+ export const DISCONNECTED_SINGLE_STACK_FIELD = ProtocolVersion.ipv4;
9
+
10
+ const isIpv6OnlyAddress = (ip: string): boolean => Address6.isValid(ip) && !Address4.isValid(ip);
11
+
12
+ export const normalizeDisconnectedNetworkWideForLoad = (
13
+ values: FormViewNetworkWideValues,
14
+ ): FormViewNetworkWideValues => {
15
+ if (values.protocolType === 'dualStack') {
16
+ return values;
17
+ }
18
+ if (values.protocolType === 'ipv6') {
19
+ return {
20
+ ...values,
21
+ protocolType: 'ipv4',
22
+ ipConfigs: {
23
+ ipv4: { ...values.ipConfigs.ipv6 },
24
+ ipv6: getEmptyIpConfig(),
25
+ },
26
+ };
27
+ }
28
+ return {
29
+ ...values,
30
+ ipConfigs: {
31
+ ipv4: { ...values.ipConfigs.ipv4 },
32
+ ipv6: getEmptyIpConfig(),
33
+ },
34
+ };
35
+ };
36
+
37
+ export const normalizeDisconnectedNetworkWideForSave = (
38
+ values: FormViewNetworkWideValues,
39
+ ): FormViewNetworkWideValues => {
40
+ if (values.protocolType === 'dualStack') {
41
+ return values;
42
+ }
43
+
44
+ const singleStackConfig = values.ipConfigs[DISCONNECTED_SINGLE_STACK_FIELD];
45
+ const subnetIp = singleStackConfig.machineNetwork.ip;
46
+
47
+ if (subnetIp && isIpv6OnlyAddress(subnetIp)) {
48
+ return {
49
+ ...values,
50
+ protocolType: 'ipv6',
51
+ ipConfigs: {
52
+ ipv4: getEmptyIpConfig(),
53
+ ipv6: { ...singleStackConfig },
54
+ },
55
+ };
56
+ }
57
+
58
+ return {
59
+ ...values,
60
+ protocolType: 'ipv4',
61
+ ipConfigs: {
62
+ ipv4: { ...singleStackConfig },
63
+ ipv6: getEmptyIpConfig(),
64
+ },
65
+ };
66
+ };
67
+
68
+ export const getDisconnectedFormViewNetworkWideValues = (
69
+ infraEnv: InfraEnv,
70
+ ): FormViewNetworkWideValues => {
71
+ const values = getFormViewNetworkWideValues(infraEnv);
72
+ return normalizeDisconnectedNetworkWideForLoad(values);
73
+ };
74
+
75
+ export const disconnectedNetworkWideToInfraEnvField = (
76
+ currentInfraEnv: InfraEnv,
77
+ networkWide: FormViewNetworkWideValues,
78
+ ) => {
79
+ const normalized = normalizeDisconnectedNetworkWideForSave(networkWide);
80
+ return networkWideToInfraEnvField(currentInfraEnv, normalized);
81
+ };
@@ -9,7 +9,7 @@ import {
9
9
  StaticProtocolType,
10
10
  } from './dataTypes';
11
11
  import {
12
- getProtocolVersionIdx,
12
+ getRouteGateway,
13
13
  YAML_COMMENT_CHAR,
14
14
  getMachineNetworks,
15
15
  getProtocolType,
@@ -89,7 +89,11 @@ const getGateway = (nmstate: Nmstate, protocolVersion: ProtocolVersion): string
89
89
  if (!routesConfig) {
90
90
  throw `Nmstate yaml doesn't contain routes section`;
91
91
  }
92
- return routesConfig[getProtocolVersionIdx(protocolVersion)]['next-hop-address'];
92
+ const gateway = getRouteGateway(routesConfig, protocolVersion);
93
+ if (!gateway) {
94
+ throw `Nmstate yaml doesn't contain a route for protocol version ${protocolVersion}`;
95
+ }
96
+ return gateway;
93
97
  };
94
98
 
95
99
  const getNetworkWideConfigurations = (
@@ -1,9 +1,15 @@
1
+ import { Address6 } from 'ip-address';
1
2
  import {
2
3
  HostStaticNetworkConfig,
3
4
  InfraEnv,
4
5
  } from '@openshift-assisted/types/assisted-installer-service';
5
6
  import { stringToJSON } from '../../../../../../common';
6
- import { FORM_VIEW_PREFIX, getProtocolType, getYamlComments } from './nmstateYaml';
7
+ import {
8
+ FORM_VIEW_PREFIX,
9
+ getProtocolType,
10
+ getYamlComments,
11
+ yamlToNmstateObject,
12
+ } from './nmstateYaml';
7
13
  import {
8
14
  StaticIpInfo,
9
15
  StaticIpView,
@@ -11,8 +17,10 @@ import {
11
17
  FormViewHostsValues,
12
18
  FormViewNetworkWideValues,
13
19
  YamlViewValues,
20
+ ProtocolVersion,
14
21
  } from './dataTypes';
15
- import { isDummyYaml } from './dummyData';
22
+ import { isDummyInterface, isDummyYaml } from './dummyData';
23
+ import { NmstateInterface } from './nmstateTypes';
16
24
  import { formDataFromInfraEnvField } from './formDataFromInfraEnvField';
17
25
  import { getEmptyFormViewHost } from './emptyData';
18
26
 
@@ -91,3 +99,50 @@ export const getYamlViewValues = (infraEnv: InfraEnv): YamlViewValues => {
91
99
  }
92
100
  return { hosts: staticNetworkConfig };
93
101
  };
102
+
103
+ export const canonicalizeIp = (ip: string): string => {
104
+ if (Address6.isValid(ip)) {
105
+ return new Address6(ip).correctForm();
106
+ }
107
+ return ip;
108
+ };
109
+
110
+ const getIpsFromInterface = (networkInterface: NmstateInterface): string[] => {
111
+ const ips: string[] = [];
112
+ for (const protocolVersion of [ProtocolVersion.ipv4, ProtocolVersion.ipv6]) {
113
+ const addresses = networkInterface[protocolVersion]?.address;
114
+ if (addresses?.length) {
115
+ for (const entry of addresses) {
116
+ if (entry.ip) {
117
+ ips.push(entry.ip);
118
+ }
119
+ }
120
+ }
121
+ }
122
+ return ips;
123
+ };
124
+
125
+ export const getHostIpsFromInfraEnv = (infraEnv: InfraEnv | undefined): string[] => {
126
+ if (!infraEnv) {
127
+ return [];
128
+ }
129
+ const staticNetworkConfig = getStaticNetworkConfig(infraEnv);
130
+ if (!staticNetworkConfig?.length) {
131
+ return [];
132
+ }
133
+
134
+ const hostIps: string[] = [];
135
+ for (const hostConfig of staticNetworkConfig) {
136
+ if (!hostConfig.networkYaml || isDummyYaml(hostConfig.networkYaml)) {
137
+ continue;
138
+ }
139
+ const nmstate = yamlToNmstateObject(hostConfig.networkYaml);
140
+ for (const networkInterface of nmstate.interfaces) {
141
+ if (isDummyInterface(networkInterface.name)) {
142
+ continue;
143
+ }
144
+ hostIps.push(...getIpsFromInterface(networkInterface));
145
+ }
146
+ }
147
+ return hostIps;
148
+ };
@@ -2,6 +2,7 @@ export * from './dataTypes';
2
2
  export * from './fromInfraEnv';
3
3
  export * from './emptyData';
4
4
  export * from './formDataToInfraEnvField';
5
+ export * from './disconnectedNetworkWide';
5
6
  export * from './dummyData';
6
7
  export * from './protocolVersion';
7
8
  export * from './machineNetwork';