diff --git a/virtweb_frontend/src/widgets/forms/CloudInitEditor.tsx b/virtweb_frontend/src/widgets/forms/CloudInitEditor.tsx
new file mode 100644
index 0000000..7e9a78e
--- /dev/null
+++ b/virtweb_frontend/src/widgets/forms/CloudInitEditor.tsx
@@ -0,0 +1,102 @@
+import RefreshIcon from "@mui/icons-material/Refresh";
+import { Grid, IconButton, InputAdornment, Tooltip } from "@mui/material";
+import { v4 as uuidv4 } from "uuid";
+import { VMInfo } from "../../api/VMApi";
+import { CheckboxInput } from "./CheckboxInput";
+import { EditSection } from "./EditSection";
+import { SelectInput } from "./SelectInput";
+import { TextInput } from "./TextInput";
+
+type CloudInitProps = {
+ vm: VMInfo;
+ onChange?: () => void;
+ editable: boolean;
+};
+
+export function CloudInitEditor(p: CloudInitProps): React.ReactElement {
+ return (
+ <>
+
+ {/* Attach cloud init disk */}
+ {
+ p.vm.cloud_init.attach_config = v;
+ p.onChange?.();
+ }}
+ />
+
+
+
+
+ >
+ );
+}
+
+function CloudInitMetadata(p: CloudInitProps): React.ReactElement {
+ // Regenerate instance id
+ const reGenerateInstanceId = () => {
+ p.vm.cloud_init.instance_id = uuidv4();
+ p.onChange?.();
+ };
+
+ return (
+
+ {/* Instance ID */}
+ {
+ p.vm.cloud_init.instance_id = v;
+ p.onChange?.();
+ }}
+ endAdornment={
+ p.editable ? (
+
+
+
+
+
+
+
+ ) : (
+ <>>
+ )
+ }
+ />
+
+ {/* Instance hostname */}
+ {
+ p.vm.cloud_init.local_hostname = v;
+ p.onChange?.();
+ }}
+ />
+
+ {/* Data source mode */}
+ {
+ p.vm.cloud_init.dsmode = v as any;
+ p.onChange?.();
+ }}
+ options={[
+ { label: "None", value: undefined },
+ { value: "Net" },
+ { value: "Local" },
+ ]}
+ />
+
+ );
+}
diff --git a/virtweb_frontend/src/widgets/forms/TextInput.tsx b/virtweb_frontend/src/widgets/forms/TextInput.tsx
index 2502abe..8a01ef9 100644
--- a/virtweb_frontend/src/widgets/forms/TextInput.tsx
+++ b/virtweb_frontend/src/widgets/forms/TextInput.tsx
@@ -18,6 +18,7 @@ export function TextInput(p: {
style?: React.CSSProperties;
helperText?: string;
disabled?: boolean;
+ endAdornment?: React.ReactNode;
}): React.ReactElement {
if (!p.editable && (p.value ?? "") === "") return <>>;
@@ -51,6 +52,7 @@ export function TextInput(p: {
input: {
readOnly: !p.editable,
type: p.type,
+ endAdornment: p.endAdornment,
},
}}
variant={"standard"}
diff --git a/virtweb_frontend/src/widgets/vms/VMDetails.tsx b/virtweb_frontend/src/widgets/vms/VMDetails.tsx
index aa43d77..5fea369 100644
--- a/virtweb_frontend/src/widgets/vms/VMDetails.tsx
+++ b/virtweb_frontend/src/widgets/vms/VMDetails.tsx
@@ -5,6 +5,7 @@ import Grid from "@mui/material/Grid";
import React from "react";
import { useNavigate } from "react-router-dom";
import { validate as validateUUID } from "uuid";
+import { DiskImage, DiskImageApi } from "../../api/DiskImageApi";
import { GroupApi } from "../../api/GroupApi";
import { IsoFile, IsoFilesApi } from "../../api/IsoFilesApi";
import { NWFilter, NWFilterApi } from "../../api/NWFilterApi";
@@ -18,6 +19,7 @@ import { AsyncWidget } from "../AsyncWidget";
import { TabsWidget } from "../TabsWidget";
import { XMLAsyncWidget } from "../XMLWidget";
import { CheckboxInput } from "../forms/CheckboxInput";
+import { CloudInitEditor } from "../forms/CloudInitEditor";
import { EditSection } from "../forms/EditSection";
import { OEMStringFormWidget } from "../forms/OEMStringFormWidget";
import { ResAutostartInput } from "../forms/ResAutostartInput";
@@ -27,7 +29,6 @@ import { VMDisksList } from "../forms/VMDisksList";
import { VMNetworksList } from "../forms/VMNetworksList";
import { VMSelectIsoInput } from "../forms/VMSelectIsoInput";
import { VMScreenshot } from "./VMScreenshot";
-import { DiskImage, DiskImageApi } from "../../api/DiskImageApi";
interface DetailsProps {
vm: VMInfo;
@@ -89,6 +90,7 @@ enum VMTab {
General = 0,
Storage,
Network,
+ CloudInit,
Advanced,
XML,
Danger,
@@ -116,6 +118,11 @@ 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: "Cloud Init",
+ value: VMTab.CloudInit,
+ visible: p.editable || p.vm.cloud_init.attach_config,
+ },
{ label: "Avanced", value: VMTab.Advanced, visible: true },
{
@@ -135,6 +142,7 @@ function VMDetailsInner(p: DetailsInnerProps): React.ReactElement {
{currTab === VMTab.General && }
{currTab === VMTab.Storage && }
{currTab === VMTab.Network && }
+ {currTab === VMTab.CloudInit && }
{currTab === VMTab.Advanced && }
{currTab === VMTab.XML && }
{currTab === VMTab.Danger && }
@@ -381,6 +389,10 @@ function VMDetailsTabNetwork(p: DetailsInnerProps): React.ReactElement {
return ;
}
+function VMDetailsTabCloudInit(p: DetailsInnerProps): React.ReactElement {
+ return ;
+}
+
function VMDetailsTabAdvanced(p: DetailsInnerProps): React.ReactElement {
return (