@openshift-assisted/ui-lib 2.54.2 → 2.55.0

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 (148) hide show
  1. package/build/@types/cim/components/modals/CimConfiguration/CimStorageMissingAlert.d.ts.map +1 -1
  2. package/build/@types/cim/components/modals/CimConfiguration/constants.d.ts +1 -1
  3. package/build/@types/common/components/clusterConfiguration/utils.d.ts +9 -1
  4. package/build/@types/common/components/clusterConfiguration/utils.d.ts.map +1 -1
  5. package/build/@types/common/components/clusterWizard/networkingSteps/AvailableSubnetsControl.d.ts.map +1 -1
  6. package/build/@types/common/components/clusterWizard/networkingSteps/StackTypeControlGroup.d.ts.map +1 -1
  7. package/build/@types/common/components/operators/operatorSpecs.d.ts.map +1 -1
  8. package/build/@types/common/config/docs_links.d.ts.map +1 -1
  9. package/build/@types/ocm/components/Routes.d.ts +1 -1
  10. package/build/@types/ocm/components/Routes.d.ts.map +1 -1
  11. package/build/@types/ocm/components/clusterConfiguration/manifestsConfiguration/components/customManifestsValidationSchema.d.ts +10 -2
  12. package/build/@types/ocm/components/clusterConfiguration/manifestsConfiguration/components/customManifestsValidationSchema.d.ts.map +1 -1
  13. package/build/@types/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfiguration.d.ts.map +1 -1
  14. package/build/@types/ocm/components/clusterConfiguration/networkConfiguration/networkConfigurationValidation.d.ts.map +1 -1
  15. package/build/@types/ocm/components/clusterConfiguration/review/ReviewNetworkingTable.d.ts.map +1 -1
  16. package/build/@types/ocm/components/clusterConfiguration/review/utils.d.ts +1 -1
  17. package/build/@types/ocm/components/clusterConfiguration/review/utils.d.ts.map +1 -1
  18. package/build/@types/ocm/components/clusterDetail/AssistedInstallerDetailCard.d.ts +1 -1
  19. package/build/@types/ocm/components/clusterDetail/AssistedInstallerDetailCard.d.ts.map +1 -1
  20. package/build/@types/ocm/components/ui/Buttons/BackButton.d.ts +1 -1
  21. package/build/@types/ocm/components/ui/Buttons/BackButton.d.ts.map +1 -1
  22. package/build/@types/ocm/store/hooks.d.ts +1 -1
  23. package/build/@types/ocm/store/slices/clusters/selectors.d.ts +5 -61
  24. package/build/@types/ocm/store/slices/clusters/selectors.d.ts.map +1 -1
  25. package/build/@types/ocm/store/slices/feature-flags/selectors.d.ts +2 -20
  26. package/build/@types/ocm/store/slices/feature-flags/selectors.d.ts.map +1 -1
  27. package/build/@types/ocm/store/slices/infra-envs/selectors.d.ts +4 -21
  28. package/build/@types/ocm/store/slices/infra-envs/selectors.d.ts.map +1 -1
  29. package/build/cjs/cim/components/Agent/NoAgentsAlert.js +2 -2
  30. package/build/cjs/cim/components/Agent/NoAgentsAlert.js.map +1 -1
  31. package/build/cjs/cim/components/Agent/tableColumns.js +3 -3
  32. package/build/cjs/cim/components/Agent/tableColumns.js.map +1 -1
  33. package/build/cjs/cim/components/Hypershift/DetailsPage/NodePoolsTable.js +2 -2
  34. package/build/cjs/cim/components/Hypershift/DetailsPage/NodePoolsTable.js.map +1 -1
  35. package/build/cjs/cim/components/InfraEnv/AddHostDropdown.js +2 -2
  36. package/build/cjs/cim/components/InfraEnv/AddHostDropdown.js.map +1 -1
  37. package/build/cjs/cim/components/modals/CimConfiguration/CimConfigProgressAlert.js +3 -3
  38. package/build/cjs/cim/components/modals/CimConfiguration/CimConfigProgressAlert.js.map +1 -1
  39. package/build/cjs/cim/components/modals/CimConfiguration/CimConfigurationFormFields.js +1 -1
  40. package/build/cjs/cim/components/modals/CimConfiguration/CimConfigurationFormFields.js.map +1 -1
  41. package/build/cjs/cim/components/modals/CimConfiguration/CimConfigurationModal.js +2 -2
  42. package/build/cjs/cim/components/modals/CimConfiguration/CimConfigurationModal.js.map +1 -1
  43. package/build/cjs/cim/components/modals/CimConfiguration/CimStorageMissingAlert.js +3 -3
  44. package/build/cjs/cim/components/modals/CimConfiguration/CimStorageMissingAlert.js.map +1 -1
  45. package/build/cjs/cim/components/modals/CimConfiguration/constants.js +1 -1
  46. package/build/cjs/cim/components/modals/MassDeleteAgentModal.js +2 -2
  47. package/build/cjs/cim/components/modals/MassDeleteAgentModal.js.map +1 -1
  48. package/build/cjs/common/components/clusterConfiguration/utils.js +13 -1
  49. package/build/cjs/common/components/clusterConfiguration/utils.js.map +1 -1
  50. package/build/cjs/common/components/clusterConfiguration/utils.test.js +36 -0
  51. package/build/cjs/common/components/clusterConfiguration/utils.test.js.map +1 -1
  52. package/build/cjs/common/components/clusterWizard/networkingSteps/AvailableSubnetsControl.js +29 -31
  53. package/build/cjs/common/components/clusterWizard/networkingSteps/AvailableSubnetsControl.js.map +1 -1
  54. package/build/cjs/common/components/clusterWizard/networkingSteps/StackTypeControlGroup.js +19 -30
  55. package/build/cjs/common/components/clusterWizard/networkingSteps/StackTypeControlGroup.js.map +1 -1
  56. package/build/cjs/common/components/operators/operatorSpecs.js +0 -3
  57. package/build/cjs/common/components/operators/operatorSpecs.js.map +1 -1
  58. package/build/cjs/common/config/docs_links.js +2 -2
  59. package/build/cjs/common/config/docs_links.js.map +1 -1
  60. package/build/cjs/common/validationSchemas/addressValidation.js +1 -1
  61. package/build/cjs/common/validationSchemas/addressValidation.js.map +1 -1
  62. package/build/cjs/ocm/components/Routes.js +8 -8
  63. package/build/cjs/ocm/components/Routes.js.map +1 -1
  64. package/build/cjs/ocm/components/clusterConfiguration/manifestsConfiguration/components/customManifestsValidationSchema.js.map +1 -1
  65. package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfiguration.js +68 -17
  66. package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfiguration.js.map +1 -1
  67. package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfigurationForm.js +1 -1
  68. package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfigurationForm.js.map +1 -1
  69. package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/networkConfigurationValidation.js +50 -13
  70. package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/networkConfigurationValidation.js.map +1 -1
  71. package/build/cjs/ocm/components/clusterConfiguration/review/ReviewNetworkingTable.js +4 -2
  72. package/build/cjs/ocm/components/clusterConfiguration/review/ReviewNetworkingTable.js.map +1 -1
  73. package/build/cjs/ocm/components/clusterConfiguration/review/utils.js +7 -1
  74. package/build/cjs/ocm/components/clusterConfiguration/review/utils.js.map +1 -1
  75. package/build/cjs/ocm/components/clusterConfiguration/review/utils.test.js +35 -0
  76. package/build/cjs/ocm/components/clusterConfiguration/review/utils.test.js.map +1 -1
  77. package/build/cjs/ocm/components/clusterDetail/AssistedInstallerDetailCard.js +4 -4
  78. package/build/cjs/ocm/components/clusterDetail/AssistedInstallerDetailCard.js.map +1 -1
  79. package/build/cjs/ocm/components/clusterDetail/ClusterDetail.js +4 -4
  80. package/build/cjs/ocm/components/clusterDetail/ClusterDetail.js.map +1 -1
  81. package/build/cjs/ocm/components/clusterWizard/ClusterDetails.js +4 -4
  82. package/build/cjs/ocm/components/clusterWizard/ClusterDetails.js.map +1 -1
  83. package/build/cjs/ocm/components/clusterWizard/ClusterDetailsForm.js +2 -2
  84. package/build/cjs/ocm/components/clusterWizard/ClusterDetailsForm.js.map +1 -1
  85. package/build/cjs/ocm/components/clusterWizard/ClusterWizardContextProvider.js +2 -2
  86. package/build/cjs/ocm/components/clusterWizard/ClusterWizardContextProvider.js.map +1 -1
  87. package/build/cjs/ocm/components/clusterWizard/ClusterWizardFooter.js +2 -2
  88. package/build/cjs/ocm/components/clusterWizard/ClusterWizardFooter.js.map +1 -1
  89. package/build/cjs/ocm/components/clusterWizard/Operators.js +4 -4
  90. package/build/cjs/ocm/components/clusterWizard/Operators.js.map +1 -1
  91. package/build/cjs/ocm/components/clusterWizard/disconnected/ReviewStep.js +3 -3
  92. package/build/cjs/ocm/components/clusterWizard/disconnected/ReviewStep.js.map +1 -1
  93. package/build/cjs/ocm/components/clusters/ClusterBreadcrumbs.js +2 -2
  94. package/build/cjs/ocm/components/clusters/ClusterBreadcrumbs.js.map +1 -1
  95. package/build/cjs/ocm/components/clusters/ClusterPage.js +4 -4
  96. package/build/cjs/ocm/components/clusters/ClusterPage.js.map +1 -1
  97. package/build/cjs/ocm/components/clusters/Clusters.js +3 -3
  98. package/build/cjs/ocm/components/clusters/Clusters.js.map +1 -1
  99. package/build/cjs/ocm/components/clusters/ClustersListToolbar.js +3 -3
  100. package/build/cjs/ocm/components/clusters/ClustersListToolbar.js.map +1 -1
  101. package/build/cjs/ocm/components/ui/Buttons/BackButton.js +2 -2
  102. package/build/cjs/ocm/components/ui/Buttons/BackButton.js.map +1 -1
  103. package/build/cjs/ocm/store/slices/clusters/selectors.js +2 -2
  104. package/build/cjs/ocm/store/slices/clusters/selectors.js.map +1 -1
  105. package/build/cjs/ocm/store/slices/feature-flags/selectors.js.map +1 -1
  106. package/build/cjs/ocm/store/slices/infra-envs/selectors.js.map +1 -1
  107. package/lib/cim/components/Agent/NoAgentsAlert.tsx +1 -1
  108. package/lib/cim/components/Agent/tableColumns.tsx +1 -1
  109. package/lib/cim/components/Hypershift/DetailsPage/NodePoolsTable.tsx +1 -1
  110. package/lib/cim/components/InfraEnv/AddHostDropdown.tsx +1 -1
  111. package/lib/cim/components/modals/CimConfiguration/CimConfigProgressAlert.tsx +2 -2
  112. package/lib/cim/components/modals/CimConfiguration/CimConfigurationFormFields.tsx +1 -1
  113. package/lib/cim/components/modals/CimConfiguration/CimConfigurationModal.tsx +2 -2
  114. package/lib/cim/components/modals/CimConfiguration/CimStorageMissingAlert.tsx +5 -2
  115. package/lib/cim/components/modals/CimConfiguration/constants.ts +1 -1
  116. package/lib/cim/components/modals/MassDeleteAgentModal.tsx +1 -1
  117. package/lib/common/components/clusterConfiguration/utils.test.ts +43 -1
  118. package/lib/common/components/clusterConfiguration/utils.ts +13 -0
  119. package/lib/common/components/clusterWizard/networkingSteps/AvailableSubnetsControl.tsx +31 -57
  120. package/lib/common/components/clusterWizard/networkingSteps/StackTypeControlGroup.tsx +33 -38
  121. package/lib/common/components/operators/operatorSpecs.tsx +0 -16
  122. package/lib/common/config/docs_links.ts +2 -2
  123. package/lib/common/validationSchemas/addressValidation.tsx +2 -2
  124. package/lib/ocm/components/Routes.tsx +2 -2
  125. package/lib/ocm/components/clusterConfiguration/manifestsConfiguration/components/customManifestsValidationSchema.tsx +2 -2
  126. package/lib/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfiguration.tsx +86 -22
  127. package/lib/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfigurationForm.tsx +2 -2
  128. package/lib/ocm/components/clusterConfiguration/networkConfiguration/networkConfigurationValidation.ts +57 -20
  129. package/lib/ocm/components/clusterConfiguration/review/ReviewNetworkingTable.tsx +4 -2
  130. package/lib/ocm/components/clusterConfiguration/review/utils.test.ts +41 -0
  131. package/lib/ocm/components/clusterConfiguration/review/utils.tsx +5 -2
  132. package/lib/ocm/components/clusterDetail/AssistedInstallerDetailCard.tsx +1 -1
  133. package/lib/ocm/components/clusterDetail/ClusterDetail.tsx +3 -3
  134. package/lib/ocm/components/clusterWizard/ClusterDetails.tsx +2 -2
  135. package/lib/ocm/components/clusterWizard/ClusterDetailsForm.tsx +1 -1
  136. package/lib/ocm/components/clusterWizard/ClusterWizardContextProvider.tsx +1 -1
  137. package/lib/ocm/components/clusterWizard/ClusterWizardFooter.tsx +1 -1
  138. package/lib/ocm/components/clusterWizard/Operators.tsx +2 -2
  139. package/lib/ocm/components/clusterWizard/disconnected/ReviewStep.tsx +2 -2
  140. package/lib/ocm/components/clusters/ClusterBreadcrumbs.tsx +1 -1
  141. package/lib/ocm/components/clusters/ClusterPage.tsx +1 -1
  142. package/lib/ocm/components/clusters/Clusters.tsx +2 -2
  143. package/lib/ocm/components/clusters/ClustersListToolbar.tsx +2 -2
  144. package/lib/ocm/components/ui/Buttons/BackButton.tsx +1 -1
  145. package/lib/ocm/store/slices/clusters/selectors.tsx +7 -5
  146. package/lib/ocm/store/slices/feature-flags/selectors.ts +5 -2
  147. package/lib/ocm/store/slices/infra-envs/selectors.ts +4 -2
  148. package/package.json +8 -8
@@ -6,13 +6,16 @@ import {
6
6
  canBeDualStack,
7
7
  CpuArchitecture,
8
8
  DUAL_STACK,
9
+ getIPv6FromDualstack,
9
10
  HostSubnets,
10
11
  isAdvNetworkConf,
11
12
  isSNO,
12
13
  NetworkConfigurationValues,
14
+ NO_SUBNET_SET,
13
15
  selectApiVip,
14
16
  selectIngressVip,
15
17
  } from '../../../../common';
18
+ import { Address6 } from 'ip-address';
16
19
  import {
17
20
  ManagedNetworkingControlGroup,
18
21
  UserManagedNetworkingTextContent,
@@ -36,6 +39,7 @@ import {
36
39
  } from '@openshift-assisted/types/assisted-installer-service';
37
40
  import useSupportLevelsAPI from '../../../hooks/useSupportLevelsAPI';
38
41
  import { isOciPlatformType } from '../../utils';
42
+ import { useFeature } from '../../../hooks/use-feature';
39
43
 
40
44
  export type NetworkConfigurationProps = VirtualIPControlGroupProps & {
41
45
  hostSubnets: HostSubnets;
@@ -122,6 +126,33 @@ const getManagedNetworkingState = (
122
126
  };
123
127
  };
124
128
 
129
+ type DefaultNetworkSettings = NetworkConfigurationProps['defaultNetworkSettings'];
130
+
131
+ const getNetworkDefaultsByFamily = (
132
+ settings: DefaultNetworkSettings,
133
+ isDualStack: boolean,
134
+ isIPv6: boolean,
135
+ ) => {
136
+ if (isDualStack) {
137
+ return {
138
+ clusterNetworkDefaults: settings.clusterNetworksDualstack,
139
+ serviceNetworkDefaults: settings.serviceNetworksDualstack,
140
+ };
141
+ }
142
+ if (isIPv6) {
143
+ const ipv6Cluster = getIPv6FromDualstack(settings.clusterNetworksDualstack);
144
+ const ipv6Service = getIPv6FromDualstack(settings.serviceNetworksDualstack);
145
+ return {
146
+ clusterNetworkDefaults: ipv6Cluster ? [ipv6Cluster] : settings.clusterNetworksIpv4,
147
+ serviceNetworkDefaults: ipv6Service ? [ipv6Service] : settings.serviceNetworksIpv4,
148
+ };
149
+ }
150
+ return {
151
+ clusterNetworkDefaults: settings.clusterNetworksIpv4,
152
+ serviceNetworkDefaults: settings.serviceNetworksIpv4,
153
+ };
154
+ };
155
+
125
156
  const NetworkConfiguration = ({
126
157
  cluster,
127
158
  hostSubnets,
@@ -136,8 +167,10 @@ const NetworkConfiguration = ({
136
167
  cluster.platform?.type,
137
168
  );
138
169
  const { isViewerMode } = useSelector(selectCurrentClusterPermissionsState);
139
- const { setFieldValue, values, validateField } = useFormikContext<NetworkConfigurationValues>();
170
+ const { setFieldValue, values, validateField, validateForm } =
171
+ useFormikContext<NetworkConfigurationValues>();
140
172
 
173
+ const isSingleClusterFeature = useFeature('ASSISTED_INSTALLER_SINGLE_CLUSTER_FEATURE');
141
174
  const isSNOCluster = isSNO(cluster);
142
175
  const isUserManagedNetworking = values.managedNetworkingType === 'userManaged';
143
176
  const isDualStack = values.stackType === DUAL_STACK;
@@ -177,30 +210,18 @@ const NetworkConfiguration = ({
177
210
  setAdvanced(checked);
178
211
 
179
212
  if (!checked) {
180
- setFieldValue(
181
- 'clusterNetworks',
182
- isDualStack
183
- ? defaultNetworkSettings.clusterNetworksDualstack
184
- : defaultNetworkSettings.clusterNetworksIpv4,
185
- true,
186
- );
187
- setFieldValue(
188
- 'serviceNetworks',
189
- isDualStack
190
- ? defaultNetworkSettings.serviceNetworksDualstack
191
- : defaultNetworkSettings.serviceNetworksIpv4,
192
- true,
213
+ const primaryCidr = values.machineNetworks?.[0]?.cidr;
214
+ const isIPv6Stack = !!primaryCidr && Address6.isValid(primaryCidr);
215
+ const { clusterNetworkDefaults, serviceNetworkDefaults } = getNetworkDefaultsByFamily(
216
+ defaultNetworkSettings,
217
+ isDualStack,
218
+ isIPv6Stack,
193
219
  );
220
+ setFieldValue('clusterNetworks', clusterNetworkDefaults, true);
221
+ setFieldValue('serviceNetworks', serviceNetworkDefaults, true);
194
222
  }
195
223
  },
196
- [
197
- setFieldValue,
198
- isDualStack,
199
- defaultNetworkSettings.clusterNetworksDualstack,
200
- defaultNetworkSettings.clusterNetworksIpv4,
201
- defaultNetworkSettings.serviceNetworksDualstack,
202
- defaultNetworkSettings.serviceNetworksIpv4,
203
- ],
224
+ [setFieldValue, isDualStack, values.machineNetworks, defaultNetworkSettings],
204
225
  );
205
226
 
206
227
  React.useEffect(() => {
@@ -209,6 +230,47 @@ const NetworkConfiguration = ({
209
230
  }
210
231
  }, [isDualStack, isViewerMode, toggleAdvConfiguration]);
211
232
 
233
+ React.useEffect(() => {
234
+ if (!isSingleClusterFeature || isViewerMode || isDualStack || isAdvanced) {
235
+ return;
236
+ }
237
+ const primaryCidr = values.machineNetworks?.[0]?.cidr;
238
+ if (!primaryCidr || primaryCidr === NO_SUBNET_SET) {
239
+ return;
240
+ }
241
+ const isIPv6 = Address6.isValid(primaryCidr);
242
+ const currentClusterCidr = values.clusterNetworks?.[0]?.cidr;
243
+ const clusterIsIPv6 = currentClusterCidr ? Address6.isValid(currentClusterCidr) : null;
244
+
245
+ if (clusterIsIPv6 === isIPv6) {
246
+ return;
247
+ }
248
+
249
+ const { clusterNetworkDefaults, serviceNetworkDefaults } = getNetworkDefaultsByFamily(
250
+ defaultNetworkSettings,
251
+ false,
252
+ isIPv6,
253
+ );
254
+ if (clusterNetworkDefaults) {
255
+ setFieldValue('clusterNetworks', clusterNetworkDefaults);
256
+ }
257
+ if (serviceNetworkDefaults) {
258
+ setFieldValue('serviceNetworks', serviceNetworkDefaults);
259
+ }
260
+ void validateForm();
261
+ }, [
262
+ isSingleClusterFeature,
263
+ isViewerMode,
264
+ isDualStack,
265
+ isAdvanced,
266
+ values.machineNetworks,
267
+ values.clusterNetworks,
268
+ defaultNetworkSettings,
269
+ setFieldValue,
270
+ validateForm,
271
+ cluster.id,
272
+ ]);
273
+
212
274
  const managedNetworkingState = React.useMemo(
213
275
  () =>
214
276
  getManagedNetworkingState(
@@ -252,6 +314,7 @@ const NetworkConfiguration = ({
252
314
  hostSubnets={hostSubnets}
253
315
  defaultNetworkValues={defaultNetworkSettings}
254
316
  isViewerMode={isViewerMode}
317
+ allowSingleStackIPv6={isSingleClusterFeature}
255
318
  />
256
319
  </StackItem>
257
320
  )}
@@ -277,6 +340,7 @@ const NetworkConfiguration = ({
277
340
  }
278
341
  openshiftVersion={cluster.openshiftVersion}
279
342
  isViewerMode={isViewerMode}
343
+ allowSingleStackIPv6={isSingleClusterFeature}
280
344
  />
281
345
  </StackItem>
282
346
  )}
@@ -9,7 +9,7 @@ import {
9
9
  getFormikErrorFields,
10
10
  getHostSubnets,
11
11
  HostSubnets,
12
- SINGLE_STACK,
12
+ DUAL_STACK,
13
13
  isSNO,
14
14
  LoadingState,
15
15
  NetworkConfigurationValues,
@@ -262,7 +262,7 @@ const NetworkConfigurationPage = ({ cluster }: { cluster: Cluster }) => {
262
262
  if (values.vipDhcpAllocation) {
263
263
  delete params.apiVips;
264
264
  delete params.ingressVips;
265
- } else if (values.stackType === SINGLE_STACK) {
265
+ } else if (values.stackType !== DUAL_STACK) {
266
266
  // The API will rebuild the default machineNetwork
267
267
  params.machineNetworks = [];
268
268
  }
@@ -1,8 +1,11 @@
1
1
  import * as Yup from 'yup';
2
2
  import { TFunction } from 'i18next';
3
+ import { Address6 } from 'ip-address';
3
4
  import {
4
5
  clusterNetworksValidationSchema,
5
6
  dualStackValidationSchema,
7
+ getIPv6FromDualstack,
8
+ isPrimaryIPv6,
6
9
  HostSubnets,
7
10
  isDualStack,
8
11
  isSNO,
@@ -10,6 +13,7 @@ import {
10
13
  NetworkConfigurationValues,
11
14
  serviceNetworkValidationSchema,
12
15
  IPv4ValidationSchema,
16
+ IPv6ValidationSchema,
13
17
  sshPublicKeyValidationSchema,
14
18
  SINGLE_STACK,
15
19
  DUAL_STACK,
@@ -36,6 +40,38 @@ export const getNetworkInitialValues = (
36
40
  ): NetworkConfigurationValues => {
37
41
  const isSNOCluster = isSNO(cluster);
38
42
  const isDualStackType = isDualStack(cluster);
43
+ const primaryMachineNetworkCidr = cluster.machineNetworks?.[0]?.cidr;
44
+ const isIPv6OnlyStack =
45
+ !isDualStackType && !!primaryMachineNetworkCidr && Address6.isValid(primaryMachineNetworkCidr);
46
+
47
+ const getStackType = (): NetworkConfigurationValues['stackType'] => {
48
+ if (isDualStackType) return DUAL_STACK;
49
+ return SINGLE_STACK;
50
+ };
51
+
52
+ const getDefaultClusterNetworks = () => {
53
+ if (isDualStackType) return defaultNetworkValues.clusterNetworksDualstack;
54
+ if (isIPv6OnlyStack) {
55
+ const ipv6Entry = getIPv6FromDualstack(defaultNetworkValues.clusterNetworksDualstack);
56
+ return ipv6Entry ? [{ ...ipv6Entry, clusterId: cluster?.id }] : undefined;
57
+ }
58
+ return defaultNetworkValues.clusterNetworksIpv4?.map((network) => ({
59
+ ...network,
60
+ clusterId: cluster?.id,
61
+ }));
62
+ };
63
+
64
+ const getDefaultServiceNetworks = () => {
65
+ if (isDualStackType) return defaultNetworkValues.serviceNetworksDualstack;
66
+ if (isIPv6OnlyStack) {
67
+ const ipv6Entry = getIPv6FromDualstack(defaultNetworkValues.serviceNetworksDualstack);
68
+ return ipv6Entry ? [{ ...ipv6Entry, clusterId: cluster?.id }] : undefined;
69
+ }
70
+ return defaultNetworkValues.serviceNetworksIpv4?.map((network) => ({
71
+ ...network,
72
+ clusterId: cluster?.id,
73
+ }));
74
+ };
39
75
 
40
76
  return {
41
77
  apiVips: cluster.apiVips,
@@ -50,23 +86,9 @@ export const getNetworkInitialValues = (
50
86
  : 'clusterManaged',
51
87
  networkType: cluster.networkType || NETWORK_TYPE_OVN,
52
88
  machineNetworks: cluster.machineNetworks || [],
53
- stackType: isDualStackType ? DUAL_STACK : SINGLE_STACK,
54
- clusterNetworks:
55
- cluster.clusterNetworks ||
56
- (isDualStackType
57
- ? defaultNetworkValues.clusterNetworksDualstack
58
- : defaultNetworkValues.clusterNetworksIpv4?.map((network) => ({
59
- ...network,
60
- clusterId: cluster?.id,
61
- }))),
62
- serviceNetworks:
63
- cluster.serviceNetworks ||
64
- (isDualStackType
65
- ? defaultNetworkValues.serviceNetworksDualstack
66
- : defaultNetworkValues.serviceNetworksIpv4?.map((network) => ({
67
- ...network,
68
- clusterId: cluster?.id,
69
- }))),
89
+ stackType: getStackType(),
90
+ clusterNetworks: cluster.clusterNetworks || getDefaultClusterNetworks(),
91
+ serviceNetworks: cluster.serviceNetworks || getDefaultServiceNetworks(),
70
92
  };
71
93
  };
72
94
 
@@ -87,7 +109,12 @@ export const getNetworkConfigurationValidationSchema = (
87
109
  : machineNetworksValidationSchema.when('stackType', {
88
110
  is: (stackType: NetworkConfigurationValues['stackType']) =>
89
111
  stackType === SINGLE_STACK,
90
- then: () => machineNetworksValidationSchema.concat(IPv4ValidationSchema),
112
+ then: () => {
113
+ if (isPrimaryIPv6(values.machineNetworks)) {
114
+ return machineNetworksValidationSchema.concat(IPv6ValidationSchema);
115
+ }
116
+ return machineNetworksValidationSchema.concat(IPv4ValidationSchema);
117
+ },
91
118
  otherwise: () =>
92
119
  values.machineNetworks && values.machineNetworks?.length >= 2
93
120
  ? machineNetworksValidationSchema.concat(
@@ -97,13 +124,23 @@ export const getNetworkConfigurationValidationSchema = (
97
124
  }),
98
125
  clusterNetworks: clusterNetworksValidationSchema(t).when('stackType', {
99
126
  is: (stackType: NetworkConfigurationValues['stackType']) => stackType === SINGLE_STACK,
100
- then: (schema) => schema.concat(IPv4ValidationSchema),
127
+ then: (schema) => {
128
+ if (isPrimaryIPv6(values.machineNetworks)) {
129
+ return schema.concat(IPv6ValidationSchema);
130
+ }
131
+ return schema.concat(IPv4ValidationSchema);
132
+ },
101
133
  otherwise: (schema) =>
102
134
  schema.concat(dualStackValidationSchema('cluster network', t, openshiftVersion)),
103
135
  }),
104
136
  serviceNetworks: serviceNetworkValidationSchema(t).when('stackType', {
105
137
  is: (stackType: NetworkConfigurationValues['stackType']) => stackType === SINGLE_STACK,
106
- then: (schema) => schema.concat(IPv4ValidationSchema),
138
+ then: (schema) => {
139
+ if (isPrimaryIPv6(values.machineNetworks)) {
140
+ return schema.concat(IPv6ValidationSchema);
141
+ }
142
+ return schema.concat(IPv4ValidationSchema);
143
+ },
107
144
  otherwise: (schema) =>
108
145
  schema.concat(dualStackValidationSchema('service network', t, openshiftVersion)),
109
146
  }),
@@ -8,6 +8,7 @@ import {
8
8
  useTranslation,
9
9
  } from '../../../../common';
10
10
  import { getManagementType, getStackTypeLabel } from './utils';
11
+ import { useFeature } from '../../../hooks/use-feature';
11
12
  import { Cluster } from '@openshift-assisted/types/assisted-installer-service';
12
13
 
13
14
  type ReviewTableRowsType = {
@@ -17,6 +18,7 @@ type ReviewTableRowsType = {
17
18
 
18
19
  export const ReviewNetworkingTable = ({ cluster }: { cluster: Cluster }) => {
19
20
  const { t } = useTranslation();
21
+ const isSingleClusterFeature = useFeature('ASSISTED_INSTALLER_SINGLE_CLUSTER_FEATURE');
20
22
  const rows = React.useMemo(() => {
21
23
  const networkRows = [
22
24
  {
@@ -34,7 +36,7 @@ export const ReviewNetworkingTable = ({ cluster }: { cluster: Cluster }) => {
34
36
  cells: [
35
37
  { title: 'Stack type', colSpan: 2 },
36
38
  {
37
- title: getStackTypeLabel(cluster),
39
+ title: getStackTypeLabel(cluster, isSingleClusterFeature),
38
40
  props: { 'data-testid': 'stack-type', colSpan: 2 },
39
41
  },
40
42
  ],
@@ -125,7 +127,7 @@ export const ReviewNetworkingTable = ({ cluster }: { cluster: Cluster }) => {
125
127
  });
126
128
 
127
129
  return networkRows;
128
- }, [cluster]);
130
+ }, [cluster, isSingleClusterFeature]);
129
131
 
130
132
  const rowsAdvanced = React.useMemo(() => {
131
133
  return [
@@ -169,6 +169,47 @@ describe('getStackTypeLabel', () => {
169
169
  });
170
170
  });
171
171
 
172
+ describe('with isSingleClusterFeature=true', () => {
173
+ test('returns "Single stack" for IPv4 single-stack cluster', () => {
174
+ const cluster = createCluster({
175
+ machineNetworks: [createMachineNetwork('192.168.1.0/24')],
176
+ clusterNetworks: [createClusterNetwork('10.128.0.0/14', 23)],
177
+ serviceNetworks: [createServiceNetwork('172.30.0.0/16')],
178
+ });
179
+
180
+ expect(getStackTypeLabel(cluster, true)).toBe('Single stack');
181
+ });
182
+
183
+ test('returns "Single stack" for IPv6 single-stack cluster', () => {
184
+ const cluster = createCluster({
185
+ machineNetworks: [createMachineNetwork('2001:db8::/64')],
186
+ clusterNetworks: [createClusterNetwork('fd01::/48', 64)],
187
+ serviceNetworks: [createServiceNetwork('fd02::/112')],
188
+ });
189
+
190
+ expect(getStackTypeLabel(cluster, true)).toBe('Single stack');
191
+ });
192
+
193
+ test('still returns "Dual-stack" for dual-stack cluster', () => {
194
+ const cluster = createCluster({
195
+ machineNetworks: [
196
+ createMachineNetwork('192.168.1.0/24'),
197
+ createMachineNetwork('2001:db8::/64'),
198
+ ],
199
+ clusterNetworks: [
200
+ createClusterNetwork('10.128.0.0/14', 23),
201
+ createClusterNetwork('fd01::/48', 64),
202
+ ],
203
+ serviceNetworks: [
204
+ createServiceNetwork('172.30.0.0/16'),
205
+ createServiceNetwork('fd02::/112'),
206
+ ],
207
+ });
208
+
209
+ expect(getStackTypeLabel(cluster, true)).toBe('Dual-stack');
210
+ });
211
+ });
212
+
172
213
  describe('edge cases', () => {
173
214
  test('handles undefined openshiftVersion', () => {
174
215
  const cluster = createCluster({
@@ -5,8 +5,11 @@ import { Cluster, DiskEncryption } from '@openshift-assisted/types/assisted-inst
5
5
  export const getManagementType = ({ userManagedNetworking }: Cluster): string =>
6
6
  userManagedNetworking ? 'User-managed networking' : 'Cluster-managed networking';
7
7
 
8
- export const getStackTypeLabel = (cluster: Cluster): string =>
9
- isDualStack(cluster) ? 'Dual-stack' : 'IPv4';
8
+ export const getStackTypeLabel = (cluster: Cluster, isSingleClusterFeature = false): string => {
9
+ if (isDualStack(cluster)) return 'Dual-stack';
10
+ if (isSingleClusterFeature) return 'Single stack';
11
+ return 'IPv4';
12
+ };
10
13
 
11
14
  export const getDiskEncryptionEnabledOnStatus = (diskEncryption: DiskEncryption['enableOn']) => {
12
15
  let diskEncryptionType = null;
@@ -33,7 +33,7 @@ import {
33
33
  unstable_HistoryRouter as HistoryRouter,
34
34
  HistoryRouterProps,
35
35
  BrowserRouter,
36
- } from 'react-router-dom-v5-compat';
36
+ } from 'react-router';
37
37
 
38
38
  type AssistedInstallerDetailCardProps = {
39
39
  aiClusterId: string;
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { useNavigate } from 'react-router-dom-v5-compat';
2
+ import { useNavigate } from 'react-router';
3
3
  import { Stack, StackItem, Content, ButtonVariant, GridItem, Grid } from '@patternfly/react-core';
4
4
  import { ToolbarButton, Alerts, getEnabledHosts, selectOlmOperators, isSNO } from '../../../common';
5
5
  import { Cluster } from '@openshift-assisted/types/assisted-installer-service';
@@ -41,7 +41,7 @@ const ClusterDetail: React.FC<ClusterDetailProps> = ({ cluster }) => {
41
41
  try {
42
42
  const { data } = await ClustersAPI.allowAddHosts(cluster.id);
43
43
  updateCluster(data);
44
- navigate(`${routeBasePath}/clusters/${cluster.id}`);
44
+ void navigate(`${routeBasePath}/clusters/${cluster.id}`);
45
45
  } catch (e) {
46
46
  handleApiError(e);
47
47
  }
@@ -102,7 +102,7 @@ const ClusterDetail: React.FC<ClusterDetailProps> = ({ cluster }) => {
102
102
  {!isSingleClusterFeatureEnabled && (
103
103
  <ToolbarButton
104
104
  variant={ButtonVariant.link}
105
- onClick={() => navigate('..')}
105
+ onClick={() => void navigate('..')}
106
106
  id={getClusterDetailId('button-back-to-all-clusters')}
107
107
  >
108
108
  Back to all clusters
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { useLocation, useNavigate } from 'react-router-dom-v5-compat';
2
+ import { useLocation, useNavigate } from 'react-router';
3
3
  import { useDispatch } from 'react-redux';
4
4
  import {
5
5
  useAlerts,
@@ -89,7 +89,7 @@ const ClusterDetails = ({ cluster, infraEnv }: ClusterDetailsProps) => {
89
89
  isAssistedMigration,
90
90
  isSingleClusterFeatureEnabled,
91
91
  );
92
- navigate(`../${cluster.id}`, { state: ClusterWizardFlowStateNew });
92
+ void navigate(`../${cluster.id}`, { state: ClusterWizardFlowStateNew });
93
93
 
94
94
  if (isAssistedMigration) {
95
95
  try {
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { useSelector } from 'react-redux';
3
- import { useLocation } from 'react-router-dom-v5-compat';
3
+ import { useLocation } from 'react-router';
4
4
  import { Flex, Grid, GridItem } from '@patternfly/react-core';
5
5
  import isUndefined from 'lodash-es/isUndefined.js';
6
6
  import { Formik, FormikHelpers } from 'formik';
@@ -1,5 +1,5 @@
1
1
  import React, { PropsWithChildren } from 'react';
2
- import { useLocation } from 'react-router-dom-v5-compat';
2
+ import { useLocation } from 'react-router';
3
3
  import { ClusterWizardContextType, ClusterWizardContext } from './ClusterWizardContext';
4
4
  import {
5
5
  ClusterWizardFlowStateType,
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { useNavigate } from 'react-router-dom-v5-compat';
2
+ import { useNavigate } from 'react-router';
3
3
  import { Alert, AlertGroup, AlertVariant } from '@patternfly/react-core';
4
4
  import {
5
5
  WizardFooter,
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { useDispatch } from 'react-redux';
3
- import { useLocation, useNavigate } from 'react-router-dom-v5-compat';
3
+ import { useLocation, useNavigate } from 'react-router';
4
4
  import { Formik, FormikConfig, useFormikContext } from 'formik';
5
5
  import { Cluster } from '@openshift-assisted/types/assisted-installer-service';
6
6
  import {
@@ -74,7 +74,7 @@ const OperatorsForm = ({ cluster }: { cluster: Cluster }) => {
74
74
 
75
75
  const handleNext = () => {
76
76
  if (window.location.pathname.indexOf('assisted-installer') > -1) {
77
- navigate(pathname, { state: undefined, replace: true });
77
+ void navigate(pathname, { state: undefined, replace: true });
78
78
  }
79
79
  clusterWizardContext.moveNext();
80
80
  };
@@ -26,7 +26,7 @@ import {
26
26
  } from '@patternfly/react-core';
27
27
  import { Formik } from 'formik';
28
28
  import { saveAs } from 'file-saver';
29
- import { useNavigate } from 'react-router-dom-v5-compat';
29
+ import { useNavigate } from 'react-router';
30
30
 
31
31
  import { getOperatorSpecs } from '../../../../common/components/operators/operatorSpecs';
32
32
 
@@ -51,7 +51,7 @@ const ReviewStep = () => {
51
51
  <ClusterWizardFooter
52
52
  onNext={() => {
53
53
  downloadUrl && saveAs(downloadUrl);
54
- navigate('/cluster-list');
54
+ void navigate('/cluster-list');
55
55
  }}
56
56
  onBack={moveBack}
57
57
  nextButtonText="Download ISO"
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { Breadcrumb, BreadcrumbItem, PageSection } from '@patternfly/react-core';
3
- import { Link } from 'react-router-dom-v5-compat';
3
+ import { Link } from 'react-router';
4
4
  import { isInOcm } from '../../../common/api';
5
5
 
6
6
  const ClusterBreadcrumbs = ({ clusterName }: { clusterName?: string }) => {
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { useParams, Navigate } from 'react-router-dom-v5-compat';
2
+ import { useParams, Navigate } from 'react-router';
3
3
  import { useDispatch } from 'react-redux';
4
4
  import { PageSection, Content } from '@patternfly/react-core';
5
5
  import {
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { useNavigate } from 'react-router-dom-v5-compat';
2
+ import { useNavigate } from 'react-router';
3
3
  import { Button, ButtonVariant, Content, PageSection } from '@patternfly/react-core';
4
4
  import { AddCircleOIcon } from '@patternfly/react-icons/dist/js/icons/add-circle-o-icon';
5
5
  import {
@@ -85,7 +85,7 @@ const Clusters = () => {
85
85
  primaryAction={
86
86
  <Button
87
87
  variant={ButtonVariant.primary}
88
- onClick={() => navigate(`~new`)}
88
+ onClick={() => void navigate(`~new`)}
89
89
  id="empty-state-new-cluster-button"
90
90
  data-ouia-id="button-create-new-cluster"
91
91
  >
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { useNavigate } from 'react-router-dom-v5-compat';
2
+ import { useNavigate } from 'react-router';
3
3
  import {
4
4
  Toolbar,
5
5
  ToolbarItem,
@@ -172,7 +172,7 @@ const ClustersListToolbar: React.FC<ClustersListToolbarProps> = ({
172
172
  </CustomToolbarFilter>
173
173
  <ToolbarButton
174
174
  variant={ButtonVariant.primary}
175
- onClick={() => navigate(`~new`)}
175
+ onClick={() => void navigate(`~new`)}
176
176
  id="button-create-new-cluster"
177
177
  data-ouia-id="button-create-new-cluster"
178
178
  >
@@ -1,4 +1,4 @@
1
- import { Link, LinkProps } from 'react-router-dom-v5-compat';
1
+ import { Link, LinkProps } from 'react-router';
2
2
  import type { ButtonProps } from '@patternfly/react-core';
3
3
  import { Button } from '@patternfly/react-core';
4
4
  import React from 'react';
@@ -1,7 +1,8 @@
1
1
  import React from 'react';
2
2
  import { createSelector } from '@reduxjs/toolkit';
3
+ import type { Selector } from 'reselect';
3
4
  import type { IRow, IRowData } from '@patternfly/react-table';
4
- import { Link } from 'react-router-dom-v5-compat';
5
+ import { Link } from 'react-router';
5
6
  import type { TFunction } from 'i18next';
6
7
  import {
7
8
  DASH,
@@ -22,7 +23,7 @@ const selectClusters = (state: RootStateDay1) =>
22
23
  state.clusters.data.filter((c) => c.kind !== 'DisconnectedCluster');
23
24
  const clustersUIState = (state: RootStateDay1) => state.clusters.uiState;
24
25
 
25
- export const selectClustersUIState = createSelector(
26
+ export const selectClustersUIState: (state: RootStateDay1) => ResourceUIState = createSelector(
26
27
  [selectClusters, clustersUIState],
27
28
  (clusters, uiState): ResourceUIState => {
28
29
  const { LOADED, EMPTY } = ResourceUIState;
@@ -95,13 +96,14 @@ const clusterToClusterTableRow = (cluster: Cluster, t: TFunction): IRow => {
95
96
  export const getClusterTableStatusCell = (rowData: IRowData) =>
96
97
  rowData?.cells?.[3] as HumanizedSortable;
97
98
 
98
- export const selectClusterTableRows = (t: TFunction) => {
99
+ export const selectClusterTableRows = (t: TFunction): Selector<RootStateDay1, ClusterTableRows> => {
99
100
  return createSelector(
100
101
  selectClusters,
101
102
  (clusters): ClusterTableRows => clusters.map((c) => clusterToClusterTableRow(c, t)),
102
103
  );
103
104
  };
104
105
 
105
- export const selectClusterNames = createSelector(selectClusters, (clusters) =>
106
- clusters.map((c) => c.name),
106
+ export const selectClusterNames: Selector<RootStateDay1, (string | undefined)[]> = createSelector(
107
+ selectClusters,
108
+ (clusters) => clusters.map((c) => c.name),
107
109
  );
@@ -1,12 +1,15 @@
1
1
  import type { FeatureListType } from '../../../../common/features/featureGate';
2
2
  import type { RootStateDay1 } from '../../store-day1';
3
3
  import { createSelector } from '@reduxjs/toolkit';
4
+ import type { Selector } from 'reselect';
4
5
 
5
6
  export const selectFeatureFlagsSlice = (state: RootStateDay1) => state.featureFlags;
6
7
 
7
- export const isFeatureEnabled = (featureId: keyof FeatureListType) =>
8
+ export const isFeatureEnabled = (
9
+ featureId: keyof FeatureListType,
10
+ ): Selector<RootStateDay1, boolean> =>
8
11
  createSelector(
9
12
  selectFeatureFlagsSlice,
10
- (featureFlags: ReturnType<typeof selectFeatureFlagsSlice>) =>
13
+ (featureFlags: ReturnType<typeof selectFeatureFlagsSlice>): boolean =>
11
14
  featureFlags.data[featureId] ?? false,
12
15
  );
@@ -1,10 +1,12 @@
1
1
  import { createSelector } from '@reduxjs/toolkit';
2
+ import type { Selector } from 'reselect';
2
3
  import type { RootStateDay1 } from '../../store-day1';
3
4
  import type { CpuArchitecture } from '../../../../common';
5
+ import type { InfraEnv } from '@openshift-assisted/types/assisted-installer-service';
4
6
 
5
7
  export const selectInfraEnvsSlice = (state: RootStateDay1) => state.infraEnvs;
6
8
  export const selectInfraEnvByCpuArchitecture = createSelector(
7
9
  [selectInfraEnvsSlice, (_: RootStateDay1, cpuArchitecture: CpuArchitecture) => cpuArchitecture],
8
- (infraEnvs, cpuArchitecture) =>
10
+ (infraEnvs, cpuArchitecture): InfraEnv | undefined =>
9
11
  infraEnvs.find((infraEnv) => infraEnv.cpuArchitecture === cpuArchitecture),
10
- );
12
+ ) as Selector<RootStateDay1, InfraEnv | undefined, [CpuArchitecture]>;