fix(striker-ui): correct and reduce validations

main
Tsu-ba-me 3 years ago
parent 3c0925065b
commit 113fda4900
  1. 272
      striker-ui/components/ProvisionServerDialog.tsx

@ -23,7 +23,7 @@ import {
import Autocomplete from './Autocomplete'; import Autocomplete from './Autocomplete';
import ContainedButton, { ContainedButtonProps } from './ContainedButton'; import ContainedButton, { ContainedButtonProps } from './ContainedButton';
import MenuItem from './MenuItem'; import MenuItem from './MenuItem';
import MessageBox from './MessageBox'; import MessageBox, { MessageBoxProps } from './MessageBox';
import OutlinedInput from './OutlinedInput'; import OutlinedInput from './OutlinedInput';
import OutlinedInputLabel from './OutlinedInputLabel'; import OutlinedInputLabel from './OutlinedInputLabel';
import OutlinedInputWithLabel, { import OutlinedInputWithLabel, {
@ -34,6 +34,8 @@ import Select, { SelectProps } from './Select';
import Slider, { SliderProps } from './Slider'; import Slider, { SliderProps } from './Slider';
import { BodyText, HeaderText } from './Text'; import { BodyText, HeaderText } from './Text';
type InputMessage = Partial<Pick<MessageBoxProps, 'type' | 'text'>>;
type SelectItem<SelectItemValueType = string> = { type SelectItem<SelectItemValueType = string> = {
displayValue?: SelectItemValueType; displayValue?: SelectItemValueType;
value: SelectItemValueType; value: SelectItemValueType;
@ -148,9 +150,10 @@ type FilterAnvilsFunction = (
type VirtualDiskStates = { type VirtualDiskStates = {
maxes: bigint[]; maxes: bigint[];
inputErrors: Array<string | undefined>;
inputMaxes: string[]; inputMaxes: string[];
inputSizeMessages: Array<InputMessage | undefined>;
inputSizes: string[]; inputSizes: string[];
inputStorageGroupUUIDMessages: Array<InputMessage | undefined>;
inputStorageGroupUUIDs: string[]; inputStorageGroupUUIDs: string[];
inputUnits: DataSizeUnit[]; inputUnits: DataSizeUnit[];
sizes: bigint[]; sizes: bigint[];
@ -175,8 +178,18 @@ type TestArgs = {
value: bigint | number | string; value: bigint | number | string;
}; };
type TestInputFunction = (inputs: { type InputTest = {
onFailure?: (args: TestArgs) => void;
onSuccess?: () => void;
test: (args: TestArgs) => boolean;
};
type TestInputFunction = (options?: {
inputs?: {
[id: string]: Partial<TestArgs>; [id: string]: Partial<TestArgs>;
};
isContinueOnFailure?: boolean;
isIgnoreOnCallbacks?: boolean;
}) => boolean; }) => boolean;
const MOCK_DATA = { const MOCK_DATA = {
@ -361,8 +374,8 @@ const DATA_SIZE_UNIT_SELECT_ITEMS: SelectItem<DataSizeUnit>[] = [
{ value: 'TB' }, { value: 'TB' },
]; ];
const createErrorMessage = (error?: string) => const createInputMessage = ({ text, type }: Partial<MessageBoxProps> = {}) =>
error && <MessageBox sx={{ marginTop: '.4em' }} type="error" text={error} />; text && <MessageBox {...{ sx: { marginTop: '.4em' }, text, type }} />;
const createOutlinedSelect = ( const createOutlinedSelect = (
id: string, id: string,
@ -372,6 +385,7 @@ const createOutlinedSelect = (
checkItem, checkItem,
disableItem, disableItem,
hideItem, hideItem,
messageBoxProps,
selectProps, selectProps,
isCheckableItems = selectProps?.multiple, isCheckableItems = selectProps?.multiple,
}: { }: {
@ -379,6 +393,7 @@ const createOutlinedSelect = (
disableItem?: (value: string) => boolean; disableItem?: (value: string) => boolean;
hideItem?: (value: string) => boolean; hideItem?: (value: string) => boolean;
isCheckableItems?: boolean; isCheckableItems?: boolean;
messageBoxProps?: Partial<MessageBoxProps>;
selectProps?: Partial<SelectProps>; selectProps?: Partial<SelectProps>;
} = {}, } = {},
): JSX.Element => ( ): JSX.Element => (
@ -410,6 +425,7 @@ const createOutlinedSelect = (
</MenuItem> </MenuItem>
))} ))}
</Select> </Select>
{createInputMessage(messageBoxProps)}
</FormControl> </FormControl>
); );
@ -436,12 +452,12 @@ const createOutlinedInputWithSelect = (
label: string, label: string,
selectItems: SelectItem[], selectItems: SelectItem[],
{ {
error, messageBoxProps,
inputWithLabelProps, inputWithLabelProps,
selectProps, selectProps,
}: { }: {
error?: string;
inputWithLabelProps?: Partial<OutlinedInputWithLabelProps>; inputWithLabelProps?: Partial<OutlinedInputWithLabelProps>;
messageBoxProps?: Partial<MessageBoxProps>;
selectProps?: Partial<SelectProps>; selectProps?: Partial<SelectProps>;
} = {}, } = {},
) => ( ) => (
@ -468,7 +484,7 @@ const createOutlinedInputWithSelect = (
selectProps, selectProps,
})} })}
</Box> </Box>
{createErrorMessage(error)} {createInputMessage(messageBoxProps)}
</Box> </Box>
); );
@ -885,10 +901,12 @@ const createVirtualDiskForm = (
const { maxVirtualDiskSizes } = updateLimits({ virtualDisks }); const { maxVirtualDiskSizes } = updateLimits({ virtualDisks });
testInput({ testInput({
inputs: {
[`vd${vdIndex}Size`]: { [`vd${vdIndex}Size`]: {
max: maxVirtualDiskSizes?.[vdIndex], max: maxVirtualDiskSizes?.[vdIndex],
value: cvsValue, value: cvsValue,
}, },
},
}); });
}; };
@ -940,6 +958,7 @@ const createVirtualDiskForm = (
'sizes', 'sizes',
).toString()}, Max: ${get('maxes').toString()}`} ).toString()}, Max: ${get('maxes').toString()}`}
/> />
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
{createOutlinedInputWithSelect( {createOutlinedInputWithSelect(
`ps-virtual-disk-size-${vdIndex}`, `ps-virtual-disk-size-${vdIndex}`,
'Virtual disk size', 'Virtual disk size',
@ -973,6 +992,9 @@ const createVirtualDiskForm = (
}, },
}, },
)} )}
{createInputMessage(get('inputSizeMessages'))}
</Box>
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
{createOutlinedSelect( {createOutlinedSelect(
`ps-storage-group-${vdIndex}`, `ps-storage-group-${vdIndex}`,
'Storage group', 'Storage group',
@ -994,7 +1016,8 @@ const createVirtualDiskForm = (
}, },
}, },
)} )}
{createErrorMessage(get('inputErrors'))} {createInputMessage(get('inputStorageGroupUUIDMessages'))}
</Box>
</Box> </Box>
); );
}; };
@ -1002,46 +1025,51 @@ const createVirtualDiskForm = (
const addVirtualDisk = ({ const addVirtualDisk = ({
existingVirtualDisks: virtualDisks = { existingVirtualDisks: virtualDisks = {
maxes: [], maxes: [],
inputErrors: [],
inputMaxes: [], inputMaxes: [],
inputSizeMessages: [],
inputSizes: [], inputSizes: [],
inputStorageGroupUUIDMessages: [],
inputStorageGroupUUIDs: [], inputStorageGroupUUIDs: [],
inputUnits: [], inputUnits: [],
sizes: [], sizes: [],
}, },
max = BIGINT_ZERO, max = BIGINT_ZERO,
inputError = undefined,
inputMax = '0', inputMax = '0',
inputSize = '', inputSize = '',
inputSizeMessage = undefined,
inputStorageGroupUUID = '', inputStorageGroupUUID = '',
inputStorageGroupUUIDMessage = undefined,
inputUnit = 'B', inputUnit = 'B',
setVirtualDisks, setVirtualDisks,
size = BIGINT_ZERO, size = BIGINT_ZERO,
}: { }: {
existingVirtualDisks?: VirtualDiskStates; existingVirtualDisks?: VirtualDiskStates;
max?: bigint; max?: bigint;
inputError?: string | undefined;
inputMax?: string; inputMax?: string;
inputSize?: string; inputSize?: string;
inputSizeMessage?: InputMessage | undefined;
inputStorageGroupUUID?: string; inputStorageGroupUUID?: string;
inputStorageGroupUUIDMessage?: InputMessage | undefined;
inputUnit?: DataSizeUnit; inputUnit?: DataSizeUnit;
setVirtualDisks?: Dispatch<SetStateAction<VirtualDiskStates>>; setVirtualDisks?: Dispatch<SetStateAction<VirtualDiskStates>>;
size?: bigint; size?: bigint;
} = {}) => { } = {}) => {
const { const {
maxes, maxes,
inputErrors,
inputMaxes, inputMaxes,
inputSizeMessages,
inputSizes, inputSizes,
inputStorageGroupUUIDMessages,
inputStorageGroupUUIDs, inputStorageGroupUUIDs,
inputUnits, inputUnits,
sizes, sizes,
} = virtualDisks; } = virtualDisks;
maxes.push(max); maxes.push(max);
inputErrors.push(inputError);
inputMaxes.push(inputMax); inputMaxes.push(inputMax);
inputSizeMessages.push(inputSizeMessage);
inputSizes.push(inputSize); inputSizes.push(inputSize);
inputStorageGroupUUIDMessages.push(inputStorageGroupUUIDMessage);
inputStorageGroupUUIDs.push(inputStorageGroupUUID); inputStorageGroupUUIDs.push(inputStorageGroupUUID);
inputUnits.push(inputUnit); inputUnits.push(inputUnit);
sizes.push(size); sizes.push(size);
@ -1076,14 +1104,14 @@ const ProvisionServerDialog = ({
const [inputCPUCoresValue, setInputCPUCoresValue] = useState<number>(1); const [inputCPUCoresValue, setInputCPUCoresValue] = useState<number>(1);
const [inputCPUCoresMax, setInputCPUCoresMax] = useState<number>(0); const [inputCPUCoresMax, setInputCPUCoresMax] = useState<number>(0);
const [inputCPUCoresError, setInputCPUCoresError] = useState< const [inputCPUCoresMessage, setInputCPUCoresMessage] = useState<
string | undefined InputMessage | undefined
>(); >();
const [memory, setMemory] = useState<bigint>(BIGINT_ZERO); const [memory, setMemory] = useState<bigint>(BIGINT_ZERO);
const [memoryMax, setMemoryMax] = useState<bigint>(BIGINT_ZERO); const [memoryMax, setMemoryMax] = useState<bigint>(BIGINT_ZERO);
const [inputMemoryError, setInputMemoryError] = useState< const [inputMemoryMessage, setInputMemoryMessage] = useState<
string | undefined InputMessage | undefined
>(); >();
const [inputMemoryMax, setInputMemoryMax] = useState<string>('0'); const [inputMemoryMax, setInputMemoryMax] = useState<string>('0');
const [inputMemoryValue, setInputMemoryValue] = useState<string>(''); const [inputMemoryValue, setInputMemoryValue] = useState<string>('');
@ -1095,10 +1123,17 @@ const ProvisionServerDialog = ({
const [inputInstallISOFileUUID, setInputInstallISOFileUUID] = const [inputInstallISOFileUUID, setInputInstallISOFileUUID] =
useState<string>(''); useState<string>('');
const [inputInstallISOMessage, setInputInstallISOMessage] = useState<
InputMessage | undefined
>();
const [inputDriverISOFileUUID, setInputDriverISOFileUUID] = const [inputDriverISOFileUUID, setInputDriverISOFileUUID] =
useState<string>(''); useState<string>('');
const [inputDriverISOMessage] = useState<InputMessage | undefined>();
const [inputAnvilValue, setInputAnvilValue] = useState<string>(''); const [inputAnvilValue, setInputAnvilValue] = useState<string>('');
const [inputAnvilMessage, setInputAnvilMessage] = useState<
InputMessage | undefined
>();
const [includeAnvilUUIDs, setIncludeAnvilUUIDs] = useState<string[]>([]); const [includeAnvilUUIDs, setIncludeAnvilUUIDs] = useState<string[]>([]);
const [includeFileUUIDs, setIncludeFileUUIDs] = useState<string[]>([]); const [includeFileUUIDs, setIncludeFileUUIDs] = useState<string[]>([]);
@ -1180,11 +1215,13 @@ const ProvisionServerDialog = ({
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
const initLimits = useCallback(updateLimits, []); const initLimits = useCallback(updateLimits, []);
const testInput: TestInputFunction = (inputs: { const testInput: TestInputFunction = ({
[id: string]: Partial<TestArgs>; inputs,
}): boolean => { isContinueOnFailure,
const testNotBlank = ({ value }: TestArgs) => value !== ''; isIgnoreOnCallbacks,
const testMax = ({ max, min }: TestArgs) => max >= min; } = {}): boolean => {
const testNotBlank = ({ value }: Pick<TestArgs, 'value'>) => value !== '';
const testMax = ({ max, min }: Pick<TestArgs, 'max' | 'min'>) => max >= min;
const testRange = ({ max, min, value }: TestArgs) => const testRange = ({ max, min, value }: TestArgs) =>
value >= min && value <= max; value >= min && value <= max;
@ -1193,11 +1230,9 @@ const ProvisionServerDialog = ({
defaults: TestArgs & { defaults: TestArgs & {
onSuccess: () => void; onSuccess: () => void;
}; };
tests: Array<{ onFinishBatch?: () => void;
onFailure?: (args: TestArgs) => void; optionalTests?: Array<InputTest>;
onSuccess?: () => void; tests: Array<InputTest>;
test: (args: TestArgs) => boolean;
}>;
}; };
} = { } = {
cpuCores: { cpuCores: {
@ -1205,22 +1240,26 @@ const ProvisionServerDialog = ({
max: inputCPUCoresMax, max: inputCPUCoresMax,
min: inputCPUCoresMin, min: inputCPUCoresMin,
onSuccess: () => { onSuccess: () => {
setInputCPUCoresError(''); setInputCPUCoresMessage(undefined);
}, },
value: inputCPUCoresValue, value: inputCPUCoresValue,
}, },
tests: [ tests: [
{ {
onFailure: () => { onFailure: () => {
setInputCPUCoresError('Non available.'); setInputCPUCoresMessage({
text: 'Non available.',
type: 'error',
});
}, },
test: testMax, test: testMax,
}, },
{ {
onFailure: ({ max, min }) => { onFailure: ({ max, min }) => {
setInputCPUCoresError( setInputCPUCoresMessage({
`The number of CPU cores is expected to be between ${min} and ${max}.`, text: `The number of CPU cores is expected to be between ${min} and ${max}.`,
); type: 'error',
});
}, },
test: testRange, test: testRange,
}, },
@ -1231,56 +1270,89 @@ const ProvisionServerDialog = ({
max: memoryMax, max: memoryMax,
min: 1, min: 1,
onSuccess: () => { onSuccess: () => {
setInputMemoryError(''); setInputMemoryMessage(undefined);
}, },
value: memory, value: memory,
}, },
tests: [ tests: [
{ {
onFailure: () => { onFailure: () => {
setInputMemoryError('Non available.'); setInputMemoryMessage({ text: 'Non available.', type: 'error' });
}, },
test: testMax, test: testMax,
}, },
{ {
onFailure: ({ max, min }) => { onFailure: ({ max, min }) => {
setInputMemoryError( setInputMemoryMessage({
`Memory is expected to be between ${min} B and ${max} B.`, text: `Memory is expected to be between ${min} B and ${max} B.`,
); type: 'error',
});
}, },
test: testRange, test: testRange,
}, },
], ],
}, },
installISO: {
defaults: {
max: 0,
min: 0,
onSuccess: () => {
setInputInstallISOMessage(undefined);
},
value: inputInstallISOFileUUID,
},
tests: [
{
test: testNotBlank,
},
],
},
anvil: {
defaults: {
max: 0,
min: 0,
onSuccess: () => {
setInputAnvilMessage(undefined);
},
value: inputAnvilValue,
},
tests: [
{
test: testNotBlank,
},
],
},
}; };
virtualDisks.inputErrors.forEach((error, vdIndex) => { virtualDisks.inputSizeMessages.forEach((error, vdIndex) => {
const defaultOnSuccess = () => {
virtualDisks.inputErrors[vdIndex] = undefined;
setVirtualDisks({ ...virtualDisks });
};
tests[`vd${vdIndex}Size`] = { tests[`vd${vdIndex}Size`] = {
defaults: { defaults: {
max: virtualDisks.maxes[vdIndex], max: virtualDisks.maxes[vdIndex],
min: 1, min: 1,
onSuccess: defaultOnSuccess, onSuccess: () => {
virtualDisks.inputSizeMessages[vdIndex] = undefined;
},
value: virtualDisks.sizes[vdIndex], value: virtualDisks.sizes[vdIndex],
}, },
onFinishBatch: () => {
setVirtualDisks({ ...virtualDisks });
},
tests: [ tests: [
{ {
onFailure: () => { onFailure: () => {
virtualDisks.inputErrors[vdIndex] = 'Non available.'; virtualDisks.inputSizeMessages[vdIndex] = {
setVirtualDisks({ ...virtualDisks }); text: 'Non available.',
type: 'error',
};
}, },
test: testMax, test: testMax,
}, },
{ {
onFailure: ({ max, min }) => { onFailure: ({ max, min }) => {
virtualDisks.inputErrors[ virtualDisks.inputSizeMessages[vdIndex] = {
vdIndex text: `Virtual disk ${vdIndex} size is expected to be between ${min} B and ${max} B.`,
] = `Virtual disk ${vdIndex} size is expected to be between ${min} B and ${max} B.`; type: 'error',
setVirtualDisks({ ...virtualDisks }); };
}, },
test: testRange, test: testRange,
}, },
@ -1291,24 +1363,37 @@ const ProvisionServerDialog = ({
defaults: { defaults: {
max: 0, max: 0,
min: 0, min: 0,
onSuccess: defaultOnSuccess, onSuccess: () => {
virtualDisks.inputStorageGroupUUIDMessages[vdIndex] = undefined;
},
value: virtualDisks.inputStorageGroupUUIDs[vdIndex], value: virtualDisks.inputStorageGroupUUIDs[vdIndex],
}, },
tests: [ onFinishBatch: () => {
{
onFailure: () => {
virtualDisks.inputErrors[
vdIndex
] = `Virtual disk ${vdIndex} storage group shouldn't be blank.`;
setVirtualDisks({ ...virtualDisks }); setVirtualDisks({ ...virtualDisks });
}, },
tests: [
{
test: testNotBlank, test: testNotBlank,
}, },
], ],
}; };
}); });
return Object.keys(inputs).every((id: string) => { const testsToRun =
inputs ??
Object.keys(tests).reduce<
Exclude<
Exclude<Parameters<TestInputFunction>[0], undefined>['inputs'],
undefined
>
>((reduceContainer, id: string) => {
reduceContainer[id] = {};
return reduceContainer;
}, {});
let allResult = true;
Object.keys(testsToRun).every((id: string) => {
const { const {
defaults: { defaults: {
max: dMax, max: dMax,
@ -1316,23 +1401,59 @@ const ProvisionServerDialog = ({
onSuccess: dOnSuccess, onSuccess: dOnSuccess,
value: dValue, value: dValue,
}, },
tests: group, onFinishBatch: dOnFinishBatch,
optionalTests,
tests: requiredTests,
} = tests[id]; } = tests[id];
const { max = dMax, min = dMin, value = dValue } = inputs[id]; const { max = dMax, min = dMin, value = dValue } = testsToRun[id];
return group.every(({ onFailure, onSuccess = dOnSuccess, test }) => { let cbFinishBatch;
let setOnResult: (test?: Partial<InputTest>) => {
cbFailure: InputTest['onFailure'];
cbSuccess: InputTest['onSuccess'];
} = () => ({ cbFailure: undefined, cbSuccess: undefined });
if (!isIgnoreOnCallbacks) {
cbFinishBatch = dOnFinishBatch;
setOnResult = ({ onFailure, onSuccess }: Partial<InputTest> = {}) => ({
cbFailure: onFailure,
cbSuccess: onSuccess,
});
}
const runTest: (test: InputTest) => boolean = ({
onFailure,
onSuccess = dOnSuccess,
test,
}) => {
const args = { max, min, value }; const args = { max, min, value };
const result: boolean = test(args); const singleResult: boolean = test(args);
const { cbFailure, cbSuccess } = setOnResult({ onFailure, onSuccess });
if (result) { if (singleResult) {
onSuccess?.call(null); cbSuccess?.call(null);
} else { } else {
onFailure?.call(null, args); allResult = singleResult;
cbFailure?.call(null, args);
} }
return result; return singleResult;
}); };
// Don't need to pass optional tests for input to be valid.
optionalTests?.forEach(runTest);
const requiredTestsResult = requiredTests.every(runTest);
cbFinishBatch?.call(null);
return requiredTestsResult || isContinueOnFailure;
}); });
return allResult;
}; };
const changeMemory = ({ const changeMemory = ({
@ -1346,7 +1467,7 @@ const ProvisionServerDialog = ({
memory: cmValue, memory: cmValue,
}); });
testInput({ memory: { max: maxMemory, value: cmValue } }); testInput({ inputs: { memory: { max: maxMemory, value: cmValue } } });
}; };
const handleInputMemoryValueChange = ({ const handleInputMemoryValueChange = ({
@ -1462,7 +1583,7 @@ const ProvisionServerDialog = ({
> >
<OutlinedInputWithLabel id="ps-server-name" label="Server name" /> <OutlinedInputWithLabel id="ps-server-name" label="Server name" />
{createOutlinedSlider('ps-cpu-cores', 'CPU cores', inputCPUCoresValue, { {createOutlinedSlider('ps-cpu-cores', 'CPU cores', inputCPUCoresValue, {
error: inputCPUCoresError, messageBoxProps: inputCPUCoresMessage,
sliderProps: { sliderProps: {
onChange: (value) => { onChange: (value) => {
const newCPUCoresValue = value as number; const newCPUCoresValue = value as number;
@ -1475,7 +1596,9 @@ const ProvisionServerDialog = ({
}); });
testInput({ testInput({
inputs: {
cpuCores: { max: newCPUCoresMax, value: newCPUCoresValue }, cpuCores: { max: newCPUCoresMax, value: newCPUCoresValue },
},
}); });
} }
}, },
@ -1491,7 +1614,7 @@ const ProvisionServerDialog = ({
'Memory', 'Memory',
DATA_SIZE_UNIT_SELECT_ITEMS, DATA_SIZE_UNIT_SELECT_ITEMS,
{ {
error: inputMemoryError, messageBoxProps: inputMemoryMessage,
inputWithLabelProps: { inputWithLabelProps: {
inputProps: { inputProps: {
endAdornment: createMaxValueButton( endAdornment: createMaxValueButton(
@ -1539,6 +1662,7 @@ const ProvisionServerDialog = ({
{ {
disableItem: (value) => value === inputDriverISOFileUUID, disableItem: (value) => value === inputDriverISOFileUUID,
hideItem: (value) => !includeFileUUIDs.includes(value), hideItem: (value) => !includeFileUUIDs.includes(value),
messageBoxProps: inputInstallISOMessage,
selectProps: { selectProps: {
onChange: ({ target: { value } }) => { onChange: ({ target: { value } }) => {
const newInstallISOFileUUID = value as string; const newInstallISOFileUUID = value as string;
@ -1558,6 +1682,7 @@ const ProvisionServerDialog = ({
{ {
disableItem: (value) => value === inputInstallISOFileUUID, disableItem: (value) => value === inputInstallISOFileUUID,
hideItem: (value) => !includeFileUUIDs.includes(value), hideItem: (value) => !includeFileUUIDs.includes(value),
messageBoxProps: inputDriverISOMessage,
selectProps: { selectProps: {
onChange: ({ target: { value } }) => { onChange: ({ target: { value } }) => {
const newDriverISOFileUUID = value as string; const newDriverISOFileUUID = value as string;
@ -1572,6 +1697,7 @@ const ProvisionServerDialog = ({
)} )}
{createOutlinedSelect('ps-anvil', 'Anvil', anvilSelectItems, { {createOutlinedSelect('ps-anvil', 'Anvil', anvilSelectItems, {
disableItem: (value) => !includeAnvilUUIDs.includes(value), disableItem: (value) => !includeAnvilUUIDs.includes(value),
messageBoxProps: inputAnvilMessage,
selectProps: { selectProps: {
onChange: ({ target: { value } }) => { onChange: ({ target: { value } }) => {
const newAnvilUUID: string = value as string; const newAnvilUUID: string = value as string;
@ -1599,7 +1725,9 @@ const ProvisionServerDialog = ({
width: '100%', width: '100%',
}} }}
> >
<ContainedButton>Provision</ContainedButton> <ContainedButton disabled={!testInput({ isIgnoreOnCallbacks: true })}>
Provision
</ContainedButton>
</Box> </Box>
</Dialog> </Dialog>
); );

Loading…
Cancel
Save