feat(striker-ui): add manage host components

main
Tsu-ba-me 11 months ago
parent ddece3750d
commit 47a84a86e8
  1. 47
      striker-ui/components/ManageHost/ManageHost.tsx
  2. 228
      striker-ui/components/ManageHost/PrepareHostForm.tsx
  3. 145
      striker-ui/components/ManageHost/TestAccessForm.tsx
  4. 4
      striker-ui/components/ManageHost/index.tsx
  5. 35
      striker-ui/components/ManageHost/schema.ts
  6. 15
      striker-ui/components/ManageHost/testAccessSchema.ts
  7. 38
      striker-ui/types/ManageHost.d.ts

@ -0,0 +1,47 @@
import { FC, useState } from 'react';
import CrudList from '../CrudList';
import PrepareHostForm from './PrepareHostForm';
import TestAccessForm from './TestAccessForm';
import { BodyText } from '../Text';
const ManageHost: FC = () => {
const [inquireHostResponse, setInquireHostResponse] = useState<
InquireHostResponse | undefined
>();
return (
<CrudList<APIHostOverview, APIHostDetail>
addHeader="Initialize host"
editHeader=""
entriesUrl="/host"
getDeleteErrorMessage={(children, ...rest) => ({
...rest,
children: <>Failed to delete host(s). {children}</>,
})}
getDeleteHeader={(count) => `Delete the following ${count} host(s)?`}
getDeleteSuccessMessage={() => ({
children: <>Successfully deleted host(s)</>,
})}
listEmpty="No host(s) found"
listProps={{ allowAddItem: true, allowEdit: false }}
renderAddForm={(tools) => (
<>
<TestAccessForm setResponse={setInquireHostResponse} />
{inquireHostResponse && (
<PrepareHostForm host={inquireHostResponse} tools={tools} />
)}
</>
)}
renderDeleteItem={(hosts, { key }) => {
const host = hosts?.[key];
return <BodyText>{host?.shortHostName}</BodyText>;
}}
renderEditForm={() => <></>}
renderListItem={(uuid, { hostName }) => <BodyText>{hostName}</BodyText>}
/>
);
};
export default ManageHost;

@ -0,0 +1,228 @@
import { Grid } from '@mui/material';
import { FC, useMemo } from 'react';
import ActionGroup from '../ActionGroup';
import api from '../../lib/api';
import FormSummary from '../FormSummary';
import handleAPIError from '../../lib/handleAPIError';
import MessageGroup from '../MessageGroup';
import OutlinedInputWithLabel from '../OutlinedInputWithLabel';
import RadioGroupWithLabel from '../RadioGroupWithLabel';
import schema from './schema';
import UncontrolledInput from '../UncontrolledInput';
import useFormikUtils from '../../hooks/useFormikUtils';
const HOST_TYPE_OPTIONS: RadioItemList = {
subnode: { label: 'Subnode', value: 'subnode' },
dr: { label: 'Disaster Recovery (DR) host', value: 'dr' },
};
const PrepareHostForm: FC<PreapreHostFormProps> = (props) => {
const { host, tools } = props;
const { disabledSubmit, formik, formikErrors, handleChange } =
useFormikUtils<PrepareHostFormikValues>({
initialValues: {
ip: host.hostIpAddress,
name: host.hostName,
password: host.hostPassword,
type: '',
uuid: host.hostUUID,
},
onSubmit: (values, { setSubmitting }) => {
const {
enterpriseKey,
ip,
name,
password,
type,
uuid,
redhatPassword,
redhatUsername,
} = values;
tools.confirm.prepare({
actionProceedText: 'Prepare',
content: <FormSummary entries={values} hasPassword />,
onCancelAppend: () => setSubmitting(false),
onProceedAppend: () => {
tools.confirm.loading(true);
api
.put('/host/prepare', {
enterpriseUUID: enterpriseKey,
hostIPAddress: ip,
hostName: name,
hostPassword: password,
hostType: type,
hostUUID: uuid,
redhatPassword,
redhatUser: redhatUsername,
})
.then(() => {
tools.confirm.finish('Success', {
children: <>Host at {ip} prepared.</>,
});
tools.add.open(false);
})
.catch((error) => {
const emsg = handleAPIError(error);
emsg.children = (
<>
Failed to prepare host at {ip}. {emsg.children}
</>
);
tools.confirm.finish('Error', emsg);
setSubmitting(false);
});
},
titleText: `Prepare host at ${values.ip} with the following?`,
});
tools.confirm.open();
},
validationSchema: schema,
});
const enterpriseKeyChain = useMemo<string>(() => 'enterpriseKey', []);
const nameChain = useMemo<string>(() => 'name', []);
const redhatConfirmPasswordChain = useMemo<string>(
() => 'redhatConfirmPassword',
[],
);
const redhatPasswordChain = useMemo<string>(() => 'redhatPassword', []);
const redhatUsernameChain = useMemo<string>(() => 'redhatUsername', []);
const typeChain = useMemo<string>(() => 'type', []);
const showRedhatSection = useMemo<boolean>(
() =>
host.isInetConnected && /rhel/i.test(host.hostOS) && !host.isOSRegistered,
[host.hostOS, host.isInetConnected, host.isOSRegistered],
);
return (
<Grid
columns={{ xs: 1, sm: 2 }}
component="form"
container
onSubmit={(event) => {
event.preventDefault();
formik.submitForm();
}}
spacing="1em"
>
<Grid item width="100%">
<UncontrolledInput
input={
<RadioGroupWithLabel
id={typeChain}
label="Host type"
name={typeChain}
onChange={handleChange}
radioItems={HOST_TYPE_OPTIONS}
value={formik.values.type}
/>
}
/>
</Grid>
<Grid item xs={1}>
<UncontrolledInput
input={
<OutlinedInputWithLabel
id={nameChain}
label="Host name"
name={nameChain}
onChange={handleChange}
required
value={formik.values.name}
/>
}
/>
</Grid>
<Grid item xs={1}>
<UncontrolledInput
input={
<OutlinedInputWithLabel
id={enterpriseKeyChain}
label="Alteeve enterprise key"
name={enterpriseKeyChain}
onChange={handleChange}
value={formik.values.enterpriseKey}
/>
}
/>
</Grid>
{showRedhatSection && (
<>
<Grid item xs={1}>
<UncontrolledInput
input={
<OutlinedInputWithLabel
disableAutofill
id={redhatUsernameChain}
label="RedHat username"
name={redhatUsernameChain}
onChange={handleChange}
value={formik.values.redhatUsername}
/>
}
/>
</Grid>
<Grid item xs={1}>
<UncontrolledInput
input={
<OutlinedInputWithLabel
disableAutofill
id={redhatPasswordChain}
label="RedHat password"
name={redhatPasswordChain}
onChange={handleChange}
type="password"
value={formik.values.redhatPassword}
/>
}
/>
</Grid>
<Grid display={{ xs: 'none', sm: 'initial' }} item sm={1} />
<Grid item xs={1}>
<UncontrolledInput
input={
<OutlinedInputWithLabel
disableAutofill
id={redhatConfirmPasswordChain}
label="Confirm RedHat password"
name={redhatConfirmPasswordChain}
onChange={handleChange}
type="password"
value={formik.values.redhatConfirmPassword}
/>
}
/>
</Grid>
</>
)}
<Grid item width="100%">
<MessageGroup count={1} messages={formikErrors} />
</Grid>
<Grid item width="100%">
<ActionGroup
actions={[
{
background: 'blue',
children: 'Prepare host',
disabled: disabledSubmit,
type: 'submit',
},
]}
/>
</Grid>
</Grid>
);
};
export default PrepareHostForm;

@ -0,0 +1,145 @@
import { FC, useCallback, useMemo, useRef, useState } from 'react';
import { Grid } from '@mui/material';
import ActionGroup from '../ActionGroup';
import api from '../../lib/api';
import handleAPIError from '../../lib/handleAPIError';
import MessageGroup, { MessageGroupForwardedRefContent } from '../MessageGroup';
import OutlinedInputWithLabel from '../OutlinedInputWithLabel';
import UncontrolledInput from '../UncontrolledInput';
import useFormikUtils from '../../hooks/useFormikUtils';
import Spinner from '../Spinner';
import schema from './testAccessSchema';
const TestAccessForm: FC<TestAccessFormProps> = (props) => {
const { setResponse } = props;
const messageGroupRef = useRef<MessageGroupForwardedRefContent>(null);
const [loadingInquiry, setLoadingInquiry] = useState<boolean>(false);
const setApiMessage = useCallback(
(message?: Message) =>
messageGroupRef?.current?.setMessage?.call(null, 'api', message),
[],
);
const { disabledSubmit, formik, formikErrors, handleChange } =
useFormikUtils<TestAccessFormikValues>({
initialValues: {
ip: '',
password: '',
},
onSubmit: (values, { setSubmitting }) => {
setLoadingInquiry(true);
setResponse(undefined);
const { ip, password } = values;
api
.put<APICommandInquireHostResponseBody>('/command/inquire-host', {
ipAddress: ip,
password,
})
.then(({ data }) => {
setResponse({
...data,
hostIpAddress: ip,
hostPassword: password,
});
setApiMessage();
})
.catch((error) => {
const emsg = handleAPIError(error);
emsg.children = (
<>
Failed to access {ip}. {emsg.children}
</>
);
setApiMessage(emsg);
})
.finally(() => {
setSubmitting(false);
setLoadingInquiry(false);
});
},
validationSchema: schema,
});
const ipChain = useMemo<string>(() => 'ip', []);
const passwordChain = useMemo<string>(() => 'password', []);
return (
<Grid
component="form"
container
columns={{ xs: 1, sm: 2 }}
onSubmit={(event) => {
event.preventDefault();
formik.submitForm();
}}
spacing="1em"
>
<Grid item xs={1}>
<UncontrolledInput
input={
<OutlinedInputWithLabel
disableAutofill
id={ipChain}
label="IP address"
name={ipChain}
onChange={handleChange}
required
value={formik.values.ip}
/>
}
/>
</Grid>
<Grid item xs={1}>
<UncontrolledInput
input={
<OutlinedInputWithLabel
disableAutofill
id={passwordChain}
label="Password"
name={passwordChain}
onChange={handleChange}
required
type="password"
value={formik.values.password}
/>
}
/>
</Grid>
{loadingInquiry ? (
<Grid item width="100%">
<Spinner />
</Grid>
) : (
<>
<Grid item width="100%">
<MessageGroup count={1} messages={formikErrors} />
</Grid>
<Grid item width="100%">
<ActionGroup
actions={[
{
background: 'blue',
children: 'Test access',
disabled: disabledSubmit,
type: 'submit',
},
]}
/>
</Grid>
</>
)}
</Grid>
);
};
export default TestAccessForm;

@ -0,0 +1,4 @@
import ManageHost from './ManageHost';
import PrepareHostForm from './PrepareHostForm';
export { ManageHost, PrepareHostForm };

@ -0,0 +1,35 @@
import * as yup from 'yup';
import { REP_IPV4 } from '../../lib/consts/REG_EXP_PATTERNS';
const schema = yup.object().shape(
{
enterpriseKey: yup.string().uuid().optional(),
ip: yup.string().matches(REP_IPV4, {
message: 'Expected IP address to be a valid IPv4 address.',
}),
name: yup.string().required(),
redhatConfirmPassword: yup
.string()
.when('redhatPassword', (redhatPassword, field) =>
String(redhatPassword).length > 0
? field.required().oneOf([yup.ref('redhatPassword')])
: field.optional(),
),
redhatPassword: yup
.string()
.when('redhatUsername', (redhatUsername, field) =>
String(redhatUsername).length > 0 ? field.required() : field.optional(),
),
redhatUsername: yup
.string()
.when('redhatPassword', (redhatPassword, field) =>
String(redhatPassword).length > 0 ? field.required() : field.optional(),
),
type: yup.string().oneOf(['dr', 'subnode']).required(),
uuid: yup.string().uuid().required(),
},
[['redhatUsername', 'redhatPassword']],
);
export default schema;

@ -0,0 +1,15 @@
import * as yup from 'yup';
import { REP_IPV4 } from '../../lib/consts/REG_EXP_PATTERNS';
const schema = yup.object({
ip: yup
.string()
.matches(REP_IPV4, {
message: 'Expected IP address to be a valid IPv4 address.',
})
.required(),
password: yup.string().required(),
});
export default schema;

@ -0,0 +1,38 @@
type InquireHostResponse = APICommandInquireHostResponseBody & {
hostIpAddress: string;
hostPassword: string;
};
/** TestAccessForm */
type TestAccessFormikValues = {
ip: string;
password: string;
};
type TestAccessFormProps = {
setResponse: React.Dispatch<
React.SetStateAction<InquireHostResponse | undefined>
>;
};
/** PrepareHostForm */
/**
* @property hostType - Type of host to prepare; note that `node` is `subnode`
* due to renaming.
*/
type PrepareHostFormikValues = TestAccessFormikValues & {
enterpriseKey?: string;
name: string;
redhatConfirmPassword?: string;
redhatPassword?: string;
redhatUsername?: string;
type: '' | 'dr' | 'subnode';
uuid: string;
};
type PreapreHostFormProps = {
host: InquireHostResponse;
tools: CrudListFormTools;
};
Loading…
Cancel
Save