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