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.
 
 
 
 
 
 

1027 lines
30 KiB

import { 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 MenuItem from './MenuItem';
import OutlinedInput, { OutlinedInputProps } from './OutlinedInput';
import OutlinedInputLabel from './OutlinedInputLabel';
import { Panel, PanelHeader } from './Panels';
import Select, { SelectProps } from './Select';
import Slider, { SliderProps } from './Slider';
import { BodyText, HeaderText } from './Text';
import ContainedButton from './ContainedButton';
import { OutlinedInputLabelProps } from './OutlinedInputLabel/OutlinedInputLabel';
import OutlinedInputWithLabel from './OutlinedInputWithLabel';
type SelectItem<SelectItemValueType = string> = {
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<HostMetadataForProvisionServerHost>;
servers: Array<ServerMetadataForProvisionServer>;
storageGroups: Array<StorageGroupMetadataForProvisionServer>;
files: Array<FileMetadataForProvisionServer>;
};
type OrganizedAnvilDetailMetadataForProvisionServer = Omit<
AnvilDetailMetadataForProvisionServer,
| 'anvilTotalMemory'
| 'anvilTotalAllocatedMemory'
| 'anvilTotalAvailableMemory'
| 'hosts'
| 'servers'
| 'storageGroups'
> & {
anvilTotalMemory: bigint;
anvilTotalAllocatedMemory: bigint;
anvilTotalAvailableMemory: bigint;
hosts: Array<
Omit<HostMetadataForProvisionServerHost, 'hostMemory'> & {
hostMemory: bigint;
}
>;
servers: Array<
Omit<ServerMetadataForProvisionServer, 'serverMemory'> & {
serverMemory: bigint;
}
>;
storageGroupUUIDs: string[];
storageGroups: Array<OrganizedStorageGroupMetadataForProvisionServer>;
fileUUIDs: string[];
};
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<DataSizeUnit>[] = [
{ 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<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(({ value, displayValue = value }) => (
<MenuItem
disabled={disableItem?.call(null, value)}
key={`${id}-${value}`}
sx={{
display: hideItem?.call(null, value) ? 'none' : undefined,
}}
value={value}
>
{isCheckableItems && (
<Checkbox checked={checkItem?.call(null, value)} />
)}
{displayValue}
</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: SelectItem[],
{
inputProps,
inputLabelProps,
selectProps,
}: {
inputProps?: Partial<OutlinedInputProps>;
inputLabelProps?: Partial<OutlinedInputLabelProps>;
selectProps?: Partial<SelectProps>;
} = {},
) => (
<FormControl
sx={{
display: 'flex',
flexDirection: 'row',
'& > :first-child': {
flexGrow: 1,
},
}}
>
<OutlinedInputWithLabel {...{ id, label, inputProps, inputLabelProps }} />
{createOutlinedSelect(`${id}-nested-select`, undefined, selectItems, {
selectProps,
})}
</FormControl>
);
const organizeAnvils = (
data: AnvilDetailMetadataForProvisionServer[],
): OrganizedAnvilDetailMetadataForProvisionServer[] =>
data.map((anvil) => {
const {
anvilUUID,
anvilName,
anvilTotalMemory,
anvilTotalAllocatedMemory,
anvilTotalAvailableMemory,
hosts,
servers,
storageGroups,
files,
} = anvil;
const { storageGroupUUIDs, organizedStorageGroups } = storageGroups.reduce<{
storageGroupUUIDs: string[];
organizedStorageGroups: OrganizedStorageGroupMetadataForProvisionServer[];
}>(
(reduced, storageGroup) => {
reduced.storageGroupUUIDs.push(storageGroup.storageGroupUUID);
reduced.organizedStorageGroups.push({
...storageGroup,
anvilUUID,
anvilName,
storageGroupSize: BigInt(storageGroup.storageGroupSize),
storageGroupFree: BigInt(storageGroup.storageGroupFree),
});
return reduced;
},
{
storageGroupUUIDs: [],
organizedStorageGroups: [],
},
);
const fileUUIDs = files.map(({ fileUUID }) => fileUUID);
return {
...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,
storageGroups: organizedStorageGroups,
fileUUIDs,
};
});
const organizeStorageGroups = (
organizedAnvils: OrganizedAnvilDetailMetadataForProvisionServer[],
) =>
organizedAnvils.reduce<OrganizedStorageGroupMetadataForProvisionServer[]>(
(reducedStorageGroups, { storageGroups }) => {
reducedStorageGroups.push(...storageGroups);
return reducedStorageGroups;
},
[],
);
const organizeFiles = (
organizedAnvils: OrganizedAnvilDetailMetadataForProvisionServer[],
) =>
organizedAnvils.reduce<FileMetadataForProvisionServer[]>(
(reducedFiles, { files }) => {
files.forEach((file) => {
// Avoid pushing duplicate file UUIDs.
if (
reducedFiles.find(({ fileUUID }) => file.fileUUID === fileUUID) ===
undefined
) {
reducedFiles.push(file);
}
});
return reducedFiles;
},
[],
);
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,
) => {
dSize(value, {
fromUnit,
onSuccess: {
bigint: onSuccess,
},
precision: 0,
toUnit: 'B',
});
};
const filterAnvils = (
organizedAnvils: OrganizedAnvilDetailMetadataForProvisionServer[],
cpuCores: number,
memory: bigint,
{
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);
}
return organizedAnvils.reduce<{
anvils: OrganizedAnvilDetailMetadataForProvisionServer[];
anvilUUIDs: string[];
maxCPUCores: number;
maxMemory: bigint;
maxVirtualDiskSize: bigint;
}>(
(result, organizedAnvil) => {
const { anvilUUID } = organizedAnvil;
if (testIncludeAnvil(anvilUUID)) {
const { anvilTotalCPUCores, anvilTotalAvailableMemory, storageGroups } =
organizedAnvil;
const isEnoughCPUCores = cpuCores <= anvilTotalCPUCores;
const isEnoughMemory = memory <= anvilTotalAvailableMemory;
if (isEnoughCPUCores && isEnoughMemory) {
result.anvils.push(organizedAnvil);
result.anvilUUIDs.push(anvilUUID);
result.maxCPUCores = Math.max(anvilTotalCPUCores, result.maxCPUCores);
if (anvilTotalAvailableMemory > result.maxMemory) {
result.maxMemory = anvilTotalAvailableMemory;
}
storageGroups.forEach(({ storageGroupUUID, storageGroupFree }) => {
if (
testIncludeStorageGroup(storageGroupUUID) &&
storageGroupFree > result.maxVirtualDiskSize
) {
result.maxVirtualDiskSize = storageGroupFree;
}
});
}
}
return result;
},
{
anvils: [],
anvilUUIDs: [],
maxCPUCores: 0,
maxMemory: BIGINT_ZERO,
maxVirtualDiskSize: BIGINT_ZERO,
},
);
};
const filterStorageGroups = (
organizedStorageGroups: OrganizedStorageGroupMetadataForProvisionServer[],
virtualDiskSize: bigint,
includeUUIDs?: string[],
) => {
let testInclude: (uuid: string) => boolean = () => true;
if (includeUUIDs && includeUUIDs.length > 0) {
testInclude = (uuid: string) => includeUUIDs.includes(uuid);
}
return organizedStorageGroups.filter(
({ storageGroupUUID, storageGroupFree }) => {
const isEnoughStorage = virtualDiskSize <= storageGroupFree;
const isIncluded = testInclude(storageGroupUUID);
return isEnoughStorage && isIncluded;
},
);
};
const ProvisionServerDialog = ({
dialogProps: { open },
}: ProvisionServerDialogProps): JSX.Element => {
const [cpuCoresValue, setCPUCoresValue] = useState<number>(1);
const [inputCPUCoresMax, setInputCPUCoresMax] = useState<number>(0);
const [memoryValue, setMemoryValue] = useState<bigint>(BIGINT_ZERO);
const [inputMemoryMax, setInputMemoryMax] = useState<bigint>(BIGINT_ZERO);
const [inputMemoryValue, setInputMemoryValue] = useState<string>('');
const [inputMemoryUnit, setInputMemoryUnit] = useState<DataSizeUnit>('B');
const [virtualDiskSizeValue, setVirtualDiskSizeValue] =
useState<bigint>(BIGINT_ZERO);
const [inputVirtualDiskSizeMax, setInputVirtualDiskSizeMax] =
useState<bigint>(BIGINT_ZERO);
const [inputVirtualDiskSizeValue, setInputVirtualDiskSizeValue] =
useState<string>('');
const [inputVirtualDiskSizeUnit, setInputVirtualDiskSizeUnit] =
useState<DataSizeUnit>('B');
const [storageGroupValue, setStorageGroupValue] = useState<string[]>([]);
const [excludedStorageGroupUUIDs, setExcludedStorageGroupUUIDs] = useState<
string[]
>([]);
const [selectedStorageGroupUUID, setSelectedStorageGroupUUID] = useState<
string | undefined
>();
const [installISOFileUUID, setInstallISOFileUUID] = useState<string>('');
const [driverISOFileUUID, setDriverISOFileUUID] = useState<string>('');
const [excludedFileUUIDs, setExcludedFileUUIDs] = useState<string[]>([]);
const [anvilValue, setAnvilValue] = useState<string[]>([]);
const [includeAnvilUUIDs, setIncludeAnvilUUIDs] = useState<string[]>([]);
const data = MOCK_DATA;
const organizedAnvils = organizeAnvils(data.anvils);
const organizedStorageGroups = organizeStorageGroups(organizedAnvils);
const organizedFiles = organizeFiles(organizedAnvils);
const { maxCPUCores, maxMemory, maxVirtualDiskSize } = filterAnvils(
organizedAnvils,
0,
BIGINT_ZERO,
);
const selectFiles = organizedFiles.map(({ fileUUID, fileName }) => ({
displayValue: fileName,
value: fileUUID,
}));
const optimizeOSList = data.osList.map((keyValuePair) => {
const [osKey, osValue] = keyValuePair.split(',');
return {
label: osValue,
key: osKey,
};
});
useEffect(() => {
setInputCPUCoresMax(maxCPUCores);
setInputMemoryMax(maxMemory);
setInputVirtualDiskSizeMax(maxVirtualDiskSize);
}, [maxCPUCores, maxMemory, maxVirtualDiskSize]);
return (
<Dialog
{...{
fullWidth: true,
maxWidth: 'sm',
open,
PaperComponent: Panel,
PaperProps: { sx: { overflow: 'visible' } },
}}
>
<PanelHeader>
<HeaderText text="Provision a Server" />
</PanelHeader>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
maxHeight: '50vh',
overflowY: 'scroll',
'& > :not(:first-child)': {
marginTop: '1em',
},
}}
>
<OutlinedInputWithLabel id="ps-server-name" label="Server name" />
{createOutlinedSlider('ps-cpu-cores', 'CPU cores', cpuCoresValue, {
sliderProps: {
onChange: (value) => {
const setValue = value as number;
setCPUCoresValue(setValue);
const {
anvilUUIDs,
maxCPUCores: localMaxCPUCores,
maxMemory: localMaxMemory,
maxVirtualDiskSize: localMaxVDSize,
} = filterAnvils(organizedAnvils, setValue, memoryValue);
setIncludeAnvilUUIDs(anvilUUIDs);
setInputCPUCoresMax(localMaxCPUCores);
setInputMemoryMax(localMaxMemory);
setInputVirtualDiskSizeMax(localMaxVDSize);
},
max: inputCPUCoresMax,
min: 1,
},
})}
<BodyText
text={`Memory: ${memoryValue.toString()}, Max: ${inputMemoryMax.toString()}`}
/>
{createOutlinedInputWithSelect('ps-memory', 'Memory', DATA_SIZE_UNITS, {
inputProps: {
type: 'number',
onChange: ({ target: { value } }) => {
setInputMemoryValue(value);
dSizeToBytes(value, inputMemoryUnit, (convertedMemoryValue) => {
setMemoryValue(convertedMemoryValue);
const {
anvilUUIDs,
maxCPUCores: localMaxCPUCores,
maxMemory: localMaxMemory,
maxVirtualDiskSize: localMaxVDSize,
} = filterAnvils(
organizedAnvils,
cpuCoresValue,
convertedMemoryValue,
);
setIncludeAnvilUUIDs(anvilUUIDs);
setInputCPUCoresMax(localMaxCPUCores);
setInputMemoryMax(localMaxMemory);
setInputVirtualDiskSizeMax(localMaxVDSize);
});
},
value: inputMemoryValue,
},
selectProps: {
onChange: ({ target: { value } }) => {
const selectedUnit = value as DataSizeUnit;
setInputMemoryUnit(selectedUnit);
dSizeToBytes(
inputMemoryValue,
selectedUnit,
(convertedMemoryValue) => {
setMemoryValue(convertedMemoryValue);
const {
anvilUUIDs,
maxCPUCores: localMaxCPUCores,
maxMemory: localMaxMemory,
maxVirtualDiskSize: localMaxVDSize,
} = filterAnvils(
organizedAnvils,
cpuCoresValue,
convertedMemoryValue,
);
setIncludeAnvilUUIDs(anvilUUIDs);
setInputCPUCoresMax(localMaxCPUCores);
setInputMemoryMax(localMaxMemory);
setInputVirtualDiskSizeMax(localMaxVDSize);
},
);
},
value: inputMemoryUnit,
},
})}
<BodyText
text={`Virtual disk size: ${virtualDiskSizeValue.toString()}, Max: ${inputVirtualDiskSizeMax.toString()}`}
/>
{createOutlinedInputWithSelect(
'ps-virtual-disk-size',
'Virtual disk size',
DATA_SIZE_UNITS,
{
inputProps: {
type: 'number',
onChange: ({ target: { value } }) => {
setInputVirtualDiskSizeValue(value);
dSizeToBytes(
value,
inputVirtualDiskSizeUnit,
setVirtualDiskSizeValue,
);
},
value: inputVirtualDiskSizeValue,
},
selectProps: {
onChange: ({ target: { value } }) => {
const selectedUnit = value as DataSizeUnit;
setInputVirtualDiskSizeUnit(selectedUnit);
dSizeToBytes(
inputVirtualDiskSizeValue,
selectedUnit,
setVirtualDiskSizeValue,
);
},
value: inputVirtualDiskSizeUnit,
},
},
)}
<BodyText
text={`Selected storage group UUID: ${selectedStorageGroupUUID}`}
/>
{createOutlinedSelect(
'ps-storage-group',
'Storage group',
organizedStorageGroups.map(
({ anvilName, storageGroupUUID, storageGroupName }) => ({
displayValue: `${anvilName} -- ${storageGroupName}`,
value: storageGroupUUID,
}),
),
{
checkItem: (value) => storageGroupValue.includes(value),
hideItem: (value) => excludedStorageGroupUUIDs.includes(value),
selectProps: {
multiple: true,
onChange: ({ target: { value } }) => {
const subsetStorageGroupsUUID: string[] =
typeof value === 'string'
? value.split(',')
: (value as string[]);
setStorageGroupValue(subsetStorageGroupsUUID);
setSelectedStorageGroupUUID(
filterStorageGroups(
organizedStorageGroups,
virtualDiskSizeValue,
subsetStorageGroupsUUID,
)[0]?.storageGroupUUID,
);
// setInputVirtualDiskSizeMax(
// getMaxAvailableValues(organizedAnvils, {
// includeAnvilUUIDs: anvilValue,
// includeStorageGroupUUIDs: subsetStorageGroupsUUID,
// }).maxVirtualDiskSize,
// );
},
value: storageGroupValue,
},
},
)}
{createOutlinedSelect('ps-install-image', 'Install ISO', selectFiles, {
hideItem: (value) => excludedFileUUIDs.includes(value),
selectProps: {
onChange: ({ target: { value } }) => {
setInstallISOFileUUID(value as string);
},
value: installISOFileUUID,
},
})}
{createOutlinedSelect('ps-driver-image', 'Driver ISO', selectFiles, {
hideItem: (value) => excludedFileUUIDs.includes(value),
selectProps: {
onChange: ({ target: { value } }) => {
setDriverISOFileUUID(value as string);
},
value: driverISOFileUUID,
},
})}
{createOutlinedSelect(
'ps-anvil',
'Anvil',
organizedAnvils.map(({ anvilUUID, anvilName }) => ({
displayValue: anvilName,
value: anvilUUID,
})),
{
checkItem: (value) => anvilValue.includes(value),
hideItem: (value) => !includeAnvilUUIDs.includes(value),
selectProps: {
multiple: true,
onChange: ({ target: { value } }) => {
const subsetAnvilUUIDs: string[] =
typeof value === 'string'
? value.split(',')
: (value as string[]);
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,
);
});
}
setExcludedStorageGroupUUIDs(newExcludedStorageGroupUUIDs);
setExcludedFileUUIDs(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,
},
},
)}
<Autocomplete
id="ps-optimize-for-os"
label="Optimize for OS"
noOptionsText="No matching OS"
openOnFocus
options={optimizeOSList}
/>
</Box>
<Box
sx={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-end',
marginTop: '1em',
width: '100%',
}}
>
<ContainedButton>Provision</ContainedButton>
</Box>
</Dialog>
);
};
export default ProvisionServerDialog;