From 73d04b7078e4aa9a34a06aa76cb7d0293605e94f Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 10 Jan 2023 15:48:06 -0500 Subject: [PATCH] fix(striker-ui): add storage network for nodes in NetworkInitForm --- striker-ui/components/NetworkInitForm.tsx | 1462 ++++++++++----------- 1 file changed, 721 insertions(+), 741 deletions(-) diff --git a/striker-ui/components/NetworkInitForm.tsx b/striker-ui/components/NetworkInitForm.tsx index e3f239cc..23278ae4 100644 --- a/striker-ui/components/NetworkInitForm.tsx +++ b/striker-ui/components/NetworkInitForm.tsx @@ -100,41 +100,6 @@ type TestInputToToggleSubmitDisabled = ( >, ) => void; -const MOCK_NICS: NetworkInterfaceOverviewMetadata[] = [ - { - networkInterfaceUUID: 'fe299134-c8fe-47bd-ab7a-3aa95eada1f6', - networkInterfaceMACAddress: '52:54:00:d2:31:36', - networkInterfaceName: 'ens10', - networkInterfaceState: 'up', - networkInterfaceSpeed: 10000, - networkInterfaceOrder: 1, - }, - { - networkInterfaceUUID: 'a652bfd5-61ac-4495-9881-185be8a2ac74', - networkInterfaceMACAddress: '52:54:00:d4:4d:b5', - networkInterfaceName: 'ens11', - networkInterfaceState: 'up', - networkInterfaceSpeed: 10000, - networkInterfaceOrder: 2, - }, - { - networkInterfaceUUID: 'b8089b40-0969-49c3-ad65-2470ddb420ef', - networkInterfaceMACAddress: '52:54:00:ba:f5:a3', - networkInterfaceName: 'ens3', - networkInterfaceState: 'up', - networkInterfaceSpeed: 10000, - networkInterfaceOrder: 3, - }, - { - networkInterfaceUUID: '42a17465-31b1-4e47-9a91-f803f22ffcc1', - networkInterfaceMACAddress: '52:54:00:ae:31:70', - networkInterfaceName: 'ens9', - networkInterfaceState: 'up', - networkInterfaceSpeed: 10000, - networkInterfaceOrder: 4, - }, -]; - const CLASS_PREFIX = 'NetworkInitForm'; const CLASSES = { ifaceNotApplied: `${CLASS_PREFIX}-network-interface-not-applied`, @@ -144,9 +109,10 @@ const INITIAL_IFACES = [undefined, undefined]; const NETWORK_TYPES: Record = { bcn: 'Back-Channel Network', ifn: 'Internet-Facing Network', + sn: 'Storage Network', }; -const REQUIRED_NETWORKS: NetworkInput[] = [ +const STRIKER_REQUIRED_NETWORKS: NetworkInput[] = [ { inputUUID: '30dd2ac5-8024-4a7e-83a1-6a3df7218972', interfaces: [...INITIAL_IFACES], @@ -168,6 +134,19 @@ const REQUIRED_NETWORKS: NetworkInput[] = [ typeCount: 1, }, ]; +const NODE_REQUIRED_NETWORKS: NetworkInput[] = [ + ...STRIKER_REQUIRED_NETWORKS, + { + inputUUID: '525e4847-f929-44a7-83b2-28eb289ffb57', + interfaces: [...INITIAL_IFACES], + ipAddress: '10.202.1.1', + isRequired: true, + name: `${NETWORK_TYPES.sn} 1`, + subnetMask: '255.255.0.0', + type: 'sn', + typeCount: 1, + }, +]; const MAX_INTERFACES_PER_NETWORK = 2; const IT_IDS = { @@ -584,805 +563,806 @@ const NetworkInitForm = forwardRef< hostDetail?: APIHostDetail; toggleSubmitDisabled?: (testResult: boolean) => void; } ->(({ hostDetail: { hostUUID = 'local' } = {}, toggleSubmitDisabled }, ref) => { - const [dragMousePosition, setDragMousePosition] = useState<{ - x: number; - y: number; - }>({ x: 0, y: 0 }); - const [networkInterfaceInputMap, setNetworkInterfaceInputMap] = - useState({}); - const [networkInputs, setNetworkInputs] = - useState(REQUIRED_NETWORKS); - const [networkInterfaceHeld, setNetworkInterfaceHeld] = useState< - NetworkInterfaceOverviewMetadata | undefined - >(); - const [gatewayInterface, setGatewayInterface] = useState(''); - - const gatewayInputRef = useRef>({}); - const dnsCSVInputRef = useRef>({}); - const messageGroupRef = useRef({}); - - const { data: networkInterfaces = MOCK_NICS, isLoading } = periodicFetch< - NetworkInterfaceOverviewMetadata[] - >(`${API_BASE_URL}/network-interface/${hostUUID}`, { - refreshInterval: 2000, - onSuccess: (data) => { - const map = data.reduce((result, metadata) => { - const { networkInterfaceUUID } = metadata; - - result[networkInterfaceUUID] = networkInterfaceInputMap[ - networkInterfaceUUID - ] ?? { metadata }; - - return result; - }, {}); - - setNetworkInterfaceInputMap(map); - }, - }); - - const isDisableAddNetworkButton: boolean = useMemo( - () => - networkInputs.length >= networkInterfaces.length || - Object.values(networkInterfaceInputMap).every( - ({ isApplied }) => isApplied, - ), - [networkInputs, networkInterfaces, networkInterfaceInputMap], - ); - - const setMessage = useCallback( - (key: string, message?: Message) => - messageGroupRef.current.setMessage?.call(null, key, message), - [], - ); - const setMessageRe = useCallback( - (re: RegExp, message?: Message) => - messageGroupRef.current.setMessageRe?.call(null, re, message), - [], - ); - const setDomainNameServerCSVInputMessage = useCallback( - (message?: Message) => setMessage(IT_IDS.dnsCSV, message), - [setMessage], - ); - const setGatewayInputMessage = useCallback( - (message?: Message) => setMessage(IT_IDS.gateway, message), - [setMessage], - ); - const subnetContains = useCallback( - ({ - fn = 'every', - ip = '', - mask = '', - isNegateMatch = fn === 'every', - onMatch, - onMiss, - skipUUID, - }: { - fn?: Extract, 'every' | 'some'>; - ip?: string; - isNegateMatch?: boolean; - mask?: string; - onMatch?: (otherInput: NetworkInput) => void; - onMiss?: (otherInput: NetworkInput) => void; - skipUUID?: string; - }) => { - const skipReturn = fn === 'every'; - const match = ( - a: Netmask, - { b, bIP = '' }: { aIP?: string; b?: Netmask; bIP?: string }, - ) => a.contains(b ?? bIP) || (b !== undefined && b.contains(a)); - - let subnet: Netmask | undefined; - - try { - subnet = new Netmask(`${ip}/${mask}`); - // eslint-disable-next-line no-empty - } catch (netmaskError) {} - - return networkInputs[fn]((networkInput) => { - const { inputUUID, ipAddressInputRef, subnetMaskInputRef } = - networkInput; - - if (inputUUID === skipUUID) { - return skipReturn; - } +>( + ( + { hostDetail: { hostType, hostUUID = 'local' } = {}, toggleSubmitDisabled }, + ref, + ) => { + const [dragMousePosition, setDragMousePosition] = useState<{ + x: number; + y: number; + }>({ x: 0, y: 0 }); + const [networkInterfaceInputMap, setNetworkInterfaceInputMap] = + useState({}); + const [networkInputs, setNetworkInputs] = useState( + hostType === 'node' ? NODE_REQUIRED_NETWORKS : STRIKER_REQUIRED_NETWORKS, + ); + const [networkInterfaceHeld, setNetworkInterfaceHeld] = useState< + NetworkInterfaceOverviewMetadata | undefined + >(); + const [gatewayInterface, setGatewayInterface] = useState(''); + + const gatewayInputRef = useRef>({}); + const dnsCSVInputRef = useRef>({}); + const messageGroupRef = useRef({}); + + const { data: networkInterfaces = [], isLoading } = periodicFetch< + NetworkInterfaceOverviewMetadata[] + >(`${API_BASE_URL}/network-interface/${hostUUID}`, { + refreshInterval: 2000, + onSuccess: (data) => { + const map = data.reduce( + (result, metadata) => { + const { networkInterfaceUUID } = metadata; + + result[networkInterfaceUUID] = networkInterfaceInputMap[ + networkInterfaceUUID + ] ?? { metadata }; + + return result; + }, + {}, + ); - const otherIP = ipAddressInputRef?.current.getValue?.call(null); - const otherMask = subnetMaskInputRef?.current.getValue?.call(null); + setNetworkInterfaceInputMap(map); + }, + }); - let isMatch = false; + const isDisableAddNetworkButton: boolean = useMemo( + () => + networkInputs.length >= networkInterfaces.length || + Object.values(networkInterfaceInputMap).every( + ({ isApplied }) => isApplied, + ), + [networkInputs, networkInterfaces, networkInterfaceInputMap], + ); - try { - const otherSubnet = new Netmask(`${otherIP}/${otherMask}`); + const setMessage = useCallback( + (key: string, message?: Message) => + messageGroupRef.current.setMessage?.call(null, key, message), + [], + ); + const setMessageRe = useCallback( + (re: RegExp, message?: Message) => + messageGroupRef.current.setMessageRe?.call(null, re, message), + [], + ); + const setDomainNameServerCSVInputMessage = useCallback( + (message?: Message) => setMessage(IT_IDS.dnsCSV, message), + [setMessage], + ); + const setGatewayInputMessage = useCallback( + (message?: Message) => setMessage(IT_IDS.gateway, message), + [setMessage], + ); + const subnetContains = useCallback( + ({ + fn = 'every', + ip = '', + mask = '', + isNegateMatch = fn === 'every', + onMatch, + onMiss, + skipUUID, + }: { + fn?: Extract, 'every' | 'some'>; + ip?: string; + isNegateMatch?: boolean; + mask?: string; + onMatch?: (otherInput: NetworkInput) => void; + onMiss?: (otherInput: NetworkInput) => void; + skipUUID?: string; + }) => { + const skipReturn = fn === 'every'; + const match = ( + a: Netmask, + { b, bIP = '' }: { aIP?: string; b?: Netmask; bIP?: string }, + ) => a.contains(b ?? bIP) || (b !== undefined && b.contains(a)); - isMatch = match(otherSubnet, { b: subnet, bIP: ip }); + let subnet: Netmask | undefined; + try { + subnet = new Netmask(`${ip}/${mask}`); // eslint-disable-next-line no-empty } catch (netmaskError) {} - if (isMatch) { - onMatch?.call(null, networkInput); - } else { - onMiss?.call(null, networkInput); - } + return networkInputs[fn]((networkInput) => { + const { inputUUID, ipAddressInputRef, subnetMaskInputRef } = + networkInput; - return isNegateMatch ? !isMatch : isMatch; - }); - }, - [networkInputs], - ); + if (inputUUID === skipUUID) { + return skipReturn; + } - const inputTests: InputTestBatches = useMemo(() => { - const tests: InputTestBatches = { - [IT_IDS.dnsCSV]: { - defaults: { - getValue: () => dnsCSVInputRef.current.getValue?.call(null), - onSuccess: () => { - setDomainNameServerCSVInputMessage(); - }, - }, - tests: [ - { - onFailure: () => { - setDomainNameServerCSVInputMessage({ - children: - 'Domain name servers should be a comma-separated list of IPv4 addresses without trailing comma(s).', - }); - }, - test: ({ value }) => REP_IPV4_CSV.test(value as string), - }, - { test: testNotBlank }, - ], - }, - [IT_IDS.gateway]: { - defaults: { - getValue: () => gatewayInputRef.current.getValue?.call(null), - onSuccess: () => { - setGatewayInputMessage(); - }, - }, - tests: [ - { - onFailure: () => { - setGatewayInputMessage({ - children: 'Gateway should be a valid IPv4 address.', - }); - }, - test: ({ value }) => REP_IPV4.test(value as string), - }, - { - test: ({ value }) => { - let isDistinctIP = true; - - const isIPInOneNetwork = subnetContains({ - fn: 'some', - ip: value as string, - onMatch: ({ ipAddress, name, type, typeCount }) => { - if (value === ipAddress) { - isDistinctIP = false; - - setGatewayInputMessage({ - children: `Gateway cannot be the same as IP address in ${name}.`, - }); + const otherIP = ipAddressInputRef?.current.getValue?.call(null); + const otherMask = subnetMaskInputRef?.current.getValue?.call(null); - return; - } + let isMatch = false; - setGatewayInterface(`${type}${typeCount}`); - }, - }); + try { + const otherSubnet = new Netmask(`${otherIP}/${otherMask}`); - if (!isIPInOneNetwork) { - setGatewayInputMessage({ - children: "Gateway must be in one network's subnet.", - }); - } + isMatch = match(otherSubnet, { b: subnet, bIP: ip }); - return isIPInOneNetwork && isDistinctIP; - }, - }, - { test: testNotBlank }, - ], - }, - }; + // eslint-disable-next-line no-empty + } catch (netmaskError) {} - networkInputs.forEach( - ({ - inputUUID, - interfaces, - ipAddressInputRef, - name, - subnetMaskInputRef, - }) => { - const inputTestPrefix = createInputTestPrefix(inputUUID); - const inputTestIDIfaces = IT_IDS.networkInterfaces(inputTestPrefix); - const inputTestIDIPAddress = IT_IDS.networkIPAddress(inputTestPrefix); - const inputTestIDSubnetMask = IT_IDS.networkSubnetMask(inputTestPrefix); - - const setNetworkIfacesInputMessage = (message?: Message) => - setMessage(inputTestIDIfaces, message); - const setNetworkIPAddressInputMessage = (message?: Message) => - setMessage(inputTestIDIPAddress, message); - const setNetworkSubnetMaskInputMessage = (message?: Message) => - setMessage(inputTestIDSubnetMask, message); - const setNetworkSubnetConflictInputMessage = ( - uuid: string, - otherUUID: string, - message?: Message, - ) => { - const id = `${IT_IDS.networkSubnetConflict( - inputTestPrefix, - )}-${otherUUID}`; - const reverseID = `${IT_IDS.networkSubnetConflict( - createInputTestPrefix(otherUUID), - )}-${uuid}`; - - setMessage( - messageGroupRef.current.exists?.call(null, reverseID) - ? reverseID - : id, - message, - ); - }; - const testNetworkSubnetConflictWithDefaults = ({ - ip = ipAddressInputRef?.current.getValue?.call(null), - mask = subnetMaskInputRef?.current.getValue?.call(null), - }: { - ip?: string; - mask?: string; - }) => - subnetContains({ - ip, - mask, - onMatch: ({ inputUUID: otherUUID, name: otherName }) => { - setNetworkSubnetConflictInputMessage(inputUUID, otherUUID, { - children: `"${name}" and "${otherName}" cannot be in the same subnet.`, - }); - }, - onMiss: ({ inputUUID: otherUUID }) => { - setNetworkSubnetConflictInputMessage(inputUUID, otherUUID); - }, - skipUUID: inputUUID, - }); + if (isMatch) { + onMatch?.call(null, networkInput); + } else { + onMiss?.call(null, networkInput); + } - tests[inputTestIDIfaces] = { + return isNegateMatch ? !isMatch : isMatch; + }); + }, + [networkInputs], + ); + + const inputTests: InputTestBatches = useMemo(() => { + const tests: InputTestBatches = { + [IT_IDS.dnsCSV]: { defaults: { - getCompare: () => interfaces.map((iface) => iface !== undefined), + getValue: () => dnsCSVInputRef.current.getValue?.call(null), onSuccess: () => { - setNetworkIfacesInputMessage(); + setDomainNameServerCSVInputMessage(); }, }, tests: [ { onFailure: () => { - setNetworkIfacesInputMessage({ - children: `${name} must have at least 1 interface.`, - }); - }, - test: ({ compare }) => - (compare as boolean[]).some((ifaceSet) => ifaceSet), - }, - { - onFailure: () => { - setNetworkIfacesInputMessage({ - children: `${name} must have a Link 1 interface.`, + setDomainNameServerCSVInputMessage({ + children: + 'Domain name servers should be a comma-separated list of IPv4 addresses without trailing comma(s).', }); }, - test: ({ compare: [iface1Exists, iface2Exists] }) => - !(iface2Exists && !iface1Exists), + test: ({ value }) => REP_IPV4_CSV.test(value as string), }, + { test: testNotBlank }, ], - }; - tests[inputTestIDIPAddress] = { + }, + [IT_IDS.gateway]: { defaults: { - getValue: () => ipAddressInputRef?.current.getValue?.call(null), + getValue: () => gatewayInputRef.current.getValue?.call(null), onSuccess: () => { - setNetworkIPAddressInputMessage(); + setGatewayInputMessage(); }, }, tests: [ { onFailure: () => { - setNetworkIPAddressInputMessage({ - children: `IP address in ${name} must be a valid IPv4 address.`, + setGatewayInputMessage({ + children: 'Gateway should be a valid IPv4 address.', }); }, test: ({ value }) => REP_IPV4.test(value as string), }, { - test: ({ value }) => - testNetworkSubnetConflictWithDefaults({ ip: value as string }), + test: ({ value }) => { + let isDistinctIP = true; + + const isIPInOneNetwork = subnetContains({ + fn: 'some', + ip: value as string, + onMatch: ({ ipAddress, name, type, typeCount }) => { + if (value === ipAddress) { + isDistinctIP = false; + + setGatewayInputMessage({ + children: `Gateway cannot be the same as IP address in ${name}.`, + }); + + return; + } + + setGatewayInterface(`${type}${typeCount}`); + }, + }); + + if (!isIPInOneNetwork) { + setGatewayInputMessage({ + children: "Gateway must be in one network's subnet.", + }); + } + + return isIPInOneNetwork && isDistinctIP; + }, }, { test: testNotBlank }, ], - }; - tests[IT_IDS.networkName(inputTestPrefix)] = { - defaults: { value: name }, - tests: [{ test: testNotBlank }], - }; - tests[inputTestIDSubnetMask] = { - defaults: { - getValue: () => subnetMaskInputRef?.current.getValue?.call(null), - onSuccess: () => { - setNetworkSubnetMaskInputMessage(); - }, - }, - tests: [ - { - onFailure: () => { - setNetworkSubnetMaskInputMessage({ - children: `Subnet mask in ${name} must be a valid IPv4 address.`, + }, + }; + + networkInputs.forEach( + ({ + inputUUID, + interfaces, + ipAddressInputRef, + name, + subnetMaskInputRef, + }) => { + const inputTestPrefix = createInputTestPrefix(inputUUID); + const inputTestIDIfaces = IT_IDS.networkInterfaces(inputTestPrefix); + const inputTestIDIPAddress = IT_IDS.networkIPAddress(inputTestPrefix); + const inputTestIDSubnetMask = + IT_IDS.networkSubnetMask(inputTestPrefix); + + const setNetworkIfacesInputMessage = (message?: Message) => + setMessage(inputTestIDIfaces, message); + const setNetworkIPAddressInputMessage = (message?: Message) => + setMessage(inputTestIDIPAddress, message); + const setNetworkSubnetMaskInputMessage = (message?: Message) => + setMessage(inputTestIDSubnetMask, message); + const setNetworkSubnetConflictInputMessage = ( + uuid: string, + otherUUID: string, + message?: Message, + ) => { + const id = `${IT_IDS.networkSubnetConflict( + inputTestPrefix, + )}-${otherUUID}`; + const reverseID = `${IT_IDS.networkSubnetConflict( + createInputTestPrefix(otherUUID), + )}-${uuid}`; + + setMessage( + messageGroupRef.current.exists?.call(null, reverseID) + ? reverseID + : id, + message, + ); + }; + const testNetworkSubnetConflictWithDefaults = ({ + ip = ipAddressInputRef?.current.getValue?.call(null), + mask = subnetMaskInputRef?.current.getValue?.call(null), + }: { + ip?: string; + mask?: string; + }) => + subnetContains({ + ip, + mask, + onMatch: ({ inputUUID: otherUUID, name: otherName }) => { + setNetworkSubnetConflictInputMessage(inputUUID, otherUUID, { + children: `"${name}" and "${otherName}" cannot be in the same subnet.`, }); }, - test: ({ value }) => REP_IPV4.test(value as string), + onMiss: ({ inputUUID: otherUUID }) => { + setNetworkSubnetConflictInputMessage(inputUUID, otherUUID); + }, + skipUUID: inputUUID, + }); + + tests[inputTestIDIfaces] = { + defaults: { + getCompare: () => interfaces.map((iface) => iface !== undefined), + onSuccess: () => { + setNetworkIfacesInputMessage(); + }, }, - { - test: ({ value }) => - testNetworkSubnetConflictWithDefaults({ - mask: value as string, - }), + tests: [ + { + onFailure: () => { + setNetworkIfacesInputMessage({ + children: `${name} must have at least 1 interface.`, + }); + }, + test: ({ compare }) => + (compare as boolean[]).some((ifaceSet) => ifaceSet), + }, + { + onFailure: () => { + setNetworkIfacesInputMessage({ + children: `${name} must have a Link 1 interface.`, + }); + }, + test: ({ compare: [iface1Exists, iface2Exists] }) => + !(iface2Exists && !iface1Exists), + }, + ], + }; + tests[inputTestIDIPAddress] = { + defaults: { + getValue: () => ipAddressInputRef?.current.getValue?.call(null), + onSuccess: () => { + setNetworkIPAddressInputMessage(); + }, }, - { test: testNotBlank }, - ], - }; - }, - ); - - return tests; - }, [ - networkInputs, - setDomainNameServerCSVInputMessage, - setGatewayInputMessage, - setMessage, - subnetContains, - ]); - const testInput = useMemo( - () => createTestInputFunction(inputTests), - [inputTests], - ); + tests: [ + { + onFailure: () => { + setNetworkIPAddressInputMessage({ + children: `IP address in ${name} must be a valid IPv4 address.`, + }); + }, + test: ({ value }) => REP_IPV4.test(value as string), + }, + { + test: ({ value }) => + testNetworkSubnetConflictWithDefaults({ + ip: value as string, + }), + }, + { test: testNotBlank }, + ], + }; + tests[IT_IDS.networkName(inputTestPrefix)] = { + defaults: { value: name }, + tests: [{ test: testNotBlank }], + }; + tests[inputTestIDSubnetMask] = { + defaults: { + getValue: () => subnetMaskInputRef?.current.getValue?.call(null), + onSuccess: () => { + setNetworkSubnetMaskInputMessage(); + }, + }, + tests: [ + { + onFailure: () => { + setNetworkSubnetMaskInputMessage({ + children: `Subnet mask in ${name} must be a valid IPv4 address.`, + }); + }, + test: ({ value }) => REP_IPV4.test(value as string), + }, + { + test: ({ value }) => + testNetworkSubnetConflictWithDefaults({ + mask: value as string, + }), + }, + { test: testNotBlank }, + ], + }; + }, + ); - const testInputToToggleSubmitDisabled: TestInputToToggleSubmitDisabled = - useCallback( - (options) => { - toggleSubmitDisabled?.call( - null, - testInput({ - isIgnoreOnCallbacks: true, - isTestAll: true, - - ...options, - }), - ); - }, - [testInput, toggleSubmitDisabled], + return tests; + }, [ + networkInputs, + setDomainNameServerCSVInputMessage, + setGatewayInputMessage, + setMessage, + subnetContains, + ]); + const testInput = useMemo( + () => createTestInputFunction(inputTests), + [inputTests], ); - const clearNetworkInterfaceHeld = useCallback(() => { - setNetworkInterfaceHeld(undefined); - }, []); - const createNetwork = useCallback(() => { - networkInputs.unshift({ - inputUUID: uuidv4(), - interfaces: [...INITIAL_IFACES], - ipAddress: '', - name: 'Unknown Network', - subnetMask: '', - type: '', - typeCount: 0, - }); - toggleSubmitDisabled?.call(null, false); - setNetworkInputs([...networkInputs]); - }, [networkInputs, toggleSubmitDisabled]); - const removeNetwork = useCallback( - (networkIndex: number) => { - const [{ inputUUID, interfaces }] = networkInputs.splice(networkIndex, 1); - - interfaces.forEach((iface) => { - if (iface === undefined) { - return; - } - - const { networkInterfaceUUID } = iface; - - networkInterfaceInputMap[networkInterfaceUUID].isApplied = false; + const testInputToToggleSubmitDisabled: TestInputToToggleSubmitDisabled = + useCallback( + (options) => { + toggleSubmitDisabled?.call( + null, + testInput({ + isIgnoreOnCallbacks: true, + isTestAll: true, + + ...options, + }), + ); + }, + [testInput, toggleSubmitDisabled], + ); + const clearNetworkInterfaceHeld = useCallback(() => { + setNetworkInterfaceHeld(undefined); + }, []); + const createNetwork = useCallback(() => { + networkInputs.unshift({ + inputUUID: uuidv4(), + interfaces: [...INITIAL_IFACES], + ipAddress: '', + name: 'Unknown Network', + subnetMask: '', + type: '', + typeCount: 0, }); - testInputToToggleSubmitDisabled({ excludeTestIdsRe: RegExp(inputUUID) }); + toggleSubmitDisabled?.call(null, false); setNetworkInputs([...networkInputs]); - setNetworkInterfaceInputMap((previous) => ({ - ...previous, - })); - }, - [networkInputs, networkInterfaceInputMap, testInputToToggleSubmitDisabled], - ); - const getNetworkTypeCount: GetNetworkTypeCountFunction = useCallback( - ( - targetType: string, - { - inputs = networkInputs, - lastIndex = 0, - }: { - inputs?: NetworkInput[]; - lastIndex?: number; - } = {}, - ) => { - let count = 0; - - for (let index = inputs.length - 1; index >= lastIndex; index -= 1) { - if (inputs[index].type === targetType) { - count += 1; - } - } - - return count; - }, - [networkInputs], - ); - - const createDropMouseUpHandler: - | (( - interfaces: (NetworkInterfaceOverviewMetadata | undefined)[], - interfaceIndex: number, - ) => MUIBoxProps['onMouseUp']) - | undefined = useMemo(() => { - if (networkInterfaceHeld === undefined) { - return undefined; - } + }, [networkInputs, toggleSubmitDisabled]); + const removeNetwork = useCallback( + (networkIndex: number) => { + const [{ inputUUID, interfaces }] = networkInputs.splice( + networkIndex, + 1, + ); - const { networkInterfaceUUID } = networkInterfaceHeld; - - return ( - interfaces: (NetworkInterfaceOverviewMetadata | undefined)[], - interfaceIndex: number, - ) => - () => { - const { networkInterfaceUUID: previousNetworkInterfaceUUID } = - interfaces[interfaceIndex] ?? {}; - - if ( - previousNetworkInterfaceUUID && - previousNetworkInterfaceUUID !== networkInterfaceUUID - ) { - networkInterfaceInputMap[previousNetworkInterfaceUUID].isApplied = - false; - } + interfaces.forEach((iface) => { + if (iface === undefined) { + return; + } - interfaces[interfaceIndex] = networkInterfaceHeld; - networkInterfaceInputMap[networkInterfaceUUID].isApplied = true; - }; - }, [networkInterfaceHeld, networkInterfaceInputMap]); - const dragAreaDraggingSx: MUIBoxProps['sx'] = useMemo( - () => - networkInterfaceHeld ? { cursor: 'grabbing', userSelect: 'none' } : {}, - [networkInterfaceHeld], - ); - const floatingNetworkInterface: JSX.Element = useMemo(() => { - if (networkInterfaceHeld === undefined) { - return <>; - } + const { networkInterfaceUUID } = iface; - const { x, y } = dragMousePosition; + networkInterfaceInputMap[networkInterfaceUUID].isApplied = false; + }); - return ( - + testInputToToggleSubmitDisabled({ + excludeTestIdsRe: RegExp(inputUUID), + }); + setNetworkInputs([...networkInputs]); + setNetworkInterfaceInputMap((previous) => ({ + ...previous, + })); + }, + [ + networkInputs, + networkInterfaceInputMap, + testInputToToggleSubmitDisabled, + ], ); - }, [dragMousePosition, networkInterfaceHeld]); - const handleDragAreaMouseLeave: MUIBoxProps['onMouseLeave'] = useMemo( - () => - networkInterfaceHeld - ? () => { - clearNetworkInterfaceHeld(); - } - : undefined, - [clearNetworkInterfaceHeld, networkInterfaceHeld], - ); - const handleDragAreaMouseMove: MUIBoxProps['onMouseMove'] = useMemo( - () => - networkInterfaceHeld - ? ({ currentTarget, nativeEvent: { clientX, clientY } }) => { - const { left, top } = currentTarget.getBoundingClientRect(); - - setDragMousePosition({ - x: clientX - left, - y: clientY - top, - }); - } - : undefined, - [networkInterfaceHeld], - ); - const handleDragAreaMouseUp: MUIBoxProps['onMouseUp'] = useMemo( - () => - networkInterfaceHeld - ? () => { - clearNetworkInterfaceHeld(); + const getNetworkTypeCount: GetNetworkTypeCountFunction = useCallback( + ( + targetType: string, + { + inputs = networkInputs, + lastIndex = 0, + }: { + inputs?: NetworkInput[]; + lastIndex?: number; + } = {}, + ) => { + let count = 0; + + for (let index = inputs.length - 1; index >= lastIndex; index -= 1) { + if (inputs[index].type === targetType) { + count += 1; } - : undefined, - [clearNetworkInterfaceHeld, networkInterfaceHeld], - ); + } - useEffect(() => { - const map = networkInterfaces.reduce( - (result, metadata) => { - const { networkInterfaceUUID } = metadata; + return count; + }, + [networkInputs], + ); - result[networkInterfaceUUID] = networkInterfaceInputMap[ - networkInterfaceUUID - ] ?? { metadata }; + const createDropMouseUpHandler: + | (( + interfaces: (NetworkInterfaceOverviewMetadata | undefined)[], + interfaceIndex: number, + ) => MUIBoxProps['onMouseUp']) + | undefined = useMemo(() => { + if (networkInterfaceHeld === undefined) { + return undefined; + } - return result; - }, - {}, + const { networkInterfaceUUID } = networkInterfaceHeld; + + return ( + interfaces: (NetworkInterfaceOverviewMetadata | undefined)[], + interfaceIndex: number, + ) => + () => { + const { networkInterfaceUUID: previousNetworkInterfaceUUID } = + interfaces[interfaceIndex] ?? {}; + + if ( + previousNetworkInterfaceUUID && + previousNetworkInterfaceUUID !== networkInterfaceUUID + ) { + networkInterfaceInputMap[previousNetworkInterfaceUUID].isApplied = + false; + } + + interfaces[interfaceIndex] = networkInterfaceHeld; + networkInterfaceInputMap[networkInterfaceUUID].isApplied = true; + }; + }, [networkInterfaceHeld, networkInterfaceInputMap]); + const dragAreaDraggingSx: MUIBoxProps['sx'] = useMemo( + () => + networkInterfaceHeld ? { cursor: 'grabbing', userSelect: 'none' } : {}, + [networkInterfaceHeld], ); + const floatingNetworkInterface: JSX.Element = useMemo(() => { + if (networkInterfaceHeld === undefined) { + return <>; + } - setNetworkInterfaceInputMap(map); + const { x, y } = dragMousePosition; - // This block inits the input map for the MOCK_NICS. - // TODO: remove after testing. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + return ( + + ); + }, [dragMousePosition, networkInterfaceHeld]); + const handleDragAreaMouseLeave: MUIBoxProps['onMouseLeave'] = useMemo( + () => + networkInterfaceHeld + ? () => { + clearNetworkInterfaceHeld(); + } + : undefined, + [clearNetworkInterfaceHeld, networkInterfaceHeld], + ); + const handleDragAreaMouseMove: MUIBoxProps['onMouseMove'] = useMemo( + () => + networkInterfaceHeld + ? ({ currentTarget, nativeEvent: { clientX, clientY } }) => { + const { left, top } = currentTarget.getBoundingClientRect(); + + setDragMousePosition({ + x: clientX - left, + y: clientY - top, + }); + } + : undefined, + [networkInterfaceHeld], + ); + const handleDragAreaMouseUp: MUIBoxProps['onMouseUp'] = useMemo( + () => + networkInterfaceHeld + ? () => { + clearNetworkInterfaceHeld(); + } + : undefined, + [clearNetworkInterfaceHeld, networkInterfaceHeld], + ); - useImperativeHandle( - ref, - () => ({ - get: () => ({ - domainNameServerCSV: dnsCSVInputRef.current.getValue?.call(null), - gateway: gatewayInputRef.current.getValue?.call(null), - gatewayInterface, - networks: networkInputs.map( - ({ - inputUUID, - interfaces, - ipAddressInputRef, - name, - subnetMaskInputRef, - type, - typeCount, - }) => ({ - inputUUID, - interfaces, - ipAddress: ipAddressInputRef?.current.getValue?.call(null) ?? '', - name, - subnetMask: subnetMaskInputRef?.current.getValue?.call(null) ?? '', - type, - typeCount, - }), - ), + useImperativeHandle( + ref, + () => ({ + get: () => ({ + domainNameServerCSV: dnsCSVInputRef.current.getValue?.call(null), + gateway: gatewayInputRef.current.getValue?.call(null), + gatewayInterface, + networks: networkInputs.map( + ({ + inputUUID, + interfaces, + ipAddressInputRef, + name, + subnetMaskInputRef, + type, + typeCount, + }) => ({ + inputUUID, + interfaces, + ipAddress: ipAddressInputRef?.current.getValue?.call(null) ?? '', + name, + subnetMask: + subnetMaskInputRef?.current.getValue?.call(null) ?? '', + type, + typeCount, + }), + ), + }), }), - }), - [gatewayInterface, networkInputs], - ); - - const networkInputMinWidth = '13em'; - const networkInputWidth = '25%'; + [gatewayInterface, networkInputs], + ); - return isLoading ? ( - - ) : ( - { - const { left, top } = currentTarget.getBoundingClientRect(); + const networkInputMinWidth = '13em'; + const networkInputWidth = '25%'; - setDragMousePosition({ - x: clientX - left, - y: clientY - top, - }); - }} - onMouseLeave={handleDragAreaMouseLeave} - onMouseMove={handleDragAreaMouseMove} - onMouseUp={handleDragAreaMouseUp} - sx={{ position: 'relative', ...dragAreaDraggingSx }} - > - {floatingNetworkInterface} + return isLoading ? ( + + ) : ( { + const { left, top } = currentTarget.getBoundingClientRect(); - '& > :not(:first-child, :nth-child(3))': { - marginTop: '1em', - }, + setDragMousePosition({ + x: clientX - left, + y: clientY - top, + }); }} + onMouseLeave={handleDragAreaMouseLeave} + onMouseMove={handleDragAreaMouseMove} + onMouseUp={handleDragAreaMouseUp} + sx={{ position: 'relative', ...dragAreaDraggingSx }} > - { - setNetworkInterfaceHeld(row); - }, networkInterfaceInputMap)} - componentsProps={{ - row: { - onMouseDown: ({ - target: { - parentElement: { - dataset: { id: networkInterfaceUUID = undefined } = {}, + {floatingNetworkInterface} + :not(:first-child, :nth-child(3))': { + marginTop: '1em', + }, + }} + > + { + setNetworkInterfaceHeld(row); + }, networkInterfaceInputMap)} + componentsProps={{ + row: { + onMouseDown: ({ + target: { + parentElement: { + dataset: { id: networkInterfaceUUID = undefined } = {}, + } = {}, } = {}, - } = {}, - }: { - target?: { parentElement?: { dataset?: { id?: string } } }; - }) => { - if (networkInterfaceUUID) { - const { isApplied, metadata } = - networkInterfaceInputMap[networkInterfaceUUID]; - - if (!isApplied) { - setNetworkInterfaceHeld(metadata); + }: { + target?: { parentElement?: { dataset?: { id?: string } } }; + }) => { + if (networkInterfaceUUID) { + const { isApplied, metadata } = + networkInterfaceInputMap[networkInterfaceUUID]; + + if (!isApplied) { + setNetworkInterfaceHeld(metadata); + } } - } + }, }, - }, - }} - disableColumnMenu - disableSelectionOnClick - getRowClassName={({ row: { networkInterfaceUUID } }) => { - const { isApplied } = - networkInterfaceInputMap[networkInterfaceUUID] ?? false; + }} + disableColumnMenu + disableSelectionOnClick + getRowClassName={({ row: { networkInterfaceUUID } }) => { + const { isApplied } = + networkInterfaceInputMap[networkInterfaceUUID] ?? false; - let className = ''; + let className = ''; - if (!isApplied) { - className += ` ${CLASSES.ifaceNotApplied}`; - } + if (!isApplied) { + className += ` ${CLASSES.ifaceNotApplied}`; + } - return className; - }} - getRowId={({ networkInterfaceUUID }) => networkInterfaceUUID} - hideFooter - rows={networkInterfaces} - sx={{ - color: GREY, + return className; + }} + getRowId={({ networkInterfaceUUID }) => networkInterfaceUUID} + hideFooter + rows={networkInterfaces} + sx={{ + color: GREY, - [`& .${muiIconButtonClasses.root}`]: { - color: 'inherit', - }, + [`& .${muiIconButtonClasses.root}`]: { + color: 'inherit', + }, - [`& .${muiGridClasses.cell}:focus`]: { - outline: 'none', - }, + [`& .${muiGridClasses.cell}:focus`]: { + outline: 'none', + }, - [`& .${muiGridClasses.row}.${CLASSES.ifaceNotApplied}:hover`]: { - cursor: 'grab', + [`& .${muiGridClasses.row}.${CLASSES.ifaceNotApplied}:hover`]: { + cursor: 'grab', - [`& .${muiGridClasses.cell} p`]: { - cursor: 'auto', + [`& .${muiGridClasses.cell} p`]: { + cursor: 'auto', + }, + }, + }} + /> + :first-child': { + alignSelf: 'start', + marginTop: '.7em', }, - }, - }} - /> - :first-child': { - alignSelf: 'start', - marginTop: '.7em', - }, - '& > :last-child': { - flexGrow: 1, - }, - }} - > - :last-child': { + flexGrow: 1, + }, + }} > - - - + + + div': { + marginBottom: '.8em', + marginTop: '.4em', + minWidth: networkInputMinWidth, + width: networkInputWidth, + }, + + '& > :not(:first-child)': { + marginLeft: '1em', + }, + }} + > + {networkInputs.map((networkInput, networkIndex) => { + const { inputUUID } = networkInput; + + return ( + + ); + })} + + + div': { - marginBottom: '.8em', - marginTop: '.4em', - minWidth: networkInputMinWidth, - width: networkInputWidth, - }, + marginLeft: { sm: '3.8em' }, + marginTop: '.2em', - '& > :not(:first-child)': { - marginLeft: '1em', + '& > *': { + minWidth: networkInputMinWidth, + width: { sm: networkInputWidth }, }, }} > - {networkInputs.map((networkInput, networkIndex) => { - const { inputUUID } = networkInput; - - return ( - { + testInput({ inputs: { [IT_IDS.gateway]: { value } } }); + }, + }} + inputLabelProps={{ isNotifyRequired: true }} + onChange={({ target: { value } }) => { + testInputToToggleSubmitDisabled({ + inputs: { [IT_IDS.gateway]: { value } }, + }); + setGatewayInputMessage(); }} + label="Gateway" /> - ); - })} - - - *': { - minWidth: networkInputMinWidth, - width: { sm: networkInputWidth }, - }, - }} - > - { - testInput({ inputs: { [IT_IDS.gateway]: { value } } }); - }, - }} - inputLabelProps={{ isNotifyRequired: true }} - onChange={({ target: { value } }) => { - testInputToToggleSubmitDisabled({ - inputs: { [IT_IDS.gateway]: { value } }, - }); - setGatewayInputMessage(); - }} - label="Gateway" - /> - } - ref={gatewayInputRef} - /> - { - testInput({ inputs: { [IT_IDS.dnsCSV]: { value } } }); - }, - }} - inputLabelProps={{ isNotifyRequired: true }} - onChange={({ target: { value } }) => { - testInputToToggleSubmitDisabled({ - inputs: { [IT_IDS.dnsCSV]: { value } }, - }); - setDomainNameServerCSVInputMessage(); - }} - label="Domain name server(s)" - /> - } - ref={dnsCSVInputRef} + } + ref={gatewayInputRef} + /> + { + testInput({ inputs: { [IT_IDS.dnsCSV]: { value } } }); + }, + }} + inputLabelProps={{ isNotifyRequired: true }} + onChange={({ target: { value } }) => { + testInputToToggleSubmitDisabled({ + inputs: { [IT_IDS.dnsCSV]: { value } }, + }); + setDomainNameServerCSVInputMessage(); + }} + label="Domain name server(s)" + /> + } + ref={dnsCSVInputRef} + /> + + - - + - - ); -}); + ); + }, +); NetworkInitForm.defaultProps = { hostDetail: undefined,