parent
ddece3750d
commit
47a84a86e8
7 changed files with 512 additions and 0 deletions
@ -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…
Reference in new issue