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 (