fix(striker-ui): add subnet checks to NetworkInitForm

main
Tsu-ba-me 2 years ago
parent 90148a4fda
commit df91749802
  1. 241
      striker-ui/components/NetworkInitForm.tsx

@ -15,6 +15,7 @@ import {
DataGridProps as MUIDataGridProps, DataGridProps as MUIDataGridProps,
gridClasses as muiGridClasses, gridClasses as muiGridClasses,
} from '@mui/x-data-grid'; } from '@mui/x-data-grid';
import { Netmask } from 'netmask';
import { import {
Dispatch, Dispatch,
FC, FC,
@ -141,10 +142,11 @@ const REQUIRED_NETWORKS: NetworkInput[] = [
}, },
]; ];
const BASE_INPUT_COUNT = 2;
const MAX_INTERFACES_PER_NETWORK = 2; const MAX_INTERFACES_PER_NETWORK = 2;
const PER_NETWORK_INPUT_COUNT = 3; const IT_APPEND_KEYS = {
const INPUT_TEST_IDS = { conflictNetworkName: 'conflictNetworkName',
};
const IT_IDS = {
dnsCSV: 'domainNameServerCSV', dnsCSV: 'domainNameServerCSV',
gateway: 'gateway', gateway: 'gateway',
networkInterfaces: (prefix: string) => `${prefix}Interface`, networkInterfaces: (prefix: string) => `${prefix}Interface`,
@ -266,7 +268,6 @@ const NetworkForm: FC<{
getNetworkTypeCount: (targetType: string, lastIndex?: number) => number; getNetworkTypeCount: (targetType: string, lastIndex?: number) => number;
networkIndex: number; networkIndex: number;
networkInput: NetworkInput; networkInput: NetworkInput;
networkInputs: NetworkInput[];
networkInterfaceInputMap: NetworkInterfaceInputMap; networkInterfaceInputMap: NetworkInterfaceInputMap;
optionalNetworkInputsLength: number; optionalNetworkInputsLength: number;
setNetworkInputs: Dispatch<SetStateAction<NetworkInput[]>>; setNetworkInputs: Dispatch<SetStateAction<NetworkInput[]>>;
@ -279,7 +280,6 @@ const NetworkForm: FC<{
getNetworkTypeCount, getNetworkTypeCount,
networkIndex, networkIndex,
networkInput, networkInput,
networkInputs,
networkInterfaceInputMap, networkInterfaceInputMap,
optionalNetworkInputsLength, optionalNetworkInputsLength,
setNetworkInputs, setNetworkInputs,
@ -300,15 +300,15 @@ const NetworkForm: FC<{
[networkIndex], [networkIndex],
); );
const interfacesInputTestId = useMemo( const interfacesInputTestId = useMemo(
() => INPUT_TEST_IDS.networkInterfaces(inputTestPrefix), () => IT_IDS.networkInterfaces(inputTestPrefix),
[inputTestPrefix], [inputTestPrefix],
); );
const ipAddressInputTestId = useMemo( const ipAddressInputTestId = useMemo(
() => INPUT_TEST_IDS.networkIPAddress(inputTestPrefix), () => IT_IDS.networkIPAddress(inputTestPrefix),
[inputTestPrefix], [inputTestPrefix],
); );
const subnetMaskInputTestId = useMemo( const subnetMaskInputTestId = useMemo(
() => INPUT_TEST_IDS.networkSubnet(inputTestPrefix), () => IT_IDS.networkSubnet(inputTestPrefix),
[inputTestPrefix], [inputTestPrefix],
); );
const isNetworkOptional = useMemo( const isNetworkOptional = useMemo(
@ -316,8 +316,17 @@ const NetworkForm: FC<{
[networkIndex, optionalNetworkInputsLength], [networkIndex, optionalNetworkInputsLength],
); );
networkInput.ipAddressInputRef = ipAddressInputRef; useEffect(() => {
networkInput.subnetMaskInputRef = subnetMaskInputRef; const { ipAddressInputRef: ipRef, subnetMaskInputRef: maskRef } =
networkInput;
if (ipRef !== ipAddressInputRef || maskRef !== subnetMaskInputRef) {
networkInput.ipAddressInputRef = ipAddressInputRef;
networkInput.subnetMaskInputRef = subnetMaskInputRef;
setNetworkInputs((previous) => [...previous]);
}
}, [networkInput, setNetworkInputs]);
return ( return (
<InnerPanel> <InnerPanel>
@ -349,7 +358,7 @@ const NetworkForm: FC<{
NETWORK_TYPES[networkType] NETWORK_TYPES[networkType]
} ${getNetworkTypeCount(networkType, networkIndex)}`; } ${getNetworkTypeCount(networkType, networkIndex)}`;
setNetworkInputs([...networkInputs]); setNetworkInputs((previous) => [...previous]);
}, },
renderValue: breakpointLarge renderValue: breakpointLarge
? undefined ? undefined
@ -428,9 +437,9 @@ const NetworkForm: FC<{
networkInterfaceInputMap[networkInterfaceUUID].isApplied = networkInterfaceInputMap[networkInterfaceUUID].isApplied =
false; false;
setNetworkInterfaceInputMap({ setNetworkInterfaceInputMap((previous) => ({
...networkInterfaceInputMap, ...previous,
}); }));
testInputSeparate(interfacesInputTestId, { testInputSeparate(interfacesInputTestId, {
value: getFilled(interfaces).length, value: getFilled(interfaces).length,
}); });
@ -534,49 +543,94 @@ const NetworkInitForm = forwardRef<
[networkInputs, networkInterfaces, networkInterfaceInputMap], [networkInputs, networkInterfaces, networkInterfaceInputMap],
); );
const setGatewayInputMessage = useCallback( const setMessage = useCallback(
(message?: Message) => (key: string, message?: Message) =>
messageGroupRef.current.setMessage?.call(null, 0, message), messageGroupRef.current.setMessage?.call(null, key, message),
[], [],
); );
const setDomainNameServerCSVInputMessage = useCallback( const setDomainNameServerCSVInputMessage = useCallback(
(message?: Message) => (message?: Message) => setMessage(IT_IDS.dnsCSV, message),
messageGroupRef.current.setMessage?.call(null, 1, message), [setMessage],
[],
);
const getNetworkInputMessageIndex = useCallback(
(networkIndex: number, inputIndex: number) =>
BASE_INPUT_COUNT +
(networkInputs.length - 1 - networkIndex) * PER_NETWORK_INPUT_COUNT +
inputIndex,
[networkInputs],
); );
const setNetworkIPAddressInputMessage = useCallback( const setGatewayInputMessage = useCallback(
(networkIndex: number, message?: Message) => (message?: Message) => setMessage(IT_IDS.gateway, message),
messageGroupRef.current.setMessage?.call( [setMessage],
null,
getNetworkInputMessageIndex(networkIndex, 1),
message,
),
[getNetworkInputMessageIndex],
); );
const setNetworkSubnetMaskInputMessage = useCallback( const testSubnetConflict = useCallback(
(networkIndex: number, message?: Message) => (
messageGroupRef.current.setMessage?.call( changedIP = '',
null, changedMask = '',
getNetworkInputMessageIndex(networkIndex, 2), {
message, onConflict,
), onNoConflict,
[getNetworkInputMessageIndex], skipUUID,
}: {
onConflict?: (input: Partial<NetworkInput>, index: number) => void;
onNoConflict?: (index: number) => void;
skipUUID?: string;
},
) => {
let changedSubnet: Netmask | undefined;
try {
changedSubnet = new Netmask(`${changedIP}/${changedMask}`);
// eslint-disable-next-line no-empty
} catch (netmaskError) {}
return networkInputs.every(
(
{ inputUUID, ipAddressInputRef, name, subnetMaskInputRef },
networkIndex,
) => {
if (inputUUID === skipUUID) {
return true;
}
const otherIP = ipAddressInputRef?.current.getValue?.call(null);
const otherMask = subnetMaskInputRef?.current.getValue?.call(null);
// console.log(
// `local=${otherIP}/${otherMask},current=${changedIP}/${changedMask}`,
// );
let isConflict = false;
try {
const otherSubnet = new Netmask(`${otherIP}/${otherMask}`);
isConflict = otherSubnet.contains(changedIP);
// eslint-disable-next-line no-empty
} catch (netmaskError) {}
// console.log(`isConflict=${isConflict}`);
if (changedSubnet) {
isConflict = isConflict || changedSubnet.contains(String(otherIP));
}
// console.log(`isReverseConflict=${isConflict}`);
if (isConflict) {
onConflict?.call(null, { name }, networkIndex);
} else {
onNoConflict?.call(null, networkIndex);
}
return !isConflict;
},
);
},
[networkInputs],
); );
const inputTests: InputTestBatches = useMemo(() => { const inputTests: InputTestBatches = useMemo(() => {
const tests: InputTestBatches = { const tests: InputTestBatches = {
[INPUT_TEST_IDS.dnsCSV]: { [IT_IDS.dnsCSV]: {
defaults: { defaults: {
getValue: () => dnsCSVInputRef.current.getValue?.call(null), getValue: () => dnsCSVInputRef.current.getValue?.call(null),
onSuccess: () => { onSuccess: () => {
setDomainNameServerCSVInputMessage(undefined); setDomainNameServerCSVInputMessage();
}, },
}, },
tests: [ tests: [
@ -592,11 +646,11 @@ const NetworkInitForm = forwardRef<
{ test: testNotBlank }, { test: testNotBlank },
], ],
}, },
[INPUT_TEST_IDS.gateway]: { [IT_IDS.gateway]: {
defaults: { defaults: {
getValue: () => gatewayInputRef.current.getValue?.call(null), getValue: () => gatewayInputRef.current.getValue?.call(null),
onSuccess: () => { onSuccess: () => {
setGatewayInputMessage(undefined); setGatewayInputMessage();
}, },
}, },
tests: [ tests: [
@ -614,52 +668,104 @@ const NetworkInitForm = forwardRef<
}; };
networkInputs.forEach( networkInputs.forEach(
({ interfaces, ipAddress, name, subnetMask }, networkIndex) => { (
{ inputUUID, interfaces, ipAddressInputRef, name, subnetMaskInputRef },
networkIndex,
) => {
const inputTestPrefix = `network${networkIndex}`; const inputTestPrefix = `network${networkIndex}`;
const inputTestIDIPAddress = IT_IDS.networkIPAddress(inputTestPrefix);
const inputTestIDSubnetMask = IT_IDS.networkSubnet(inputTestPrefix);
const setNetworkIPAddressInputMessage = (message?: Message) =>
setMessage(inputTestIDIPAddress, message);
const setNetworkSubnetMaskInputMessage = (message?: Message) =>
setMessage(inputTestIDSubnetMask, message);
tests[INPUT_TEST_IDS.networkInterfaces(inputTestPrefix)] = { tests[IT_IDS.networkInterfaces(inputTestPrefix)] = {
defaults: { value: getFilled(interfaces).length }, defaults: { getValue: () => getFilled(interfaces).length },
tests: [{ test: ({ value }) => (value as number) > 0 }], tests: [{ test: ({ value }) => (value as number) > 0 }],
}; };
tests[INPUT_TEST_IDS.networkIPAddress(inputTestPrefix)] = { tests[inputTestIDIPAddress] = {
defaults: { defaults: {
getValue: () => ipAddressInputRef?.current.getValue?.call(null),
onSuccess: () => { onSuccess: () => {
setNetworkIPAddressInputMessage(networkIndex, undefined); setNetworkIPAddressInputMessage();
}, },
value: ipAddress,
}, },
tests: [ tests: [
{ {
onFailure: () => { onFailure: () => {
setNetworkIPAddressInputMessage(networkIndex, { setNetworkIPAddressInputMessage({
children: `IP address in ${name} must be a valid IPv4 address.`, children: `IP address in ${name} must be a valid IPv4 address.`,
}); });
}, },
test: ({ value }) => REP_IPV4.test(value as string), test: ({ value }) => REP_IPV4.test(value as string),
}, },
{
onFailure: ({ append }) => {
setNetworkIPAddressInputMessage({
children: `"${name}" and "${
append[IT_APPEND_KEYS.conflictNetworkName]
}" cannot be in the same subnet.`,
});
},
test: ({ append, value }) => {
const changedIP = value as string;
const changedMask =
subnetMaskInputRef?.current.getValue?.call(null);
return testSubnetConflict(changedIP, changedMask, {
onConflict: ({ name: networkName }) => {
append[IT_APPEND_KEYS.conflictNetworkName] = networkName;
},
skipUUID: inputUUID,
});
},
},
{ test: testNotBlank }, { test: testNotBlank },
], ],
}; };
tests[INPUT_TEST_IDS.networkName(inputTestPrefix)] = { tests[IT_IDS.networkName(inputTestPrefix)] = {
defaults: { value: name }, defaults: { value: name },
tests: [{ test: testNotBlank }], tests: [{ test: testNotBlank }],
}; };
tests[INPUT_TEST_IDS.networkSubnet(inputTestPrefix)] = { tests[IT_IDS.networkSubnet(inputTestPrefix)] = {
defaults: { defaults: {
getValue: () => subnetMaskInputRef?.current.getValue?.call(null),
onSuccess: () => { onSuccess: () => {
setNetworkSubnetMaskInputMessage(networkIndex, undefined); setNetworkSubnetMaskInputMessage();
}, },
value: subnetMask,
}, },
tests: [ tests: [
{ {
onFailure: () => { onFailure: () => {
setNetworkSubnetMaskInputMessage(networkIndex, { setNetworkSubnetMaskInputMessage({
children: `Subnet mask in ${name} must be a valid IPv4 address.`, children: `Subnet mask in ${name} must be a valid IPv4 address.`,
}); });
}, },
test: ({ value }) => REP_IPV4.test(value as string), test: ({ value }) => REP_IPV4.test(value as string),
}, },
{
onFailure: ({ append }) => {
setNetworkSubnetMaskInputMessage({
children: `IP address in ${name} conflicts with ${
append[IT_APPEND_KEYS.conflictNetworkName]
}.`,
});
},
test: ({ append, value }) => {
const changedIP =
ipAddressInputRef?.current.getValue?.call(null);
const changedMask = value as string;
return testSubnetConflict(changedIP, changedMask, {
onConflict: ({ name: networkName }) => {
append[IT_APPEND_KEYS.conflictNetworkName] = networkName;
},
skipUUID: inputUUID,
});
},
},
{ test: testNotBlank }, { test: testNotBlank },
], ],
}; };
@ -671,8 +777,8 @@ const NetworkInitForm = forwardRef<
networkInputs, networkInputs,
setDomainNameServerCSVInputMessage, setDomainNameServerCSVInputMessage,
setGatewayInputMessage, setGatewayInputMessage,
setNetworkIPAddressInputMessage, setMessage,
setNetworkSubnetMaskInputMessage, testSubnetConflict,
]); ]);
const testInput = useMemo( const testInput = useMemo(
() => createTestInputFunction(inputTests), () => createTestInputFunction(inputTests),
@ -960,7 +1066,6 @@ const NetworkInitForm = forwardRef<
getNetworkTypeCount, getNetworkTypeCount,
networkIndex, networkIndex,
networkInput, networkInput,
networkInputs,
networkInterfaceInputMap, networkInterfaceInputMap,
optionalNetworkInputsLength, optionalNetworkInputsLength,
setNetworkInputs, setNetworkInputs,
@ -982,7 +1087,7 @@ const NetworkInitForm = forwardRef<
id="network-init-gateway" id="network-init-gateway"
inputLabelProps={{ isNotifyRequired: true }} inputLabelProps={{ isNotifyRequired: true }}
onChange={({ target: { value } }) => { onChange={({ target: { value } }) => {
testInputSeparate(INPUT_TEST_IDS.gateway, { value }); testInputSeparate(IT_IDS.gateway, { value });
}} }}
label="Gateway" label="Gateway"
/> />
@ -995,7 +1100,7 @@ const NetworkInitForm = forwardRef<
id="network-init-dns-csv" id="network-init-dns-csv"
inputLabelProps={{ isNotifyRequired: true }} inputLabelProps={{ isNotifyRequired: true }}
onChange={({ target: { value } }) => { onChange={({ target: { value } }) => {
testInputSeparate(INPUT_TEST_IDS.dnsCSV, { value }); testInputSeparate(IT_IDS.dnsCSV, { value });
}} }}
label="Domain name server(s)" label="Domain name server(s)"
/> />
@ -1003,13 +1108,7 @@ const NetworkInitForm = forwardRef<
ref={dnsCSVInputRef} ref={dnsCSVInputRef}
/> />
</FlexBox> </FlexBox>
<MessageGroup <MessageGroup defaultMessageType="warning" ref={messageGroupRef} />
count={
BASE_INPUT_COUNT + networkInputs.length * PER_NETWORK_INPUT_COUNT
}
defaultMessageType="warning"
ref={messageGroupRef}
/>
</MUIBox> </MUIBox>
</MUIBox> </MUIBox>
); );

Loading…
Cancel
Save