Local modifications to ClusterLabs/Anvil by Alteeve
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.
 
 
 
 
 
 

319 lines
10 KiB

import { Box as MUIBox } from '@mui/material';
import { forwardRef, useImperativeHandle, useRef, useState } from 'react';
import FlexBox from './FlexBox';
import InputWithRef, { InputForwardedRefContent } from './InputWithRef';
import isEmpty from '../lib/isEmpty';
import OutlinedInputWithLabel, {
OutlinedInputWithLabelProps,
} from './OutlinedInputWithLabel';
import pad from '../lib/pad';
import SuggestButton from './SuggestButton';
type GeneralInitFormForwardRefContent = {
get?: () => {
organizationName?: string;
organizationPrefix?: string;
domainName?: string;
hostNumber?: number;
hostName?: string;
};
};
type OutlinedInputWithLabelOnBlur = Exclude<
OutlinedInputWithLabelProps['inputProps'],
undefined
>['onBlur'];
const MAX_ORGANIZATION_PREFIX_LENGTH = 5;
const MIN_ORGANIZATION_PREFIX_LENGTH = 2;
const MAX_HOST_NUMBER_LENGTH = 2;
const MAP_TO_ORGANIZATION_PREFIX_BUILDER: Record<
number,
(words: string[]) => string
> = {
0: () => '',
1: ([word]) =>
word.substring(0, MIN_ORGANIZATION_PREFIX_LENGTH).toLocaleLowerCase(),
2: (words) =>
words.map((word) => word.substring(0, 1).toLocaleLowerCase()).join(''),
};
const buildOrganizationPrefix = (organizationName = '') => {
const words: string[] = organizationName
.split(/\s/)
.filter((word) => !/and|of/.test(word))
.slice(0, MAX_ORGANIZATION_PREFIX_LENGTH);
const builderKey: number = words.length > 1 ? 2 : words.length;
return MAP_TO_ORGANIZATION_PREFIX_BUILDER[builderKey](words);
};
const buildHostName = ({
organizationPrefix,
hostNumber,
domainName,
}: {
organizationPrefix?: string;
hostNumber?: number;
domainName?: string;
}) =>
isEmpty([organizationPrefix, hostNumber, domainName], { not: true })
? `${organizationPrefix}-striker${pad(hostNumber)}.${domainName}`
: '';
const GeneralInitForm = forwardRef<GeneralInitFormForwardRefContent>(
(generalInitFormProps, ref) => {
const [
isShowOrganizationPrefixSuggest,
setIsShowOrganizationPrefixSuggest,
] = useState<boolean>(false);
const [isShowHostNameSuggest, setIsShowHostNameSuggest] =
useState<boolean>(false);
const organizationNameInputRef = useRef<InputForwardedRefContent<'string'>>(
{},
);
const organizationPrefixInputRef = useRef<
InputForwardedRefContent<'string'>
>({});
const domainNameInputRef = useRef<InputForwardedRefContent<'string'>>({});
const hostNumberInputRef = useRef<InputForwardedRefContent<'number'>>({});
const hostNameInputRef = useRef<InputForwardedRefContent<'string'>>({});
const populateOrganizationPrefixInput = ({
organizationName = organizationNameInputRef.current.getValue?.call(null),
} = {}) => {
const organizationPrefix = buildOrganizationPrefix(organizationName);
organizationPrefixInputRef.current.setValue?.call(
null,
organizationPrefix,
);
return organizationPrefix;
};
const populateHostNameInput = ({
organizationPrefix = organizationPrefixInputRef.current.getValue?.call(
null,
),
hostNumber = hostNumberInputRef.current.getValue?.call(null),
domainName = domainNameInputRef.current.getValue?.call(null),
} = {}) => {
const hostName = buildHostName({
organizationPrefix,
hostNumber,
domainName,
});
hostNameInputRef.current.setValue?.call(null, hostName);
return hostName;
};
const isOrganizationPrefixPrereqFilled = () =>
isEmpty([organizationNameInputRef.current.getValue?.call(null)], {
not: true,
});
const isHostNamePrereqFilled = () =>
isEmpty(
[
organizationPrefixInputRef.current.getValue?.call(null),
hostNumberInputRef.current.getValue?.call(null),
domainNameInputRef.current.getValue?.call(null),
],
{
not: true,
},
);
const populateOrganizationPrefixInputOnBlur: OutlinedInputWithLabelOnBlur =
() => {
if (organizationPrefixInputRef.current.getIsChangedByUser?.call(null)) {
setIsShowOrganizationPrefixSuggest(
isOrganizationPrefixPrereqFilled(),
);
} else {
populateOrganizationPrefixInput();
}
};
const populateHostNameInputOnBlur: OutlinedInputWithLabelOnBlur = () => {
if (hostNameInputRef.current.getIsChangedByUser?.call(null)) {
setIsShowHostNameSuggest(isHostNamePrereqFilled());
} else {
populateHostNameInput();
}
};
const handleOrganizationPrefixSuggest = () => {
const organizationPrefix = populateOrganizationPrefixInput();
if (!hostNameInputRef.current.getIsChangedByUser?.call(null)) {
populateHostNameInput({ organizationPrefix });
}
};
const handlerHostNameSuggest = () => {
populateHostNameInput();
};
useImperativeHandle(ref, () => ({
get: () => ({
organizationName: organizationNameInputRef.current.getValue?.call(null),
organizationPrefix:
organizationPrefixInputRef.current.getValue?.call(null),
domainName: domainNameInputRef.current.getValue?.call(null),
hostNumber: hostNumberInputRef.current.getValue?.call(null),
hostName: hostNameInputRef.current.getValue?.call(null),
}),
}));
return (
<MUIBox
sx={{
display: 'flex',
flexDirection: { xs: 'column', sm: 'row' },
'& > *': {
flexBasis: '50%',
},
'& > :not(:first-child)': {
marginLeft: { xs: 0, sm: '1em' },
marginTop: { xs: '1em', sm: 0 },
},
}}
>
<FlexBox>
<InputWithRef
input={
<OutlinedInputWithLabel
helpMessageBoxProps={{
text: 'Name of the organization that maintains this Anvil! system. You can enter anything that makes sense to you.',
}}
id="striker-init-general-organization-name"
inputProps={{
onBlur: populateOrganizationPrefixInputOnBlur,
}}
label="Organization name"
/>
}
ref={organizationNameInputRef}
/>
<FlexBox row>
<InputWithRef
input={
<OutlinedInputWithLabel
helpMessageBoxProps={{
text: "Alphanumberic short-form of the organization name. It's used as the prefix for host names.",
}}
id="striker-init-general-organization-prefix"
inputProps={{
endAdornment: (
<SuggestButton
show={isShowOrganizationPrefixSuggest}
onClick={handleOrganizationPrefixSuggest}
/>
),
inputProps: {
maxLength: MAX_ORGANIZATION_PREFIX_LENGTH,
style: { width: '2.5em' },
},
onBlur: populateHostNameInputOnBlur,
sx: {
minWidth: 'min-content',
width: 'fit-content',
},
}}
label="Prefix"
onChange={() => {
setIsShowOrganizationPrefixSuggest(
isOrganizationPrefixPrereqFilled(),
);
}}
/>
}
ref={organizationPrefixInputRef}
/>
<InputWithRef
input={
<OutlinedInputWithLabel
helpMessageBoxProps={{
text: "Number or count of this striker; this should be '1' for the first striker, '2' for the second striker, and such.",
}}
id="striker-init-general-host-number"
inputProps={{
inputProps: { maxLength: MAX_HOST_NUMBER_LENGTH },
onBlur: populateHostNameInputOnBlur,
sx: {
width: '5em',
},
}}
label="Host #"
/>
}
ref={hostNumberInputRef}
valueType="number"
/>
</FlexBox>
</FlexBox>
<FlexBox>
<InputWithRef
input={
<OutlinedInputWithLabel
helpMessageBoxProps={{
text: "Domain name for this striker. It's also the default domain used when creating new install manifests.",
}}
id="striker-init-general-domain-name"
inputProps={{
onBlur: populateHostNameInputOnBlur,
sx: {
minWidth: { sm: '16em' },
},
}}
label="Domain name"
/>
}
ref={domainNameInputRef}
/>
<InputWithRef
input={
<OutlinedInputWithLabel
helpMessageBoxProps={{
text: "Host name for this striker. It's usually a good idea to use the auto-generated value.",
}}
id="striker-init-general-host-name"
inputProps={{
endAdornment: (
<SuggestButton
show={isShowHostNameSuggest}
onClick={handlerHostNameSuggest}
/>
),
inputProps: {
style: {
minWidth: '4em',
},
},
sx: {
minWidth: 'min-content',
},
}}
label="Host name"
onChange={() => {
setIsShowHostNameSuggest(isHostNamePrereqFilled());
}}
/>
}
ref={hostNameInputRef}
/>
</FlexBox>
</MUIBox>
);
},
);
GeneralInitForm.displayName = 'GeneralInitForm';
export type { GeneralInitFormForwardRefContent };
export default GeneralInitForm;