You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
349 lines
12 KiB
349 lines
12 KiB
import { Assignment as AssignmentIcon } from '@mui/icons-material'; |
|
import { Grid } from '@mui/material'; |
|
import { useRouter } from 'next/router'; |
|
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; |
|
|
|
import API_BASE_URL from '../lib/consts/API_BASE_URL'; |
|
import { BLACK } from '../lib/consts/DEFAULT_THEME'; |
|
|
|
import api from '../lib/api'; |
|
import ConfirmDialog from './ConfirmDialog'; |
|
import ContainedButton from './ContainedButton'; |
|
import FlexBox from './FlexBox'; |
|
import GeneralInitForm, { |
|
GeneralInitFormForwardedRefContent, |
|
GeneralInitFormValues, |
|
} from './GeneralInitForm'; |
|
import handleAPIError from '../lib/handleAPIError'; |
|
import IconButton from './IconButton'; |
|
import IconWithIndicator, { |
|
IconWithIndicatorForwardedRefContent, |
|
} from './IconWithIndicator'; |
|
import JobSummary, { JobSummaryForwardedRefContent } from './JobSummary'; |
|
import Link from './Link'; |
|
import MessageBox, { Message } from './MessageBox'; |
|
import NetworkInitForm, { |
|
NetworkInitFormForwardedRefContent, |
|
NetworkInitFormValues, |
|
} from './NetworkInitForm'; |
|
import { Panel, PanelHeader } from './Panels'; |
|
import setMapNetwork from '../lib/setMapNetwork'; |
|
import Spinner from './Spinner'; |
|
import { BodyText, HeaderText, InlineMonoText, MonoText } from './Text'; |
|
import useProtectedState from '../hooks/useProtectedState'; |
|
|
|
const StrikerInitForm: FC = () => { |
|
const { |
|
isReady, |
|
query: { re }, |
|
} = useRouter(); |
|
|
|
const [submitMessage, setSubmitMessage] = useState<Message | undefined>(); |
|
const [requestBody, setRequestBody] = useState< |
|
(GeneralInitFormValues & NetworkInitFormValues) | undefined |
|
>(); |
|
const [isOpenConfirm, setIsOpenConfirm] = useState<boolean>(false); |
|
const [isDisableSubmit, setIsDisableSubmit] = useState<boolean>(true); |
|
const [isGeneralInitFormValid, setIsGeneralInitFormValid] = |
|
useState<boolean>(false); |
|
const [isNetworkInitFormValid, setIsNetworkInitFormValid] = |
|
useState<boolean>(false); |
|
const [isSubmittingForm, setIsSubmittingForm] = useState<boolean>(false); |
|
const [hostNumber, setHostNumber] = useState<string | undefined>(); |
|
|
|
const [hostDetail, setHostDetail] = useProtectedState< |
|
APIHostDetail | undefined |
|
>(undefined); |
|
|
|
const allowGetHostDetail = useRef<boolean>(true); |
|
|
|
const generalInitFormRef = useRef<GeneralInitFormForwardedRefContent>({}); |
|
const networkInitFormRef = useRef<NetworkInitFormForwardedRefContent>({}); |
|
|
|
const jobIconRef = useRef<IconWithIndicatorForwardedRefContent>({}); |
|
const jobSummaryRef = useRef<JobSummaryForwardedRefContent>({}); |
|
|
|
const reconfig = useMemo<boolean>(() => Boolean(re), [re]); |
|
|
|
const buildSubmitSection = useMemo( |
|
() => |
|
isSubmittingForm ? ( |
|
<Spinner /> |
|
) : ( |
|
<FlexBox row sx={{ flexDirection: 'row-reverse' }}> |
|
<ContainedButton |
|
disabled={isDisableSubmit} |
|
onClick={() => { |
|
setRequestBody({ |
|
...(generalInitFormRef.current.get?.call(null) ?? {}), |
|
...(networkInitFormRef.current.get?.call(null) ?? { |
|
networks: [], |
|
}), |
|
}); |
|
|
|
setIsOpenConfirm(true); |
|
}} |
|
> |
|
Initialize |
|
</ContainedButton> |
|
</FlexBox> |
|
), |
|
[isDisableSubmit, isSubmittingForm], |
|
); |
|
|
|
const panelTitle = useMemo(() => { |
|
let title = 'Loading...'; |
|
|
|
if (isReady) { |
|
title = reconfig |
|
? `Reconfigure ${hostDetail?.shortHostName ?? 'striker'}` |
|
: 'Initialize striker'; |
|
} |
|
|
|
return title; |
|
}, [hostDetail?.shortHostName, isReady, reconfig]); |
|
|
|
const toggleSubmitDisabled = useCallback((...testResults: boolean[]) => { |
|
setIsDisableSubmit(!testResults.every((testResult) => testResult)); |
|
}, []); |
|
|
|
useEffect(() => { |
|
if (isReady) { |
|
if (reconfig && allowGetHostDetail.current) { |
|
allowGetHostDetail.current = false; |
|
|
|
api |
|
.get<APIHostDetail>('/host/local') |
|
.then(({ data }) => { |
|
setHostDetail(data); |
|
}) |
|
.catch((error) => { |
|
const emsg = handleAPIError(error); |
|
|
|
emsg.children = ( |
|
<>Failed to get host detail data. {emsg.children}</> |
|
); |
|
|
|
setSubmitMessage(emsg); |
|
}); |
|
} |
|
} |
|
}, [isReady, reconfig, setHostDetail]); |
|
|
|
return ( |
|
<> |
|
<Panel> |
|
<PanelHeader> |
|
<HeaderText>{panelTitle}</HeaderText> |
|
<IconButton |
|
onClick={({ currentTarget }) => { |
|
jobSummaryRef.current.setAnchor?.call(null, currentTarget); |
|
jobSummaryRef.current.setOpen?.call(null, true); |
|
}} |
|
variant="normal" |
|
> |
|
<IconWithIndicator icon={AssignmentIcon} ref={jobIconRef} /> |
|
</IconButton> |
|
</PanelHeader> |
|
<FlexBox> |
|
<GeneralInitForm |
|
expectHostDetail={reconfig} |
|
hostDetail={hostDetail} |
|
onHostNumberBlurAppend={({ target: { value } }) => { |
|
setHostNumber(value); |
|
}} |
|
ref={generalInitFormRef} |
|
toggleSubmitDisabled={(testResult) => { |
|
if (testResult !== isGeneralInitFormValid) { |
|
setIsGeneralInitFormValid(testResult); |
|
toggleSubmitDisabled(testResult, isNetworkInitFormValid); |
|
} |
|
}} |
|
/> |
|
<NetworkInitForm |
|
expectHostDetail={reconfig} |
|
hostDetail={hostDetail} |
|
hostSequence={hostNumber} |
|
ref={networkInitFormRef} |
|
toggleSubmitDisabled={(testResult) => { |
|
if (testResult !== isNetworkInitFormValid) { |
|
setIsNetworkInitFormValid(testResult); |
|
toggleSubmitDisabled(isGeneralInitFormValid, testResult); |
|
} |
|
}} |
|
/> |
|
{submitMessage && ( |
|
<MessageBox |
|
{...submitMessage} |
|
onClose={() => setSubmitMessage(undefined)} |
|
/> |
|
)} |
|
{buildSubmitSection} |
|
</FlexBox> |
|
</Panel> |
|
<ConfirmDialog |
|
actionProceedText="Initialize" |
|
content={ |
|
<Grid container spacing=".6em" columns={{ xs: 2 }}> |
|
<Grid item xs={1}> |
|
<BodyText>Organization name</BodyText> |
|
</Grid> |
|
<Grid item xs={1}> |
|
<MonoText>{requestBody?.organizationName}</MonoText> |
|
</Grid> |
|
<Grid item xs={1}> |
|
<BodyText>Organization prefix</BodyText> |
|
</Grid> |
|
<Grid item xs={1}> |
|
<MonoText>{requestBody?.organizationPrefix}</MonoText> |
|
</Grid> |
|
<Grid item xs={1}> |
|
<BodyText>Striker number</BodyText> |
|
</Grid> |
|
<Grid item xs={1}> |
|
<MonoText>{requestBody?.hostNumber}</MonoText> |
|
</Grid> |
|
<Grid item xs={1}> |
|
<BodyText>Domain name</BodyText> |
|
</Grid> |
|
<Grid item xs={1}> |
|
<MonoText>{requestBody?.domainName}</MonoText> |
|
</Grid> |
|
<Grid item xs={1}> |
|
<BodyText>Host name</BodyText> |
|
</Grid> |
|
<Grid item xs={1}> |
|
<MonoText>{requestBody?.hostName}</MonoText> |
|
</Grid> |
|
<Grid item sx={{ marginTop: '1.4em' }} xs={2}> |
|
<BodyText>Networks</BodyText> |
|
</Grid> |
|
{requestBody?.networks.map( |
|
({ |
|
inputUUID, |
|
interfaces, |
|
ipAddress, |
|
name, |
|
subnetMask, |
|
type, |
|
typeCount, |
|
}) => ( |
|
<Grid key={`network-confirm-${inputUUID}`} item xs={1}> |
|
<Grid container spacing=".6em" columns={{ xs: 2 }}> |
|
<Grid item xs={2}> |
|
<BodyText> |
|
{name} ( |
|
<InlineMonoText> |
|
{`${type.toUpperCase()}${typeCount}`} |
|
</InlineMonoText> |
|
) |
|
</BodyText> |
|
</Grid> |
|
{interfaces.map((iface, ifaceIndex) => { |
|
let key = `network-confirm-${inputUUID}-interface${ifaceIndex}`; |
|
let ifaceName = 'none'; |
|
|
|
if (iface) { |
|
const { networkInterfaceName, networkInterfaceUUID } = |
|
iface; |
|
|
|
key = `${key}-${networkInterfaceUUID}`; |
|
ifaceName = networkInterfaceName; |
|
} |
|
|
|
return ( |
|
<Grid container key={key} item> |
|
<Grid item xs={1}> |
|
<BodyText>{`Link ${ifaceIndex + 1}`}</BodyText> |
|
</Grid> |
|
<Grid item xs={1}> |
|
<MonoText>{ifaceName}</MonoText> |
|
</Grid> |
|
</Grid> |
|
); |
|
})} |
|
<Grid item xs={2}> |
|
<MonoText>{`${ipAddress}/${subnetMask}`}</MonoText> |
|
</Grid> |
|
</Grid> |
|
</Grid> |
|
), |
|
)} |
|
<Grid item sx={{ marginBottom: '1.4em' }} xs={2} /> |
|
<Grid item xs={1}> |
|
<BodyText>Gateway</BodyText> |
|
</Grid> |
|
<Grid item xs={1}> |
|
<MonoText>{requestBody?.gateway}</MonoText> |
|
</Grid> |
|
<Grid item xs={1}> |
|
<BodyText>Gateway network</BodyText> |
|
</Grid> |
|
<Grid item xs={1}> |
|
<MonoText> |
|
{requestBody?.gatewayInterface?.toUpperCase()} |
|
</MonoText> |
|
</Grid> |
|
<Grid item xs={1}> |
|
<BodyText>Domain name server(s)</BodyText> |
|
</Grid> |
|
<Grid item xs={1}> |
|
<MonoText>{requestBody?.dns}</MonoText> |
|
</Grid> |
|
</Grid> |
|
} |
|
dialogProps={{ open: isOpenConfirm }} |
|
onCancelAppend={() => { |
|
setIsOpenConfirm(false); |
|
}} |
|
onProceedAppend={() => { |
|
setSubmitMessage(undefined); |
|
setIsSubmittingForm(true); |
|
setIsOpenConfirm(false); |
|
|
|
api |
|
.put('/init', requestBody) |
|
.then(() => { |
|
// Stop network mapping only on successful form submission. |
|
setMapNetwork(0); |
|
|
|
setIsSubmittingForm(false); |
|
setSubmitMessage({ |
|
children: ( |
|
<> |
|
Successfully registered the configuration job! You can check |
|
the progress at the top right icon. Once the job completes, |
|
you can access the{' '} |
|
<Link |
|
href="/login" |
|
sx={{ color: BLACK, display: 'inline-flex' }} |
|
> |
|
login page |
|
</Link> |
|
. |
|
</> |
|
), |
|
type: 'info', |
|
}); |
|
}) |
|
.catch((error) => { |
|
const errorMessage = handleAPIError(error); |
|
|
|
setSubmitMessage(errorMessage); |
|
setIsSubmittingForm(false); |
|
}); |
|
}} |
|
titleText="Confirm striker initialization" |
|
/> |
|
<JobSummary |
|
getJobUrl={(epoch) => `${API_BASE_URL}/init/job?start=${epoch}`} |
|
onFetchSuccessAppend={(jobs) => { |
|
jobIconRef.current.indicate?.call(null, Object.keys(jobs).length > 0); |
|
}} |
|
ref={jobSummaryRef} |
|
/> |
|
</> |
|
); |
|
}; |
|
|
|
export default StrikerInitForm;
|
|
|