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.
676 lines
22 KiB
676 lines
22 KiB
import { |
|
Visibility as MUIVisibilityIcon, |
|
VisibilityOff as MUIVisibilityOffIcon, |
|
} from '@mui/icons-material'; |
|
import { Box as MUIBox, IconButton as MUIIconButton } from '@mui/material'; |
|
import { FC, useCallback, useMemo, useRef, useState } from 'react'; |
|
|
|
import { GREY } from '../lib/consts/DEFAULT_THEME'; |
|
import INPUT_TYPES from '../lib/consts/INPUT_TYPES'; |
|
|
|
import api from '../lib/api'; |
|
import handleAPIError from '../lib/handleAPIError'; |
|
import { |
|
buildDomainTestBatch, |
|
buildIPAddressTestBatch, |
|
buildPeacefulStringTestBatch, |
|
buildUUIDTestBatch, |
|
createTestInputFunction, |
|
} from '../lib/test_input'; |
|
|
|
import ConfirmDialog from './ConfirmDialog'; |
|
import ContainedButton from './ContainedButton'; |
|
import FlexBox from './FlexBox'; |
|
import GateForm from './GateForm'; |
|
import Grid from './Grid'; |
|
import InputWithRef, { InputForwardedRefContent } from './InputWithRef'; |
|
import { Message } from './MessageBox'; |
|
import MessageGroup, { MessageGroupForwardedRefContent } from './MessageGroup'; |
|
import OutlinedInputWithLabel from './OutlinedInputWithLabel'; |
|
import { Panel, PanelHeader } from './Panels'; |
|
import RadioGroupWithLabel from './RadioGroupWithLabel'; |
|
import Spinner from './Spinner'; |
|
import { BodyText, HeaderText, MonoText } from './Text'; |
|
import useProtect from '../hooks/useProtect'; |
|
import useProtectedState from '../hooks/useProtectedState'; |
|
|
|
const ENTERPRISE_KEY_LABEL = 'Alteeve enterprise key'; |
|
const HOST_IP_LABEL = 'Host IP address'; |
|
const HOST_NAME_LABEL = 'Host name'; |
|
const REDHAT_PASSWORD_LABEL = 'RedHat password'; |
|
const REDHAT_USER_LABEL = 'RedHat user'; |
|
const SUCCESS_MESSAGE_TIMEOUT = 5000; |
|
|
|
const IT_IDS = { |
|
enterpriseKey: 'enterpriseKey', |
|
hostName: 'hostName', |
|
redhatPassword: 'redhatPassword', |
|
redhatUser: 'redhatUser', |
|
}; |
|
|
|
const GRID_COLUMNS: Exclude<GridProps['columns'], undefined> = { |
|
xs: 1, |
|
sm: 2, |
|
}; |
|
const GRID_SPACING: Exclude<GridProps['spacing'], undefined> = '1em'; |
|
|
|
const PrepareHostForm: FC = () => { |
|
const { protect } = useProtect(); |
|
|
|
const confirmDialogRef = useRef<ConfirmDialogForwardedRefContent>({}); |
|
const inputEnterpriseKeyRef = useRef<InputForwardedRefContent<'string'>>({}); |
|
const inputHostNameRef = useRef<InputForwardedRefContent<'string'>>({}); |
|
const inputRedhatPassword = useRef<InputForwardedRefContent<'string'>>({}); |
|
const inputRedhatUser = useRef<InputForwardedRefContent<'string'>>({}); |
|
const messageGroupRef = useRef<MessageGroupForwardedRefContent>({}); |
|
|
|
const [confirmValues, setConfirmValues] = useState< |
|
| { |
|
enterpriseKey: string; |
|
hostName: string; |
|
redhatPassword: string; |
|
redhatPasswordHidden: string; |
|
redhatUser: string; |
|
} |
|
| undefined |
|
>(); |
|
const [connectedHostIPAddress, setConnectedHostIPAddress] = useState< |
|
string | undefined |
|
>(); |
|
const [connectedHostPassword, setConnectedHostPassword] = useProtectedState< |
|
string | undefined |
|
>(undefined, protect); |
|
const [connectedHostUUID, setConnectedHostUUID] = useProtectedState<string>( |
|
'', |
|
protect, |
|
); |
|
const [inputHostType, setInputHostType] = useState<string>(''); |
|
const [isInputEnterpriseKeyValid, setIsInputEnterpriseKeyValid] = |
|
useState<boolean>(true); |
|
const [isInputHostNameValid, setIsInputHostNameValid] = |
|
useState<boolean>(false); |
|
const [isInputRedhatPasswordValid, setIsInputRedhatPasswordValid] = |
|
useState<boolean>(true); |
|
const [isInputRedhatUserValid, setIsInputRedhatUserValid] = |
|
useState<boolean>(true); |
|
const [isShowAccessSection, setIsShowAccessSection] = |
|
useState<boolean>(false); |
|
const [isShowAccessSubmit, setIsShowAccessSubmit] = useState<boolean>(true); |
|
const [isShowOptionalSection, setIsShowOptionalSection] = |
|
useState<boolean>(false); |
|
const [isShowRedhatPassword, setIsShowRedhatPassword] = |
|
useState<boolean>(false); |
|
const [isShowRedhatSection, setIsShowRedhatSection] = |
|
useState<boolean>(false); |
|
const [isSubmittingPrepareHost, setIsSubmittingPrepareHost] = |
|
useState<boolean>(false); |
|
|
|
const setHostNameInputMessage = useCallback((message?: Message) => { |
|
messageGroupRef.current.setMessage?.call(null, IT_IDS.hostName, message); |
|
}, []); |
|
const setEnterpriseKeyInputMessage = useCallback((message?: Message) => { |
|
messageGroupRef.current.setMessage?.call( |
|
null, |
|
IT_IDS.enterpriseKey, |
|
message, |
|
); |
|
}, []); |
|
const setRedhatPasswordInputMessage = useCallback((message?: Message) => { |
|
messageGroupRef.current.setMessage?.call( |
|
null, |
|
IT_IDS.redhatPassword, |
|
message, |
|
); |
|
}, []); |
|
const setRedhatUserInputMessage = useCallback((message?: Message) => { |
|
messageGroupRef.current.setMessage?.call(null, IT_IDS.redhatUser, message); |
|
}, []); |
|
const setSubmitPrepareHostMessage = useCallback( |
|
(message?: Message) => |
|
messageGroupRef.current.setMessage?.call( |
|
null, |
|
'submitPrepareHost', |
|
message, |
|
), |
|
[], |
|
); |
|
|
|
const inputTests = useMemo( |
|
() => ({ |
|
[IT_IDS.enterpriseKey]: buildUUIDTestBatch( |
|
ENTERPRISE_KEY_LABEL, |
|
() => { |
|
setEnterpriseKeyInputMessage(); |
|
}, |
|
undefined, |
|
(message) => { |
|
setEnterpriseKeyInputMessage({ children: message, type: 'warning' }); |
|
}, |
|
), |
|
[IT_IDS.hostName]: buildDomainTestBatch( |
|
HOST_NAME_LABEL, |
|
() => { |
|
setHostNameInputMessage(); |
|
}, |
|
undefined, |
|
(message) => { |
|
setHostNameInputMessage({ children: message, type: 'warning' }); |
|
}, |
|
), |
|
[IT_IDS.redhatPassword]: buildPeacefulStringTestBatch( |
|
REDHAT_PASSWORD_LABEL, |
|
() => { |
|
setRedhatPasswordInputMessage(); |
|
}, |
|
undefined, |
|
(message) => { |
|
setRedhatPasswordInputMessage({ children: message, type: 'warning' }); |
|
}, |
|
), |
|
[IT_IDS.redhatUser]: buildPeacefulStringTestBatch( |
|
REDHAT_USER_LABEL, |
|
() => { |
|
setRedhatUserInputMessage(); |
|
}, |
|
undefined, |
|
(message) => { |
|
setRedhatUserInputMessage({ children: message, type: 'warning' }); |
|
}, |
|
), |
|
}), |
|
[ |
|
setEnterpriseKeyInputMessage, |
|
setHostNameInputMessage, |
|
setRedhatPasswordInputMessage, |
|
setRedhatUserInputMessage, |
|
], |
|
); |
|
const testInput = useMemo( |
|
() => createTestInputFunction(inputTests), |
|
[inputTests], |
|
); |
|
|
|
const redhatElementSxDisplay = useMemo( |
|
() => (isShowRedhatSection ? undefined : 'none'), |
|
[isShowRedhatSection], |
|
); |
|
|
|
const accessSection = useMemo( |
|
() => ( |
|
<GateForm |
|
allowSubmit={isShowAccessSubmit} |
|
gridProps={{ |
|
wrapperBoxProps: { |
|
sx: { |
|
display: isShowAccessSection ? 'flex' : 'none', |
|
}, |
|
}, |
|
}} |
|
identifierInputTestBatchBuilder={buildIPAddressTestBatch} |
|
identifierLabel={HOST_IP_LABEL} |
|
onIdentifierBlurAppend={({ target: { value } }) => { |
|
if (connectedHostIPAddress) { |
|
const isIdentifierChanged = value !== connectedHostIPAddress; |
|
|
|
setIsShowAccessSubmit(isIdentifierChanged); |
|
setIsShowOptionalSection(!isIdentifierChanged); |
|
setIsShowRedhatSection(!isIdentifierChanged); |
|
} |
|
}} |
|
onSubmitAppend={( |
|
ipAddress, |
|
password, |
|
setGateMessage, |
|
setGateIsSubmitting, |
|
) => { |
|
const body = { ipAddress, password }; |
|
|
|
api |
|
.put<APICommandInquireHostResponseBody>( |
|
'/command/inquire-host', |
|
body, |
|
) |
|
.then( |
|
({ |
|
data: { |
|
hostName, |
|
hostOS, |
|
hostUUID, |
|
isConnected, |
|
isInetConnected, |
|
isOSRegistered, |
|
}, |
|
}) => { |
|
if (isConnected) { |
|
inputHostNameRef.current.setValue?.call(null, hostName); |
|
|
|
const valid = testInput({ |
|
inputs: { [IT_IDS.hostName]: { value: hostName } }, |
|
}); |
|
setIsInputHostNameValid(valid); |
|
|
|
if ( |
|
isInetConnected && |
|
/rhel/i.test(hostOS) && |
|
!isOSRegistered |
|
) { |
|
setIsShowRedhatSection(true); |
|
} |
|
|
|
setConnectedHostIPAddress(ipAddress); |
|
setConnectedHostPassword(password); |
|
setConnectedHostUUID(hostUUID); |
|
|
|
setIsShowAccessSubmit(false); |
|
setIsShowOptionalSection(true); |
|
} else { |
|
setGateMessage({ |
|
children: `Failed to establish a connection with the given host credentials.`, |
|
type: 'error', |
|
}); |
|
} |
|
}, |
|
) |
|
.catch((apiError) => { |
|
const emsg = handleAPIError(apiError); |
|
|
|
setGateMessage?.call(null, emsg); |
|
}) |
|
.finally(() => { |
|
setGateIsSubmitting(false); |
|
}); |
|
}} |
|
passphraseLabel="Host root password" |
|
submitLabel="Test access" |
|
/> |
|
), |
|
[ |
|
isShowAccessSection, |
|
isShowAccessSubmit, |
|
connectedHostIPAddress, |
|
setConnectedHostPassword, |
|
setConnectedHostUUID, |
|
testInput, |
|
], |
|
); |
|
|
|
const optionalSection = useMemo( |
|
() => ( |
|
<Grid |
|
columns={GRID_COLUMNS} |
|
layout={{ |
|
'preparehost-host-name': { |
|
children: ( |
|
<InputWithRef |
|
input={ |
|
<OutlinedInputWithLabel |
|
formControlProps={{ sx: { width: '100%' } }} |
|
id="preparehost-host-name-input" |
|
inputProps={{ |
|
onBlur: ({ target: { value } }) => { |
|
const valid = testInput({ |
|
inputs: { [IT_IDS.hostName]: { value } }, |
|
}); |
|
setIsInputHostNameValid(valid); |
|
}, |
|
onFocus: () => { |
|
setHostNameInputMessage(); |
|
}, |
|
}} |
|
label={HOST_NAME_LABEL} |
|
/> |
|
} |
|
ref={inputHostNameRef} |
|
/> |
|
), |
|
}, |
|
'preparehost-enterprise-key': { |
|
children: ( |
|
<InputWithRef |
|
input={ |
|
<OutlinedInputWithLabel |
|
formControlProps={{ sx: { width: '100%' } }} |
|
id="preparehost-enterprise-key-input" |
|
inputProps={{ |
|
onBlur: ({ target: { value } }) => { |
|
if (value) { |
|
const valid = testInput({ |
|
inputs: { [IT_IDS.enterpriseKey]: { value } }, |
|
}); |
|
setIsInputEnterpriseKeyValid(valid); |
|
} |
|
}, |
|
onFocus: () => { |
|
setEnterpriseKeyInputMessage(); |
|
}, |
|
}} |
|
label={ENTERPRISE_KEY_LABEL} |
|
/> |
|
} |
|
ref={inputEnterpriseKeyRef} |
|
/> |
|
), |
|
}, |
|
}} |
|
spacing={GRID_SPACING} |
|
wrapperBoxProps={{ |
|
sx: { display: isShowOptionalSection ? undefined : 'none' }, |
|
}} |
|
/> |
|
), |
|
[ |
|
isShowOptionalSection, |
|
setEnterpriseKeyInputMessage, |
|
setHostNameInputMessage, |
|
testInput, |
|
], |
|
); |
|
|
|
const redhatSection = useMemo( |
|
() => ( |
|
<Grid |
|
columns={GRID_COLUMNS} |
|
layout={{ |
|
'preparehost-redhat-user': { |
|
children: ( |
|
<InputWithRef |
|
input={ |
|
<OutlinedInputWithLabel |
|
formControlProps={{ sx: { width: '100%' } }} |
|
id="preparehost-redhat-user-input" |
|
inputProps={{ |
|
onBlur: ({ target: { value } }) => { |
|
if (value) { |
|
const valid = testInput({ |
|
inputs: { [IT_IDS.redhatUser]: { value } }, |
|
}); |
|
setIsInputRedhatUserValid(valid); |
|
} |
|
}, |
|
onFocus: () => { |
|
setRedhatUserInputMessage(); |
|
}, |
|
}} |
|
label={REDHAT_USER_LABEL} |
|
/> |
|
} |
|
ref={inputRedhatUser} |
|
/> |
|
), |
|
}, |
|
'preparehost-redhat-password': { |
|
children: ( |
|
<InputWithRef |
|
input={ |
|
<OutlinedInputWithLabel |
|
formControlProps={{ sx: { width: '100%' } }} |
|
id="preparehost-redhat-password-input" |
|
inputProps={{ |
|
onBlur: ({ target: { value } }) => { |
|
if (value) { |
|
const valid = testInput({ |
|
inputs: { [IT_IDS.redhatPassword]: { value } }, |
|
}); |
|
setIsInputRedhatPasswordValid(valid); |
|
} |
|
}, |
|
onFocus: () => { |
|
setRedhatPasswordInputMessage(); |
|
}, |
|
onPasswordVisibilityAppend: (type) => { |
|
setIsShowRedhatPassword(type !== INPUT_TYPES.password); |
|
}, |
|
type: INPUT_TYPES.password, |
|
}} |
|
label={REDHAT_PASSWORD_LABEL} |
|
/> |
|
} |
|
ref={inputRedhatPassword} |
|
/> |
|
), |
|
}, |
|
}} |
|
spacing={GRID_SPACING} |
|
wrapperBoxProps={{ |
|
sx: { display: redhatElementSxDisplay }, |
|
}} |
|
/> |
|
), |
|
[ |
|
redhatElementSxDisplay, |
|
setRedhatPasswordInputMessage, |
|
setRedhatUserInputMessage, |
|
testInput, |
|
], |
|
); |
|
|
|
const messageSection = useMemo( |
|
() => ( |
|
<MUIBox sx={{ display: isShowOptionalSection ? undefined : 'none' }}> |
|
<MessageGroup count={1} ref={messageGroupRef} /> |
|
</MUIBox> |
|
), |
|
[isShowOptionalSection], |
|
); |
|
|
|
const submitSection = useMemo( |
|
() => |
|
isSubmittingPrepareHost ? ( |
|
<Spinner mt={0} /> |
|
) : ( |
|
<FlexBox |
|
row |
|
sx={{ |
|
display: isShowOptionalSection ? 'flex' : 'none', |
|
justifyContent: 'flex-end', |
|
}} |
|
> |
|
<ContainedButton |
|
disabled={ |
|
!isInputHostNameValid || |
|
!isInputEnterpriseKeyValid || |
|
!isInputRedhatUserValid || |
|
!isInputRedhatPasswordValid |
|
} |
|
onClick={() => { |
|
const redhatPasswordInputValue = |
|
inputRedhatPassword.current.getValue?.call(null); |
|
|
|
setConfirmValues({ |
|
enterpriseKey: |
|
inputEnterpriseKeyRef.current.getValue?.call(null) || |
|
'none; using community version', |
|
hostName: inputHostNameRef.current.getValue?.call(null) || '', |
|
redhatPassword: redhatPasswordInputValue || 'none', |
|
redhatPasswordHidden: |
|
redhatPasswordInputValue?.replace(/./g, '*') || 'none', |
|
redhatUser: |
|
inputRedhatUser.current.getValue?.call(null) || 'none', |
|
}); |
|
setSubmitPrepareHostMessage(); |
|
|
|
confirmDialogRef.current.setOpen?.call(null, true); |
|
}} |
|
> |
|
Prepare host |
|
</ContainedButton> |
|
</FlexBox> |
|
), |
|
[ |
|
isInputEnterpriseKeyValid, |
|
isInputHostNameValid, |
|
isInputRedhatPasswordValid, |
|
isInputRedhatUserValid, |
|
isShowOptionalSection, |
|
isSubmittingPrepareHost, |
|
setSubmitPrepareHostMessage, |
|
], |
|
); |
|
|
|
return ( |
|
<> |
|
<Panel> |
|
<PanelHeader> |
|
<HeaderText>Prepare a host to include in Anvil!</HeaderText> |
|
</PanelHeader> |
|
<FlexBox> |
|
<RadioGroupWithLabel |
|
id="preparehost-host-type" |
|
label="Host type" |
|
onChange={(event, value) => { |
|
setInputHostType(value); |
|
setIsShowAccessSection(true); |
|
}} |
|
radioItems={{ |
|
node: { label: 'Node', value: 'node' }, |
|
dr: { label: 'Disaster Recovery (DR) host', value: 'dr' }, |
|
}} |
|
/> |
|
{accessSection} |
|
{optionalSection} |
|
{redhatSection} |
|
{messageSection} |
|
{submitSection} |
|
</FlexBox> |
|
</Panel> |
|
<ConfirmDialog |
|
actionProceedText="Prepare" |
|
closeOnProceed |
|
content={ |
|
<Grid |
|
calculateItemBreakpoints={(index) => ({ |
|
xs: index % 2 === 0 ? 1 : 2, |
|
})} |
|
columns={3} |
|
layout={{ |
|
'preparehost-confirm-host-type-label': { |
|
children: <BodyText>Host type</BodyText>, |
|
}, |
|
'preparehost-confirm-host-type-value': { |
|
children: ( |
|
<MonoText> |
|
{inputHostType === 'dr' ? 'Disaster Recovery (DR)' : 'Node'} |
|
</MonoText> |
|
), |
|
}, |
|
'preparehost-confirm-host-name-label': { |
|
children: <BodyText>Host name</BodyText>, |
|
}, |
|
'preparehost-confirm-host-name-value': { |
|
children: <MonoText>{confirmValues?.hostName}</MonoText>, |
|
}, |
|
'preparehost-confirm-enterprise-key-label': { |
|
children: <BodyText>Alteeve enterprise key</BodyText>, |
|
}, |
|
'preparehost-confirm-enterprise-key-value': { |
|
children: <MonoText>{confirmValues?.enterpriseKey}</MonoText>, |
|
}, |
|
'preparehost-confirm-redhat-user-label': { |
|
children: <BodyText>RedHat user</BodyText>, |
|
sx: { display: redhatElementSxDisplay }, |
|
}, |
|
'preparehost-confirm-redhat-user-value': { |
|
children: <MonoText>{confirmValues?.redhatUser}</MonoText>, |
|
sx: { display: redhatElementSxDisplay }, |
|
}, |
|
'preparehost-confirm-redhat-password-label': { |
|
children: <BodyText>RedHat password</BodyText>, |
|
sx: { display: redhatElementSxDisplay }, |
|
}, |
|
'preparehost-confirm-redhat-password-value': { |
|
children: ( |
|
<FlexBox |
|
row |
|
sx={{ |
|
height: '100%', |
|
maxWidth: '100%', |
|
}} |
|
> |
|
<MonoText |
|
sx={{ |
|
flexGrow: 1, |
|
maxWidth: 'calc(100% - 3em)', |
|
overflowX: 'scroll', |
|
}} |
|
> |
|
{isShowRedhatPassword |
|
? confirmValues?.redhatPassword |
|
: confirmValues?.redhatPasswordHidden} |
|
</MonoText> |
|
<MUIIconButton |
|
onClick={() => { |
|
setIsShowRedhatPassword((previous) => !previous); |
|
}} |
|
sx={{ color: GREY, padding: 0 }} |
|
> |
|
{isShowRedhatPassword ? ( |
|
<MUIVisibilityOffIcon /> |
|
) : ( |
|
<MUIVisibilityIcon /> |
|
)} |
|
</MUIIconButton> |
|
</FlexBox> |
|
), |
|
sx: { display: redhatElementSxDisplay }, |
|
}, |
|
}} |
|
spacing=".6em" |
|
/> |
|
} |
|
onCancelAppend={() => { |
|
setIsShowRedhatPassword(false); |
|
}} |
|
onProceedAppend={() => { |
|
setIsSubmittingPrepareHost(true); |
|
|
|
api |
|
.put('/host/prepare', { |
|
enterpriseUUID: |
|
inputEnterpriseKeyRef.current.getValue?.call(null), |
|
hostIPAddress: connectedHostIPAddress, |
|
hostName: inputHostNameRef.current.getValue?.call(null), |
|
hostPassword: connectedHostPassword, |
|
hostType: inputHostType, |
|
hostUUID: connectedHostUUID, |
|
redhatPassword: inputRedhatPassword.current.getValue?.call(null), |
|
redhatUser: inputRedhatUser.current.getValue?.call(null), |
|
}) |
|
.then(() => { |
|
setSubmitPrepareHostMessage({ |
|
children: `Successfully initiated prepare host.`, |
|
}); |
|
|
|
setTimeout(() => { |
|
setSubmitPrepareHostMessage(); |
|
}, SUCCESS_MESSAGE_TIMEOUT); |
|
}) |
|
.catch((error) => { |
|
const errorMessage = handleAPIError(error, { |
|
onResponseErrorAppend: ({ status }) => { |
|
let result: Message | undefined; |
|
|
|
if (status === 400) { |
|
result = { |
|
children: `The API found invalid values. Did you forget to fill in one of the RedHat fields?`, |
|
type: 'warning', |
|
}; |
|
} |
|
|
|
return result; |
|
}, |
|
}); |
|
|
|
setSubmitPrepareHostMessage(errorMessage); |
|
}) |
|
.finally(() => { |
|
setIsSubmittingPrepareHost(false); |
|
}); |
|
}} |
|
ref={confirmDialogRef} |
|
titleText="Confirm host preparation" |
|
/> |
|
</> |
|
); |
|
}; |
|
|
|
export default PrepareHostForm;
|
|
|