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 (