|
|
|
@ -4,28 +4,34 @@ import { |
|
|
|
|
ReactNode, |
|
|
|
|
useCallback, |
|
|
|
|
useImperativeHandle, |
|
|
|
|
useMemo, |
|
|
|
|
useRef, |
|
|
|
|
useState, |
|
|
|
|
} from 'react'; |
|
|
|
|
import { v4 as uuidv4 } from 'uuid'; |
|
|
|
|
|
|
|
|
|
import INPUT_TYPES from '../lib/consts/INPUT_TYPES'; |
|
|
|
|
|
|
|
|
|
import FlexBox from './FlexBox'; |
|
|
|
|
import InputWithRef, { InputForwardedRefContent } from './InputWithRef'; |
|
|
|
|
import isEmpty from '../lib/isEmpty'; |
|
|
|
|
import MessageBox from './MessageBox'; |
|
|
|
|
import MessageBox, { Message } from './MessageBox'; |
|
|
|
|
import OutlinedInputWithLabel, { |
|
|
|
|
OutlinedInputWithLabelProps, |
|
|
|
|
} from './OutlinedInputWithLabel'; |
|
|
|
|
import pad from '../lib/pad'; |
|
|
|
|
import SuggestButton from './SuggestButton'; |
|
|
|
|
import { testInput, testLength, testNotBlank } from '../lib/test_input'; |
|
|
|
|
import { InputTestBatches } from '../types/TestInputFunction'; |
|
|
|
|
|
|
|
|
|
type GeneralInitFormForwardRefContent = { |
|
|
|
|
get?: () => { |
|
|
|
|
adminPassword?: string; |
|
|
|
|
organizationName?: string; |
|
|
|
|
organizationPrefix?: string; |
|
|
|
|
domainName?: string; |
|
|
|
|
hostNumber?: number; |
|
|
|
|
hostName?: string; |
|
|
|
|
hostNumber?: number; |
|
|
|
|
organizationName?: string; |
|
|
|
|
organizationPrefix?: string; |
|
|
|
|
}; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
@ -35,8 +41,10 @@ type OutlinedInputWithLabelOnBlur = Exclude< |
|
|
|
|
>['onBlur']; |
|
|
|
|
|
|
|
|
|
const MAX_ORGANIZATION_PREFIX_LENGTH = 5; |
|
|
|
|
const MIN_ORGANIZATION_PREFIX_LENGTH = 2; |
|
|
|
|
const MIN_ORGANIZATION_PREFIX_LENGTH = 1; |
|
|
|
|
const MAX_HOST_NUMBER_LENGTH = 2; |
|
|
|
|
const INPUT_COUNT = 7; |
|
|
|
|
const REP_DN_CHAR = /^[a-z0-9-.]+$/; |
|
|
|
|
|
|
|
|
|
const MAP_TO_ORGANIZATION_PREFIX_BUILDER: Record< |
|
|
|
|
number, |
|
|
|
@ -49,6 +57,13 @@ const MAP_TO_ORGANIZATION_PREFIX_BUILDER: Record< |
|
|
|
|
words.map((word) => word.substring(0, 1).toLocaleLowerCase()).join(''), |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const INPUT_TEST_MESSAGE_KEYS: string[] = Array.from( |
|
|
|
|
{ |
|
|
|
|
length: INPUT_COUNT, |
|
|
|
|
}, |
|
|
|
|
() => uuidv4(), |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
const buildOrganizationPrefix = (organizationName = '') => { |
|
|
|
|
const words: string[] = organizationName |
|
|
|
|
.split(/\s/) |
|
|
|
@ -75,13 +90,18 @@ const buildHostName = ({ |
|
|
|
|
|
|
|
|
|
const GeneralInitForm = forwardRef<GeneralInitFormForwardRefContent>( |
|
|
|
|
(generalInitFormProps, ref) => { |
|
|
|
|
const [helpMessage, setHelpText] = useState<ReactNode | undefined>(); |
|
|
|
|
const [helpMessage, setHelpMessage] = useState<ReactNode | undefined>(); |
|
|
|
|
const [inputMessages, setInputMessages] = useState< |
|
|
|
|
Array<Message | undefined> |
|
|
|
|
>([]); |
|
|
|
|
const [ |
|
|
|
|
isShowOrganizationPrefixSuggest, |
|
|
|
|
setIsShowOrganizationPrefixSuggest, |
|
|
|
|
] = useState<boolean>(false); |
|
|
|
|
const [isShowHostNameSuggest, setIsShowHostNameSuggest] = |
|
|
|
|
useState<boolean>(false); |
|
|
|
|
const [isConfirmAdminPassword, setIsConfirmAdminPassword] = |
|
|
|
|
useState<boolean>(true); |
|
|
|
|
|
|
|
|
|
const adminPasswordInputRef = useRef<InputForwardedRefContent<'string'>>( |
|
|
|
|
{}, |
|
|
|
@ -99,6 +119,58 @@ const GeneralInitForm = forwardRef<GeneralInitFormForwardRefContent>( |
|
|
|
|
const hostNumberInputRef = useRef<InputForwardedRefContent<'number'>>({}); |
|
|
|
|
const hostNameInputRef = useRef<InputForwardedRefContent<'string'>>({}); |
|
|
|
|
|
|
|
|
|
const setInputMessage = useCallback((index: number, message?: Message) => { |
|
|
|
|
setInputMessages((previous) => { |
|
|
|
|
previous.splice(index, 1, message); |
|
|
|
|
return [...previous]; |
|
|
|
|
}); |
|
|
|
|
}, []); |
|
|
|
|
const setOrganizationPrefixInputMessage = useCallback( |
|
|
|
|
(message?: Message) => setInputMessage(1, message), |
|
|
|
|
[setInputMessage], |
|
|
|
|
); |
|
|
|
|
const setHostNumberInputMessage = useCallback( |
|
|
|
|
(message?: Message) => setInputMessage(2, message), |
|
|
|
|
[setInputMessage], |
|
|
|
|
); |
|
|
|
|
const setDomainNameInputMessage = useCallback( |
|
|
|
|
(message?: Message) => setInputMessage(3, message), |
|
|
|
|
[setInputMessage], |
|
|
|
|
); |
|
|
|
|
const setHostNameInputMessage = useCallback( |
|
|
|
|
(message?: Message) => setInputMessage(4, message), |
|
|
|
|
[setInputMessage], |
|
|
|
|
); |
|
|
|
|
const setAdminPasswordInputMessage = useCallback( |
|
|
|
|
(message?: Message) => setInputMessage(5, message), |
|
|
|
|
[setInputMessage], |
|
|
|
|
); |
|
|
|
|
const setConfirmAdminPasswordInputMessage = useCallback( |
|
|
|
|
(message?: Message) => setInputMessage(6, message), |
|
|
|
|
[setInputMessage], |
|
|
|
|
); |
|
|
|
|
const createInputTestMessages = useCallback( |
|
|
|
|
() => |
|
|
|
|
inputMessages.map((message, index) => { |
|
|
|
|
let messageElement; |
|
|
|
|
|
|
|
|
|
if (message) { |
|
|
|
|
const { children, type = 'warning' } = message; |
|
|
|
|
|
|
|
|
|
messageElement = ( |
|
|
|
|
<MessageBox |
|
|
|
|
key={`input-test-message-${INPUT_TEST_MESSAGE_KEYS[index]}`} |
|
|
|
|
type={type} |
|
|
|
|
> |
|
|
|
|
{children} |
|
|
|
|
</MessageBox> |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return messageElement; |
|
|
|
|
}), |
|
|
|
|
[inputMessages], |
|
|
|
|
); |
|
|
|
|
const populateOrganizationPrefixInput = useCallback( |
|
|
|
|
({ |
|
|
|
|
organizationName = organizationNameInputRef.current.getValue?.call( |
|
|
|
@ -191,6 +263,136 @@ const GeneralInitForm = forwardRef<GeneralInitFormForwardRefContent>( |
|
|
|
|
[], |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
const inputTests: InputTestBatches = useMemo( |
|
|
|
|
() => ({ |
|
|
|
|
adminPassword: { |
|
|
|
|
defaults: { |
|
|
|
|
onSuccess: () => { |
|
|
|
|
setAdminPasswordInputMessage(undefined); |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
tests: [ |
|
|
|
|
{ |
|
|
|
|
onFailure: () => { |
|
|
|
|
setAdminPasswordInputMessage({ |
|
|
|
|
children: |
|
|
|
|
'Admin password cannot contain single-quote, double-quote, slash, backslash, angle brackets, and curly brackets.', |
|
|
|
|
}); |
|
|
|
|
}, |
|
|
|
|
test: ({ value }) => !/['"/\\><}{]/g.test(value as string), |
|
|
|
|
}, |
|
|
|
|
], |
|
|
|
|
}, |
|
|
|
|
confirmAdminPassword: { |
|
|
|
|
defaults: { |
|
|
|
|
onSuccess: () => { |
|
|
|
|
setConfirmAdminPasswordInputMessage(undefined); |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
tests: [ |
|
|
|
|
{ |
|
|
|
|
onFailure: () => { |
|
|
|
|
setConfirmAdminPasswordInputMessage({ |
|
|
|
|
children: 'Admin password confirmation failed.', |
|
|
|
|
}); |
|
|
|
|
}, |
|
|
|
|
test: ({ value, compare }) => value === compare, |
|
|
|
|
}, |
|
|
|
|
], |
|
|
|
|
}, |
|
|
|
|
domainName: { |
|
|
|
|
defaults: { |
|
|
|
|
onSuccess: () => { |
|
|
|
|
setDomainNameInputMessage(undefined); |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
tests: [ |
|
|
|
|
{ |
|
|
|
|
onFailure: () => { |
|
|
|
|
setDomainNameInputMessage({ |
|
|
|
|
children: |
|
|
|
|
'Domain name can only contain lowercase alphanumeric, hyphen, and decimal characters.', |
|
|
|
|
}); |
|
|
|
|
}, |
|
|
|
|
test: ({ value }) => REP_DN_CHAR.test(value as string), |
|
|
|
|
}, |
|
|
|
|
], |
|
|
|
|
}, |
|
|
|
|
hostName: { |
|
|
|
|
defaults: { |
|
|
|
|
onSuccess: () => { |
|
|
|
|
setHostNameInputMessage(undefined); |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
tests: [ |
|
|
|
|
{ |
|
|
|
|
onFailure: () => { |
|
|
|
|
setHostNameInputMessage({ |
|
|
|
|
children: |
|
|
|
|
'Host name can only contain lowercase alphanumeric, hyphen, and decimal characters.', |
|
|
|
|
}); |
|
|
|
|
}, |
|
|
|
|
test: ({ value }) => REP_DN_CHAR.test(value as string), |
|
|
|
|
}, |
|
|
|
|
], |
|
|
|
|
}, |
|
|
|
|
hostNumber: { |
|
|
|
|
defaults: { |
|
|
|
|
onSuccess: () => { |
|
|
|
|
setHostNumberInputMessage(undefined); |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
tests: [ |
|
|
|
|
{ |
|
|
|
|
onFailure: () => { |
|
|
|
|
setHostNumberInputMessage({ |
|
|
|
|
children: 'Host number can only contain digits.', |
|
|
|
|
}); |
|
|
|
|
}, |
|
|
|
|
test: ({ value }) => /^\d+$/.test(value as string), |
|
|
|
|
}, |
|
|
|
|
], |
|
|
|
|
}, |
|
|
|
|
organizationName: { |
|
|
|
|
tests: [{ test: testNotBlank }], |
|
|
|
|
}, |
|
|
|
|
organizationPrefix: { |
|
|
|
|
defaults: { |
|
|
|
|
onSuccess: () => { |
|
|
|
|
setOrganizationPrefixInputMessage(undefined); |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
tests: [ |
|
|
|
|
{ |
|
|
|
|
onFailure: ({ max, min }) => { |
|
|
|
|
setOrganizationPrefixInputMessage({ |
|
|
|
|
children: `Organization prefix must be ${min} to ${max} characters.`, |
|
|
|
|
}); |
|
|
|
|
}, |
|
|
|
|
test: testLength, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
onFailure: () => { |
|
|
|
|
setOrganizationPrefixInputMessage({ |
|
|
|
|
children: |
|
|
|
|
'Organization prefix can only contain lowercase alphanumeric characters.', |
|
|
|
|
}); |
|
|
|
|
}, |
|
|
|
|
test: ({ value }) => /^[a-z0-9]+$/.test(value as string), |
|
|
|
|
}, |
|
|
|
|
], |
|
|
|
|
}, |
|
|
|
|
}), |
|
|
|
|
[ |
|
|
|
|
setAdminPasswordInputMessage, |
|
|
|
|
setConfirmAdminPasswordInputMessage, |
|
|
|
|
setDomainNameInputMessage, |
|
|
|
|
setHostNameInputMessage, |
|
|
|
|
setHostNumberInputMessage, |
|
|
|
|
setOrganizationPrefixInputMessage, |
|
|
|
|
], |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
useImperativeHandle(ref, () => ({ |
|
|
|
|
get: () => ({ |
|
|
|
|
adminPassword: adminPasswordInputRef.current.getValue?.call(null), |
|
|
|
@ -215,9 +417,16 @@ const GeneralInitForm = forwardRef<GeneralInitFormForwardRefContent>( |
|
|
|
|
inputProps={{ |
|
|
|
|
onBlur: populateOrganizationPrefixInputOnBlur, |
|
|
|
|
}} |
|
|
|
|
inputLabelProps={{ isNotifyRequired: true }} |
|
|
|
|
label="Organization name" |
|
|
|
|
onChange={({ target: { value } }) => { |
|
|
|
|
testInput({ |
|
|
|
|
inputs: { organizationName: { value } }, |
|
|
|
|
tests: inputTests, |
|
|
|
|
}); |
|
|
|
|
}} |
|
|
|
|
onHelp={() => { |
|
|
|
|
setHelpText( |
|
|
|
|
setHelpMessage( |
|
|
|
|
buildHelpMessage( |
|
|
|
|
'Name of the organization that maintains this Anvil! system. You can enter anything that makes sense to you.', |
|
|
|
|
), |
|
|
|
@ -254,14 +463,26 @@ const GeneralInitForm = forwardRef<GeneralInitFormForwardRefContent>( |
|
|
|
|
}, |
|
|
|
|
onBlur: populateHostNameInputOnBlur, |
|
|
|
|
}} |
|
|
|
|
inputLabelProps={{ isNotifyRequired: true }} |
|
|
|
|
label="Prefix" |
|
|
|
|
onChange={() => { |
|
|
|
|
onChange={({ target: { value } }) => { |
|
|
|
|
testInput({ |
|
|
|
|
inputs: { |
|
|
|
|
organizationPrefix: { |
|
|
|
|
max: MAX_ORGANIZATION_PREFIX_LENGTH, |
|
|
|
|
min: MIN_ORGANIZATION_PREFIX_LENGTH, |
|
|
|
|
value, |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
tests: inputTests, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
setIsShowOrganizationPrefixSuggest( |
|
|
|
|
isOrganizationPrefixPrereqFilled(), |
|
|
|
|
); |
|
|
|
|
}} |
|
|
|
|
onHelp={() => { |
|
|
|
|
setHelpText( |
|
|
|
|
setHelpMessage( |
|
|
|
|
buildHelpMessage( |
|
|
|
|
"Alphanumberic short-form of the organization name. It's used as the prefix for host names.", |
|
|
|
|
), |
|
|
|
@ -284,9 +505,16 @@ const GeneralInitForm = forwardRef<GeneralInitFormForwardRefContent>( |
|
|
|
|
}, |
|
|
|
|
onBlur: populateHostNameInputOnBlur, |
|
|
|
|
}} |
|
|
|
|
label="Host #" |
|
|
|
|
inputLabelProps={{ isNotifyRequired: true }} |
|
|
|
|
label="Striker #" |
|
|
|
|
onChange={({ target: { value } }) => { |
|
|
|
|
testInput({ |
|
|
|
|
inputs: { hostNumber: { value } }, |
|
|
|
|
tests: inputTests, |
|
|
|
|
}); |
|
|
|
|
}} |
|
|
|
|
onHelp={() => { |
|
|
|
|
setHelpText( |
|
|
|
|
setHelpMessage( |
|
|
|
|
buildHelpMessage( |
|
|
|
|
"Number or count of this striker; this should be '1' for the first striker, '2' for the second striker, and such.", |
|
|
|
|
), |
|
|
|
@ -309,9 +537,16 @@ const GeneralInitForm = forwardRef<GeneralInitFormForwardRefContent>( |
|
|
|
|
inputProps={{ |
|
|
|
|
onBlur: populateHostNameInputOnBlur, |
|
|
|
|
}} |
|
|
|
|
inputLabelProps={{ isNotifyRequired: true }} |
|
|
|
|
label="Domain name" |
|
|
|
|
onChange={({ target: { value } }) => { |
|
|
|
|
testInput({ |
|
|
|
|
inputs: { domainName: { value } }, |
|
|
|
|
tests: inputTests, |
|
|
|
|
}); |
|
|
|
|
}} |
|
|
|
|
onHelp={() => { |
|
|
|
|
setHelpText( |
|
|
|
|
setHelpMessage( |
|
|
|
|
buildHelpMessage( |
|
|
|
|
"Domain name for this striker. It's also the default domain used when creating new install manifests.", |
|
|
|
|
), |
|
|
|
@ -334,12 +569,18 @@ const GeneralInitForm = forwardRef<GeneralInitFormForwardRefContent>( |
|
|
|
|
/> |
|
|
|
|
), |
|
|
|
|
}} |
|
|
|
|
inputLabelProps={{ isNotifyRequired: true }} |
|
|
|
|
label="Host name" |
|
|
|
|
onChange={() => { |
|
|
|
|
onChange={({ target: { value } }) => { |
|
|
|
|
testInput({ |
|
|
|
|
inputs: { hostName: { value } }, |
|
|
|
|
tests: inputTests, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
setIsShowHostNameSuggest(isHostNamePrereqFilled()); |
|
|
|
|
}} |
|
|
|
|
onHelp={() => { |
|
|
|
|
setHelpText( |
|
|
|
|
setHelpMessage( |
|
|
|
|
buildHelpMessage( |
|
|
|
|
"Host name for this striker. It's usually a good idea to use the auto-generated value.", |
|
|
|
|
), |
|
|
|
@ -369,12 +610,24 @@ const GeneralInitForm = forwardRef<GeneralInitFormForwardRefContent>( |
|
|
|
|
id="striker-init-general-admin-password" |
|
|
|
|
inputProps={{ |
|
|
|
|
inputProps: { |
|
|
|
|
type: 'password', |
|
|
|
|
type: INPUT_TYPES.password, |
|
|
|
|
}, |
|
|
|
|
onPasswordVisibilityAppend: (inputType) => { |
|
|
|
|
setIsConfirmAdminPassword( |
|
|
|
|
inputType === INPUT_TYPES.password, |
|
|
|
|
); |
|
|
|
|
}, |
|
|
|
|
}} |
|
|
|
|
inputLabelProps={{ isNotifyRequired: true }} |
|
|
|
|
label="Admin password" |
|
|
|
|
onChange={({ target: { value } }) => { |
|
|
|
|
testInput({ |
|
|
|
|
inputs: { adminPassword: { value } }, |
|
|
|
|
tests: inputTests, |
|
|
|
|
}); |
|
|
|
|
}} |
|
|
|
|
onHelp={() => { |
|
|
|
|
setHelpText( |
|
|
|
|
setHelpMessage( |
|
|
|
|
buildHelpMessage( |
|
|
|
|
"Password use to login to this Striker and connect to its database. Don't provide an used password here because it'll be stored as plaintext.", |
|
|
|
|
), |
|
|
|
@ -385,29 +638,49 @@ const GeneralInitForm = forwardRef<GeneralInitFormForwardRefContent>( |
|
|
|
|
ref={adminPasswordInputRef} |
|
|
|
|
/> |
|
|
|
|
</MUIGrid> |
|
|
|
|
<MUIGrid item xs={1}> |
|
|
|
|
<InputWithRef |
|
|
|
|
input={ |
|
|
|
|
<OutlinedInputWithLabel |
|
|
|
|
id="striker-init-general-confirm-admin-password" |
|
|
|
|
inputProps={{ |
|
|
|
|
inputProps: { |
|
|
|
|
type: 'password', |
|
|
|
|
}, |
|
|
|
|
}} |
|
|
|
|
label="Confirm password" |
|
|
|
|
/> |
|
|
|
|
} |
|
|
|
|
ref={confirmAdminPasswordInputRef} |
|
|
|
|
/> |
|
|
|
|
</MUIGrid> |
|
|
|
|
{isConfirmAdminPassword && ( |
|
|
|
|
<MUIGrid item xs={1}> |
|
|
|
|
<InputWithRef |
|
|
|
|
input={ |
|
|
|
|
<OutlinedInputWithLabel |
|
|
|
|
id="striker-init-general-confirm-admin-password" |
|
|
|
|
inputProps={{ |
|
|
|
|
inputProps: { |
|
|
|
|
type: INPUT_TYPES.password, |
|
|
|
|
}, |
|
|
|
|
}} |
|
|
|
|
inputLabelProps={{ |
|
|
|
|
isNotifyRequired: isConfirmAdminPassword, |
|
|
|
|
}} |
|
|
|
|
label="Confirm password" |
|
|
|
|
onChange={({ target: { value } }) => { |
|
|
|
|
testInput({ |
|
|
|
|
inputs: { |
|
|
|
|
confirmAdminPassword: { |
|
|
|
|
value, |
|
|
|
|
compare: |
|
|
|
|
adminPasswordInputRef.current.getValue?.call( |
|
|
|
|
null, |
|
|
|
|
), |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
tests: inputTests, |
|
|
|
|
}); |
|
|
|
|
}} |
|
|
|
|
/> |
|
|
|
|
} |
|
|
|
|
ref={confirmAdminPasswordInputRef} |
|
|
|
|
/> |
|
|
|
|
</MUIGrid> |
|
|
|
|
)} |
|
|
|
|
</MUIGrid> |
|
|
|
|
</MUIGrid> |
|
|
|
|
</MUIGrid> |
|
|
|
|
{createInputTestMessages()} |
|
|
|
|
{helpMessage && ( |
|
|
|
|
<MessageBox |
|
|
|
|
onClose={() => { |
|
|
|
|
setHelpText(undefined); |
|
|
|
|
setHelpMessage(undefined); |
|
|
|
|
}} |
|
|
|
|
> |
|
|
|
|
{helpMessage} |
|
|
|
|