fix(striker-ui): correct filter logic based on 3 max values

main
Tsu-ba-me 3 years ago
parent 19a6777512
commit 393afaee41
  1. 598
      striker-ui/components/ProvisionServerDialog.tsx

@ -1,97 +1,101 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Dialog, DialogProps, FormControl, FormGroup } from '@mui/material';
import { import {
Box, dSize as baseDSize,
Dialog, DataSizeUnit,
DialogProps, FormatDataSizeOptions,
FormControl, FormatDataSizeInputValue,
FormGroup, } from 'format-data-size';
} from '@mui/material';
import { dSize, dSizeStr } from 'format-data-size';
import MenuItem from './MenuItem'; import MenuItem from './MenuItem';
import OutlinedInput from './OutlinedInput'; import OutlinedInput, { OutlinedInputProps } from './OutlinedInput';
import OutlinedInputLabel from './OutlinedInputLabel'; import OutlinedInputLabel from './OutlinedInputLabel';
import { Panel, PanelHeader } from './Panels'; import { Panel, PanelHeader } from './Panels';
import Select from './Select'; 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';
import ContainedButton from './ContainedButton';
type ProvisionServerDialogProps = { type ProvisionServerDialogProps = {
dialogProps: DialogProps; dialogProps: DialogProps;
}; };
const BIGINT_ZERO = BigInt(0); type HostMetadataForProvisionServerHost = {
hostUUID: string;
const createOutlinedInput = (id: string, label: string): JSX.Element => ( hostName: string;
<FormControl> hostCPUCores: number;
<OutlinedInputLabel {...{ htmlFor: id }}>{label}</OutlinedInputLabel> hostMemory: string;
<OutlinedInput {...{ id, label }} /> };
</FormControl>
);
const createOutlinedSelect = (
id: string,
label: string | undefined,
value: string,
selectItems: string[][] | string[],
): JSX.Element => (
<FormControl>
{label && (
<OutlinedInputLabel {...{ htmlFor: id }}>{label}</OutlinedInputLabel>
)}
<Select {...{ id, input: <OutlinedInput {...{ label }} />, value }}>
{selectItems.map((item) => {
let itemValue;
let itemDisplayValue;
if (item instanceof Array) { type ServerMetadataForProvisionServer = {
[itemValue, itemDisplayValue] = item; serverUUID: string;
} else { serverName: string;
itemValue = item; serverCPUCores: number;
itemDisplayValue = item; serverMemory: string;
} };
return ( type StorageGroupMetadataForProvisionServer = {
<MenuItem key={`${id}-${itemValue}`} value={itemValue}> storageGroupUUID: string;
{itemDisplayValue} storageGroupName: string;
</MenuItem> storageGroupSize: string;
); storageGroupFree: string;
})} };
</Select>
</FormControl>
);
const createOutlinedSlider = ( type FileMetadataForProvisionServer = {
id: string, fileUUID: string;
label: string, fileName: string;
sliderProps?: Partial<SliderProps>, };
): JSX.Element => (
<FormControl>
<Slider
// eslint-disable-next-line react/jsx-props-no-spreading
{...{
isAllowTextInput: true,
label,
labelId: `${id}-label`,
value: 1,
...sliderProps,
}}
/>
</FormControl>
);
const ProvisionServerDialog = ({ type AnvilDetailMetadataForProvisionServer = {
dialogProps: { open }, anvilUUID: string;
}: ProvisionServerDialogProps): JSX.Element => { anvilName: string;
const [sliderMaxAvailableCPUCores, setSliderMaxAvailableCPUCores] = anvilTotalCPUCores: number;
useState<number>(0); anvilTotalMemory: string;
const [sliderMaxAvailableMemory, setSliderMaxAvailableMemory] = anvilTotalAllocatedCPUCores: number;
useState<number>(0); anvilTotalAllocatedMemory: string;
anvilTotalAvailableCPUCores: number;
anvilTotalAvailableMemory: string;
hosts: Array<HostMetadataForProvisionServerHost>;
servers: Array<ServerMetadataForProvisionServer>;
storageGroups: Array<StorageGroupMetadataForProvisionServer>;
files: Array<FileMetadataForProvisionServer>;
};
const [memory, setMemory] = useState<bigint>(BIGINT_ZERO); type OrganizedAnvilDetailMetadataForProvisionServer = Omit<
const [memoryUnit, setMemoryUnit] = useState<string>(''); AnvilDetailMetadataForProvisionServer,
| 'anvilTotalMemory'
| 'anvilTotalAllocatedMemory'
| 'anvilTotalAvailableMemory'
| 'hosts'
| 'servers'
| 'storageGroups'
> & {
anvilTotalMemory: bigint;
anvilTotalAllocatedMemory: bigint;
anvilTotalAvailableMemory: bigint;
anvilMaxAvailableStorage: bigint;
hosts: Array<
Omit<HostMetadataForProvisionServerHost, 'hostMemory'> & {
hostMemory: bigint;
}
>;
servers: Array<
Omit<ServerMetadataForProvisionServer, 'serverMemory'> & {
serverMemory: bigint;
}
>;
storageGroups: Array<
Omit<
StorageGroupMetadataForProvisionServer,
'storageGroupSize' | 'storageGroupFree'
> & {
storageGroupSize: bigint;
storageGroupFree: bigint;
}
>;
};
const data = { const MOCK_DATA = {
anvils: [ anvils: [
{ {
anvilUUID: 'ad590bcb-24e1-4592-8cd1-9cd6229b7bf2', anvilUUID: 'ad590bcb-24e1-4592-8cd1-9cd6229b7bf2',
@ -175,7 +179,20 @@ const ProvisionServerDialog = ({
}, },
], ],
servers: [], servers: [],
storageGroups: [], storageGroups: [
{
storageGroupUUID: '271651b0-c064-401b-9391-549bbced2383',
storageGroupName: 'Mock storage group 1',
storageGroupSize: '274651414528',
storageGroupFree: '85882568704',
},
{
storageGroupUUID: '271651b0-c064-401b-9391-549bbced2383',
storageGroupName: 'Mock storage group 2',
storageGroupSize: '205988560896',
storageGroupFree: '171765137408',
},
],
files: [ files: [
{ {
fileUUID: '5d6fc6d9-03f8-40ec-9bff-38e31b3a5bc5', fileUUID: '5d6fc6d9-03f8-40ec-9bff-38e31b3a5bc5',
@ -213,27 +230,189 @@ const ProvisionServerDialog = ({
], ],
}; };
const { maxAvailableCPUCores, maxAvailableMemory } = data.anvils.reduce<{ const BIGINT_ZERO = BigInt(0);
const DATA_SIZE_UNITS: DataSizeUnit[] = [
'B',
'KiB',
'MiB',
'GiB',
'TiB',
'kB',
'MB',
'GB',
'TB',
];
const createOutlinedInput = (
id: string,
label: string,
inputProps?: Partial<OutlinedInputProps>,
): JSX.Element => (
<FormControl>
<OutlinedInputLabel {...{ htmlFor: id }}>{label}</OutlinedInputLabel>
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<OutlinedInput {...{ id, label, ...inputProps }} />
</FormControl>
);
const createOutlinedSelect = (
id: string,
label: string | undefined,
selectItems: [string, string][] | string[],
selectProps?: Partial<SelectProps>,
): JSX.Element => (
<FormControl>
{label && (
<OutlinedInputLabel {...{ htmlFor: id }}>{label}</OutlinedInputLabel>
)}
<Select
// eslint-disable-next-line react/jsx-props-no-spreading
{...{
id,
input: <OutlinedInput {...{ label }} />,
...selectProps,
}}
>
{selectItems.map((item) => {
let itemValue;
let itemDisplayValue;
if (item instanceof Array) {
[itemValue, itemDisplayValue] = item;
} else {
itemValue = item;
itemDisplayValue = item;
}
return (
<MenuItem key={`${id}-${itemValue}`} value={itemValue}>
{itemDisplayValue}
</MenuItem>
);
})}
</Select>
</FormControl>
);
const createOutlinedSlider = (
id: string,
label: string,
value: number,
sliderProps?: Partial<SliderProps>,
): JSX.Element => (
<FormControl>
<Slider
// eslint-disable-next-line react/jsx-props-no-spreading
{...{
isAllowTextInput: true,
label,
labelId: `${id}-label`,
value,
...sliderProps,
}}
/>
</FormControl>
);
const createOutlinedInputWithSelect = (
id: string,
label: string,
selectItems: [string, string][] | string[],
{
inputProps,
selectProps,
}: {
inputProps?: Partial<OutlinedInputProps>;
selectProps?: Partial<SelectProps>;
} = {},
) => (
<FormControl
sx={{
display: 'flex',
flexDirection: 'row',
'& > :first-child': {
flexGrow: 1,
},
}}
>
{createOutlinedInput(id, label, inputProps)}
{createOutlinedSelect(
`${id}-nested-select`,
undefined,
selectItems,
selectProps,
)}
</FormControl>
);
const organizeAnvils = (
data: AnvilDetailMetadataForProvisionServer[],
): OrganizedAnvilDetailMetadataForProvisionServer[] =>
data.map((anvil) => {
const anvilMaxAvailableStorage = anvil.storageGroups.reduce<bigint>(
(reducedStorageGroupFree, { storageGroupFree }) => {
const convertedStorageGroupFree = BigInt(storageGroupFree);
return convertedStorageGroupFree > reducedStorageGroupFree
? convertedStorageGroupFree
: reducedStorageGroupFree;
},
BIGINT_ZERO,
);
return {
...anvil,
anvilTotalMemory: BigInt(anvil.anvilTotalMemory),
anvilTotalAllocatedMemory: BigInt(anvil.anvilTotalAllocatedMemory),
anvilTotalAvailableMemory: BigInt(anvil.anvilTotalAvailableMemory),
anvilMaxAvailableStorage,
hosts: anvil.hosts.map((host) => ({
...host,
hostMemory: BigInt(host.hostMemory),
})),
servers: anvil.servers.map((server) => ({
...server,
serverMemory: BigInt(server.serverMemory),
})),
storageGroups: anvil.storageGroups.map((storageGroup) => ({
...storageGroup,
storageGroupSize: BigInt(storageGroup.storageGroupSize),
storageGroupFree: BigInt(storageGroup.storageGroupFree),
})),
};
});
const getMaxAvailableValues = (
anvils: OrganizedAnvilDetailMetadataForProvisionServer[],
) =>
anvils.reduce<{
maxAvailableCPUCores: number; maxAvailableCPUCores: number;
maxAvailableMemory: bigint; maxAvailableMemory: bigint;
maxAvailableVirtualDiskSize: bigint;
}>( }>(
( (
reducedValues, reducedValues,
{ anvilTotalAvailableCPUCores, anvilTotalAvailableMemory }, {
) => { anvilTotalAvailableCPUCores,
const convertedAnvilTotalAvailableMemory = BigInt(
anvilTotalAvailableMemory, anvilTotalAvailableMemory,
); anvilMaxAvailableStorage,
},
) => {
reducedValues.maxAvailableCPUCores = Math.max( reducedValues.maxAvailableCPUCores = Math.max(
anvilTotalAvailableCPUCores, anvilTotalAvailableCPUCores,
reducedValues.maxAvailableCPUCores, reducedValues.maxAvailableCPUCores,
); );
if (anvilTotalAvailableMemory > reducedValues.maxAvailableMemory) {
reducedValues.maxAvailableMemory = anvilTotalAvailableMemory;
}
if ( if (
convertedAnvilTotalAvailableMemory > reducedValues.maxAvailableMemory anvilMaxAvailableStorage > reducedValues.maxAvailableVirtualDiskSize
) { ) {
reducedValues.maxAvailableMemory = convertedAnvilTotalAvailableMemory; reducedValues.maxAvailableVirtualDiskSize = anvilMaxAvailableStorage;
} }
return reducedValues; return reducedValues;
@ -241,30 +420,116 @@ const ProvisionServerDialog = ({
{ {
maxAvailableCPUCores: 0, maxAvailableCPUCores: 0,
maxAvailableMemory: BIGINT_ZERO, maxAvailableMemory: BIGINT_ZERO,
maxAvailableVirtualDiskSize: BIGINT_ZERO,
}, },
); );
const optimizeOSList = data.osList.map((keyValuePair) => const dSize = (
keyValuePair.split(','), valueToFormat: FormatDataSizeInputValue,
); {
fromUnit,
onFailure,
onSuccess,
precision,
toUnit,
}: FormatDataSizeOptions & {
onFailure?: (error?: unknown, value?: string, unit?: DataSizeUnit) => void;
onSuccess?: {
bigint?: (value: bigint, unit: DataSizeUnit) => void;
number?: (value: number, unit: DataSizeUnit) => void;
string?: (value: string, unit: DataSizeUnit) => void;
};
} = {},
) => {
const formatted = baseDSize(valueToFormat, {
fromUnit,
precision,
toUnit,
});
useEffect(() => { if (formatted) {
setSliderMaxAvailableCPUCores(maxAvailableCPUCores); const { value, unit } = formatted;
const formattedMaxAvailableMemory = dSize(maxAvailableMemory, { try {
precision: 2, onSuccess?.bigint?.call(null, BigInt(value), unit);
}); onSuccess?.number?.call(null, parseFloat(value), unit);
onSuccess?.string?.call(null, value, unit);
} catch (convertValueToTypeError) {
onFailure?.call(null, convertValueToTypeError, value, unit);
}
} else {
onFailure?.call(null);
}
};
console.dir(formattedMaxAvailableMemory, { depth: null }); const filterAnvils = (
organizedAnvils: OrganizedAnvilDetailMetadataForProvisionServer[],
cpuCores: number,
memory: bigint,
virtualDiskSize: bigint,
) =>
organizedAnvils
.filter((anvil) => {
const isEnoughCPUCores = cpuCores <= anvil.anvilTotalAvailableCPUCores;
const isEnoughMemory = memory <= anvil.anvilTotalAvailableMemory;
const isEnoughStorage = virtualDiskSize <= anvil.anvilMaxAvailableStorage;
if (formattedMaxAvailableMemory) { return isEnoughCPUCores && isEnoughMemory && isEnoughStorage;
setSliderMaxAvailableMemory( })
parseFloat(formattedMaxAvailableMemory.value), .map(({ anvilUUID, anvilName }) => [anvilUUID, anvilName]);
);
setMemoryUnit(formattedMaxAvailableMemory.unit); /**
} * 1. Fetch anvils detail for provision server from the back-end.
}, [maxAvailableCPUCores, maxAvailableMemory]); * 2. Get the max values for CPU cores, memory, and virtual disk size.
*/
const ProvisionServerDialog = ({
dialogProps: { open },
}: ProvisionServerDialogProps): JSX.Element => {
const [sliderCPUCoresMax, setSliderCPUCoresMax] = useState<number>(0);
// const [sliderMemoryMax, setSliderMemoryMax] = useState<number>(0);
const [cpuCoresValue, setCPUCoresValue] = useState<number>(1);
const [memoryValue, setMemoryValue] = useState<bigint>(BIGINT_ZERO);
const [inputMemoryValue, setInputMemoryValue] = useState<string>('');
const [inputMemoryUnit, setInputMemoryUnit] = useState<DataSizeUnit>('B');
const [virtualDiskSizeValue, setVirtualDiskSizeValue] =
useState<bigint>(BIGINT_ZERO);
const [inputVirtualDiskSizeValue, setInputVirtualDiskSizeValue] =
useState<string>('');
const [inputVirtualDiskSizeUnit, setInputVirtualDiskSizeUnit] =
useState<DataSizeUnit>('B');
// const [storageGroupUUID, setStorageGroupUUID] = useState<string>('');
const data = MOCK_DATA;
const organizedAnvils = organizeAnvils(data.anvils);
const {
maxAvailableCPUCores,
maxAvailableMemory,
maxAvailableVirtualDiskSize,
} = getMaxAvailableValues(organizedAnvils);
// const optimizeOSList = data.osList.map((keyValuePair) =>
// keyValuePair.split(','),
// );
useEffect(() => {
setSliderCPUCoresMax(maxAvailableCPUCores);
dSize(maxAvailableMemory, {
onSuccess: {
number: (value, unit) => {
setSliderMemoryMax(value);
setInputMemoryUnit(unit);
},
},
});
}, [maxAvailableCPUCores, maxAvailableMemory, maxAvailableVirtualDiskSize]);
return ( return (
<Dialog <Dialog
@ -281,42 +546,103 @@ const ProvisionServerDialog = ({
</PanelHeader> </PanelHeader>
<FormGroup> <FormGroup>
{createOutlinedInput('ps-server-name', 'Server name')} {createOutlinedInput('ps-server-name', 'Server name')}
{createOutlinedSlider('ps-cpu-cores', 'CPU cores', { {createOutlinedSlider('ps-cpu-cores', 'CPU cores', cpuCoresValue, {
sliderProps: { max: sliderMaxAvailableCPUCores, min: 1 }, sliderProps: {
onChange: (event, value) => {
setCPUCoresValue(value as number);
},
max: sliderCPUCoresMax,
min: 1,
},
})} })}
<BodyText text={`Memory: ${memory.toString()}`} /> <BodyText text={`Memory: ${memoryValue.toString()}`} />
<Box {createOutlinedInputWithSelect('ps-memory', 'Memory', DATA_SIZE_UNITS, {
sx={{ inputProps: {
display: 'flex', type: 'number',
flexDirection: 'row', onChange: ({ target: { value } }) => {
setInputMemoryValue(value);
'& > :first-child': { dSize(value, {
flexGrow: 1, fromUnit: inputMemoryUnit,
onSuccess: {
bigint: (newValue) => {
setMemoryValue(newValue);
}, },
}} },
> precision: 0,
{createOutlinedSlider('ps-memory', 'Memory', { toUnit: 'B',
sliderProps: { });
max: sliderMaxAvailableMemory, },
min: 1, value: inputMemoryValue,
onChange: (event, newValue) => { },
console.log(`newValue=${newValue}`); selectProps: {
onChange: ({ target: { value } }) => {
const selectedUnit = value as DataSizeUnit;
setInputMemoryUnit(selectedUnit);
dSize(inputMemoryValue, {
fromUnit: selectedUnit,
onSuccess: {
bigint: (newValue) => {
setMemoryValue(newValue);
}, },
}, },
precision: 0,
toUnit: 'B',
});
},
value: inputMemoryUnit,
},
})} })}
{createOutlinedSelect('ps-memory-unit', undefined, memoryUnit, [ <BodyText
'B', text={`Virtual disk size: ${virtualDiskSizeValue.toString()}`}
'KiB', />
'kB', {createOutlinedInputWithSelect(
'MiB', 'ps-virtual-disk-size',
'MB', 'Virtual disk size',
'GiB', DATA_SIZE_UNITS,
'GB', {
'TiB', inputProps: {
'TB', type: 'number',
])} onChange: ({ target: { value } }) => {
</Box> setInputVirtualDiskSizeValue(value);
{/* {createOutlinedSlider('ps-memory', 'Memory')}
dSize(value, {
fromUnit: inputVirtualDiskSizeUnit,
onSuccess: {
bigint: (newValue) => {
setVirtualDiskSizeValue(newValue);
},
},
precision: 0,
toUnit: 'B',
});
},
value: inputVirtualDiskSizeValue,
},
selectProps: {
onChange: ({ target: { value } }) => {
const selectedUnit = value as DataSizeUnit;
setInputVirtualDiskSizeUnit(selectedUnit);
dSize(inputVirtualDiskSizeValue, {
fromUnit: selectedUnit,
onSuccess: {
bigint: (newValue) => {
setVirtualDiskSizeValue(newValue);
},
},
precision: 0,
toUnit: 'B',
});
},
value: inputVirtualDiskSizeUnit,
},
},
)}
{/*
{createOutlinedSelect('ps-storage-group', 'Storage group', [ {createOutlinedSelect('ps-storage-group', 'Storage group', [
['b594f417-852a-4bd4-a215-fae32d226b0b', 'Storage group 1'], ['b594f417-852a-4bd4-a215-fae32d226b0b', 'Storage group 1'],
])} ])}
@ -328,7 +654,19 @@ const ProvisionServerDialog = ({
'Optimize for OS', 'Optimize for OS',
optimizeOSList, optimizeOSList,
)} */} )} */}
{filterAnvils(
organizedAnvils,
cpuCoresValue,
memoryValue,
virtualDiskSizeValue,
).map(([anvilUUID, anvilName]) => (
<BodyText
key={`ps-filtered-anvils-${anvilUUID}`}
text={`${anvilUUID},${anvilName}`}
/>
))}
</FormGroup> </FormGroup>
<ContainedButton>Provision</ContainedButton>
</Dialog> </Dialog>
); );
}; };

Loading…
Cancel
Save