diff --git a/striker-ui/components/NetworkInitForm.tsx b/striker-ui/components/NetworkInitForm.tsx index 82316682..a3a1a102 100644 --- a/striker-ui/components/NetworkInitForm.tsx +++ b/striker-ui/components/NetworkInitForm.tsx @@ -577,17 +577,33 @@ NetworkForm.defaultProps = { const NetworkInitForm = forwardRef< NetworkInitFormForwardedRefContent, { + expectHostDetail?: boolean; hostDetail?: APIHostDetail; toggleSubmitDisabled?: (testResult: boolean) => void; } ->(({ hostDetail, toggleSubmitDisabled }, ref) => { +>(({ expectHostDetail = false, hostDetail, toggleSubmitDisabled }, ref) => { const { - dns: xDns, - gateway: xGateway, + dns: previousDns, + gateway: previousGateway, hostType, hostUUID = 'local', + networks: previousNetworks, }: APIHostDetail = hostDetail ?? ({} as APIHostDetail); + const uninitRequiredNetworks: NetworkInput[] = useMemo( + () => + hostType === 'node' ? NODE_REQUIRED_NETWORKS : STRIKER_REQUIRED_NETWORKS, + [hostType], + ); + + const requiredNetworks = useMemo>>( + () => + hostType === 'node' ? { bcn: 1, ifn: 1, sn: 1 } : { bcn: 1, ifn: 1 }, + [hostType], + ); + + const [isReadHostDetail, setIsReadHostDetail] = useState(false); + const [dragMousePosition, setDragMousePosition] = useState<{ x: number; y: number; @@ -595,7 +611,7 @@ const NetworkInitForm = forwardRef< const [networkInterfaceInputMap, setNetworkInterfaceInputMap] = useState({}); const [networkInputs, setNetworkInputs] = useState( - hostType === 'node' ? NODE_REQUIRED_NETWORKS : STRIKER_REQUIRED_NETWORKS, + uninitRequiredNetworks, ); const [networkInterfaceHeld, setNetworkInterfaceHeld] = useState< NetworkInterfaceOverviewMetadata | undefined @@ -606,24 +622,31 @@ const NetworkInitForm = forwardRef< const dnsCSVInputRef = useRef>({}); const messageGroupRef = useRef({}); - const { data: networkInterfaces = [], isLoading } = periodicFetch< - NetworkInterfaceOverviewMetadata[] - >(`${API_BASE_URL}/init/network-interface/${hostUUID}`, { - refreshInterval: 2000, - onSuccess: (data) => { - const map = data.reduce((result, metadata) => { - const { networkInterfaceUUID } = metadata; - - result[networkInterfaceUUID] = networkInterfaceInputMap[ - networkInterfaceUUID - ] ?? { metadata }; - - return result; - }, {}); + const { + data: networkInterfaces = [], + isLoading: isLoadingNetworkInterfaces, + } = periodicFetch( + `${API_BASE_URL}/init/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); + setNetworkInterfaceInputMap(map); + }, }, - }); + ); const isDisableAddNetworkButton: boolean = useMemo( () => @@ -634,6 +657,10 @@ const NetworkInitForm = forwardRef< (hostType === 'node' && networkInterfaces.length <= 6), [hostType, networkInputs, networkInterfaces, networkInterfaceInputMap], ); + const isLoadingHostDetail: boolean = useMemo( + () => expectHostDetail && !hostDetail, + [expectHostDetail, hostDetail], + ); const setMessage = useCallback( (key: string, message?: Message) => @@ -986,20 +1013,31 @@ const NetworkInitForm = forwardRef< 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 createNetwork = useCallback( + ({ + inputUUID = uuidv4(), + interfaces = [...INITIAL_IFACES], + ipAddress = '', + name = 'Unknown Network', + subnetMask = '', + type = '', + typeCount = 0, + }: Partial = {}) => { + networkInputs.unshift({ + inputUUID, + interfaces, + ipAddress, + name, + subnetMask, + type, + typeCount, + }); + + toggleSubmitDisabled?.call(null, false); + setNetworkInputs([...networkInputs]); + }, + [networkInputs, toggleSubmitDisabled], + ); const removeNetwork = useCallback( (networkIndex: number) => { const [{ inputUUID, interfaces }] = networkInputs.splice(networkIndex, 1); @@ -1138,6 +1176,55 @@ const NetworkInitForm = forwardRef< [clearNetworkInterfaceHeld, networkInterfaceHeld], ); + useEffect(() => { + if ( + Object.keys(networkInterfaceInputMap).length > 0 && + expectHostDetail && + hostDetail && + !isReadHostDetail + ) { + setNetworkInputs( + Object.values(previousNetworks).reduce( + (previous, { ip, link1Uuid, link2Uuid = '', subnetMask, type }) => { + const name = NETWORK_TYPES[type]; + const typeCount = + getNetworkTypeCount(type, { inputs: previous }) + 1; + const isRequired = requiredNetworks[type] === typeCount; + + previous.push({ + inputUUID: uuidv4(), + interfaces: [ + networkInterfaceInputMap[link1Uuid]?.metadata, + networkInterfaceInputMap[link2Uuid]?.metadata, + ], + ipAddress: ip, + isRequired, + name, + subnetMask, + type, + typeCount, + }); + + return previous; + }, + [], + ), + ); + + setIsReadHostDetail(true); + } + }, [ + createNetwork, + expectHostDetail, + getNetworkTypeCount, + hostDetail, + isReadHostDetail, + networkInputs, + networkInterfaceInputMap, + previousNetworks, + requiredNetworks, + ]); + useEffect(() => { // Enable network mapping on component mount. setMapNetwork(1); @@ -1196,7 +1283,7 @@ const NetworkInitForm = forwardRef< const networkInputMinWidth = '13em'; const networkInputWidth = '25%'; - return isLoading ? ( + return isLoadingNetworkInterfaces ? ( ) : ( - :first-child': { - alignSelf: 'start', - marginTop: '.7em', - }, - - '& > :last-child': { - flexGrow: 1, - }, - }} - > - div': { - marginBottom: '.8em', - marginTop: '.4em', - minWidth: networkInputMinWidth, - width: networkInputWidth, + '& > :first-child': { + alignSelf: 'start', + marginTop: '.7em', }, - '& > :not(:first-child)': { - marginLeft: '1em', + '& > :last-child': { + flexGrow: 1, }, }} > - {networkInputs.map((networkInput, networkIndex) => { - const { inputUUID } = networkInput; - - return ( - - ); - })} - - + div': { + marginBottom: '.8em', + marginTop: '.4em', + minWidth: networkInputMinWidth, + width: networkInputWidth, + }, + + '& > :not(:first-child)': { + marginLeft: '1em', + }, + }} + > + {networkInputs.map((networkInput, networkIndex) => { + const { inputUUID } = networkInput; + + return ( + + ); + })} + + + )} { + createNetwork(); + }} > @@ -1381,7 +1472,7 @@ const NetworkInitForm = forwardRef< setGatewayInputMessage(); }} label="Gateway" - value={xGateway} + value={previousGateway} /> } ref={gatewayInputRef} @@ -1403,7 +1494,7 @@ const NetworkInitForm = forwardRef< setDnsInputMessage(); }} label="Domain name server(s)" - value={xDns} + value={previousDns} /> } ref={dnsCSVInputRef} @@ -1420,6 +1511,7 @@ const NetworkInitForm = forwardRef< }); NetworkInitForm.defaultProps = { + expectHostDetail: false, hostDetail: undefined, toggleSubmitDisabled: undefined, }; diff --git a/striker-ui/components/PrepareNetworkForm.tsx b/striker-ui/components/PrepareNetworkForm.tsx index 7bfdb64a..0ed10ea1 100644 --- a/striker-ui/components/PrepareNetworkForm.tsx +++ b/striker-ui/components/PrepareNetworkForm.tsx @@ -13,30 +13,27 @@ import OutlinedInputWithLabel from './OutlinedInputWithLabel'; import { Panel, PanelHeader } from './Panels'; import Spinner from './Spinner'; import { HeaderText } from './Text'; -import useProtect from '../hooks/useProtect'; import useProtectedState from '../hooks/useProtectedState'; const PrepareNetworkForm: FC = ({ expectUUID: isExpectExternalHostUUID = false, hostUUID, }) => { - const { protect } = useProtect(); - const { isReady, query: { host_uuid: queryHostUUID }, } = useRouter(); - const [dataHostDetail, setDataHostDetail] = useProtectedState< + const [hostDetail, setHostDetail] = useProtectedState< APIHostDetail | undefined - >(undefined, protect); + >(undefined); const [fatalErrorMessage, setFatalErrorMessage] = useProtectedState< Message | undefined - >(undefined, protect); - const [isLoading, setIsLoading] = useProtectedState(true, protect); - const [previousHostUUID, setPreviousHostUUID] = useProtectedState< - PrepareNetworkFormProps['hostUUID'] - >(undefined, protect); + >(undefined); + const [isLoadingHostDetail, setIsLoadingHostDetail] = + useProtectedState(true); + const [previousHostUUID, setPreviousHostUUID] = + useProtectedState(undefined); const isDifferentHostUUID = useMemo( () => hostUUID !== previousHostUUID, @@ -50,17 +47,15 @@ const PrepareNetworkForm: FC = ({ const panelHeaderElement = useMemo( () => ( - - Prepare network on {dataHostDetail?.shortHostName} - + Prepare network on {hostDetail?.shortHostName} ), - [dataHostDetail], + [hostDetail], ); const contentElement = useMemo(() => { let result; - if (isLoading) { + if (isLoadingHostDetail) { result = ; } else if (fatalErrorMessage) { result = ; @@ -75,12 +70,12 @@ const PrepareNetworkForm: FC = ({ formControlProps={{ sx: { maxWidth: '20em' } }} id="prepare-network-host-name" label="Host name" - value={dataHostDetail?.hostName} + value={hostDetail?.hostName} /> } required /> - + Prepare network @@ -90,18 +85,18 @@ const PrepareNetworkForm: FC = ({ } return result; - }, [dataHostDetail, fatalErrorMessage, isLoading, panelHeaderElement]); + }, [hostDetail, fatalErrorMessage, isLoadingHostDetail, panelHeaderElement]); const getHostDetail = useCallback( (uuid: string) => { - setIsLoading(true); + setIsLoadingHostDetail(true); - if (isLoading) { + if (isLoadingHostDetail) { api .get(`/host/${uuid}`) .then(({ data }) => { setPreviousHostUUID(data.hostUUID); - setDataHostDetail(data); + setHostDetail(data); }) .catch((error) => { const { children } = handleAPIError(error); @@ -112,15 +107,15 @@ const PrepareNetworkForm: FC = ({ }); }) .finally(() => { - setIsLoading(false); + setIsLoadingHostDetail(false); }); } }, [ - setIsLoading, - isLoading, + setIsLoadingHostDetail, + isLoadingHostDetail, setPreviousHostUUID, - setDataHostDetail, + setHostDetail, setFatalErrorMessage, ], ); @@ -139,7 +134,7 @@ const PrepareNetworkForm: FC = ({ type: 'error', }); - setIsLoading(false); + setIsLoadingHostDetail(false); } } }, [ @@ -150,8 +145,8 @@ const PrepareNetworkForm: FC = ({ isReady, queryHostUUID, setFatalErrorMessage, - setDataHostDetail, - setIsLoading, + setHostDetail, + setIsLoadingHostDetail, isReloadHostDetail, ]); diff --git a/striker-ui/lib/consts/NETWORK_TYPES.ts b/striker-ui/lib/consts/NETWORK_TYPES.ts index a1954860..ce74380b 100644 --- a/striker-ui/lib/consts/NETWORK_TYPES.ts +++ b/striker-ui/lib/consts/NETWORK_TYPES.ts @@ -1,4 +1,4 @@ -const NETWORK_TYPES: Record = { +const NETWORK_TYPES: Record & Record = { bcn: 'Back-Channel Network', ifn: 'Internet-Facing Network', mn: 'Migration Network', diff --git a/striker-ui/types/APIHost.d.ts b/striker-ui/types/APIHost.d.ts index 6dcfd14d..5176f6ed 100644 --- a/striker-ui/types/APIHost.d.ts +++ b/striker-ui/types/APIHost.d.ts @@ -41,8 +41,27 @@ type APIHostOverviewList = { type APIHostDetail = APIHostOverview & { dns: string; + domain?: string; gateway: string; + gatewayInterface: string; installTarget: APIHostInstallTarget; + networks: { + [networkId: string]: { + createBridge?: NumberBoolean; + ip: string; + link1MacToSet: string; + link1Uuid: string; + link2MacToSet?: string; + link2Uuid?: string; + subnetMask: string; + type: NetworkType; + }; + }; + organization?: string; + prefix?: string; + sequence?: string; + strikerPassword?: string; + strikerUser?: string; }; type APIDeleteHostConnectionRequestBody = { [key: 'local' | string]: string[] }; diff --git a/striker-ui/types/NetworkType.d.ts b/striker-ui/types/NetworkType.d.ts new file mode 100644 index 00000000..13641680 --- /dev/null +++ b/striker-ui/types/NetworkType.d.ts @@ -0,0 +1 @@ +type NetworkType = 'bcn' | 'ifn' | 'mn' | 'sn'; diff --git a/striker-ui/types/NumberBoolean.d.ts b/striker-ui/types/NumberBoolean.d.ts new file mode 100644 index 00000000..f015d9ff --- /dev/null +++ b/striker-ui/types/NumberBoolean.d.ts @@ -0,0 +1 @@ +type NumberBoolean = '0' | '1';