Local modifications to ClusterLabs/Anvil by Alteeve
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.

352 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: reconfig ? (
<>Successfully initiated reconfiguration.</>
) : (
<>
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;