import { ReactElement, useCallback, useMemo } from 'react'; import { v4 as uuidv4 } from 'uuid'; import NETWORK_TYPES from '../../lib/consts/NETWORK_TYPES'; import AnvilNetworkInputGroup from './AnvilNetworkInputGroup'; import buildObjectStateSetterCallback from '../../lib/buildObjectStateSetterCallback'; import Grid from '../Grid'; import IconButton from '../IconButton'; import InputWithRef from '../InputWithRef'; import OutlinedInputWithLabel from '../OutlinedInputWithLabel'; import { buildNumberTestBatch } from '../../lib/test_input'; const INPUT_ID_PREFIX_ANVIL_NETWORK_CONFIG = 'anvil-network-config-input'; const INPUT_CELL_ID_PREFIX_ANVIL_NETWORK_CONFIG = `${INPUT_ID_PREFIX_ANVIL_NETWORK_CONFIG}-cell`; const INPUT_ID_ANVIL_NETWORK_CONFIG_DNS = 'anvil-network-config-input-dns'; const INPUT_ID_ANVIL_NETWORK_CONFIG_MTU = 'anvil-network-config-input-mtu'; const INPUT_ID_ANVIL_NETWORK_CONFIG_NTP = 'anvil-network-config-input-ntp'; const INPUT_LABEL_ANVIL_NETWORK_CONFIG_DNS = 'DNS'; const INPUT_LABEL_ANVIL_NETWORK_CONFIG_MTU = 'MTU'; const INPUT_LABEL_ANVIL_NETWORK_CONFIG_NTP = 'NTP'; const DEFAULT_DNS_CSV = '8.8.8.8, 8.8.4.4'; const NETWORK_TYPE_ENTRIES = Object.entries(NETWORK_TYPES); const assertIfn = (type: string) => type === 'ifn'; const assertMn = (type: string) => type === 'mn'; const AnvilNetworkConfigInputGroup = < M extends MapToInputTestID & { [K in | typeof INPUT_ID_ANVIL_NETWORK_CONFIG_DNS | typeof INPUT_ID_ANVIL_NETWORK_CONFIG_MTU | typeof INPUT_ID_ANVIL_NETWORK_CONFIG_NTP]: string; }, >({ formUtils, networkListEntries, previous: { dnsCsv: previousDnsCsv = DEFAULT_DNS_CSV, mtu: previousMtu, ntpCsv: previousNtpCsv, } = {}, setNetworkList, }: AnvilNetworkConfigInputGroupProps): ReactElement => { const { buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, msgSetters, } = formUtils; const getNetworkNumber = useCallback( ( type: string, { input = networkListEntries, end = networkListEntries.length, }: { input?: Array<[string, ManifestNetwork]>; end?: number; } = {}, ) => { const limit = end - 1; let netNum = 0; input.every(([, { networkType }], networkIndex) => { if (networkType === type) { netNum += 1; } return networkIndex < limit; }); return netNum; }, [networkListEntries], ); const networkTypeOptions = useMemo( () => NETWORK_TYPE_ENTRIES.map(([key, value]) => ({ displayValue: value, value: key, })), [], ); const buildNetwork = useCallback( ({ networkMinIp = '', networkSubnetMask = '', networkType = 'ifn', // Params that depend on others. networkGateway = assertIfn(networkType) ? '' : undefined, networkNumber = getNetworkNumber(networkType) + 1, }: Partial = {}): { network: ManifestNetwork; networkId: string; } => ({ network: { networkGateway, networkMinIp, networkNumber, networkSubnetMask, networkType, }, networkId: uuidv4(), }), [getNetworkNumber], ); const setNetwork = useCallback( (key: string, value?: ManifestNetwork) => setNetworkList(buildObjectStateSetterCallback(key, value)), [setNetworkList], ); const handleNetworkTypeChange = useCallback( ( { networkId: targetId, networkType: previousType }, { target: { value } }, ) => { const newType = String(value); let isIdMatch = false; let newTypeNumber = 0; const newList = networkListEntries.reduce( (previous, [networkId, networkValue]) => { const { networkNumber: initnn, networkType: initnt } = networkValue; let networkNumber = initnn; let networkType = initnt; if (networkId === targetId) { isIdMatch = true; networkType = newType; } const isTypeMatch = networkType === newType; if (isTypeMatch) { newTypeNumber += 1; } if (isIdMatch) { if (isTypeMatch) { networkNumber = newTypeNumber; } else if (networkType === previousType) { networkNumber -= 1; } previous[networkId] = { ...networkValue, networkNumber, networkType, }; } else { previous[networkId] = networkValue; } return previous; }, {}, ); setNetworkList(newList); }, [networkListEntries, setNetworkList], ); const handleNetworkRemove = useCallback( ({ networkId: rmId, networkType: rmType }) => { let isIdMatch = false; let networkNumber = 0; const newList = networkListEntries.reduce( (previous, [networkId, networkValue]) => { if (networkId === rmId) { isIdMatch = true; } else { const { networkType } = networkValue; if (networkType === rmType) { networkNumber += 1; } previous[networkId] = isIdMatch ? { ...networkValue, networkNumber } : networkValue; } return previous; }, {}, ); setNetworkList(newList); }, [networkListEntries, setNetworkList], ); const networksGridLayout = useMemo(() => { let result: GridLayout = {}; result = networkListEntries.reduce( ( previous, [ networkId, { networkGateway, networkMinIp, networkNumber, networkSubnetMask, networkType, }, ], ) => { const cellId = `${INPUT_CELL_ID_PREFIX_ANVIL_NETWORK_CONFIG}-${networkId}`; const idPrefix = `anvil-network-${networkId}`; const inputIdPrefix = `${INPUT_ID_PREFIX_ANVIL_NETWORK_CONFIG}-${networkId}`; const inputGatewayId = `${inputIdPrefix}-gateway`; const inputMinIpId = `${inputIdPrefix}-min-ip`; const inputNetworkTypeId = `${inputIdPrefix}-network-type`; const inputSubnetMaskId = `${inputIdPrefix}-subnet-mask`; const isFirstNetwork = networkNumber === 1; const isIfn = assertIfn(networkType); const isMn = assertMn(networkType); const isOptional = isMn || !isFirstNetwork; previous[cellId] = { children: ( ), md: 3, sm: 2, }; return previous; }, result, ); return result; }, [ formUtils, networkListEntries, networkTypeOptions, handleNetworkRemove, handleNetworkTypeChange, ]); return ( { const { network: newNet, networkId: newNetId } = buildNetwork(); setNetwork(newNetId, newNet); }} /> ), display: 'flex', justifyContent: 'center', md: 3, sm: 2, }, 'anvil-network-config-input-cell-dns': { children: ( } required /> ), }, 'anvil-network-config-input-cell-ntp': { children: ( } /> ), }, 'anvil-network-config-input-cell-mtu': { children: ( } inputTestBatch={buildNumberTestBatch( INPUT_LABEL_ANVIL_NETWORK_CONFIG_MTU, () => { msgSetters[INPUT_ID_ANVIL_NETWORK_CONFIG_MTU](); }, { onFinishBatch: buildFinishInputTestBatchFunction( INPUT_ID_ANVIL_NETWORK_CONFIG_MTU, ), }, (message) => { msgSetters[INPUT_ID_ANVIL_NETWORK_CONFIG_MTU]({ children: message, }); }, )} onFirstRender={buildInputFirstRenderFunction( INPUT_ID_ANVIL_NETWORK_CONFIG_MTU, )} valueType="number" /> ), }, }} spacing="1em" /> ); }; export { INPUT_ID_ANVIL_NETWORK_CONFIG_DNS, INPUT_ID_ANVIL_NETWORK_CONFIG_MTU, INPUT_ID_ANVIL_NETWORK_CONFIG_NTP, }; export default AnvilNetworkConfigInputGroup;