@openshift-assisted/ui-lib 2.46.3 → 2.47.1
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.
- package/build/@types/cim/components/ClusterDeployment/ClusterDetailsFormFields.d.ts.map +1 -1
- package/build/@types/cim/types/k8s/agent-cluster-install.d.ts +1 -0
- package/build/@types/cim/types/k8s/agent-cluster-install.d.ts.map +1 -1
- package/build/@types/common/api/assisted-service/BundleAPI.d.ts +2 -2
- package/build/@types/common/api/assisted-service/ClustersAPI.d.ts +25 -25
- package/build/@types/common/api/assisted-service/ComponentVersionsAPI.d.ts +1 -1
- package/build/@types/common/api/assisted-service/EventsAPI.d.ts +1 -1
- package/build/@types/common/api/assisted-service/HostsAPI.d.ts +6 -6
- package/build/@types/common/api/assisted-service/InfraEnvsAPI.d.ts +7 -7
- package/build/@types/common/api/assisted-service/ManagedDomainsAPI.d.ts +1 -1
- package/build/@types/common/api/assisted-service/NewFeatureSupportLevelsAPI.d.ts +2 -2
- package/build/@types/common/api/assisted-service/OperatorsAPI.d.ts +1 -1
- package/build/@types/common/api/assisted-service/SupportedOpenshiftVersionsAPI.d.ts +1 -1
- package/build/@types/common/api/assisted-service/UISettingsAPI.d.ts +2 -2
- package/build/@types/common/components/clusterConfiguration/ControlPlaneNodesDropdown.d.ts +2 -1
- package/build/@types/common/components/clusterConfiguration/ControlPlaneNodesDropdown.d.ts.map +1 -1
- package/build/@types/common/components/clusterConfiguration/DownloadIso.d.ts.map +1 -1
- package/build/@types/common/components/clusterConfiguration/OcmTNADisclaimer.d.ts.map +1 -0
- package/build/@types/common/components/clusterConfiguration/utils.d.ts +3 -1
- package/build/@types/common/components/clusterConfiguration/utils.d.ts.map +1 -1
- package/build/@types/common/components/clusterConfiguration/utils.test.d.ts +2 -0
- package/build/@types/common/components/clusterConfiguration/utils.test.d.ts.map +1 -0
- package/build/@types/common/components/ui/formik/validationSchemas.d.ts +1 -1
- package/build/@types/common/components/ui/formik/validationSchemas.d.ts.map +1 -1
- package/build/@types/common/config/constants.d.ts.map +1 -1
- package/build/@types/ocm/components/clusterConfiguration/manifestsConfiguration/components/ExpandedManifest.d.ts.map +1 -1
- package/build/@types/ocm/components/clusterConfiguration/networkConfiguration/AdvancedNetworkFields.d.ts.map +1 -1
- package/build/@types/ocm/components/clusterConfiguration/networkConfiguration/AvailableSubnetsControl.d.ts +2 -1
- package/build/@types/ocm/components/clusterConfiguration/networkConfiguration/AvailableSubnetsControl.d.ts.map +1 -1
- package/build/@types/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfiguration.d.ts.map +1 -1
- package/build/@types/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfigurationForm.d.ts.map +1 -1
- package/build/@types/ocm/components/clusterConfiguration/networkConfiguration/StackTypeControl.d.ts.map +1 -1
- package/build/@types/ocm/components/clusterConfiguration/networkConfiguration/SubnetsDropdown.d.ts.map +1 -1
- package/build/@types/ocm/components/clusterConfiguration/networkConfiguration/VirtualIPControlGroup.d.ts.map +1 -1
- package/build/@types/ocm/components/clusterConfiguration/networkConfiguration/networkConfigurationValidation.d.ts +1 -1
- package/build/@types/ocm/components/clusterConfiguration/networkConfiguration/networkConfigurationValidation.d.ts.map +1 -1
- package/build/@types/ocm/components/clusterConfiguration/review/ReviewNetworkingTable.d.ts.map +1 -1
- package/build/@types/ocm/components/clusterConfiguration/review/ReviewPreflightChecks.d.ts.map +1 -1
- package/build/@types/ocm/components/clusterDetail/ClusterProperties.d.ts.map +1 -1
- package/build/@types/ocm/components/clusterDetail/ClusterProperties.test.d.ts +2 -0
- package/build/@types/ocm/components/clusterDetail/ClusterProperties.test.d.ts.map +1 -0
- package/build/@types/ocm/components/clusterWizard/OperatorsBundle.d.ts.map +1 -1
- package/build/@types/ocm/services/ClustersService.d.ts +5 -5
- package/build/@types/ocm/services/HostsService.d.ts +11 -11
- package/build/@types/ocm/services/InfraEnvsService.d.ts +2 -2
- package/build/@types/ocm/services/UISettingsService.d.ts +1 -1
- package/build/@types/ocm/store/slices/infra-envs/thunks.d.ts +1 -1
- package/build/cjs/cim/components/ClusterDeployment/ClusterDetailsFormFields.js +9 -5
- package/build/cjs/cim/components/ClusterDeployment/ClusterDetailsFormFields.js.map +1 -1
- package/build/cjs/common/components/clusterConfiguration/ControlPlaneNodesDropdown.js +23 -6
- package/build/cjs/common/components/clusterConfiguration/ControlPlaneNodesDropdown.js.map +1 -1
- package/build/cjs/common/components/clusterConfiguration/DownloadIso.js +1 -2
- package/build/cjs/common/components/clusterConfiguration/DownloadIso.js.map +1 -1
- package/build/cjs/{ocm → common}/components/clusterConfiguration/OcmTNADisclaimer.js +1 -1
- package/build/cjs/common/components/clusterConfiguration/OcmTNADisclaimer.js.map +1 -0
- package/build/cjs/common/components/clusterConfiguration/utils.js +23 -12
- package/build/cjs/common/components/clusterConfiguration/utils.js.map +1 -1
- package/build/cjs/common/components/clusterConfiguration/utils.test.js +242 -0
- package/build/cjs/common/components/clusterConfiguration/utils.test.js.map +1 -0
- package/build/cjs/common/components/clusterWizard/clusterDetailsValidation.js +1 -1
- package/build/cjs/common/components/clusterWizard/clusterDetailsValidation.js.map +1 -1
- package/build/cjs/common/components/ui/formik/validationSchemas.js +13 -3
- package/build/cjs/common/components/ui/formik/validationSchemas.js.map +1 -1
- package/build/cjs/common/config/constants.js +5 -10
- package/build/cjs/common/config/constants.js.map +1 -1
- package/build/cjs/ocm/components/clusterConfiguration/ControlPlaneNodesDropdown.js +1 -1
- package/build/cjs/ocm/components/clusterConfiguration/ControlPlaneNodesDropdown.js.map +1 -1
- package/build/cjs/ocm/components/clusterConfiguration/manifestsConfiguration/components/ExpandedManifest.js +0 -1
- package/build/cjs/ocm/components/clusterConfiguration/manifestsConfiguration/components/ExpandedManifest.js.map +1 -1
- package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/AdvancedNetworkFields.js +55 -13
- package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/AdvancedNetworkFields.js.map +1 -1
- package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/AvailableSubnetsControl.js +49 -12
- package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/AvailableSubnetsControl.js.map +1 -1
- package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfiguration.js +3 -3
- package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfiguration.js.map +1 -1
- package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfigurationForm.js +1 -1
- package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfigurationForm.js.map +1 -1
- package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/StackTypeControl.js +67 -7
- package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/StackTypeControl.js.map +1 -1
- package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/SubnetsDropdown.js +36 -6
- package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/SubnetsDropdown.js.map +1 -1
- package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/VirtualIPControlGroup.js +29 -20
- package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/VirtualIPControlGroup.js.map +1 -1
- package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/networkConfigurationValidation.js +40 -35
- package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/networkConfigurationValidation.js.map +1 -1
- package/build/cjs/ocm/components/clusterConfiguration/review/ReviewNetworkingTable.js +26 -8
- package/build/cjs/ocm/components/clusterConfiguration/review/ReviewNetworkingTable.js.map +1 -1
- package/build/cjs/ocm/components/clusterConfiguration/review/ReviewPreflightChecks.js +5 -5
- package/build/cjs/ocm/components/clusterConfiguration/review/ReviewPreflightChecks.js.map +1 -1
- package/build/cjs/ocm/components/clusterDetail/ClusterProperties.js +2 -2
- package/build/cjs/ocm/components/clusterDetail/ClusterProperties.js.map +1 -1
- package/build/cjs/ocm/components/clusterDetail/ClusterProperties.test.js +180 -0
- package/build/cjs/ocm/components/clusterDetail/ClusterProperties.test.js.map +1 -0
- package/build/cjs/ocm/components/clusterWizard/OperatorsBundle.js +3 -1
- package/build/cjs/ocm/components/clusterWizard/OperatorsBundle.js.map +1 -1
- package/build/cjs/ocm/components/featureSupportLevels/featureStateUtils.js +0 -3
- package/build/cjs/ocm/components/featureSupportLevels/featureStateUtils.js.map +1 -1
- package/lib/cim/components/ClusterDeployment/ClusterDetailsFormFields.tsx +17 -35
- package/lib/cim/types/k8s/agent-cluster-install.ts +1 -0
- package/lib/common/components/clusterConfiguration/ControlPlaneNodesDropdown.tsx +61 -31
- package/lib/common/components/clusterConfiguration/DownloadIso.tsx +5 -2
- package/lib/{ocm → common}/components/clusterConfiguration/OcmTNADisclaimer.tsx +1 -1
- package/lib/common/components/clusterConfiguration/utils.test.ts +275 -0
- package/lib/common/components/clusterConfiguration/utils.ts +28 -9
- package/lib/common/components/clusterWizard/clusterDetailsValidation.ts +1 -1
- package/lib/common/components/ui/formik/_validationSchemas.test.ts +82 -0
- package/lib/common/components/ui/formik/validationSchemas.ts +16 -10
- package/lib/common/config/constants.ts +5 -11
- package/lib/ocm/components/clusterConfiguration/ControlPlaneNodesDropdown.tsx +1 -1
- package/lib/ocm/components/clusterConfiguration/manifestsConfiguration/components/ExpandedManifest.tsx +1 -7
- package/lib/ocm/components/clusterConfiguration/networkConfiguration/AdvancedNetworkFields.tsx +59 -13
- package/lib/ocm/components/clusterConfiguration/networkConfiguration/AvailableSubnetsControl.tsx +98 -38
- package/lib/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfiguration.tsx +3 -2
- package/lib/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfigurationForm.tsx +3 -2
- package/lib/ocm/components/clusterConfiguration/networkConfiguration/StackTypeControl.tsx +78 -11
- package/lib/ocm/components/clusterConfiguration/networkConfiguration/SubnetsDropdown.tsx +83 -10
- package/lib/ocm/components/clusterConfiguration/networkConfiguration/VirtualIPControlGroup.tsx +70 -14
- package/lib/ocm/components/clusterConfiguration/networkConfiguration/networkConfigurationValidation.ts +30 -11
- package/lib/ocm/components/clusterConfiguration/review/ReviewNetworkingTable.tsx +34 -4
- package/lib/ocm/components/clusterConfiguration/review/ReviewPreflightChecks.tsx +7 -9
- package/lib/ocm/components/clusterDetail/ClusterProperties.test.ts +215 -0
- package/lib/ocm/components/clusterDetail/ClusterProperties.tsx +2 -2
- package/lib/ocm/components/clusterWizard/OperatorsBundle.tsx +3 -1
- package/lib/ocm/components/featureSupportLevels/featureStateUtils.ts +1 -3
- package/package.json +3 -3
- package/build/@types/ocm/components/clusterConfiguration/OcmTNADisclaimer.d.ts.map +0 -1
- package/build/@types/ocm/components/clusterConfiguration/networkConfiguration/UserManagedNetworkingTextContent.d.ts +0 -6
- package/build/@types/ocm/components/clusterConfiguration/networkConfiguration/UserManagedNetworkingTextContent.d.ts.map +0 -1
- package/build/cjs/ocm/components/clusterConfiguration/OcmTNADisclaimer.js.map +0 -1
- package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/UserManagedNetworkingTextContent.js +0 -25
- package/build/cjs/ocm/components/clusterConfiguration/networkConfiguration/UserManagedNetworkingTextContent.js.map +0 -1
- package/lib/ocm/components/clusterConfiguration/networkConfiguration/UserManagedNetworkingTextContent.tsx +0 -30
- /package/build/@types/{ocm → common}/components/clusterConfiguration/OcmTNADisclaimer.d.ts +0 -0
package/lib/ocm/components/clusterConfiguration/networkConfiguration/AvailableSubnetsControl.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
NetworkConfigurationValues,
|
|
10
10
|
DUAL_STACK,
|
|
11
11
|
NO_SUBNET_SET,
|
|
12
|
+
isMajorMinorVersionEqualOrGreater,
|
|
12
13
|
} from '../../../../common';
|
|
13
14
|
import { selectCurrentClusterPermissionsState } from '../../../store/slices/current-cluster/selectors';
|
|
14
15
|
import { SubnetsDropdown } from './SubnetsDropdown';
|
|
@@ -35,6 +36,7 @@ export interface AvailableSubnetsControlProps {
|
|
|
35
36
|
hostSubnets: HostSubnet[];
|
|
36
37
|
isRequired: boolean;
|
|
37
38
|
isDisabled: boolean;
|
|
39
|
+
openshiftVersion?: string;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
export const AvailableSubnetsControl = ({
|
|
@@ -42,11 +44,16 @@ export const AvailableSubnetsControl = ({
|
|
|
42
44
|
hostSubnets,
|
|
43
45
|
isRequired = false,
|
|
44
46
|
isDisabled,
|
|
47
|
+
openshiftVersion,
|
|
45
48
|
}: AvailableSubnetsControlProps) => {
|
|
46
49
|
const { values, errors, setFieldValue } = useFormikContext<NetworkConfigurationValues>();
|
|
47
50
|
const isDualStack = values.stackType === DUAL_STACK;
|
|
48
51
|
const { isViewerMode } = useSelector(selectCurrentClusterPermissionsState);
|
|
49
52
|
|
|
53
|
+
// Check if OCP version supports IPv6 as primary network
|
|
54
|
+
const supportsIPv6Primary =
|
|
55
|
+
openshiftVersion && isMajorMinorVersionEqualOrGreater(openshiftVersion, '4.12');
|
|
56
|
+
|
|
50
57
|
const IPv4Subnets = hostSubnets
|
|
51
58
|
.filter((subnet) => Address4.isValid(subnet.subnet))
|
|
52
59
|
.sort(subnetSort);
|
|
@@ -54,53 +61,106 @@ export const AvailableSubnetsControl = ({
|
|
|
54
61
|
.filter((subnet) => Address6.isValid(subnet.subnet))
|
|
55
62
|
.sort(subnetSort);
|
|
56
63
|
|
|
64
|
+
// For OCP >= 4.12, both networks can use either IPv4 or IPv6
|
|
65
|
+
const allSubnets = supportsIPv6Primary
|
|
66
|
+
? [...IPv4Subnets, ...IPv6Subnets].sort(subnetSort)
|
|
67
|
+
: IPv4Subnets;
|
|
68
|
+
|
|
69
|
+
// For auto-selection, always use IPv4 subnets for single-stack
|
|
57
70
|
const cidr = IPv4Subnets.length >= 1 ? IPv4Subnets[0].subnet : NO_SUBNET_SET;
|
|
58
71
|
const hasEmptySelection = (values.machineNetworks ?? []).length === 0;
|
|
59
72
|
const autoSelectNetwork = !isViewerMode && hasEmptySelection;
|
|
60
73
|
useAutoSelectSingleAvailableSubnet(autoSelectNetwork, setFieldValue, cidr, clusterId);
|
|
61
74
|
|
|
62
75
|
return (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
76
|
+
<>
|
|
77
|
+
<FormGroup
|
|
78
|
+
label="Machine network"
|
|
79
|
+
labelInfo={isDualStack && 'Primary'}
|
|
80
|
+
fieldId="machine-networks"
|
|
81
|
+
isRequired={isRequired}
|
|
82
|
+
>
|
|
83
|
+
<FieldArray name="machineNetworks">
|
|
84
|
+
{() => (
|
|
85
|
+
<Stack>
|
|
86
|
+
{isDualStack ? (
|
|
87
|
+
values.machineNetworks?.map((machineNetwork, index) => {
|
|
88
|
+
if (index > 0) return null;
|
|
89
|
+
|
|
90
|
+
const machineSubnets = supportsIPv6Primary ? allSubnets : IPv4Subnets;
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<StackItem key={index}>
|
|
94
|
+
<SubnetsDropdown
|
|
95
|
+
name={`machineNetworks.${index}.cidr`}
|
|
96
|
+
machineSubnets={machineSubnets}
|
|
97
|
+
isDisabled={isDisabled}
|
|
98
|
+
data-testid={`subnets-dropdown-toggle-${index ? 'ipv6' : 'ipv4'}`}
|
|
99
|
+
/>
|
|
100
|
+
</StackItem>
|
|
101
|
+
);
|
|
102
|
+
})
|
|
103
|
+
) : (
|
|
104
|
+
<StackItem>
|
|
105
|
+
<SubnetsDropdown
|
|
106
|
+
name={`machineNetworks.0.cidr`}
|
|
107
|
+
machineSubnets={IPv4Subnets}
|
|
108
|
+
isDisabled={isDisabled}
|
|
109
|
+
data-testid={'subnets-dropdown-toggle-primary'}
|
|
110
|
+
/>
|
|
111
|
+
</StackItem>
|
|
112
|
+
)}
|
|
113
|
+
</Stack>
|
|
114
|
+
)}
|
|
115
|
+
</FieldArray>
|
|
116
|
+
|
|
117
|
+
{typeof errors.machineNetworks === 'string' && (
|
|
118
|
+
<Alert variant={AlertVariant.warning} title={errors.machineNetworks} isInline />
|
|
119
|
+
)}
|
|
120
|
+
</FormGroup>
|
|
121
|
+
|
|
122
|
+
{isDualStack && values.machineNetworks && values.machineNetworks.length > 1 && (
|
|
123
|
+
<FormGroup
|
|
124
|
+
label="Machine network"
|
|
125
|
+
labelInfo="Secondary"
|
|
126
|
+
fieldId="machine-networks-secondary"
|
|
127
|
+
isRequired={isRequired}
|
|
128
|
+
>
|
|
129
|
+
<FieldArray name="machineNetworks">
|
|
130
|
+
{() => {
|
|
131
|
+
const index = 1;
|
|
132
|
+
let machineSubnets;
|
|
133
|
+
|
|
134
|
+
if (supportsIPv6Primary) {
|
|
135
|
+
// For OCP >= 4.12, smart filtering based on the other network's selection
|
|
136
|
+
const firstNetworkCidr = values.machineNetworks?.[0]?.cidr;
|
|
137
|
+
if (firstNetworkCidr && Address6.isValid(firstNetworkCidr)) {
|
|
138
|
+
// If first is IPv6, second should be IPv4
|
|
139
|
+
machineSubnets = IPv4Subnets;
|
|
140
|
+
} else if (firstNetworkCidr && Address4.isValid(firstNetworkCidr)) {
|
|
141
|
+
// If first is IPv4, second should be IPv6
|
|
142
|
+
machineSubnets = IPv6Subnets;
|
|
143
|
+
} else {
|
|
144
|
+
// If first is not selected yet, show all subnets
|
|
145
|
+
machineSubnets = allSubnets;
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
// For older versions, second network is IPv6
|
|
149
|
+
machineSubnets = IPv6Subnets;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return (
|
|
89
153
|
<SubnetsDropdown
|
|
90
|
-
name={`machineNetworks.
|
|
91
|
-
machineSubnets={
|
|
154
|
+
name={`machineNetworks.${index}.cidr`}
|
|
155
|
+
machineSubnets={machineSubnets}
|
|
92
156
|
isDisabled={isDisabled}
|
|
93
|
-
data-testid={
|
|
157
|
+
data-testid={`subnets-dropdown-toggle-${index ? 'ipv6' : 'ipv4'}`}
|
|
94
158
|
/>
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
</
|
|
98
|
-
|
|
99
|
-
</FieldArray>
|
|
100
|
-
|
|
101
|
-
{typeof errors.machineNetworks === 'string' && (
|
|
102
|
-
<Alert variant={AlertVariant.warning} title={errors.machineNetworks} isInline />
|
|
159
|
+
);
|
|
160
|
+
}}
|
|
161
|
+
</FieldArray>
|
|
162
|
+
</FormGroup>
|
|
103
163
|
)}
|
|
104
|
-
|
|
164
|
+
</>
|
|
105
165
|
);
|
|
106
166
|
};
|
package/lib/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfiguration.tsx
CHANGED
|
@@ -224,10 +224,10 @@ const NetworkConfiguration = ({
|
|
|
224
224
|
);
|
|
225
225
|
|
|
226
226
|
React.useEffect(() => {
|
|
227
|
-
if (!isViewerMode && isDualStack
|
|
227
|
+
if (!isViewerMode && isDualStack) {
|
|
228
228
|
toggleAdvConfiguration(true);
|
|
229
229
|
}
|
|
230
|
-
}, [isDualStack,
|
|
230
|
+
}, [isDualStack, isViewerMode, toggleAdvConfiguration]);
|
|
231
231
|
|
|
232
232
|
const managedNetworkingState = React.useMemo(
|
|
233
233
|
() =>
|
|
@@ -292,6 +292,7 @@ const NetworkConfiguration = ({
|
|
|
292
292
|
hostSubnets.length === 0 ||
|
|
293
293
|
false
|
|
294
294
|
}
|
|
295
|
+
openshiftVersion={cluster.openshiftVersion}
|
|
295
296
|
/>
|
|
296
297
|
</StackItem>
|
|
297
298
|
)}
|
package/lib/ocm/components/clusterConfiguration/networkConfiguration/NetworkConfigurationForm.tsx
CHANGED
|
@@ -200,8 +200,9 @@ const NetworkConfigurationPage = ({ cluster }: { cluster: Cluster }) => {
|
|
|
200
200
|
);
|
|
201
201
|
|
|
202
202
|
const memoizedValidationSchema = React.useMemo(
|
|
203
|
-
() =>
|
|
204
|
-
|
|
203
|
+
() =>
|
|
204
|
+
getNetworkConfigurationValidationSchema(initialValues, hostSubnets, cluster.openshiftVersion),
|
|
205
|
+
[hostSubnets, initialValues, cluster.openshiftVersion],
|
|
205
206
|
);
|
|
206
207
|
|
|
207
208
|
React.useEffect(() => {
|
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { useSelector } from 'react-redux';
|
|
3
3
|
import { ButtonVariant, FormGroup, Split, SplitItem, Tooltip } from '@patternfly/react-core';
|
|
4
4
|
import { useFormikContext } from 'formik';
|
|
5
|
-
import { Address6 } from 'ip-address';
|
|
5
|
+
import { Address4, Address6 } from 'ip-address';
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
HostSubnets,
|
|
@@ -69,19 +69,67 @@ export const StackTypeControlGroup = ({
|
|
|
69
69
|
setFieldValue('stackType', IPV4_STACK);
|
|
70
70
|
|
|
71
71
|
if (values.machineNetworks && values.machineNetworks?.length >= 2) {
|
|
72
|
-
|
|
72
|
+
// For single-stack IPv4, prefer IPv4 machine network
|
|
73
|
+
const firstNetwork = values.machineNetworks[0];
|
|
74
|
+
const isFirstIPv4 = firstNetwork?.cidr && Address4.isValid(firstNetwork.cidr);
|
|
75
|
+
|
|
76
|
+
if (isFirstIPv4) {
|
|
77
|
+
// Keep the first network if it's IPv4
|
|
78
|
+
setFieldValue('machineNetworks', [firstNetwork]);
|
|
79
|
+
} else {
|
|
80
|
+
// If first is IPv6, look for IPv4 network or set empty for auto-selection
|
|
81
|
+
const ipv4Network = values.machineNetworks.find(
|
|
82
|
+
(network) => network.cidr && Address4.isValid(network.cidr),
|
|
83
|
+
);
|
|
84
|
+
if (ipv4Network) {
|
|
85
|
+
setFieldValue('machineNetworks', [ipv4Network]);
|
|
86
|
+
} else {
|
|
87
|
+
// No IPv4 network found, set empty for auto-selection
|
|
88
|
+
setFieldValue('machineNetworks', []);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
73
91
|
}
|
|
74
|
-
|
|
75
|
-
|
|
92
|
+
// Ensure single-stack inputs are IPv4 after switching from dual-stack
|
|
93
|
+
if (values.clusterNetworks && values.clusterNetworks.length >= 1) {
|
|
94
|
+
const defaultCluster =
|
|
95
|
+
defaultNetworkValues.clusterNetworksIpv4 && defaultNetworkValues.clusterNetworksIpv4[0];
|
|
96
|
+
const firstCluster = values.clusterNetworks[0];
|
|
97
|
+
const ipv4Cluster = defaultCluster
|
|
98
|
+
? {
|
|
99
|
+
cidr: defaultCluster.cidr,
|
|
100
|
+
hostPrefix: defaultCluster.hostPrefix,
|
|
101
|
+
clusterId: firstCluster.clusterId, // ← Preserve clusterId from original network
|
|
102
|
+
}
|
|
103
|
+
: {
|
|
104
|
+
cidr: '',
|
|
105
|
+
hostPrefix: '',
|
|
106
|
+
clusterId: clusterId, // ← Use current clusterId
|
|
107
|
+
};
|
|
108
|
+
setFieldValue('clusterNetworks', [ipv4Cluster]);
|
|
76
109
|
}
|
|
77
|
-
if (values.serviceNetworks && values.serviceNetworks
|
|
78
|
-
|
|
110
|
+
if (values.serviceNetworks && values.serviceNetworks.length >= 1) {
|
|
111
|
+
const defaultService =
|
|
112
|
+
defaultNetworkValues.serviceNetworksIpv4 && defaultNetworkValues.serviceNetworksIpv4[0];
|
|
113
|
+
const firstService = values.serviceNetworks[0];
|
|
114
|
+
const ipv4Service = defaultService
|
|
115
|
+
? {
|
|
116
|
+
cidr: defaultService.cidr,
|
|
117
|
+
clusterId: firstService.clusterId, // ← Preserve clusterId from original network
|
|
118
|
+
}
|
|
119
|
+
: {
|
|
120
|
+
cidr: '',
|
|
121
|
+
clusterId: clusterId, // ← Use current clusterId
|
|
122
|
+
};
|
|
123
|
+
setFieldValue('serviceNetworks', [ipv4Service]);
|
|
79
124
|
}
|
|
80
125
|
|
|
81
126
|
void validateForm();
|
|
82
127
|
}, [
|
|
83
128
|
setFieldValue,
|
|
84
129
|
validateForm,
|
|
130
|
+
clusterId,
|
|
131
|
+
defaultNetworkValues.clusterNetworksIpv4,
|
|
132
|
+
defaultNetworkValues.serviceNetworksIpv4,
|
|
85
133
|
values.clusterNetworks,
|
|
86
134
|
values.machineNetworks,
|
|
87
135
|
values.serviceNetworks,
|
|
@@ -96,11 +144,30 @@ export const StackTypeControlGroup = ({
|
|
|
96
144
|
}
|
|
97
145
|
|
|
98
146
|
if (values.machineNetworks && values.machineNetworks?.length < 2) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
)
|
|
147
|
+
// Ensure IPv4 is primary when switching to dual-stack
|
|
148
|
+
const firstNetwork = values.machineNetworks[0];
|
|
149
|
+
const isFirstIPv6 = firstNetwork?.cidr && Address6.isValid(firstNetwork.cidr);
|
|
150
|
+
|
|
151
|
+
if (isFirstIPv6) {
|
|
152
|
+
// If first network is IPv6, make IPv4 primary and IPv6 secondary
|
|
153
|
+
const IPv4Subnets = hostSubnets.filter((subnet) => Address4.isValid(subnet.subnet));
|
|
154
|
+
const cidrIPv4 = IPv4Subnets.length >= 1 ? IPv4Subnets[0].subnet : NO_SUBNET_SET;
|
|
155
|
+
setFieldValue(
|
|
156
|
+
'machineNetworks',
|
|
157
|
+
[
|
|
158
|
+
{ cidr: cidrIPv4, clusterId: clusterId },
|
|
159
|
+
{ cidr: firstNetwork.cidr, clusterId: clusterId },
|
|
160
|
+
],
|
|
161
|
+
false,
|
|
162
|
+
);
|
|
163
|
+
} else {
|
|
164
|
+
// If first network is IPv4, add IPv6 as secondary
|
|
165
|
+
setFieldValue(
|
|
166
|
+
'machineNetworks',
|
|
167
|
+
[...values.machineNetworks, { cidr: cidrIPv6, clusterId: clusterId }],
|
|
168
|
+
false,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
104
171
|
}
|
|
105
172
|
if (values.clusterNetworks && values.clusterNetworks?.length < 2) {
|
|
106
173
|
setFieldValue(
|
|
@@ -2,12 +2,21 @@ import React from 'react';
|
|
|
2
2
|
import {
|
|
3
3
|
Dropdown,
|
|
4
4
|
DropdownItem,
|
|
5
|
+
DropdownGroup,
|
|
5
6
|
MenuToggle,
|
|
6
7
|
MenuToggleElement,
|
|
7
8
|
MenuToggleProps,
|
|
9
|
+
Divider,
|
|
8
10
|
} from '@patternfly/react-core';
|
|
9
11
|
import { useField } from 'formik';
|
|
10
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
getFieldId,
|
|
14
|
+
HostSubnet,
|
|
15
|
+
NO_SUBNET_SET,
|
|
16
|
+
TechnologyPreview,
|
|
17
|
+
PreviewBadgePosition,
|
|
18
|
+
} from '../../../../common';
|
|
19
|
+
import { Address4, Address6 } from 'ip-address';
|
|
11
20
|
|
|
12
21
|
type SubnetsDropdownProps = {
|
|
13
22
|
name: string;
|
|
@@ -48,7 +57,7 @@ export const SubnetsDropdown = ({
|
|
|
48
57
|
const [isOpen, setOpen] = React.useState(false);
|
|
49
58
|
const fieldId = getFieldId(name, 'input');
|
|
50
59
|
|
|
51
|
-
const { itemsSubnets, currentDisplayValue } = React.useMemo(() => {
|
|
60
|
+
const { itemsSubnets, ipv4Subnets, ipv6Subnets, currentDisplayValue } = React.useMemo(() => {
|
|
52
61
|
const itemsSubnets =
|
|
53
62
|
machineSubnets.length === 0
|
|
54
63
|
? [noSubnetAvailableOption]
|
|
@@ -56,7 +65,12 @@ export const SubnetsDropdown = ({
|
|
|
56
65
|
toFormSelectOptions(machineSubnets),
|
|
57
66
|
);
|
|
58
67
|
|
|
59
|
-
|
|
68
|
+
// Separate IPv4 and IPv6 subnets
|
|
69
|
+
const ipv4Subnets = machineSubnets.filter((subnet) => Address4.isValid(subnet.subnet));
|
|
70
|
+
const ipv6Subnets = machineSubnets.filter((subnet) => Address6.isValid(subnet.subnet));
|
|
71
|
+
|
|
72
|
+
let currentDisplayValue =
|
|
73
|
+
itemsSubnets.length > 1 ? itemsSubnets[1].label : itemsSubnets[0].label; // The placeholder is the fallback in some cases
|
|
60
74
|
if (field.value) {
|
|
61
75
|
const subnetItem = itemsSubnets.find((item) => item.value === field.value);
|
|
62
76
|
if (subnetItem) {
|
|
@@ -67,20 +81,74 @@ export const SubnetsDropdown = ({
|
|
|
67
81
|
currentDisplayValue = itemsSubnets[1].label;
|
|
68
82
|
}
|
|
69
83
|
|
|
70
|
-
return { itemsSubnets, currentDisplayValue: currentDisplayValue };
|
|
84
|
+
return { itemsSubnets, ipv4Subnets, ipv6Subnets, currentDisplayValue: currentDisplayValue };
|
|
71
85
|
}, [machineSubnets, field.value]);
|
|
72
86
|
|
|
73
|
-
const dropdownItems =
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
87
|
+
const dropdownItems = React.useMemo(() => {
|
|
88
|
+
if (machineSubnets.length === 0) {
|
|
89
|
+
return [
|
|
90
|
+
<DropdownItem
|
|
91
|
+
key={noSubnetAvailableOption.value}
|
|
92
|
+
id={noSubnetAvailableOption.value}
|
|
93
|
+
isDisabled={noSubnetAvailableOption.isDisabled}
|
|
94
|
+
value={noSubnetAvailableOption.value}
|
|
95
|
+
>
|
|
96
|
+
{noSubnetAvailableOption.label}
|
|
97
|
+
</DropdownItem>,
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const hasIpv4 = ipv4Subnets.length > 0;
|
|
102
|
+
const hasIpv6 = ipv6Subnets.length > 0;
|
|
103
|
+
|
|
104
|
+
// If we have both IPv4 and IPv6, group them
|
|
105
|
+
if (hasIpv4 && hasIpv6) {
|
|
106
|
+
const ipv4Items = toFormSelectOptions(ipv4Subnets).map(({ value, label, isDisabled }) => (
|
|
107
|
+
<DropdownItem key={value} id={value} isDisabled={isDisabled} value={value}>
|
|
108
|
+
{label}
|
|
109
|
+
</DropdownItem>
|
|
110
|
+
));
|
|
111
|
+
ipv4Items.push(<Divider key="ipv4-divider" />);
|
|
112
|
+
const ipv6Items = toFormSelectOptions(ipv6Subnets).map(({ value, label, isDisabled }) => (
|
|
113
|
+
<DropdownItem key={value} id={value} isDisabled={isDisabled} value={value}>
|
|
114
|
+
{label}
|
|
115
|
+
</DropdownItem>
|
|
116
|
+
));
|
|
117
|
+
|
|
118
|
+
return [
|
|
119
|
+
<DropdownItem
|
|
120
|
+
key={makeNoSubnetSelectedOption(machineSubnets.length).value}
|
|
121
|
+
id={makeNoSubnetSelectedOption(machineSubnets.length).value}
|
|
122
|
+
isDisabled={makeNoSubnetSelectedOption(machineSubnets.length).isDisabled}
|
|
123
|
+
value={makeNoSubnetSelectedOption(machineSubnets.length).value}
|
|
124
|
+
>
|
|
125
|
+
{makeNoSubnetSelectedOption(machineSubnets.length).label}
|
|
126
|
+
</DropdownItem>,
|
|
127
|
+
<DropdownGroup label="IPv4" key="ipv4-group">
|
|
128
|
+
{ipv4Items}
|
|
129
|
+
</DropdownGroup>,
|
|
130
|
+
<DropdownGroup label="IPv6 (Technology Preview)" key="ipv6-group">
|
|
131
|
+
{ipv6Items}
|
|
132
|
+
</DropdownGroup>,
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// If only one type, don't group
|
|
137
|
+
return itemsSubnets.map(({ value, label, isDisabled }) => (
|
|
138
|
+
<DropdownItem key={value} id={value} isDisabled={isDisabled} value={value}>
|
|
139
|
+
{label}
|
|
140
|
+
</DropdownItem>
|
|
141
|
+
));
|
|
142
|
+
}, [machineSubnets, ipv4Subnets, ipv6Subnets, itemsSubnets]);
|
|
78
143
|
|
|
79
144
|
const onSelect = (event?: React.MouseEvent<Element, MouseEvent>): void => {
|
|
80
145
|
setValue(event?.currentTarget.id);
|
|
81
146
|
setOpen(false);
|
|
82
147
|
};
|
|
83
148
|
|
|
149
|
+
const currentItem = itemsSubnets.find((i) => i.label === currentDisplayValue);
|
|
150
|
+
const showBadge = Boolean(currentItem && Address6.isValid(currentItem.value));
|
|
151
|
+
|
|
84
152
|
const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
|
|
85
153
|
<MenuToggle
|
|
86
154
|
ref={toggleRef}
|
|
@@ -93,6 +161,11 @@ export const SubnetsDropdown = ({
|
|
|
93
161
|
{...props}
|
|
94
162
|
>
|
|
95
163
|
{currentDisplayValue}
|
|
164
|
+
{showBadge && (
|
|
165
|
+
<span onClick={(e) => e.stopPropagation()}>
|
|
166
|
+
<TechnologyPreview position={PreviewBadgePosition.inlineRight} />
|
|
167
|
+
</span>
|
|
168
|
+
)}
|
|
96
169
|
</MenuToggle>
|
|
97
170
|
);
|
|
98
171
|
|
|
@@ -100,7 +173,7 @@ export const SubnetsDropdown = ({
|
|
|
100
173
|
<Dropdown
|
|
101
174
|
{...field}
|
|
102
175
|
id={`${fieldId}-dropdown`}
|
|
103
|
-
onOpenChange={() => setOpen(
|
|
176
|
+
onOpenChange={(isOpen: boolean) => setOpen(isOpen)}
|
|
104
177
|
onSelect={onSelect}
|
|
105
178
|
toggle={toggle}
|
|
106
179
|
isOpen={isOpen}
|
package/lib/ocm/components/clusterConfiguration/networkConfiguration/VirtualIPControlGroup.tsx
CHANGED
|
@@ -14,9 +14,9 @@ import {
|
|
|
14
14
|
NetworkConfigurationValues,
|
|
15
15
|
FormikStaticField,
|
|
16
16
|
NETWORK_TYPE_SDN,
|
|
17
|
+
DUAL_STACK,
|
|
17
18
|
selectMachineNetworkCIDR,
|
|
18
19
|
getVipValidationsById,
|
|
19
|
-
DUAL_STACK,
|
|
20
20
|
PopoverIcon,
|
|
21
21
|
selectApiVip,
|
|
22
22
|
selectIngressVip,
|
|
@@ -25,7 +25,12 @@ import { selectCurrentClusterPermissionsState } from '../../../store/slices/curr
|
|
|
25
25
|
import { OcmCheckboxField, OcmInputField } from '../../ui/OcmFormFields';
|
|
26
26
|
import { useTranslation } from '../../../../common/hooks/use-translation-wrapper';
|
|
27
27
|
import NewFeatureSupportLevelBadge from '../../../../common/components/newFeatureSupportLevels/NewFeatureSupportLevelBadge';
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
ApiVip,
|
|
30
|
+
Cluster,
|
|
31
|
+
Ip,
|
|
32
|
+
SupportLevel,
|
|
33
|
+
} from '@openshift-assisted/types/assisted-installer-service';
|
|
29
34
|
|
|
30
35
|
interface VipStaticValueProps {
|
|
31
36
|
id?: string;
|
|
@@ -97,6 +102,7 @@ export const VirtualIPControlGroup = ({
|
|
|
97
102
|
);
|
|
98
103
|
|
|
99
104
|
const enableAllocation = values.networkType === NETWORK_TYPE_SDN;
|
|
105
|
+
const isDualStack = values.stackType === DUAL_STACK;
|
|
100
106
|
|
|
101
107
|
React.useEffect(() => {
|
|
102
108
|
if (!isViewerMode && !enableAllocation) {
|
|
@@ -113,11 +119,22 @@ export const VirtualIPControlGroup = ({
|
|
|
113
119
|
[cluster.apiVips, cluster.ingressVips, setFieldValue],
|
|
114
120
|
);
|
|
115
121
|
|
|
116
|
-
const
|
|
117
|
-
|
|
122
|
+
const setVipValueAtIndex = (
|
|
123
|
+
field: 'apiVips' | 'ingressVips',
|
|
124
|
+
index: number,
|
|
125
|
+
e: React.ChangeEvent<HTMLInputElement>,
|
|
126
|
+
) => {
|
|
127
|
+
const fieldArray: ApiVip[] =
|
|
128
|
+
field === 'apiVips' ? values.apiVips || [] : values.ingressVips || [];
|
|
129
|
+
const next: ApiVip[] = Array.isArray(fieldArray) ? [...fieldArray] : [];
|
|
130
|
+
// Ensure array has the desired length
|
|
131
|
+
while (next.length <= index) {
|
|
132
|
+
next.push({ ip: '', clusterId: cluster.id });
|
|
133
|
+
}
|
|
134
|
+
next[index] = { ip: e.target.value, clusterId: cluster.id };
|
|
135
|
+
setFieldValue(field, next, true);
|
|
118
136
|
};
|
|
119
137
|
|
|
120
|
-
const ipFieldsSuffix = values.stackType === DUAL_STACK ? ' (IPv4)' : '';
|
|
121
138
|
return (
|
|
122
139
|
<>
|
|
123
140
|
{!isVipDhcpAllocationDisabled && (
|
|
@@ -145,7 +162,7 @@ export const VirtualIPControlGroup = ({
|
|
|
145
162
|
<FormikStaticField
|
|
146
163
|
label={
|
|
147
164
|
<>
|
|
148
|
-
<span>API IP
|
|
165
|
+
<span>API IP</span> <PopoverIcon bodyContent={ipPopoverContent} />
|
|
149
166
|
</>
|
|
150
167
|
}
|
|
151
168
|
name="apiVip"
|
|
@@ -164,8 +181,7 @@ export const VirtualIPControlGroup = ({
|
|
|
164
181
|
<FormikStaticField
|
|
165
182
|
label={
|
|
166
183
|
<>
|
|
167
|
-
<span>Ingress IP
|
|
168
|
-
<PopoverIcon bodyContent={ipPopoverContent} />
|
|
184
|
+
<span>Ingress IP</span> <PopoverIcon bodyContent={ipPopoverContent} />
|
|
169
185
|
</>
|
|
170
186
|
}
|
|
171
187
|
name="ingressVip"
|
|
@@ -191,34 +207,74 @@ export const VirtualIPControlGroup = ({
|
|
|
191
207
|
<OcmInputField
|
|
192
208
|
label={
|
|
193
209
|
<>
|
|
194
|
-
<span>API IP
|
|
195
|
-
<PopoverIcon bodyContent={ipPopoverContent} />
|
|
210
|
+
<span>API IP</span> <PopoverIcon bodyContent={ipPopoverContent} />
|
|
196
211
|
</>
|
|
197
212
|
}
|
|
198
213
|
name="apiVips.0.ip"
|
|
199
214
|
helperText={ipHelperText}
|
|
200
215
|
isRequired
|
|
216
|
+
labelInfo={isDualStack ? 'Primary' : undefined}
|
|
201
217
|
onChange={(e) =>
|
|
202
|
-
|
|
218
|
+
setVipValueAtIndex('apiVips', 0, e as React.ChangeEvent<HTMLInputElement>)
|
|
203
219
|
}
|
|
204
220
|
/>
|
|
205
221
|
</StackItem>
|
|
222
|
+
{isDualStack && (
|
|
223
|
+
<StackItem>
|
|
224
|
+
<OcmInputField
|
|
225
|
+
label={
|
|
226
|
+
<>
|
|
227
|
+
<span>API IP</span> <PopoverIcon bodyContent={ipPopoverContent} />
|
|
228
|
+
</>
|
|
229
|
+
}
|
|
230
|
+
name="apiVips.1.ip"
|
|
231
|
+
helperText={ipHelperText}
|
|
232
|
+
isRequired
|
|
233
|
+
labelInfo={'Secondary'}
|
|
234
|
+
onChange={(e) =>
|
|
235
|
+
setVipValueAtIndex('apiVips', 1, e as React.ChangeEvent<HTMLInputElement>)
|
|
236
|
+
}
|
|
237
|
+
/>
|
|
238
|
+
</StackItem>
|
|
239
|
+
)}
|
|
206
240
|
<StackItem>
|
|
207
241
|
<OcmInputField
|
|
208
242
|
name="ingressVips.0.ip"
|
|
209
243
|
label={
|
|
210
244
|
<>
|
|
211
|
-
<span>Ingress IP
|
|
212
|
-
<PopoverIcon bodyContent={ipPopoverContent} />
|
|
245
|
+
<span>Ingress IP</span> <PopoverIcon bodyContent={ipPopoverContent} />
|
|
213
246
|
</>
|
|
214
247
|
}
|
|
215
248
|
helperText={ipHelperText}
|
|
216
249
|
isRequired
|
|
250
|
+
labelInfo={isDualStack ? 'Primary' : undefined}
|
|
217
251
|
onChange={(e) =>
|
|
218
|
-
|
|
252
|
+
setVipValueAtIndex('ingressVips', 0, e as React.ChangeEvent<HTMLInputElement>)
|
|
219
253
|
}
|
|
220
254
|
/>
|
|
221
255
|
</StackItem>
|
|
256
|
+
{isDualStack && (
|
|
257
|
+
<StackItem>
|
|
258
|
+
<OcmInputField
|
|
259
|
+
name="ingressVips.1.ip"
|
|
260
|
+
label={
|
|
261
|
+
<>
|
|
262
|
+
<span>Ingress IP</span> <PopoverIcon bodyContent={ipPopoverContent} />
|
|
263
|
+
</>
|
|
264
|
+
}
|
|
265
|
+
helperText={ipHelperText}
|
|
266
|
+
isRequired
|
|
267
|
+
labelInfo={'Secondary'}
|
|
268
|
+
onChange={(e) =>
|
|
269
|
+
setVipValueAtIndex(
|
|
270
|
+
'ingressVips',
|
|
271
|
+
1,
|
|
272
|
+
e as React.ChangeEvent<HTMLInputElement>,
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
/>
|
|
276
|
+
</StackItem>
|
|
277
|
+
)}
|
|
222
278
|
</Stack>
|
|
223
279
|
)}
|
|
224
280
|
</FieldArray>
|