diff --git a/striker-ui/components/NetworkInitForm.tsx b/striker-ui/components/NetworkInitForm.tsx index d39e3df3..1b25e4a2 100644 --- a/striker-ui/components/NetworkInitForm.tsx +++ b/striker-ui/components/NetworkInitForm.tsx @@ -143,6 +143,13 @@ const REQUIRED_NETWORKS: NetworkInput[] = [ const BASE_INPUT_COUNT = 2; const MAX_INTERFACES_PER_NETWORK = 2; const PER_NETWORK_INPUT_COUNT = 3; +const INPUT_TEST_IDS = { + dnsCSV: 'domainNameServerCSV', + gateway: 'gateway', + networkName: (prefix: string) => `${prefix}Name`, + networkIPAddress: (prefix: string) => `${prefix}IPAddress`, + networkSubnet: (prefix: string) => `${prefix}SubnetMask`, +}; const NETWORK_INTERFACE_TEMPLATE = Array.from( { length: MAX_INTERFACES_PER_NETWORK }, @@ -265,6 +272,8 @@ const NetworkForm: FC<{ setNetworkInterfaceInputMap: Dispatch< SetStateAction >; + testAllInputs: (...excludeTestIds: string[]) => boolean; + toggleSubmitDisabled?: ToggleSubmitDisabledFunction; }> = ({ createDropMouseUpHandler, getNetworkTypeCount, @@ -276,6 +285,8 @@ const NetworkForm: FC<{ optionalNetworkInputsLength, setNetworkInputs, setNetworkInterfaceInputMap, + testAllInputs, + toggleSubmitDisabled, }) => { const theme = useTheme(); const breakpointMedium = useMediaQuery(theme.breakpoints.up('md')); @@ -286,9 +297,23 @@ const NetworkForm: FC<{ const { inputUUID, interfaces, ipAddress, subnetMask, type } = networkInput; - const inputTestPrefix = `network${networkIndex}`; + const inputTestPrefix = useMemo( + () => `network${networkIndex}`, + [networkIndex], + ); + const ipAddressInputTestId = useMemo( + () => INPUT_TEST_IDS.networkIPAddress(inputTestPrefix), + [inputTestPrefix], + ); + const subnetMaskInputTestId = useMemo( + () => INPUT_TEST_IDS.networkSubnet(inputTestPrefix), + [inputTestPrefix], + ); - const isNetworkOptional = networkIndex < optionalNetworkInputsLength; + const isNetworkOptional = useMemo( + () => networkIndex < optionalNetworkInputsLength, + [networkIndex, optionalNetworkInputsLength], + ); networkInput.ipAddressInputRef = ipAddressInputRef; networkInput.subnetMaskInputRef = subnetMaskInputRef; @@ -417,10 +442,19 @@ const NetworkForm: FC<{ inputLabelProps={{ isNotifyRequired: true }} label="IP address" onChange={({ target: { value } }) => { - testInput({ - inputs: { [`${inputTestPrefix}IPAddress`]: { value } }, + const isLocalValid = testInput({ + inputs: { + [ipAddressInputTestId]: { + value, + }, + }, tests: inputTests, }); + + toggleSubmitDisabled?.call( + null, + isLocalValid && testAllInputs(ipAddressInputTestId), + ); }} value={ipAddress} /> @@ -434,10 +468,17 @@ const NetworkForm: FC<{ inputLabelProps={{ isNotifyRequired: true }} label="Subnet mask" onChange={({ target: { value } }) => { - testInput({ - inputs: { [`${inputTestPrefix}SubnetMask`]: { value } }, + const isLocalValid = testInput({ + inputs: { + [subnetMaskInputTestId]: { value }, + }, tests: inputTests, }); + + toggleSubmitDisabled?.call( + null, + isLocalValid && testAllInputs(subnetMaskInputTestId), + ); }} value={subnetMask} /> @@ -449,520 +490,550 @@ const NetworkForm: FC<{ ); }; -NetworkForm.defaultProps = { createDropMouseUpHandler: undefined }; - -const NetworkInitForm = forwardRef( - (networkInitFormProps, 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 gatewayInputRef = useRef>({}); - const dnsCSVInputRef = useRef>({}); - const messageGroupRef = useRef({}); - - const { data: networkInterfaces = MOCK_NICS, isLoading } = periodicFetch< - NetworkInterfaceOverviewMetadata[] - >(`${API_BASE_URL}/network-interface`, { - refreshInterval: 2000, - onSuccess: (data) => { - if (data instanceof Array) { - const map = data.reduce( - (reduceContainer, { networkInterfaceUUID }) => { - reduceContainer[networkInterfaceUUID] = - networkInterfaceInputMap[networkInterfaceUUID] ?? {}; - - return reduceContainer; - }, - {}, - ); +NetworkForm.defaultProps = { + createDropMouseUpHandler: undefined, + toggleSubmitDisabled: undefined, +}; - setNetworkInterfaceInputMap(map); - } - }, - }); +const NetworkInitForm = forwardRef< + NetworkInitFormForwardRefContent, + { toggleSubmitDisabled?: (testResult: boolean) => void } +>(({ 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 gatewayInputRef = useRef>({}); + const dnsCSVInputRef = useRef>({}); + const messageGroupRef = useRef({}); + + const { data: networkInterfaces = MOCK_NICS, isLoading } = periodicFetch< + NetworkInterfaceOverviewMetadata[] + >(`${API_BASE_URL}/network-interface`, { + refreshInterval: 2000, + onSuccess: (data) => { + if (data instanceof Array) { + const map = data.reduce( + (reduceContainer, { networkInterfaceUUID }) => { + reduceContainer[networkInterfaceUUID] = + networkInterfaceInputMap[networkInterfaceUUID] ?? {}; + + return reduceContainer; + }, + {}, + ); - const optionalNetworkInputsLength: number = useMemo( - () => networkInputs.length - 2, - [networkInputs], - ); - const isDisableAddNetworkButton: boolean = useMemo( - () => - networkInputs.length >= networkInterfaces.length || - Object.values(networkInterfaceInputMap).every( - ({ isApplied }) => isApplied, - ), - [networkInputs, networkInterfaces, networkInterfaceInputMap], - ); + setNetworkInterfaceInputMap(map); + } + }, + }); - const setGatewayInputMessage = useCallback( - (message?: Message) => - messageGroupRef.current.setMessage?.call(null, 0, message), - [], - ); - const setDomainNameServerCSVInputMessage = useCallback( - (message?: Message) => - messageGroupRef.current.setMessage?.call(null, 1, message), - [], - ); - const getNetworkInputMessageIndex = useCallback( - (networkIndex: number, inputIndex: number) => - BASE_INPUT_COUNT + - (networkInputs.length - 1 - networkIndex) * PER_NETWORK_INPUT_COUNT + - inputIndex, - [networkInputs], - ); - const setNetworkIPAddressInputMessage = useCallback( - (networkIndex: number, message?: Message) => - messageGroupRef.current.setMessage?.call( - null, - getNetworkInputMessageIndex(networkIndex, 1), - message, - ), - [getNetworkInputMessageIndex], - ); - const setNetworkSubnetMaskInputMessage = useCallback( - (networkIndex: number, message?: Message) => - messageGroupRef.current.setMessage?.call( - null, - getNetworkInputMessageIndex(networkIndex, 2), - message, - ), - [getNetworkInputMessageIndex], - ); + const optionalNetworkInputsLength: number = useMemo( + () => networkInputs.length - 2, + [networkInputs], + ); + const isDisableAddNetworkButton: boolean = useMemo( + () => + networkInputs.length >= networkInterfaces.length || + Object.values(networkInterfaceInputMap).every( + ({ isApplied }) => isApplied, + ), + [networkInputs, networkInterfaces, networkInterfaceInputMap], + ); - const inputTests: InputTestBatches = useMemo(() => { - const tests: InputTestBatches = { - domainNameServerCSV: { - defaults: { - onSuccess: () => { - setDomainNameServerCSVInputMessage(undefined); - }, + const setGatewayInputMessage = useCallback( + (message?: Message) => + messageGroupRef.current.setMessage?.call(null, 0, message), + [], + ); + const setDomainNameServerCSVInputMessage = useCallback( + (message?: Message) => + messageGroupRef.current.setMessage?.call(null, 1, message), + [], + ); + const getNetworkInputMessageIndex = useCallback( + (networkIndex: number, inputIndex: number) => + BASE_INPUT_COUNT + + (networkInputs.length - 1 - networkIndex) * PER_NETWORK_INPUT_COUNT + + inputIndex, + [networkInputs], + ); + const setNetworkIPAddressInputMessage = useCallback( + (networkIndex: number, message?: Message) => + messageGroupRef.current.setMessage?.call( + null, + getNetworkInputMessageIndex(networkIndex, 1), + message, + ), + [getNetworkInputMessageIndex], + ); + const setNetworkSubnetMaskInputMessage = useCallback( + (networkIndex: number, message?: Message) => + messageGroupRef.current.setMessage?.call( + null, + getNetworkInputMessageIndex(networkIndex, 2), + message, + ), + [getNetworkInputMessageIndex], + ); + + const inputTests: InputTestBatches = useMemo(() => { + const tests: InputTestBatches = { + [INPUT_TEST_IDS.dnsCSV]: { + defaults: { + getValue: () => dnsCSVInputRef.current.getValue?.call(null), + onSuccess: () => { + setDomainNameServerCSVInputMessage(undefined); }, - tests: [ - { - onFailure: () => { - setDomainNameServerCSVInputMessage({ - children: - 'Domain name servers should be a comma-separated list of IPv4 addresses.', - }); - }, - test: ({ value }) => REP_IPV4_CSV.test(value as string), - }, - { test: testNotBlank }, - ], }, - gateway: { - defaults: { - onSuccess: () => { - setGatewayInputMessage(undefined); + 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 }, + ], + }, + [INPUT_TEST_IDS.gateway]: { + defaults: { + getValue: () => gatewayInputRef.current.getValue?.call(null), + onSuccess: () => { + setGatewayInputMessage(undefined); }, - tests: [ - { - onFailure: () => { - setGatewayInputMessage({ - children: 'Gateway should be a valid IPv4 address.', - }); - }, - test: ({ value }) => REP_IPV4.test(value as string), - }, - { test: testNotBlank }, - ], }, - }; + tests: [ + { + onFailure: () => { + setGatewayInputMessage({ + children: 'Gateway should be a valid IPv4 address.', + }); + }, + test: ({ value }) => REP_IPV4.test(value as string), + }, + { test: testNotBlank }, + ], + }, + }; - networkInputs.forEach(({ name }, networkIndex) => { - const inputTestPrefix = `network${networkIndex}`; + networkInputs.forEach(({ ipAddress, name, subnetMask }, networkIndex) => { + const inputTestPrefix = `network${networkIndex}`; - tests[`${inputTestPrefix}Name`] = { - tests: [{ test: testNotBlank }], - }; - tests[`${inputTestPrefix}IPAddress`] = { - defaults: { - onSuccess: () => { - setNetworkIPAddressInputMessage(networkIndex, undefined); - }, + tests[INPUT_TEST_IDS.networkName(inputTestPrefix)] = { + defaults: { value: name }, + tests: [{ test: testNotBlank }], + }; + tests[INPUT_TEST_IDS.networkIPAddress(inputTestPrefix)] = { + defaults: { + onSuccess: () => { + setNetworkIPAddressInputMessage(networkIndex, undefined); }, - tests: [ - { - onFailure: () => { - setNetworkIPAddressInputMessage(networkIndex, { - children: `IP address in ${name} must be a valid IPv4 address.`, - }); - }, - test: ({ value }) => REP_IPV4.test(value as string), - }, - { test: testNotBlank }, - ], - }; - tests[`${inputTestPrefix}SubnetMask`] = { - defaults: { - onSuccess: () => { - setNetworkSubnetMaskInputMessage(networkIndex, undefined); + value: ipAddress, + }, + tests: [ + { + onFailure: () => { + setNetworkIPAddressInputMessage(networkIndex, { + children: `IP address in ${name} must be a valid IPv4 address.`, + }); }, + test: ({ value }) => REP_IPV4.test(value as string), }, - tests: [ - { - onFailure: () => { - setNetworkSubnetMaskInputMessage(networkIndex, { - children: `Subnet mask in ${name} must be a valid IPv4 address.`, - }); - }, - test: ({ value }) => REP_IPV4.test(value as string), + { test: testNotBlank }, + ], + }; + tests[INPUT_TEST_IDS.networkSubnet(inputTestPrefix)] = { + defaults: { + onSuccess: () => { + setNetworkSubnetMaskInputMessage(networkIndex, undefined); + }, + value: subnetMask, + }, + tests: [ + { + onFailure: () => { + setNetworkSubnetMaskInputMessage(networkIndex, { + children: `Subnet mask in ${name} must be a valid IPv4 address.`, + }); }, - { test: testNotBlank }, - ], - }; - }); - - return tests; - }, [ - networkInputs, - setDomainNameServerCSVInputMessage, - setGatewayInputMessage, - setNetworkIPAddressInputMessage, - setNetworkSubnetMaskInputMessage, - ]); - - const clearNetworkInterfaceHeld = useCallback(() => { - setNetworkInterfaceHeld(undefined); - }, []); - const createNetwork = useCallback(() => { - networkInputs.unshift({ - inputUUID: uuidv4(), - interfaces: [], - ipAddress: '', - name: 'Unknown Network', - subnetMask: '', - type: '', - }); - - setNetworkInputs([...networkInputs]); - }, [networkInputs]); - const getNetworkTypeCount = useCallback( - (targetType: string, lastIndex = 0) => { - let count = 0; - - for ( - let index = networkInputs.length - 1; - index >= lastIndex; - index -= 1 - ) { - if (networkInputs[index].type === targetType) { - count += 1; - } - } + test: ({ value }) => REP_IPV4.test(value as string), + }, + { test: testNotBlank }, + ], + }; + }); - return count; - }, - [networkInputs], - ); + return tests; + }, [ + networkInputs, + setDomainNameServerCSVInputMessage, + setGatewayInputMessage, + setNetworkIPAddressInputMessage, + setNetworkSubnetMaskInputMessage, + ]); + + const testAllInputs = useCallback( + (...excludeTestIds: string[]) => + testInput({ + excludeTestIds, + isIgnoreOnCallbacks: true, + tests: inputTests, + }), + [inputTests], + ); + const clearNetworkInterfaceHeld = useCallback(() => { + setNetworkInterfaceHeld(undefined); + }, []); + const createNetwork = useCallback(() => { + networkInputs.unshift({ + inputUUID: uuidv4(), + interfaces: [], + ipAddress: '', + name: 'Unknown Network', + subnetMask: '', + type: '', + }); - const createDropMouseUpHandler: - | (( - interfaces: (NetworkInterfaceOverviewMetadata | undefined)[], - interfaceIndex: number, - ) => MUIBoxProps['onMouseUp']) - | undefined = useMemo(() => { - if (networkInterfaceHeld === undefined) { - return undefined; + setNetworkInputs([...networkInputs]); + }, [networkInputs]); + const getNetworkTypeCount = useCallback( + (targetType: string, lastIndex = 0) => { + let count = 0; + + for ( + let index = networkInputs.length - 1; + index >= lastIndex; + index -= 1 + ) { + if (networkInputs[index].type === targetType) { + count += 1; + } } - const { networkInterfaceUUID } = networkInterfaceHeld; + return count; + }, + [networkInputs], + ); - return ( - interfaces: (NetworkInterfaceOverviewMetadata | undefined)[], - interfaceIndex: number, - ) => - () => { - const { networkInterfaceUUID: previousNetworkInterfaceUUID } = - interfaces[interfaceIndex] ?? {}; - - if ( - previousNetworkInterfaceUUID && - previousNetworkInterfaceUUID !== networkInterfaceUUID - ) { - networkInterfaceInputMap[previousNetworkInterfaceUUID].isApplied = - false; - } + const createDropMouseUpHandler: + | (( + interfaces: (NetworkInterfaceOverviewMetadata | undefined)[], + interfaceIndex: number, + ) => MUIBoxProps['onMouseUp']) + | undefined = useMemo(() => { + if (networkInterfaceHeld === undefined) { + return undefined; + } + + 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' } : {}), - [networkInterfaceHeld], - ); - const floatingNetworkInterface: JSX.Element = useMemo(() => { - if (networkInterfaceHeld === undefined) { - return <>; - } + interfaces[interfaceIndex] = networkInterfaceHeld; + networkInterfaceInputMap[networkInterfaceUUID].isApplied = true; + }; + }, [networkInterfaceHeld, networkInterfaceInputMap]); + const dragAreaDraggingSx: MUIBoxProps['sx'] = useMemo( + () => (networkInterfaceHeld ? { cursor: 'grabbing' } : {}), + [networkInterfaceHeld], + ); + const floatingNetworkInterface: JSX.Element = useMemo(() => { + if (networkInterfaceHeld === undefined) { + return <>; + } - const { x, y } = dragMousePosition; + const { x, y } = dragMousePosition; - 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], + 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], + ); - useEffect(() => { - const map = networkInterfaces.reduce( - (reduceContainer, { networkInterfaceUUID }) => { - reduceContainer[networkInterfaceUUID] = - networkInterfaceInputMap[networkInterfaceUUID] ?? {}; + useEffect(() => { + const map = networkInterfaces.reduce( + (reduceContainer, { networkInterfaceUUID }) => { + reduceContainer[networkInterfaceUUID] = + networkInterfaceInputMap[networkInterfaceUUID] ?? {}; - return reduceContainer; - }, - {}, - ); + return reduceContainer; + }, + {}, + ); - setNetworkInterfaceInputMap(map); - - // This block inits the input map for the MOCK_NICS. - // TODO: remove after testing. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useImperativeHandle( - ref, - () => ({ - get: () => ({ - domainNameServerCSV: dnsCSVInputRef.current.getValue?.call(null), - gateway: gatewayInputRef.current.getValue?.call(null), - networks: networkInputs.map( - ( - { interfaces, ipAddressInputRef, subnetMaskInputRef, type }, - networkIndex, - ) => ({ - interfaces, - ipAddress: ipAddressInputRef?.current.getValue?.call(null) ?? '', - name: `${NETWORK_TYPES[type]} ${getNetworkTypeCount( - type, - networkIndex, - )}`, - subnetMask: - subnetMaskInputRef?.current.getValue?.call(null) ?? '', + setNetworkInterfaceInputMap(map); + + // This block inits the input map for the MOCK_NICS. + // TODO: remove after testing. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useImperativeHandle( + ref, + () => ({ + get: () => ({ + domainNameServerCSV: dnsCSVInputRef.current.getValue?.call(null), + gateway: gatewayInputRef.current.getValue?.call(null), + networks: networkInputs.map( + ( + { interfaces, ipAddressInputRef, subnetMaskInputRef, type }, + networkIndex, + ) => ({ + interfaces, + ipAddress: ipAddressInputRef?.current.getValue?.call(null) ?? '', + name: `${NETWORK_TYPES[type]} ${getNetworkTypeCount( type, - }), - ), - }), + networkIndex, + )}`, + subnetMask: subnetMaskInputRef?.current.getValue?.call(null) ?? '', + type, + }), + ), }), - [getNetworkTypeCount, networkInputs], - ); + }), + [getNetworkTypeCount, networkInputs], + ); - return isLoading ? ( - - ) : ( + return isLoading ? ( + + ) : ( + { + const { left, top } = currentTarget.getBoundingClientRect(); + + setDragMousePosition({ + x: clientX - left, + y: clientY - top, + }); + }} + onMouseLeave={handleDragAreaMouseLeave} + onMouseMove={handleDragAreaMouseMove} + onMouseUp={handleDragAreaMouseUp} + sx={{ position: 'relative', ...dragAreaDraggingSx }} + > + {floatingNetworkInterface} { - const { left, top } = currentTarget.getBoundingClientRect(); + sx={{ + display: 'flex', + flexDirection: 'column', - setDragMousePosition({ - x: clientX - left, - y: clientY - top, - }); + '& > :not(:first-child, :nth-child(3))': { + marginTop: '1em', + }, }} - onMouseLeave={handleDragAreaMouseLeave} - onMouseMove={handleDragAreaMouseMove} - onMouseUp={handleDragAreaMouseUp} - sx={{ position: 'relative', ...dragAreaDraggingSx }} > - {floatingNetworkInterface} - { + setNetworkInterfaceHeld(row); + }, networkInterfaceInputMap)} + disableColumnMenu + disableSelectionOnClick + getRowId={({ networkInterfaceUUID }) => networkInterfaceUUID} + hideFooter + rows={networkInterfaces} sx={{ - display: 'flex', - flexDirection: 'column', + color: GREY, - '& > :not(:first-child, :nth-child(3))': { - marginTop: '1em', + [`& .${muiIconButtonClasses.root}`]: { + color: 'inherit', }, - }} - > - { - setNetworkInterfaceHeld(row); - }, networkInterfaceInputMap)} - disableColumnMenu - disableSelectionOnClick - getRowId={({ networkInterfaceUUID }) => networkInterfaceUUID} - hideFooter - rows={networkInterfaces} - sx={{ - color: GREY, - [`& .${muiIconButtonClasses.root}`]: { - color: 'inherit', - }, + [`& .${muiGridClasses.cell}:focus`]: { + outline: 'none', + }, + }} + /> + :first-child': { + alignSelf: 'start', + marginTop: '.7em', + }, - [`& .${muiGridClasses.cell}:focus`]: { - outline: 'none', - }, - }} - /> - :last-child': { + flexGrow: 1, + }, + }} + > + + + + :first-child': { - alignSelf: 'start', - marginTop: '.7em', + alignItems: 'strech', + display: 'flex', + flexDirection: 'row', + overflowX: 'auto', + paddingLeft: '.3em', + + '& > div': { + marginBottom: '.8em', + marginTop: '.4em', + minWidth: '13em', + width: '25%', }, - '& > :last-child': { - flexGrow: 1, + '& > :not(:first-child)': { + marginLeft: '1em', }, }} > - - - - div': { - marginBottom: '.8em', - marginTop: '.4em', - minWidth: '13em', - width: '25%', - }, - - '& > :not(:first-child)': { - marginLeft: '1em', - }, - }} - > - {networkInputs.map((networkInput, networkIndex) => { - const { inputUUID } = networkInput; - - return ( - - ); - })} - - - :last-child': { flexGrow: 1 } }} - > - { - testInput({ - inputs: { gateway: { value } }, - tests: inputTests, - }); - }} - label="Gateway" - /> - } - ref={gatewayInputRef} - /> - { - testInput({ - inputs: { domainNameServerCSV: { value } }, - tests: inputTests, - }); + {networkInputs.map((networkInput, networkIndex) => { + const { inputUUID } = networkInput; + + return ( + - } - ref={dnsCSVInputRef} - /> - - + + :last-child': { flexGrow: 1 } }} + > + { + const isLocalValid = testInput({ + inputs: { [INPUT_TEST_IDS.gateway]: { value } }, + tests: inputTests, + }); + + toggleSubmitDisabled?.call( + null, + isLocalValid && testAllInputs(INPUT_TEST_IDS.gateway), + ); + }} + label="Gateway" + /> } - defaultMessageType="warning" - ref={messageGroupRef} + ref={gatewayInputRef} /> - + { + const isLocalValid = testInput({ + inputs: { [INPUT_TEST_IDS.dnsCSV]: { value } }, + tests: inputTests, + }); + + toggleSubmitDisabled?.call( + null, + isLocalValid && testAllInputs(INPUT_TEST_IDS.dnsCSV), + ); + }} + label="Domain name server(s)" + /> + } + ref={dnsCSVInputRef} + /> + + - ); - }, -); + + ); +}); +NetworkInitForm.defaultProps = { toggleSubmitDisabled: undefined }; NetworkInitForm.displayName = 'NetworkInitForm'; export type { diff --git a/striker-ui/types/ToggleSubmitDisabledFunction.d.ts b/striker-ui/types/ToggleSubmitDisabledFunction.d.ts new file mode 100644 index 00000000..8246470b --- /dev/null +++ b/striker-ui/types/ToggleSubmitDisabledFunction.d.ts @@ -0,0 +1 @@ +type ToggleSubmitDisabledFunction = (testResult: boolean) => void;