import AddIcon from "@mui/icons-material/Add"; import ListIcon from "@mui/icons-material/List"; import { Button, IconButton, Tooltip } from "@mui/material"; import Grid from "@mui/material/Grid"; import React from "react"; import { useNavigate } from "react-router-dom"; import { validate as validateUUID } from "uuid"; import { GroupApi } from "../../api/GroupApi"; import { IsoFile, IsoFilesApi } from "../../api/IsoFilesApi"; import { NWFilter, NWFilterApi } from "../../api/NWFilterApi"; import { NetworkApi, NetworkInfo } from "../../api/NetworksApi"; import { ServerApi } from "../../api/ServerApi"; import { VMApi, VMInfo } from "../../api/VMApi"; import { useAlert } from "../../hooks/providers/AlertDialogProvider"; import { useConfirm } from "../../hooks/providers/ConfirmDialogProvider"; import { useSnackbar } from "../../hooks/providers/SnackbarProvider"; import { AsyncWidget } from "../AsyncWidget"; import { TabsWidget } from "../TabsWidget"; import { XMLAsyncWidget } from "../XMLWidget"; import { CheckboxInput } from "../forms/CheckboxInput"; import { EditSection } from "../forms/EditSection"; import { ResAutostartInput } from "../forms/ResAutostartInput"; import { SelectInput } from "../forms/SelectInput"; import { TextInput } from "../forms/TextInput"; import { VMDisksList } from "../forms/VMDisksList"; import { VMNetworksList } from "../forms/VMNetworksList"; import { VMSelectIsoInput } from "../forms/VMSelectIsoInput"; import { VMScreenshot } from "./VMScreenshot"; interface DetailsProps { vm: VMInfo; editable: boolean; onChange?: () => void; screenshot?: boolean; } export function VMDetails(p: DetailsProps): React.ReactElement { const [groupsList, setGroupsList] = React.useState(); const [isoList, setIsoList] = React.useState(); const [vcpuCombinations, setVCPUCombinations] = React.useState< number[] | undefined >(); const [networksList, setNetworksList] = React.useState< NetworkInfo[] | undefined >(); const [networkFiltersList, setNetworkFiltersList] = React.useState< NWFilter[] | undefined >(); const load = async () => { setGroupsList(await GroupApi.GetList()); setIsoList(await IsoFilesApi.GetList()); setVCPUCombinations(await ServerApi.NumberVCPUs()); setNetworksList(await NetworkApi.GetList()); setNetworkFiltersList(await NWFilterApi.GetList()); }; return ( ( )} /> ); } enum VMTab { General = 0, Storage, Network, XML, Danger, } type DetailsInnerProps = DetailsProps & { groupsList: string[]; isoList: IsoFile[]; vcpuCombinations: number[]; networksList: NetworkInfo[]; networkFiltersList: NWFilter[]; }; function VMDetailsInner(p: DetailsInnerProps): React.ReactElement { const [currTab, setCurrTab] = React.useState(VMTab.General); return ( <> {currTab === VMTab.General && } {currTab === VMTab.Storage && } {currTab === VMTab.Network && } {currTab === VMTab.XML && } {currTab === VMTab.Danger && } ); } function VMDetailsTabGeneral(p: DetailsInnerProps): React.ReactElement { const [addGroup, setAddGroup] = React.useState(false); return ( { /* Screenshot section */ p.screenshot && ( ) } {/* Metadata section */} { p.vm.name = v ?? ""; p.onChange?.(); }} checkValue={(v) => /^[a-zA-Z0-9]+$/.test(v)} size={ServerApi.Config.constraints.vm_name_size} /> { p.vm.genid = v; p.onChange?.(); }} checkValue={(v) => validateUUID(v)} /> { p.vm.title = v; p.onChange?.(); }} size={ServerApi.Config.constraints.vm_title_size} /> { p.vm.description = v; p.onChange?.(); }} multiline={true} />
{addGroup ? ( { p.vm.group = v; p.onChange?.(); }} size={ServerApi.Config.constraints.group_id_size} /> ) : ( { p.vm.group = v!; p.onChange?.(); }} value={p.vm.group} options={[ { label: "None" }, ...p.groupsList.map((g) => { return { value: g, label: g }; }), ]} /> )} {p.editable && ( { setAddGroup(!addGroup); }} > {addGroup ? : } )}
{/* General section */} { p.vm.architecture = v! as any; p.onChange?.(); }} value={p.vm.architecture} options={[ { label: "i686", value: "i686" }, { label: "x86_64", value: "x86_64" }, ]} /> { p.vm.boot_type = v! as any; p.onChange?.(); }} value={p.vm.boot_type} options={[ { label: "UEFI with Secure Boot", value: "UEFISecureBoot" }, { label: "UEFI", value: "UEFI" }, ]} /> { p.vm.memory = Number(v ?? "0"); p.onChange?.(); }} checkValue={(v) => Number(v) > ServerApi.Config.constraints.memory_size.min && Number(v) < ServerApi.Config.constraints.memory_size.max } /> { return { label: v.toString(), value: v.toString() }; })} value={p.vm.number_vcpu.toString()} onValueChange={(v) => { p.vm.number_vcpu = Number(v ?? "0"); p.onChange?.(); }} /> { p.vm.vnc_access = v; p.onChange?.(); }} />
{ p.vm.tpm_module = v; p.onChange?.(); }} /> {p.vm.uuid && ( VMApi.IsAutostart(p.vm)} setAutotostart={(e) => VMApi.SetAutostart(p.vm, e)} /> )}
); } /** * Storage section */ function VMDetailsTabStorage(p: DetailsInnerProps): React.ReactElement { return ( {(p.editable || p.vm.file_disks.length > 0) && ( )} {(p.editable || p.vm.iso_files.length > 0) && ( { p.vm.iso_files = v; p.onChange?.(); }} /> )} ); } function VMDetailsTabNetwork(p: DetailsInnerProps): React.ReactElement { return ; } function VMDetailsTabXML(p: DetailsInnerProps): React.ReactElement { return ( VMApi.GetSingleXML(p.vm.uuid!)} /> ); } function VMDetailsTabDanger(p: DetailsInnerProps): React.ReactElement { const confirm = useConfirm(); const alert = useAlert(); const snackbar = useSnackbar(); const navigate = useNavigate(); const deleteVM = async () => { try { if ( !(await confirm( `Do you really want to delete the vm ${p.vm.name}? The operation CANNOT be undone!`, "Delete a VM", "DELETE" )) ) return; const keepData = !(await confirm( "Do you want to delete the files of the VM?", "Delete a VM", "Delete the data", "keep the data" )); if ( !(await confirm( `[LAST CALL] Do you really want to procede with removal? Again, the operation CANNOT be undone!`, "Delete a VM", "DELETE" )) ) return; await VMApi.Delete(p.vm, keepData); snackbar("The VM was successfully deleted!"); navigate("/vms"); } catch (e) { console.error(e); alert(`Failed to delete VM!\n${e}`); } }; return ( ); }