import { useRouter } from 'next/router'; import { FC, useCallback, useEffect, useMemo, useRef } from 'react'; import api from '../lib/api'; import ConfirmDialog from './ConfirmDialog'; import ContainedButton from './ContainedButton'; import FlexBox from './FlexBox'; import FormSummary from './FormSummary'; import getQueryParam from '../lib/getQueryParam'; import handleAPIError from '../lib/handleAPIError'; import InputWithRef from './InputWithRef'; import MessageBox, { Message } from './MessageBox'; import MessageGroup, { MessageGroupForwardedRefContent } from './MessageGroup'; import NetworkInitForm, { NetworkInitFormForwardedRefContent, } from './NetworkInitForm'; import OutlinedInputWithLabel from './OutlinedInputWithLabel'; import { Panel, PanelHeader } from './Panels'; import Spinner from './Spinner'; import { buildPeacefulStringTestBatch } from '../lib/test_input'; import { HeaderText } from './Text'; import useConfirmDialogProps from '../hooks/useConfirmDialogProps'; import useFormUtils from '../hooks/useFormUtils'; import useProtectedState from '../hooks/useProtectedState'; const INPUT_ID_PREP_NET_HOST_NAME = 'prepare-network-host-name-input'; const INPUT_GROUP_ID_PREP_NET_NETCONF = 'prepare-network-netconf-input-group'; const INPUT_LABEL_PREP_NET_HOST_NAME = 'Host name'; const getFormData = ( { netconf, }: { netconf: NetworkInitFormForwardedRefContent; }, ...[{ target }]: DivFormEventHandlerParameters ) => { const { elements } = target as HTMLFormElement; const { value: hostName } = elements.namedItem( INPUT_ID_PREP_NET_HOST_NAME, ) as HTMLInputElement; const data = { hostName, ...netconf.get?.call(null), }; return data; }; const toFormEntries = (body: ReturnType): FormEntries => { const { networks: nets = [], ...rest } = body; const networks = nets.reduce((previous, network) => { const { interfaces: ifaces, ipAddress, name = '', type, typeCount, subnetMask, } = network; const networkId = `${type}${typeCount}`; const interfaces = ifaces.reduce((pIfaces, iface, index) => { if (iface) { const { networkInterfaceName } = iface; const linkNumber = index + 1; pIfaces[`link${linkNumber}`] = networkInterfaceName; } return pIfaces; }, {}); previous[networkId] = { name, network: `${ipAddress}/${subnetMask}`, ...interfaces, }; return previous; }, {}); return { ...rest, networks }; }; const PrepareNetworkForm: FC = ({ expectUUID: isExpectExternalHostUUID = false, hostUUID, }) => { const { isReady, query: { host_uuid: queryHostUUID }, } = useRouter(); const confirmDialogRef = useRef({}); const netconfFormRef = useRef({}); const generalInputMessageGroupRef = useRef( {}, ); const [confirmDialogProps, setConfirmDialogProps] = useConfirmDialogProps(); const [hostDetail, setHostDetail] = useProtectedState< APIHostDetail | undefined >(undefined); const [fatalErrorMessage, setFatalErrorMessage] = useProtectedState< Message | undefined >(undefined); const [isLoadingHostDetail, setIsLoadingHostDetail] = useProtectedState(true); const [previousHostUUID, setPreviousHostUUID] = useProtectedState(undefined); const { buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, isFormInvalid, setMessage, setValidity, submitForm, } = useFormUtils( [INPUT_ID_PREP_NET_HOST_NAME, INPUT_GROUP_ID_PREP_NET_NETCONF], generalInputMessageGroupRef, ); const isDifferentHostUUID = useMemo( () => hostUUID !== previousHostUUID, [hostUUID, previousHostUUID], ); const isReloadHostDetail = useMemo( () => Boolean(hostUUID) && isDifferentHostUUID, [hostUUID, isDifferentHostUUID], ); const panelHeaderElement = useMemo( () => ( Prepare network on {hostDetail?.shortHostName} ), [hostDetail], ); const netconfForm = useMemo( () => ( { setValidity(INPUT_GROUP_ID_PREP_NET_NETCONF, valid); }} /> ), [hostDetail, setValidity], ); const generalInputMessageArea = useMemo( () => ( ), [], ); const contentElement = useMemo(() => { let result; if (isLoadingHostDetail) { result = ; } else if (fatalErrorMessage) { result = ; } else { result = ( <> {panelHeaderElement} { const [event] = args; event.preventDefault(); const body = getFormData( { netconf: netconfFormRef.current }, ...args, ); setConfirmDialogProps({ actionProceedText: 'Prepare', content: ( /^(dns|[a-z]+n\d+)/.test(key) ? key.toUpperCase() : cap(key) } /> ), onProceedAppend: () => { submitForm({ body, getErrorMsg: (parentMsg) => ( <>Failed to prepare network. {parentMsg} ), method: 'put', setMsg: netconfFormRef?.current?.setMessage, successMsg: `Initiated prepare network on ${hostDetail?.shortHostName}`, url: `/host/${hostUUID}?handler=subnode-network`, }); }, titleText: `Prepare ${hostDetail?.shortHostName} network?`, }); confirmDialogRef.current.setOpen?.call(null, true); }} > } inputTestBatch={buildPeacefulStringTestBatch( INPUT_LABEL_PREP_NET_HOST_NAME, () => { setMessage(INPUT_ID_PREP_NET_HOST_NAME); }, { onFinishBatch: buildFinishInputTestBatchFunction( INPUT_ID_PREP_NET_HOST_NAME, ), }, (message) => { setMessage(INPUT_ID_PREP_NET_HOST_NAME, { children: message, }); }, )} onFirstRender={buildInputFirstRenderFunction( INPUT_ID_PREP_NET_HOST_NAME, )} required /> {generalInputMessageArea} {netconfForm} Prepare network ); } return result; }, [ isLoadingHostDetail, fatalErrorMessage, panelHeaderElement, hostDetail?.hostName, hostDetail?.shortHostName, buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, generalInputMessageArea, netconfForm, isFormInvalid, setConfirmDialogProps, submitForm, hostUUID, setMessage, ]); const getHostDetail = useCallback( (uuid: string) => { setIsLoadingHostDetail(true); if (isLoadingHostDetail) { api .get(`/host/${uuid}`) .then(({ data }) => { setPreviousHostUUID(data.hostUUID); setHostDetail(data); }) .catch((error) => { const { children } = handleAPIError(error); setFatalErrorMessage({ children: `Failed to get target host information; cannot continue. ${children}`, type: 'error', }); }) .finally(() => { setIsLoadingHostDetail(false); }); } }, [ setIsLoadingHostDetail, isLoadingHostDetail, setPreviousHostUUID, setHostDetail, setFatalErrorMessage, ], ); useEffect(() => { if (isExpectExternalHostUUID) { if (isReloadHostDetail) { getHostDetail(hostUUID as string); } } else if (isReady && !fatalErrorMessage) { if (queryHostUUID) { getHostDetail(getQueryParam(queryHostUUID)); } else { setFatalErrorMessage({ children: `No host UUID provided; cannot continue.`, type: 'error', }); setIsLoadingHostDetail(false); } } }, [ fatalErrorMessage, getHostDetail, hostUUID, isExpectExternalHostUUID, isReady, queryHostUUID, setFatalErrorMessage, setHostDetail, setIsLoadingHostDetail, isReloadHostDetail, ]); return ( <> {contentElement} ); }; export default PrepareNetworkForm;