fix(striker-ui): add run manifest form

main
Tsu-ba-me 2 years ago
parent 2ca1277b3f
commit c0189cba8d
  1. 159
      striker-ui/components/ManageManifest/ManageManifestPanel.tsx
  2. 404
      striker-ui/components/ManageManifest/RunManifestInputGroup.tsx
  3. 8
      striker-ui/types/ManageManifest.d.ts

@ -1,4 +1,3 @@
import { PlayCircle } from '@mui/icons-material';
import { FC, useCallback, useMemo, useRef, useState } from 'react'; import { FC, useCallback, useMemo, useRef, useState } from 'react';
import API_BASE_URL from '../../lib/consts/API_BASE_URL'; import API_BASE_URL from '../../lib/consts/API_BASE_URL';
@ -25,6 +24,11 @@ import List from '../List';
import { MessageGroupForwardedRefContent } from '../MessageGroup'; import { MessageGroupForwardedRefContent } from '../MessageGroup';
import { Panel, PanelHeader } from '../Panels'; import { Panel, PanelHeader } from '../Panels';
import periodicFetch from '../../lib/fetchers/periodicFetch'; import periodicFetch from '../../lib/fetchers/periodicFetch';
import RunManifestInputGroup, {
INPUT_ID_AN_CONFIRM_PASSWORD,
INPUT_ID_AN_DESCRIPTION,
INPUT_ID_AN_PASSWORD,
} from './RunManifestInputGroup';
import Spinner from '../Spinner'; import Spinner from '../Spinner';
import { BodyText, HeaderText } from '../Text'; import { BodyText, HeaderText } from '../Text';
import useConfirmDialogProps from '../../hooks/useConfirmDialogProps'; import useConfirmDialogProps from '../../hooks/useConfirmDialogProps';
@ -40,11 +44,17 @@ const ManageManifestPanel: FC = () => {
const editManifestFormDialogRef = useRef<ConfirmDialogForwardedRefContent>( const editManifestFormDialogRef = useRef<ConfirmDialogForwardedRefContent>(
{}, {},
); );
const runManifestFormDialogRef = useRef<ConfirmDialogForwardedRefContent>({});
const messageGroupRef = useRef<MessageGroupForwardedRefContent>({}); const messageGroupRef = useRef<MessageGroupForwardedRefContent>({});
const [confirmDialogProps] = useConfirmDialogProps(); const [confirmDialogProps] = useConfirmDialogProps();
const [hostOverviews, setHostOverviews] = useProtectedState<
APIHostOverviewList | undefined
>(undefined);
const [isEditManifests, setIsEditManifests] = useState<boolean>(false); const [isEditManifests, setIsEditManifests] = useState<boolean>(false);
const [isLoadingHostOverviews, setIsLoadingHostOverviews] =
useState<boolean>(true);
const [isLoadingManifestDetail, setIsLoadingManifestDetail] = const [isLoadingManifestDetail, setIsLoadingManifestDetail] =
useProtectedState<boolean>(true); useProtectedState<boolean>(true);
const [isLoadingManifestTemplate, setIsLoadingManifestTemplate] = const [isLoadingManifestTemplate, setIsLoadingManifestTemplate] =
@ -74,58 +84,94 @@ const ManageManifestPanel: FC = () => {
); );
const { isFormInvalid } = formUtils; const { isFormInvalid } = formUtils;
const addManifestFormDialogProps = useMemo<ConfirmDialogProps>(() => { const runFormUtils = useFormUtils(
let domain: string | undefined; [
let prefix: string | undefined; INPUT_ID_AN_CONFIRM_PASSWORD,
let sequence: number | undefined; INPUT_ID_AN_DESCRIPTION,
let fences: APIManifestTemplateFenceList | undefined; INPUT_ID_AN_PASSWORD,
let upses: APIManifestTemplateUpsList | undefined; ],
messageGroupRef,
);
const { isFormInvalid: isRunFormInvalid } = runFormUtils;
if (manifestTemplate) { const {
({ domain, fences, prefix, sequence, upses } = manifestTemplate); domain,
} name: anName,
prefix,
sequence,
} = useMemo<Partial<APIManifestDetail>>(
() => manifestDetail ?? {},
[manifestDetail],
);
const { fences: knownFences, upses: knownUpses } = useMemo<
Partial<APIManifestTemplate>
>(() => manifestTemplate ?? {}, [manifestTemplate]);
return { const addManifestFormDialogProps = useMemo<ConfirmDialogProps>(
() => ({
actionProceedText: 'Add', actionProceedText: 'Add',
content: ( content: (
<AddManifestInputGroup <AddManifestInputGroup
formUtils={formUtils} formUtils={formUtils}
knownFences={fences} knownFences={knownFences}
knownUpses={upses} knownUpses={knownUpses}
previous={{ domain, prefix, sequence }} previous={{ domain, prefix, sequence }}
/> />
), ),
titleText: 'Add an install manifest', titleText: 'Add an install manifest',
}; }),
}, [formUtils, manifestTemplate]); [domain, formUtils, knownFences, knownUpses, prefix, sequence],
);
const editManifestFormDialogProps = useMemo<ConfirmDialogProps>(() => {
let fences: APIManifestTemplateFenceList | undefined;
let manifestName: string | undefined;
let upses: APIManifestTemplateUpsList | undefined;
if (manifestTemplate) {
({ fences, upses } = manifestTemplate);
}
if (manifestDetail) {
({ name: manifestName } = manifestDetail);
}
return { const editManifestFormDialogProps = useMemo<ConfirmDialogProps>(
() => ({
actionProceedText: 'Edit', actionProceedText: 'Edit',
content: ( content: (
<EditManifestInputGroup <EditManifestInputGroup
formUtils={formUtils} formUtils={formUtils}
knownFences={fences} knownFences={knownFences}
knownUpses={upses} knownUpses={knownUpses}
previous={manifestDetail} previous={manifestDetail}
/> />
), ),
loading: isLoadingManifestDetail, loading: isLoadingManifestDetail,
titleText: `Update install manifest ${manifestName}`, titleText: `Update install manifest ${anName}`,
}; }),
}, [formUtils, isLoadingManifestDetail, manifestDetail, manifestTemplate]); [
formUtils,
isLoadingManifestDetail,
knownFences,
knownUpses,
anName,
manifestDetail,
],
);
const runManifestFormDialogProps = useMemo<ConfirmDialogProps>(
() => ({
actionProceedText: 'Run',
content: (
<RunManifestInputGroup
formUtils={runFormUtils}
knownFences={knownFences}
knownHosts={hostOverviews}
knownUpses={knownUpses}
previous={manifestDetail}
/>
),
loading: isLoadingManifestDetail,
titleText: `Run install manifest ${anName}`,
}),
[
anName,
hostOverviews,
isLoadingManifestDetail,
knownFences,
knownUpses,
manifestDetail,
runFormUtils,
],
);
const getManifestDetail = useCallback( const getManifestDetail = useCallback(
(manifestUuid: string, finallyAppend?: () => void) => { (manifestUuid: string, finallyAppend?: () => void) => {
@ -174,9 +220,19 @@ const ManageManifestPanel: FC = () => {
}} }}
renderListItem={(manifestUUID, { manifestName }) => ( renderListItem={(manifestUUID, { manifestName }) => (
<FlexBox fullWidth row> <FlexBox fullWidth row>
<IconButton disabled={isEditManifests} variant="normal"> <IconButton
<PlayCircle /> disabled={isEditManifests}
</IconButton> mapPreset="play"
onClick={() => {
setManifestDetail({
name: manifestName,
uuid: manifestUUID,
} as APIManifestDetail);
runManifestFormDialogRef.current.setOpen?.call(null, true);
getManifestDetail(manifestUUID);
}}
variant="normal"
/>
<BodyText>{manifestName}</BodyText> <BodyText>{manifestName}</BodyText>
</FlexBox> </FlexBox>
)} )}
@ -187,12 +243,19 @@ const ManageManifestPanel: FC = () => {
const panelContent = useMemo( const panelContent = useMemo(
() => () =>
isLoadingManifestTemplate || isLoadingManifestOverviews ? ( isLoadingHostOverviews ||
isLoadingManifestTemplate ||
isLoadingManifestOverviews ? (
<Spinner /> <Spinner />
) : ( ) : (
listElement listElement
), ),
[isLoadingManifestOverviews, isLoadingManifestTemplate, listElement], [
isLoadingHostOverviews,
isLoadingManifestOverviews,
isLoadingManifestTemplate,
listElement,
],
); );
if (isFirstRender) { if (isFirstRender) {
@ -207,6 +270,18 @@ const ManageManifestPanel: FC = () => {
.finally(() => { .finally(() => {
setIsLoadingManifestTemplate(false); setIsLoadingManifestTemplate(false);
}); });
api
.get<APIHostOverviewList>('/host', { params: { types: 'node' } })
.then(({ data }) => {
setHostOverviews(data);
})
.catch((apiError) => {
handleAPIError(apiError);
})
.finally(() => {
setIsLoadingHostOverviews(false);
});
} }
return ( return (
@ -229,6 +304,12 @@ const ManageManifestPanel: FC = () => {
ref={editManifestFormDialogRef} ref={editManifestFormDialogRef}
scrollContent scrollContent
/> />
<FormDialog
{...runManifestFormDialogProps}
disableProceed={isRunFormInvalid}
ref={runManifestFormDialogRef}
scrollContent
/>
<ConfirmDialog {...confirmDialogProps} ref={confirmDialogRef} /> <ConfirmDialog {...confirmDialogProps} ref={confirmDialogRef} />
</> </>
); );

@ -0,0 +1,404 @@
import { styled } from '@mui/material';
import { ReactElement, useMemo } from 'react';
import { buildPeacefulStringTestBatch } from '../../lib/test_input';
import FlexBox from '../FlexBox';
import Grid from '../Grid';
import InputWithRef from '../InputWithRef';
import OutlinedInputWithLabel from '../OutlinedInputWithLabel';
import SelectWithLabel from '../SelectWithLabel';
import { BodyText, MonoText } from '../Text';
const INPUT_ID_PREFIX_RUN_MANIFEST = 'run-manifest-input';
const INPUT_ID_PREFIX_HOST = `${INPUT_ID_PREFIX_RUN_MANIFEST}-host`;
const INPUT_ID_AN_DESCRIPTION = `${INPUT_ID_PREFIX_RUN_MANIFEST}-an-description`;
const INPUT_ID_AN_PASSWORD = `${INPUT_ID_PREFIX_RUN_MANIFEST}-an-password`;
const INPUT_ID_AN_CONFIRM_PASSWORD = `${INPUT_ID_PREFIX_RUN_MANIFEST}-an-confirm-password`;
const INPUT_LABEL_AN_DESCRIPTION = 'Description';
const INPUT_LABEL_AN_PASSWORD = 'Password';
const INPUT_LABEL_AN_CONFIRM_PASSWORD = 'Confirm password';
const MANIFEST_PARAM_NONE = '--';
const EndMono = styled(MonoText)({
justifyContent: 'end',
});
const RunManifestInputGroup = <M extends MapToInputTestID>({
formUtils: {
buildFinishInputTestBatchFunction,
buildInputFirstRenderFunction,
msgSetters,
setMsgSetter,
},
knownFences = {},
knownHosts = {},
knownUpses = {},
previous: { domain: anDomain, hostConfig = {}, networkConfig = {} } = {},
}: RunManifestInputGroupProps<M>): ReactElement => {
const { hosts: initHostList = {} } = hostConfig;
const {
dnsCsv,
mtu,
networks: initNetworkList = {},
ntpCsv = MANIFEST_PARAM_NONE,
} = networkConfig;
const hostListEntries = useMemo(
() => Object.entries(initHostList),
[initHostList],
);
const knownFenceListEntries = useMemo(
() => Object.entries(knownFences),
[knownFences],
);
const knownHostListEntries = useMemo(
() => Object.entries(knownHosts),
[knownHosts],
);
const knownUpsListEntries = useMemo(
() => Object.entries(knownUpses),
[knownUpses],
);
const networkListEntries = useMemo(
() => Object.entries(initNetworkList),
[initNetworkList],
);
const hostOptionList = useMemo(
() =>
knownHostListEntries.map<SelectItem>(([, { hostName, hostUUID }]) => ({
displayValue: hostName,
value: hostUUID,
})),
[knownHostListEntries],
);
const {
headers: hostHeaderRow,
hosts: hostSelectRow,
hostNames: hostNewNameRow,
} = useMemo(
() =>
hostListEntries.reduce<{
headers: GridLayout;
hosts: GridLayout;
hostNames: GridLayout;
}>(
(previous, [hostId, { hostName, hostNumber, hostType }]) => {
const { headers, hosts, hostNames } = previous;
const prettyId = `${hostType}${hostNumber}`;
headers[`run-manifest-column-header-cell-${hostId}`] = {
children: <BodyText>{prettyId}</BodyText>,
};
const inputId = `${INPUT_ID_PREFIX_HOST}-${hostId}`;
const inputLabel = `${prettyId} host`;
setMsgSetter(inputId);
hosts[`run-manifest-host-cell-${hostId}`] = {
children: (
<InputWithRef
input={
<SelectWithLabel
id={inputId}
label={inputLabel}
selectItems={hostOptionList}
value=""
/>
}
inputTestBatch={buildPeacefulStringTestBatch(
inputLabel,
() => {
msgSetters[inputId]();
},
{ onFinishBatch: buildFinishInputTestBatchFunction(inputId) },
(message) => {
msgSetters[inputId]({ children: message });
},
)}
onFirstRender={buildInputFirstRenderFunction(inputId)}
required
/>
),
};
hostNames[`run-manifest-new-host-name-cell-${hostId}`] = {
children: (
<MonoText>
{hostName}.{anDomain}
</MonoText>
),
};
return previous;
},
{
headers: {
'run-manifest-column-header-cell-offset': {},
},
hosts: {
'run-manifest-host-cell-header': {
children: <BodyText>Uses host</BodyText>,
},
},
hostNames: {
'run-manifest-new-host-name-cell-header': {
children: <BodyText>New hostname</BodyText>,
},
},
},
),
[
anDomain,
buildFinishInputTestBatchFunction,
buildInputFirstRenderFunction,
hostListEntries,
hostOptionList,
msgSetters,
setMsgSetter,
],
);
const {
gateway: defaultGatewayGridLayout,
hostNetworks: hostNetworkRowList,
} = useMemo(
() =>
networkListEntries.reduce<{
gateway: GridLayout;
hostNetworks: GridLayout;
}>(
(
previous,
[networkId, { networkGateway, networkNumber, networkType }],
) => {
const { gateway, hostNetworks } = previous;
const idPrefix = `run-manifest-host-network-cell-${networkId}`;
const networkShortName = `${networkType.toUpperCase()}${networkNumber}`;
hostNetworks[`${idPrefix}-header`] = {
children: <BodyText>{networkShortName}</BodyText>,
};
hostListEntries.forEach(([hostId, { networks = {} }]) => {
const {
[networkId]: { networkIp: ip = MANIFEST_PARAM_NONE } = {},
} = networks;
hostNetworks[`${idPrefix}-${hostId}-ip`] = {
children: <MonoText>{ip}</MonoText>,
};
});
const cellId = 'run-manifest-gateway-cell';
if (networkGateway && !gateway[cellId]) {
gateway[cellId] = {
children: <EndMono>{networkGateway}</EndMono>,
};
}
return previous;
},
{
gateway: {
'run-manifest-gateway-cell-header': {
children: <BodyText>Gateway</BodyText>,
},
},
hostNetworks: {},
},
),
[hostListEntries, networkListEntries],
);
const hostFenceRowList = useMemo(
() =>
knownFenceListEntries.reduce<GridLayout>(
(previous, [fenceUuid, { fenceName }]) => {
const idPrefix = `run-manifest-fence-cell-${fenceUuid}`;
previous[`${idPrefix}-header`] = {
children: <BodyText>Port on {fenceName}</BodyText>,
};
hostListEntries.forEach(([hostId, { fences = {} }]) => {
const { [fenceName]: { fencePort = MANIFEST_PARAM_NONE } = {} } =
fences;
previous[`${idPrefix}-${hostId}-port`] = {
children: <MonoText>{fencePort}</MonoText>,
};
});
return previous;
},
{},
),
[hostListEntries, knownFenceListEntries],
);
const hostUpsRowList = useMemo(
() =>
knownUpsListEntries.reduce<GridLayout>(
(previous, [upsUuid, { upsName }]) => {
const idPrefix = `run-manifest-ups-cell-${upsUuid}`;
previous[`${idPrefix}-header`] = {
children: <BodyText>Uses {upsName}</BodyText>,
};
hostListEntries.forEach(([hostId, { upses = {} }]) => {
const { [upsName]: { isUsed = false } = {} } = upses;
previous[`${idPrefix}-${hostId}-is-used`] = {
children: <MonoText>{isUsed ? 'yes' : 'no'}</MonoText>,
};
});
return previous;
},
{},
),
[hostListEntries, knownUpsListEntries],
);
return (
<FlexBox>
<Grid
columns={{ xs: 1, sm: 2 }}
layout={{
'anvil-description-input-cell': {
children: (
<InputWithRef
input={
<OutlinedInputWithLabel
id={INPUT_ID_AN_DESCRIPTION}
label={INPUT_LABEL_AN_DESCRIPTION}
/>
}
inputTestBatch={buildPeacefulStringTestBatch(
INPUT_LABEL_AN_DESCRIPTION,
() => {
msgSetters[INPUT_ID_AN_DESCRIPTION]();
},
{
onFinishBatch: buildFinishInputTestBatchFunction(
INPUT_ID_AN_DESCRIPTION,
),
},
(message) => {
msgSetters[INPUT_ID_AN_DESCRIPTION]({ children: message });
},
)}
onFirstRender={buildInputFirstRenderFunction(
INPUT_ID_AN_DESCRIPTION,
)}
required
/>
),
sm: 2,
},
'anvil-password-input-cell': {
children: (
<InputWithRef
input={
<OutlinedInputWithLabel
id={INPUT_ID_AN_PASSWORD}
label={INPUT_LABEL_AN_PASSWORD}
/>
}
inputTestBatch={buildPeacefulStringTestBatch(
INPUT_LABEL_AN_PASSWORD,
() => {
msgSetters[INPUT_ID_AN_PASSWORD]();
},
{
onFinishBatch:
buildFinishInputTestBatchFunction(INPUT_ID_AN_PASSWORD),
},
(message) => {
msgSetters[INPUT_ID_AN_PASSWORD]({ children: message });
},
)}
onFirstRender={buildInputFirstRenderFunction(
INPUT_ID_AN_PASSWORD,
)}
required
/>
),
},
'anvil-confirm-password-input-cell': {
children: (
<InputWithRef
input={
<OutlinedInputWithLabel
id={INPUT_ID_AN_CONFIRM_PASSWORD}
label={INPUT_LABEL_AN_CONFIRM_PASSWORD}
/>
}
required
/>
),
},
}}
spacing="1em"
/>
<Grid
alignItems="center"
columns={{ xs: hostListEntries.length + 1 }}
layout={{
...hostHeaderRow,
...hostSelectRow,
...hostNewNameRow,
...hostNetworkRowList,
...hostFenceRowList,
...hostUpsRowList,
}}
columnSpacing="1em"
rowSpacing="0.4em"
/>
<Grid
columns={{ xs: 2 }}
layout={{
...defaultGatewayGridLayout,
'run-manifest-dns-csv-cell-header': {
children: <BodyText>DNS</BodyText>,
},
'run-manifest-dns-csv-cell': {
children: <EndMono>{dnsCsv}</EndMono>,
},
'run-manifest-ntp-csv-cell-header': {
children: <BodyText>NTP</BodyText>,
},
'run-manifest-ntp-csv-cell': {
children: <EndMono>{ntpCsv}</EndMono>,
},
'run-manifest-mtu-cell-header': {
children: <BodyText>MTU</BodyText>,
},
'run-manifest-mtu-cell': {
children: <EndMono>{mtu}</EndMono>,
},
}}
spacing="0.4em"
/>
</FlexBox>
);
};
export {
INPUT_ID_AN_CONFIRM_PASSWORD,
INPUT_ID_AN_DESCRIPTION,
INPUT_ID_AN_PASSWORD,
};
export default RunManifestInputGroup;

@ -48,6 +48,7 @@ type ManifestHostUpsList = {
type ManifestHost = { type ManifestHost = {
fences?: ManifestHostFenceList; fences?: ManifestHostFenceList;
hostName: string;
hostNumber: number; hostNumber: number;
hostType: string; hostType: string;
networks?: ManifestHostNetworkList; networks?: ManifestHostNetworkList;
@ -167,3 +168,10 @@ type AddManifestInputGroupProps<M extends MapToInputTestID> =
type EditManifestInputGroupProps<M extends MapToInputTestID> = type EditManifestInputGroupProps<M extends MapToInputTestID> =
AddManifestInputGroupProps<M>; AddManifestInputGroupProps<M>;
type RunManifestInputGroupOptionalProps = {
knownHosts?: APIHostOverviewList;
};
type RunManifestInputGroupProps<M extends MapToInputTestID> =
RunManifestInputGroupOptionalProps & AddManifestInputGroupProps<M>;

Loading…
Cancel
Save