diff --git a/virtweb_backend/src/libvirt_lib_structures/domain.rs b/virtweb_backend/src/libvirt_lib_structures/domain.rs index c5ff6c2..1e49290 100644 --- a/virtweb_backend/src/libvirt_lib_structures/domain.rs +++ b/virtweb_backend/src/libvirt_lib_structures/domain.rs @@ -317,7 +317,7 @@ pub struct DomainCPUXML { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename = "entry")] pub struct OEMStringEntryXML { - #[serde(rename = "$text")] + #[serde(rename = "$text", default)] pub content: String, } diff --git a/virtweb_frontend/src/widgets/forms/OEMStringFormWidget.tsx b/virtweb_frontend/src/widgets/forms/OEMStringFormWidget.tsx new file mode 100644 index 0000000..2763d3d --- /dev/null +++ b/virtweb_frontend/src/widgets/forms/OEMStringFormWidget.tsx @@ -0,0 +1,89 @@ +/* eslint-disable react-x/no-array-index-key */ +import AddIcon from "@mui/icons-material/Add"; +import ClearIcon from "@mui/icons-material/Clear"; +import { + Alert, + IconButton, + InputAdornment, + TextField, + Tooltip, +} from "@mui/material"; +import { VMInfo } from "../../api/VMApi"; +import { useConfirm } from "../../hooks/providers/ConfirmDialogProvider"; +import { EditSection } from "./EditSection"; + +export function OEMStringFormWidget(p: { + vm: VMInfo; + editable: boolean; + onChange?: () => void; +}): React.ReactElement { + const confirm = useConfirm(); + + const handleDeleteOEMString = async (num: number) => { + if (!(await confirm("Do you really want to delete this entry?"))) return; + p.vm.oem_strings.splice(num, 1); + p.onChange?.(); + }; + + return ( + + { + p.vm.oem_strings.push(""); + p.onChange?.(); + }} + > + + + + ) : ( + <> + ) + } + > + + You can use the{" "} + + dmidecode + {" "} + tool on Linux to extract these strings on the guest. + + + {p.vm.oem_strings.map((s, num) => ( + { + p.vm.oem_strings[num] = e.target.value; + p.onChange?.(); + }} + style={{ marginTop: "5px" }} + slotProps={{ + input: { + endAdornment: p.editable ? ( + + + handleDeleteOEMString(num)}> + + + + + ) : undefined, + }, + }} + /> + ))} + + ); +} diff --git a/virtweb_frontend/src/widgets/vms/VMDetails.tsx b/virtweb_frontend/src/widgets/vms/VMDetails.tsx index 7959332..7e91f4c 100644 --- a/virtweb_frontend/src/widgets/vms/VMDetails.tsx +++ b/virtweb_frontend/src/widgets/vms/VMDetails.tsx @@ -19,6 +19,7 @@ import { TabsWidget } from "../TabsWidget"; import { XMLAsyncWidget } from "../XMLWidget"; import { CheckboxInput } from "../forms/CheckboxInput"; import { EditSection } from "../forms/EditSection"; +import { OEMStringFormWidget } from "../forms/OEMStringFormWidget"; import { ResAutostartInput } from "../forms/ResAutostartInput"; import { SelectInput } from "../forms/SelectInput"; import { TextInput } from "../forms/TextInput"; @@ -78,6 +79,7 @@ enum VMTab { General = 0, Storage, Network, + Advanced, XML, Danger, } @@ -102,6 +104,8 @@ function VMDetailsInner(p: DetailsInnerProps): React.ReactElement { { label: "General", value: VMTab.General, visible: true }, { label: "Storage", value: VMTab.Storage, visible: true }, { label: "Network", value: VMTab.Network, visible: true }, + { label: "Avanced", value: VMTab.Advanced, visible: true }, + { label: "XML", value: VMTab.XML, @@ -119,6 +123,7 @@ function VMDetailsInner(p: DetailsInnerProps): React.ReactElement { {currTab === VMTab.General && } {currTab === VMTab.Storage && } {currTab === VMTab.Network && } + {currTab === VMTab.Advanced && } {currTab === VMTab.XML && } {currTab === VMTab.Danger && } @@ -361,6 +366,15 @@ function VMDetailsTabNetwork(p: DetailsInnerProps): React.ReactElement { return ; } +function VMDetailsTabAdvanced(p: DetailsInnerProps): React.ReactElement { + return ( + + {/* OEM strings */} + + + ); +} + function VMDetailsTabXML(p: DetailsInnerProps): React.ReactElement { return (