import { Dispatch, SetStateAction, useCallback, useEffect, useState, } from 'react'; import { Box, Checkbox, Dialog, DialogProps, FormControl } from '@mui/material'; import { dSize as baseDSize, DataSizeUnit, FormatDataSizeOptions, FormatDataSizeInputValue, } from 'format-data-size'; import Autocomplete from './Autocomplete'; import ContainedButton from './ContainedButton'; import MenuItem from './MenuItem'; import OutlinedInput from './OutlinedInput'; import OutlinedInputLabel from './OutlinedInputLabel'; import OutlinedInputWithLabel, { OutlinedInputWithLabelProps, } from './OutlinedInputWithLabel'; import { Panel, PanelHeader } from './Panels'; import Select, { SelectProps } from './Select'; import Slider, { SliderProps } from './Slider'; import { BodyText, HeaderText } from './Text'; type SelectItem = { displayValue?: SelectItemValueType; value: SelectItemValueType; }; type ProvisionServerDialogProps = { dialogProps: DialogProps; }; type HostMetadataForProvisionServerHost = { hostUUID: string; hostName: string; hostCPUCores: number; hostMemory: string; }; type ServerMetadataForProvisionServer = { serverUUID: string; serverName: string; serverCPUCores: number; serverMemory: string; }; type StorageGroupMetadataForProvisionServer = { storageGroupUUID: string; storageGroupName: string; storageGroupSize: string; storageGroupFree: string; }; type FileMetadataForProvisionServer = { fileUUID: string; fileName: string; }; type OrganizedStorageGroupMetadataForProvisionServer = Omit< StorageGroupMetadataForProvisionServer, 'storageGroupSize' | 'storageGroupFree' > & { anvilUUID: string; anvilName: string; storageGroupSize: bigint; storageGroupFree: bigint; }; type AnvilDetailMetadataForProvisionServer = { anvilUUID: string; anvilName: string; anvilTotalCPUCores: number; anvilTotalMemory: string; anvilTotalAllocatedCPUCores: number; anvilTotalAllocatedMemory: string; anvilTotalAvailableCPUCores: number; anvilTotalAvailableMemory: string; hosts: Array; servers: Array; storageGroups: Array; files: Array; }; type StorageGroupUUIDMapToFree = { [uuid: string]: bigint }; type OrganizedAnvilDetailMetadataForProvisionServer = Omit< AnvilDetailMetadataForProvisionServer, | 'anvilTotalMemory' | 'anvilTotalAllocatedMemory' | 'anvilTotalAvailableMemory' | 'hosts' | 'servers' | 'storageGroups' > & { anvilTotalMemory: bigint; anvilTotalAllocatedMemory: bigint; anvilTotalAvailableMemory: bigint; hosts: Array< Omit & { hostMemory: bigint; } >; servers: Array< Omit & { serverMemory: bigint; } >; storageGroupUUIDs: string[]; storageGroups: Array; fileUUIDs: string[]; }; type FilterAnvilsFunction = ( organizedAnvils: OrganizedAnvilDetailMetadataForProvisionServer[], storageGroupUUIDMapToFree: StorageGroupUUIDMapToFree, cpuCores: number, memory: bigint, vdSizes: bigint[], storageGroupUUIDs: string[], fileUUIDs: string[], options?: { includeAnvilUUIDs?: string[]; includeStorageGroupUUIDs?: string[]; }, ) => { anvils: OrganizedAnvilDetailMetadataForProvisionServer[]; anvilUUIDs: string[]; fileUUIDs: string[]; maxCPUCores: number; maxMemory: bigint; maxVirtualDiskSizes: bigint[]; storageGroupUUIDs: string[]; }; type FilterAnvilsParameters = Parameters; type VirtualDiskStates = { maxes: bigint[]; inputMaxes: string[]; inputSizes: string[]; inputStorageGroupUUIDs: string[]; inputUnits: DataSizeUnit[]; sizes: bigint[]; }; type UpdateLimitsFunction = ( filterArgs: FilterAnvilsParameters, localInputMemoryUnit: DataSizeUnit, localVirtualDisks: VirtualDiskStates, ) => void; const MOCK_DATA = { anvils: [ { anvilUUID: 'ad590bcb-24e1-4592-8cd1-9cd6229b7bf2', anvilName: 'yan-anvil-03', anvilTotalCPUCores: 4, anvilTotalMemory: '17179869184', anvilTotalAllocatedCPUCores: 1, anvilTotalAllocatedMemory: '1073741824', anvilTotalAvailableCPUCores: 3, anvilTotalAvailableMemory: '7516192768', hosts: [ { hostUUID: 'c9b25b77-f9a1-41fa-9f04-677c58d0d9e1', hostName: 'yan-a03n01.alteeve.com', hostCPUCores: 4, hostMemory: '17179869184', }, { hostUUID: 'c0a1c2c8-3418-4dbc-80c6-c4c0cea6a511', hostName: 'yan-a03n02.alteeve.com', hostCPUCores: 4, hostMemory: '17179869184', }, { hostUUID: '8815a6dd-239d-4f8d-b248-ac8a5cac4a30', hostName: 'yan-a03dr01.alteeve.com', hostCPUCores: 4, hostMemory: '17179869184', }, ], servers: [ { serverUUID: 'd128c15a-0e21-4ba3-9084-1972dad31bd4', serverName: 'alpine-x86_64-01', serverCPUCores: 1, serverMemory: '1073741824', }, ], storageGroups: [ { storageGroupUUID: 'b594f417-852a-4bd4-a215-fae32d226b0b', storageGroupName: 'Storage group 1', storageGroupSize: '137325707264', storageGroupFree: '42941284352', }, ], files: [ { fileUUID: '5d6fc6d9-03f8-40ec-9bff-38e31b3a5bc5', fileName: 'alpine-virt-3.15.0-x86_64.iso', }, ], }, { anvilUUID: '85e0fd96-ea38-403d-992f-441d20cad679', anvilName: 'mock-anvil-01', anvilTotalCPUCores: 8, anvilTotalMemory: '34359738368', anvilTotalAllocatedCPUCores: 0, anvilTotalAllocatedMemory: '2147483648', anvilTotalAvailableCPUCores: 8, anvilTotalAvailableMemory: '32212254720', hosts: [ { hostUUID: '2198ae4a-db3a-4685-8d98-db56af75d53d', hostName: 'mock-a03n01.alteeve.com', hostCPUCores: 8, hostMemory: '34359738368', }, { hostUUID: '928f12b4-1be0-4872-adbc-f78579323d50', hostName: 'mock-a03n02.alteeve.com', hostCPUCores: 8, hostMemory: '34359738368', }, { hostUUID: 'c4837341-fd09-4b36-b1f0-e16115b704b4', hostName: 'mock-a03dr01.alteeve.com', hostCPUCores: 8, hostMemory: '34359738368', }, ], servers: [], storageGroups: [ { storageGroupUUID: '271651b0-c064-401b-9391-549bbced2383', storageGroupName: 'Mock storage group 1', storageGroupSize: '274651414528', storageGroupFree: '85882568704', }, { storageGroupUUID: '1d57d618-9c6a-4fda-bcc3-d9014ea55161', storageGroupName: 'Mock storage group 2', storageGroupSize: '205988560896', storageGroupFree: '171765137408', }, ], files: [ { fileUUID: '5d6fc6d9-03f8-40ec-9bff-38e31b3a5bc5', fileName: 'alpine-virt-3.15.0-x86_64.iso', }, ], }, { anvilUUID: '68470d36-e46b-44a5-b2cd-d57b2e7b5ddb', anvilName: 'mock-anvil-02', anvilTotalCPUCores: 16, anvilTotalMemory: '1234567890', anvilTotalAllocatedCPUCores: 7, anvilTotalAllocatedMemory: '12345', anvilTotalAvailableCPUCores: 9, anvilTotalAvailableMemory: '1234555545', hosts: [ { hostUUID: 'ee1f4852-b3bc-44ca-93b7-8000c3063292', hostName: 'mock-a03n01.alteeve.com', hostCPUCores: 16, hostMemory: '1234567890', }, { hostUUID: '26f9d3c4-0f91-4266-9f6f-1309e521c693', hostName: 'mock-a03n02.alteeve.com', hostCPUCores: 16, hostMemory: '1234567890', }, { hostUUID: 'eb1b1bd6-2caa-4907-ac68-7dba465b7a67', hostName: 'mock-a03dr01.alteeve.com', hostCPUCores: 16, hostMemory: '1234567890', }, ], servers: [], storageGroups: [], files: [], }, ], osList: [ 'os_list_almalinux8,AlmaLinux 8', 'os_list_alpinelinux3.14,Alpine Linux 3.14', 'os_list_alt.p10,ALT p10 StarterKits', 'os_list_alt9.1,ALT 9.1', 'os_list_alt9.2,ALT 9.2', 'os_list_centos-stream9,CentOS Stream 9', 'os_list_cirros0.5.0,CirrOS 0.5.0', 'os_list_cirros0.5.1,CirrOS 0.5.1', 'os_list_cirros0.5.2,CirrOS 0.5.2', 'os_list_debian11,Debian 11', 'os_list_fedora34,Fedora 34', 'os_list_freebsd13.0,FreeBSD 13.0', 'os_list_haikur1beta2,Haiku R1/Beta2', 'os_list_haikur1beta3,Haiku R1/Beta3', 'os_list_mageia8,Mageia 8', 'os_list_nixos-21.05,NixOS 21.05', 'os_list_openbsd6.8,OpenBSD 6.8', 'os_list_openbsd6.9,OpenBSD 6.9', 'os_list_opensuse15.3,openSUSE Leap 15.3', 'os_list_rhel8.5,Red Hat Enterprise Linux 8.5', 'os_list_silverblue34,Fedora Silverblue 34', 'os_list_sle15sp3,SUSE Linux Enterprise 15 SP3', 'os_list_slem5.0,SUSE Linux Enterprise Micro', 'os_list_ubuntu21.04,Ubuntu 21.04', 'os_list_win2k22,Microsoft Windows Server 2022', ], }; const BIGINT_ZERO = BigInt(0); const DATA_SIZE_UNITS: SelectItem[] = [ { value: 'B' }, { value: 'KiB' }, { value: 'MiB' }, { value: 'GiB' }, { value: 'TiB' }, { value: 'kB' }, { value: 'MB' }, { value: 'GB' }, { value: 'TB' }, ]; const createOutlinedSelect = ( id: string, label: string | undefined, selectItems: SelectItem[], { checkItem, disableItem, hideItem, selectProps, isCheckableItems = selectProps?.multiple, }: { checkItem?: (value: string) => boolean; disableItem?: (value: string) => boolean; hideItem?: (value: string) => boolean; isCheckableItems?: boolean; selectProps?: Partial; } = {}, ): JSX.Element => ( {label && ( {label} )} ); const createOutlinedSlider = ( id: string, label: string, value: number, sliderProps?: Partial, ): JSX.Element => ( ); const createOutlinedInputWithSelect = ( id: string, label: string, selectItems: SelectItem[], { inputWithLabelProps, selectProps, }: { inputWithLabelProps?: Partial; selectProps?: Partial; } = {}, ) => ( :first-child': { flexGrow: 1, }, }} > {createOutlinedSelect(`${id}-nested-select`, undefined, selectItems, { selectProps, })} ); const organizeAnvils = (data: AnvilDetailMetadataForProvisionServer[]) => { const anvilFiles: Record = {}; const result = data.reduce<{ anvils: OrganizedAnvilDetailMetadataForProvisionServer[]; anvilSelectItems: SelectItem[]; files: FileMetadataForProvisionServer[]; fileSelectItems: SelectItem[]; storageGroups: OrganizedStorageGroupMetadataForProvisionServer[]; storageGroupSelectItems: SelectItem[]; storageGroupUUIDMapToFree: StorageGroupUUIDMapToFree; }>( (reduceContainer, anvil) => { const { anvilUUID, anvilName, anvilTotalMemory, anvilTotalAllocatedMemory, anvilTotalAvailableMemory, hosts, servers, storageGroups, files, } = anvil; const { anvilStorageGroups, anvilStorageGroupUUIDs } = storageGroups.reduce<{ anvilStorageGroups: OrganizedStorageGroupMetadataForProvisionServer[]; anvilStorageGroupUUIDs: string[]; }>( (reducedStorageGroups, storageGroup) => { const anvilStorageGroup = { ...storageGroup, anvilUUID, anvilName, storageGroupSize: BigInt(storageGroup.storageGroupSize), storageGroupFree: BigInt(storageGroup.storageGroupFree), }; reducedStorageGroups.anvilStorageGroupUUIDs.push( storageGroup.storageGroupUUID, ); reducedStorageGroups.anvilStorageGroups.push(anvilStorageGroup); reduceContainer.storageGroups.push(anvilStorageGroup); reduceContainer.storageGroupSelectItems.push({ displayValue: `${anvilName} -- ${storageGroup.storageGroupName}`, value: storageGroup.storageGroupUUID, }); reduceContainer.storageGroupUUIDMapToFree[ storageGroup.storageGroupUUID ] = anvilStorageGroup.storageGroupFree; return reducedStorageGroups; }, { anvilStorageGroups: [], anvilStorageGroupUUIDs: [], }, ); const fileUUIDs: string[] = []; files.forEach((file) => { const { fileUUID } = file; fileUUIDs.push(fileUUID); anvilFiles[fileUUID] = file; }); reduceContainer.anvils.push({ ...anvil, anvilTotalMemory: BigInt(anvilTotalMemory), anvilTotalAllocatedMemory: BigInt(anvilTotalAllocatedMemory), anvilTotalAvailableMemory: BigInt(anvilTotalAvailableMemory), hosts: hosts.map((host) => ({ ...host, hostMemory: BigInt(host.hostMemory), })), servers: servers.map((server) => ({ ...server, serverMemory: BigInt(server.serverMemory), })), storageGroupUUIDs: anvilStorageGroupUUIDs, storageGroups: anvilStorageGroups, fileUUIDs, }); reduceContainer.anvilSelectItems.push({ displayValue: anvilName, value: anvilUUID, }); return reduceContainer; }, { anvils: [], anvilSelectItems: [], files: [], fileSelectItems: [], storageGroups: [], storageGroupSelectItems: [], storageGroupUUIDMapToFree: {}, }, ); Object.values(anvilFiles).forEach((distinctFile) => { result.files.push(distinctFile); result.fileSelectItems.push({ displayValue: distinctFile.fileName, value: distinctFile.fileUUID, }); }); return result; }; const dSize = ( 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, }); if (formatted) { const { value, unit } = formatted; try { 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); } }; const dSizeToBytes = ( value: FormatDataSizeInputValue, fromUnit: DataSizeUnit, onSuccess: (newValue: bigint, unit: DataSizeUnit) => void, onFailure?: ( error?: unknown, unchangedValue?: string, unit?: DataSizeUnit, ) => void, ) => { dSize(value, { fromUnit, onFailure, onSuccess: { bigint: onSuccess, }, precision: 0, toUnit: 'B', }); }; const filterAnvils: FilterAnvilsFunction = ( organizedAnvils: OrganizedAnvilDetailMetadataForProvisionServer[], storageGroupUUIDMapToFree: StorageGroupUUIDMapToFree, cpuCores: number, memory: bigint, vdSizes: bigint[], storageGroupUUIDs: string[], fileUUIDs: string[], { includeAnvilUUIDs = [], includeStorageGroupUUIDs = [], }: { includeAnvilUUIDs?: string[]; includeStorageGroupUUIDs?: string[]; } = {}, ) => { let testIncludeAnvil: (uuid: string) => boolean = () => true; let testIncludeStorageGroup: (uuid: string) => boolean = () => true; if (includeAnvilUUIDs.length > 0) { testIncludeAnvil = (uuid: string) => includeAnvilUUIDs.includes(uuid); } if (includeStorageGroupUUIDs.length > 0) { testIncludeStorageGroup = (uuid: string) => includeStorageGroupUUIDs.includes(uuid); } const storageGroupTotals = storageGroupUUIDs.reduce<{ all: bigint; [uuid: string]: bigint; }>( (totals, uuid, index) => { const vdSize: bigint = vdSizes[index] ?? BIGINT_ZERO; totals.all += vdSize; if (uuid === '') { return totals; } if (totals[uuid] === undefined) { totals[uuid] = BIGINT_ZERO; } totals[uuid] += vdSize; return totals; }, { all: BIGINT_ZERO }, ); const result = organizedAnvils.reduce<{ anvils: OrganizedAnvilDetailMetadataForProvisionServer[]; anvilUUIDs: string[]; fileUUIDs: string[]; maxCPUCores: number; maxMemory: bigint; maxVirtualDiskSizes: bigint[]; storageGroupUUIDs: string[]; }>( (reduceContainer, organizedAnvil) => { const { anvilUUID } = organizedAnvil; if (testIncludeAnvil(anvilUUID)) { const { anvilTotalCPUCores, anvilTotalAvailableMemory, files, fileUUIDs: anvilFileUUIDs, storageGroups, } = organizedAnvil; const anvilStorageGroupUUIDs: string[] = []; let anvilStorageGroupFreeMax: bigint = BIGINT_ZERO; let anvilStorageGroupFreeTotal: bigint = BIGINT_ZERO; storageGroups.forEach(({ storageGroupUUID, storageGroupFree }) => { if (testIncludeStorageGroup(storageGroupUUID)) { anvilStorageGroupUUIDs.push(storageGroupUUID); anvilStorageGroupFreeTotal += storageGroupFree; if (storageGroupFree > anvilStorageGroupFreeMax) { anvilStorageGroupFreeMax = storageGroupFree; } } }); const usableTests: (() => boolean)[] = [ // Does this anvil node pair have at least one storage group? () => storageGroups.length > 0, // Does this anvil node pair have enough CPU cores? () => cpuCores <= anvilTotalCPUCores, // Does this anvil node pair have enough memory? () => memory <= anvilTotalAvailableMemory, // For every virtual disk: // 1. Does this anvil node pair have the selected storage group which // will contain the VD? // 2. Does the selected storage group OR any storage group on this // anvil node pair have enough free space? () => storageGroupUUIDs.every((uuid, index) => { const vdSize = vdSizes[index] ?? BIGINT_ZERO; let hasStorageGroup = true; let hasEnoughStorage = vdSize <= anvilStorageGroupFreeMax; if (uuid !== '') { hasStorageGroup = anvilStorageGroupUUIDs.includes(uuid); hasEnoughStorage = vdSize <= storageGroupUUIDMapToFree[uuid]; } return hasStorageGroup && hasEnoughStorage; }), // Do storage groups on this anvil node pair have enough free space // to contain multiple VDs? () => Object.entries(storageGroupTotals).every(([uuid, total]) => uuid === 'all' ? total <= anvilStorageGroupFreeTotal : total <= storageGroupUUIDMapToFree[uuid], ), // Does this anvil node pair have access to selected files? () => fileUUIDs.every( (fileUUID) => fileUUID === '' || anvilFileUUIDs.includes(fileUUID), ), ]; // If an anvil doesn't pass all tests, then it and its parts shouldn't be used. if (usableTests.every((test) => test())) { reduceContainer.anvils.push(organizedAnvil); reduceContainer.anvilUUIDs.push(anvilUUID); reduceContainer.maxCPUCores = Math.max( anvilTotalCPUCores, reduceContainer.maxCPUCores, ); if (anvilTotalAvailableMemory > reduceContainer.maxMemory) { reduceContainer.maxMemory = anvilTotalAvailableMemory; } files.forEach(({ fileUUID }) => { if (!reduceContainer.fileUUIDs.includes(fileUUID)) { reduceContainer.fileUUIDs.push(fileUUID); } }); reduceContainer.storageGroupUUIDs.push(...anvilStorageGroupUUIDs); reduceContainer.maxVirtualDiskSizes.fill(anvilStorageGroupFreeMax); } } return reduceContainer; }, { anvils: [], anvilUUIDs: [], fileUUIDs: [], maxCPUCores: 0, maxMemory: BIGINT_ZERO, maxVirtualDiskSizes: storageGroupUUIDs.map(() => BIGINT_ZERO), storageGroupUUIDs: [], }, ); storageGroupUUIDs.forEach((uuid: string, uuidIndex: number) => { if (uuid !== '') { result.maxVirtualDiskSizes[uuidIndex] = storageGroupUUIDMapToFree[uuid]; } }); return result; }; const convertSelectValueToArray = (value: unknown) => typeof value === 'string' ? value.split(',') : (value as string[]); const createVirtualDiskForm = ( virtualDisks: VirtualDiskStates, vdIndex: number, setVirtualDisks: Dispatch>, storageGroupSelectItems: SelectItem[], includeStorageGroupUUIDs: string[], updateLimits: UpdateLimitsFunction, filterArgs: FilterAnvilsParameters, inputMemoryUnit: DataSizeUnit, ) => { const get = ( key: Key, localIndex: number = vdIndex, ) => virtualDisks[key][localIndex] as VirtualDiskStates[Key][number]; const set = ( key: Key, value: VirtualDiskStates[Key][number], localIndex: number = vdIndex, ) => { virtualDisks[key][localIndex] = value; setVirtualDisks({ ...virtualDisks }); }; return ( :not(:first-child)': { marginTop: '1em', }, }} > {createOutlinedInputWithSelect( `ps-virtual-disk-size-${vdIndex}`, 'Virtual disk size', DATA_SIZE_UNITS, { inputWithLabelProps: { inputProps: { onChange: ({ target: { value } }) => { set('inputSizes', value); dSizeToBytes( value, get('inputUnits'), (convertedVDSizeValue) => { set('sizes', convertedVDSizeValue); filterArgs[4] = virtualDisks.sizes; updateLimits(filterArgs, inputMemoryUnit, virtualDisks); }, () => updateLimits(filterArgs, inputMemoryUnit, virtualDisks), ); }, type: 'number', value: get('inputSizes'), }, }, selectProps: { onChange: ({ target: { value } }) => { const selectedUnit = value as DataSizeUnit; set('inputUnits', selectedUnit); dSizeToBytes( get('inputSizes'), selectedUnit, (convertedVDSizeValue) => { set('sizes', convertedVDSizeValue); filterArgs[4] = virtualDisks.sizes; updateLimits(filterArgs, inputMemoryUnit, virtualDisks); }, () => updateLimits(filterArgs, inputMemoryUnit, virtualDisks), ); }, value: get('inputUnits'), }, }, )} {createOutlinedSelect( `ps-storage-group-${vdIndex}`, 'Storage group', storageGroupSelectItems, { disableItem: (value) => !( includeStorageGroupUUIDs.includes(value) && get('sizes') <= filterArgs[1][value] ), selectProps: { onChange: ({ target: { value } }) => { const selectedStorageGroupUUID = value as string; set('inputStorageGroupUUIDs', selectedStorageGroupUUID); filterArgs[5] = virtualDisks.inputStorageGroupUUIDs; updateLimits(filterArgs, inputMemoryUnit, virtualDisks); }, value: get('inputStorageGroupUUIDs'), onClearIndicatorClick: () => { set('inputStorageGroupUUIDs', ''); filterArgs[5] = virtualDisks.inputStorageGroupUUIDs; updateLimits(filterArgs, inputMemoryUnit, virtualDisks); }, }, }, )} ); }; const addVirtualDisk = ({ existingVirtualDisks: virtualDisks = { maxes: [], inputMaxes: [], inputSizes: [], inputStorageGroupUUIDs: [], inputUnits: [], sizes: [], }, max = BIGINT_ZERO, inputMax = '0', inputSize = '', inputStorageGroupUUID = '', inputUnit = 'B', setVirtualDisks, size = BIGINT_ZERO, }: { existingVirtualDisks?: VirtualDiskStates; max?: bigint; inputMax?: string; inputSize?: string; inputStorageGroupUUID?: string; inputUnit?: DataSizeUnit; setVirtualDisks?: Dispatch>; size?: bigint; } = {}) => { const { maxes, inputMaxes, inputSizes, inputStorageGroupUUIDs, inputUnits, sizes, } = virtualDisks; maxes.push(max); inputMaxes.push(inputMax); inputSizes.push(inputSize); inputStorageGroupUUIDs.push(inputStorageGroupUUID); inputUnits.push(inputUnit); sizes.push(size); setVirtualDisks?.call(null, { ...virtualDisks }); return virtualDisks; }; const ProvisionServerDialog = ({ dialogProps: { open }, }: ProvisionServerDialogProps): JSX.Element => { const [allAnvils, setAllAnvils] = useState< OrganizedAnvilDetailMetadataForProvisionServer[] >([]); const [storageGroupUUIDMapToFree, setStorageGroupUUIDMapToFree] = useState({}); const [anvilSelectItems, setAnvilSelectItems] = useState([]); const [fileSelectItems, setFileSelectItems] = useState([]); const [osAutocompleteOptions, setOSAutocompleteOptions] = useState< { label: string; key: string }[] >([]); const [storageGroupSelectItems, setStorageGroupSelectItems] = useState< SelectItem[] >([]); const [cpuCoresValue, setCPUCoresValue] = useState(1); const [inputCPUCoresMax, setInputCPUCoresMax] = useState(0); const [memoryValue, setMemoryValue] = useState(BIGINT_ZERO); const [memoryValueMax, setMemoryValueMax] = useState(BIGINT_ZERO); const [inputMemoryMax, setInputMemoryMax] = useState('0'); const [inputMemoryValue, setInputMemoryValue] = useState(''); const [inputMemoryUnit, setInputMemoryUnit] = useState('B'); const [virtualDisks, setVirtualDisks] = useState( addVirtualDisk(), ); const [installISOFileUUID, setInstallISOFileUUID] = useState(''); const [driverISOFileUUID, setDriverISOFileUUID] = useState(''); const [anvilValue, setAnvilValue] = useState([]); const [includeAnvilUUIDs, setIncludeAnvilUUIDs] = useState([]); const [includeFileUUIDs, setIncludeFileUUIDs] = useState([]); const [includeStorageGroupUUIDs, setIncludeStorageGroupUUIDs] = useState< string[] >([]); const updateLimits: UpdateLimitsFunction = ( filterArgs: FilterAnvilsParameters, localInputMemoryUnit: DataSizeUnit, localVirtualDisks: VirtualDiskStates, ) => { const { anvilUUIDs, fileUUIDs, maxCPUCores, maxMemory, maxVirtualDiskSizes, storageGroupUUIDs, } = filterAnvils(...filterArgs); setInputCPUCoresMax(maxCPUCores); setMemoryValueMax(maxMemory); localVirtualDisks.maxes = maxVirtualDiskSizes; setVirtualDisks({ ...localVirtualDisks }); setIncludeAnvilUUIDs(anvilUUIDs); setIncludeFileUUIDs(fileUUIDs); setIncludeStorageGroupUUIDs(storageGroupUUIDs); dSize(maxMemory, { fromUnit: 'B', onSuccess: { string: (value) => setInputMemoryMax(value), }, toUnit: localInputMemoryUnit, }); }; const memorizedUpdateLimit = useCallback(updateLimits, []); const handleInputMemoryValueChange = (value: string) => { setInputMemoryValue(value); const filterArgs: FilterAnvilsParameters = [ allAnvils, storageGroupUUIDMapToFree, cpuCoresValue, BIGINT_ZERO, virtualDisks.sizes, virtualDisks.inputStorageGroupUUIDs, [installISOFileUUID, driverISOFileUUID], ]; dSizeToBytes( value, inputMemoryUnit, (convertedMemoryValue) => { setMemoryValue(convertedMemoryValue); filterArgs[3] = convertedMemoryValue; updateLimits(filterArgs, inputMemoryUnit, virtualDisks); }, () => updateLimits(filterArgs, inputMemoryUnit, virtualDisks), ); }; useEffect(() => { const data = MOCK_DATA; const { anvils: localAllAnvils, anvilSelectItems: localAnvilSelectItems, fileSelectItems: localFileSelectItems, storageGroupSelectItems: localStorageGroupSelectItems, storageGroupUUIDMapToFree: localStorageGroupUUIDMapToFree, } = organizeAnvils(data.anvils); setAllAnvils(localAllAnvils); setStorageGroupUUIDMapToFree(localStorageGroupUUIDMapToFree); setAnvilSelectItems(localAnvilSelectItems); setFileSelectItems(localFileSelectItems); setStorageGroupSelectItems(localStorageGroupSelectItems); const placeholderVirtualDisks = addVirtualDisk(); memorizedUpdateLimit( [ localAllAnvils, localStorageGroupUUIDMapToFree, 0, BIGINT_ZERO, placeholderVirtualDisks.sizes, placeholderVirtualDisks.inputStorageGroupUUIDs, [], ], 'B', placeholderVirtualDisks, ); setOSAutocompleteOptions( data.osList.map((keyValuePair) => { const [osKey, osValue] = keyValuePair.split(','); return { label: osValue, key: osKey, }; }), ); }, [memorizedUpdateLimit]); return ( :not(:first-child)': { marginTop: '1em', }, }} > {createOutlinedSlider('ps-cpu-cores', 'CPU cores', cpuCoresValue, { sliderProps: { onChange: (value) => { const newCPUCoresValue = value as number; setCPUCoresValue(newCPUCoresValue); updateLimits( [ allAnvils, storageGroupUUIDMapToFree, newCPUCoresValue, memoryValue, virtualDisks.sizes, virtualDisks.inputStorageGroupUUIDs, [installISOFileUUID, driverISOFileUUID], ], inputMemoryUnit, virtualDisks, ); }, max: inputCPUCoresMax, min: 1, }, })} {createOutlinedInputWithSelect('ps-memory', 'Memory', DATA_SIZE_UNITS, { inputWithLabelProps: { inputProps: { endAdornment: ( handleInputMemoryValueChange(inputMemoryMax)} sx={{ marginLeft: '14px', minWidth: 'unset', whiteSpace: 'nowrap', }} >{`Max: ${inputMemoryMax} ${inputMemoryUnit}`} ), onChange: ({ target: { value } }) => { handleInputMemoryValueChange(value); }, type: 'number', value: inputMemoryValue, }, }, selectProps: { onChange: ({ target: { value } }) => { const selectedUnit = value as DataSizeUnit; setInputMemoryUnit(selectedUnit); const filterArgs: FilterAnvilsParameters = [ allAnvils, storageGroupUUIDMapToFree, cpuCoresValue, BIGINT_ZERO, virtualDisks.sizes, virtualDisks.inputStorageGroupUUIDs, [installISOFileUUID, driverISOFileUUID], ]; dSizeToBytes( inputMemoryValue, selectedUnit, (convertedMemoryValue) => { setMemoryValue(convertedMemoryValue); filterArgs[3] = convertedMemoryValue; updateLimits(filterArgs, selectedUnit, virtualDisks); }, () => updateLimits(filterArgs, selectedUnit, virtualDisks), ); }, value: inputMemoryUnit, }, })} {virtualDisks.maxes.map((max, vdIndex) => createVirtualDiskForm( virtualDisks, vdIndex, setVirtualDisks, storageGroupSelectItems, includeStorageGroupUUIDs, updateLimits, [ allAnvils, storageGroupUUIDMapToFree, cpuCoresValue, memoryValue, virtualDisks.sizes.slice().fill(BIGINT_ZERO), virtualDisks.inputStorageGroupUUIDs.slice().fill(''), [installISOFileUUID, driverISOFileUUID], ], inputMemoryUnit, ), )} {createOutlinedSelect( 'ps-install-image', 'Install ISO', fileSelectItems, { hideItem: (value) => !includeFileUUIDs.includes(value), selectProps: { onChange: ({ target: { value } }) => { const newInstallISOFileUUID = value as string; setInstallISOFileUUID(newInstallISOFileUUID); updateLimits( [ allAnvils, storageGroupUUIDMapToFree, cpuCoresValue, memoryValue, virtualDisks.sizes, virtualDisks.inputStorageGroupUUIDs, [newInstallISOFileUUID, driverISOFileUUID], ], inputMemoryUnit, virtualDisks, ); }, value: installISOFileUUID, }, }, )} {createOutlinedSelect( 'ps-driver-image', 'Driver ISO', fileSelectItems, { hideItem: (value) => !includeFileUUIDs.includes(value), selectProps: { onChange: ({ target: { value } }) => { const newDriverISOFileUUID = value as string; setDriverISOFileUUID(newDriverISOFileUUID); updateLimits( [ allAnvils, storageGroupUUIDMapToFree, cpuCoresValue, memoryValue, virtualDisks.sizes, virtualDisks.inputStorageGroupUUIDs, [installISOFileUUID, newDriverISOFileUUID], ], inputMemoryUnit, virtualDisks, ); }, value: driverISOFileUUID, }, }, )} {createOutlinedSelect('ps-anvil', 'Anvil', anvilSelectItems, { checkItem: (value) => anvilValue.includes(value), disableItem: (value) => !includeAnvilUUIDs.includes(value), selectProps: { multiple: true, onChange: ({ target: { value } }) => { const subsetAnvilUUIDs: string[] = convertSelectValueToArray(value); setAnvilValue(subsetAnvilUUIDs); // const includedFileUUIDs: string[] = []; // let newExcludedStorageGroupUUIDs: string[] = []; // let newExcludedFileUUIDs: string[] = []; // if (subsetAnvilUUIDs.length > 0) { // ({ newExcludedStorageGroupUUIDs, newExcludedFileUUIDs } = // organizedAnvils.reduce<{ // newExcludedStorageGroupUUIDs: string[]; // newExcludedFileUUIDs: string[]; // }>( // (reduced, { anvilUUID, storageGroupUUIDs, fileUUIDs }) => { // if (subsetAnvilUUIDs.includes(anvilUUID)) { // includedFileUUIDs.push(...fileUUIDs); // } else { // reduced.newExcludedStorageGroupUUIDs.push( // ...storageGroupUUIDs, // ); // reduced.newExcludedFileUUIDs.push(...fileUUIDs); // } // return reduced; // }, // { // newExcludedStorageGroupUUIDs: [], // newExcludedFileUUIDs: [], // }, // )); // includedFileUUIDs.forEach((fileUUID) => { // newExcludedFileUUIDs.splice( // newExcludedFileUUIDs.indexOf(fileUUID), // 1, // ); // }); // } // setIncludeStorageGroupUUIDs(newExcludedStorageGroupUUIDs); // setIncludeFileUUIDs(newExcludedFileUUIDs); // const newStorageGroupValue = storageGroupValue.filter( // (uuid) => !newExcludedStorageGroupUUIDs.includes(uuid), // ); // setStorageGroupValue(newStorageGroupValue); // newExcludedFileUUIDs.forEach((excludedFileUUID) => { // if (installISOFileUUID === excludedFileUUID) { // setInstallISOFileUUID(''); // } // if (driverISOFileUUID === excludedFileUUID) { // setDriverISOFileUUID(''); // } // }); // const { // maxCPUCores: localMaxCPUCores, // maxMemory: localMaxMemory, // maxVirtualDiskSize: localMaxVDSize, // } = getMaxAvailableValues(organizedAnvils, { // includeAnvilUUIDs: subsetAnvilUUIDs, // includeStorageGroupUUIDs: newStorageGroupValue, // }); // setInputCPUCoresMax(localMaxCPUCores); // setInputMemoryMax(localMaxMemory); // setInputVirtualDiskSizeMax(localMaxVDSize); }, value: anvilValue, }, })} Provision ); }; export default ProvisionServerDialog;