Compare commits
5 Commits
a171e10ead
...
26c2072eeb
Author | SHA1 | Date | |
---|---|---|---|
26c2072eeb | |||
dce17062a3 | |||
dcb0743cbe | |||
644fd6f1bb | |||
94ee8f8c78 |
@ -26,6 +26,7 @@ pub struct OSXML {
|
|||||||
pub firmware: String,
|
pub firmware: String,
|
||||||
pub r#type: OSTypeXML,
|
pub r#type: OSTypeXML,
|
||||||
pub loader: Option<OSLoaderXML>,
|
pub loader: Option<OSLoaderXML>,
|
||||||
|
pub smbios: Option<OSSMBiosXML>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// OS Type information
|
/// OS Type information
|
||||||
@ -48,6 +49,14 @@ pub struct OSLoaderXML {
|
|||||||
pub secure: String,
|
pub secure: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// SMBIOS System information
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[serde(rename = "smbios")]
|
||||||
|
pub struct OSSMBiosXML {
|
||||||
|
#[serde(rename = "@mode")]
|
||||||
|
pub mode: String,
|
||||||
|
}
|
||||||
|
|
||||||
/// Hypervisor features
|
/// Hypervisor features
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Default, Debug)]
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Default, Debug)]
|
||||||
#[serde(rename = "features")]
|
#[serde(rename = "features")]
|
||||||
@ -305,6 +314,29 @@ pub struct DomainCPUXML {
|
|||||||
pub topology: Option<DomainCPUTopology>,
|
pub topology: Option<DomainCPUTopology>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[serde(rename = "entry")]
|
||||||
|
pub struct OEMStringEntryXML {
|
||||||
|
#[serde(rename = "$text", default)]
|
||||||
|
pub content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[serde(rename = "oemStrings")]
|
||||||
|
pub struct OEMStringsXML {
|
||||||
|
#[serde(rename = "entry")]
|
||||||
|
pub entries: Vec<OEMStringEntryXML>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[serde(rename = "sysinfo")]
|
||||||
|
pub struct SysInfoXML {
|
||||||
|
#[serde(rename = "@type")]
|
||||||
|
pub r#type: String,
|
||||||
|
#[serde(rename = "oemStrings")]
|
||||||
|
pub oem_strings: Option<OEMStringsXML>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Domain information, see https://libvirt.org/formatdomain.html
|
/// Domain information, see https://libvirt.org/formatdomain.html
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
#[serde(rename = "domain")]
|
#[serde(rename = "domain")]
|
||||||
@ -335,6 +367,10 @@ pub struct DomainXML {
|
|||||||
/// CPU information
|
/// CPU information
|
||||||
pub cpu: DomainCPUXML,
|
pub cpu: DomainCPUXML,
|
||||||
|
|
||||||
|
/// SMBios strings
|
||||||
|
pub sysinfo: Option<SysInfoXML>,
|
||||||
|
|
||||||
|
/// Behavior when guest state change
|
||||||
pub on_poweroff: String,
|
pub on_poweroff: String,
|
||||||
pub on_reboot: String,
|
pub on_reboot: String,
|
||||||
pub on_crash: String,
|
pub on_crash: String,
|
||||||
|
@ -77,12 +77,14 @@ pub struct VMInfo {
|
|||||||
pub vnc_access: bool,
|
pub vnc_access: bool,
|
||||||
/// Attach ISO file(s)
|
/// Attach ISO file(s)
|
||||||
pub iso_files: Vec<String>,
|
pub iso_files: Vec<String>,
|
||||||
/// Storage - https://access.redhat.com/documentation/fr-fr/red_hat_enterprise_linux/6/html/virtualization_administration_guide/sect-virtualization-virtualized_block_devices-adding_storage_devices_to_guests#sect-Virtualization-Adding_storage_devices_to_guests-Adding_file_based_storage_to_a_guest
|
/// File Storage - https://access.redhat.com/documentation/fr-fr/red_hat_enterprise_linux/6/html/virtualization_administration_guide/sect-virtualization-virtualized_block_devices-adding_storage_devices_to_guests#sect-Virtualization-Adding_storage_devices_to_guests-Adding_file_based_storage_to_a_guest
|
||||||
pub disks: Vec<FileDisk>,
|
pub file_disks: Vec<FileDisk>,
|
||||||
/// Network cards
|
/// Network cards
|
||||||
pub networks: Vec<Network>,
|
pub networks: Vec<Network>,
|
||||||
/// Add a TPM v2.0 module
|
/// Add a TPM v2.0 module
|
||||||
pub tpm_module: bool,
|
pub tpm_module: bool,
|
||||||
|
/// Strings injected as OEM Strings in SMBios configuration
|
||||||
|
pub oem_strings: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VMInfo {
|
impl VMInfo {
|
||||||
@ -247,15 +249,21 @@ impl VMInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check disks name for duplicates
|
// Check disks name for duplicates
|
||||||
for disk in &self.disks {
|
for disk in &self.file_disks {
|
||||||
if self.disks.iter().filter(|d| d.name == disk.name).count() > 1 {
|
if self
|
||||||
|
.file_disks
|
||||||
|
.iter()
|
||||||
|
.filter(|d| d.name == disk.name)
|
||||||
|
.count()
|
||||||
|
> 1
|
||||||
|
{
|
||||||
return Err(StructureExtraction("Two different disks have the same name!").into());
|
return Err(StructureExtraction("Two different disks have the same name!").into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply disks configuration. Starting from now, the function should ideally never fail due to
|
// Apply disks configuration. Starting from now, the function should ideally never fail due to
|
||||||
// bad user input
|
// bad user input
|
||||||
for disk in &self.disks {
|
for disk in &self.file_disks {
|
||||||
disk.check_config()?;
|
disk.check_config()?;
|
||||||
disk.apply_config(uuid)?;
|
disk.apply_config(uuid)?;
|
||||||
|
|
||||||
@ -323,6 +331,9 @@ impl VMInfo {
|
|||||||
BootType::UEFISecureBoot => "yes".to_string(),
|
BootType::UEFISecureBoot => "yes".to_string(),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
smbios: Some(OSSMBiosXML {
|
||||||
|
mode: "sysinfo".to_string(),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
|
|
||||||
features: FeaturesXML { acpi: ACPIXML {} },
|
features: FeaturesXML { acpi: ACPIXML {} },
|
||||||
@ -379,6 +390,17 @@ impl VMInfo {
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
sysinfo: Some(SysInfoXML {
|
||||||
|
r#type: "smbios".to_string(),
|
||||||
|
oem_strings: Some(OEMStringsXML {
|
||||||
|
entries: self
|
||||||
|
.oem_strings
|
||||||
|
.iter()
|
||||||
|
.map(|s| OEMStringEntryXML { content: s.clone() })
|
||||||
|
.collect(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
|
||||||
on_poweroff: "destroy".to_string(),
|
on_poweroff: "destroy".to_string(),
|
||||||
on_reboot: "restart".to_string(),
|
on_reboot: "restart".to_string(),
|
||||||
on_crash: "destroy".to_string(),
|
on_crash: "destroy".to_string(),
|
||||||
@ -428,7 +450,7 @@ impl VMInfo {
|
|||||||
.map(|d| d.source.file.rsplit_once('/').unwrap().1.to_string())
|
.map(|d| d.source.file.rsplit_once('/').unwrap().1.to_string())
|
||||||
.collect(),
|
.collect(),
|
||||||
|
|
||||||
disks: domain
|
file_disks: domain
|
||||||
.devices
|
.devices
|
||||||
.disks
|
.disks
|
||||||
.iter()
|
.iter()
|
||||||
@ -470,6 +492,12 @@ impl VMInfo {
|
|||||||
.collect::<Result<Vec<_>, _>>()?,
|
.collect::<Result<Vec<_>, _>>()?,
|
||||||
|
|
||||||
tpm_module: domain.devices.tpm.is_some(),
|
tpm_module: domain.devices.tpm.is_some(),
|
||||||
|
|
||||||
|
oem_strings: domain
|
||||||
|
.sysinfo
|
||||||
|
.and_then(|s| s.oem_strings)
|
||||||
|
.map(|s| s.entries.iter().map(|o| o.content.to_string()).collect())
|
||||||
|
.unwrap_or_default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,12 +17,11 @@ export type VMState =
|
|||||||
| "PowerManagementSuspended"
|
| "PowerManagementSuspended"
|
||||||
| "Other";
|
| "Other";
|
||||||
|
|
||||||
export type DiskAllocType = "Sparse" | "Fixed";
|
export type VMFileDisk = BaseFileVMDisk & (RawVMDisk | QCow2Disk);
|
||||||
|
|
||||||
export interface VMDisk {
|
export interface BaseFileVMDisk {
|
||||||
size: number;
|
size: number;
|
||||||
name: string;
|
name: string;
|
||||||
alloc_type: DiskAllocType;
|
|
||||||
delete: boolean;
|
delete: boolean;
|
||||||
|
|
||||||
// application attribute
|
// application attribute
|
||||||
@ -30,6 +29,17 @@ export interface VMDisk {
|
|||||||
deleteType?: "keepfile" | "deletefile";
|
deleteType?: "keepfile" | "deletefile";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DiskAllocType = "Sparse" | "Fixed";
|
||||||
|
|
||||||
|
interface RawVMDisk {
|
||||||
|
format: "Raw";
|
||||||
|
alloc_type: DiskAllocType;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface QCow2Disk {
|
||||||
|
format: "QCow2";
|
||||||
|
}
|
||||||
|
|
||||||
export interface VMNetInterfaceFilterParams {
|
export interface VMNetInterfaceFilterParams {
|
||||||
name: string;
|
name: string;
|
||||||
value: string;
|
value: string;
|
||||||
@ -70,9 +80,10 @@ interface VMInfoInterface {
|
|||||||
number_vcpu: number;
|
number_vcpu: number;
|
||||||
vnc_access: boolean;
|
vnc_access: boolean;
|
||||||
iso_files: string[];
|
iso_files: string[];
|
||||||
disks: VMDisk[];
|
file_disks: VMFileDisk[];
|
||||||
networks: VMNetInterface[];
|
networks: VMNetInterface[];
|
||||||
tpm_module: boolean;
|
tpm_module: boolean;
|
||||||
|
oem_strings: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class VMInfo implements VMInfoInterface {
|
export class VMInfo implements VMInfoInterface {
|
||||||
@ -88,9 +99,10 @@ export class VMInfo implements VMInfoInterface {
|
|||||||
memory: number;
|
memory: number;
|
||||||
vnc_access: boolean;
|
vnc_access: boolean;
|
||||||
iso_files: string[];
|
iso_files: string[];
|
||||||
disks: VMDisk[];
|
file_disks: VMFileDisk[];
|
||||||
networks: VMNetInterface[];
|
networks: VMNetInterface[];
|
||||||
tpm_module: boolean;
|
tpm_module: boolean;
|
||||||
|
oem_strings: string[];
|
||||||
|
|
||||||
constructor(int: VMInfoInterface) {
|
constructor(int: VMInfoInterface) {
|
||||||
this.name = int.name;
|
this.name = int.name;
|
||||||
@ -105,9 +117,10 @@ export class VMInfo implements VMInfoInterface {
|
|||||||
this.memory = int.memory;
|
this.memory = int.memory;
|
||||||
this.vnc_access = int.vnc_access;
|
this.vnc_access = int.vnc_access;
|
||||||
this.iso_files = int.iso_files;
|
this.iso_files = int.iso_files;
|
||||||
this.disks = int.disks;
|
this.file_disks = int.file_disks;
|
||||||
this.networks = int.networks;
|
this.networks = int.networks;
|
||||||
this.tpm_module = int.tpm_module;
|
this.tpm_module = int.tpm_module;
|
||||||
|
this.oem_strings = int.oem_strings;
|
||||||
}
|
}
|
||||||
|
|
||||||
static NewEmpty(): VMInfo {
|
static NewEmpty(): VMInfo {
|
||||||
@ -119,9 +132,10 @@ export class VMInfo implements VMInfoInterface {
|
|||||||
number_vcpu: 1,
|
number_vcpu: 1,
|
||||||
vnc_access: true,
|
vnc_access: true,
|
||||||
iso_files: [],
|
iso_files: [],
|
||||||
disks: [],
|
file_disks: [],
|
||||||
networks: [],
|
networks: [],
|
||||||
tpm_module: true,
|
tpm_module: true,
|
||||||
|
oem_strings: [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,8 +208,8 @@ export class VMApi {
|
|||||||
*/
|
*/
|
||||||
static async UpdateSingle(vm: VMInfo): Promise<VMInfo> {
|
static async UpdateSingle(vm: VMInfo): Promise<VMInfo> {
|
||||||
// Process disks list, looking for removal
|
// Process disks list, looking for removal
|
||||||
vm.disks = vm.disks.filter((d) => d.deleteType !== "keepfile");
|
vm.file_disks = vm.file_disks.filter((d) => d.deleteType !== "keepfile");
|
||||||
vm.disks.forEach((d) => {
|
vm.file_disks.forEach((d) => {
|
||||||
if (d.deleteType === "deletefile") d.delete = true;
|
if (d.deleteType === "deletefile") d.delete = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
89
virtweb_frontend/src/widgets/forms/OEMStringFormWidget.tsx
Normal file
89
virtweb_frontend/src/widgets/forms/OEMStringFormWidget.tsx
Normal file
@ -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 (
|
||||||
|
<EditSection
|
||||||
|
title="SMBIOS OEM Strings"
|
||||||
|
actions={
|
||||||
|
p.editable ? (
|
||||||
|
<Tooltip title="Add a new string entry">
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
p.vm.oem_strings.push("");
|
||||||
|
p.onChange?.();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AddIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Alert severity="info">
|
||||||
|
You can use the{" "}
|
||||||
|
<a
|
||||||
|
href="https://www.nongnu.org/dmidecode/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
style={{ color: "inherit" }}
|
||||||
|
>
|
||||||
|
<i>dmidecode</i>
|
||||||
|
</a>{" "}
|
||||||
|
tool on Linux to extract these strings on the guest.
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
{p.vm.oem_strings.map((s, num) => (
|
||||||
|
<TextField
|
||||||
|
key={num}
|
||||||
|
fullWidth
|
||||||
|
disabled={!p.editable}
|
||||||
|
value={s}
|
||||||
|
onChange={(e) => {
|
||||||
|
p.vm.oem_strings[num] = e.target.value;
|
||||||
|
p.onChange?.();
|
||||||
|
}}
|
||||||
|
style={{ marginTop: "5px" }}
|
||||||
|
slotProps={{
|
||||||
|
input: {
|
||||||
|
endAdornment: p.editable ? (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<Tooltip title="Remove entry">
|
||||||
|
<IconButton onClick={() => handleDeleteOEMString(num)}>
|
||||||
|
<ClearIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</InputAdornment>
|
||||||
|
) : undefined,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</EditSection>
|
||||||
|
);
|
||||||
|
}
|
@ -14,7 +14,7 @@ import {
|
|||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { filesize } from "filesize";
|
import { filesize } from "filesize";
|
||||||
import { ServerApi } from "../../api/ServerApi";
|
import { ServerApi } from "../../api/ServerApi";
|
||||||
import { VMDisk, VMInfo } from "../../api/VMApi";
|
import { VMFileDisk, VMInfo } from "../../api/VMApi";
|
||||||
import { useConfirm } from "../../hooks/providers/ConfirmDialogProvider";
|
import { useConfirm } from "../../hooks/providers/ConfirmDialogProvider";
|
||||||
import { SelectInput } from "./SelectInput";
|
import { SelectInput } from "./SelectInput";
|
||||||
import { TextInput } from "./TextInput";
|
import { TextInput } from "./TextInput";
|
||||||
@ -25,11 +25,11 @@ export function VMDisksList(p: {
|
|||||||
editable: boolean;
|
editable: boolean;
|
||||||
}): React.ReactElement {
|
}): React.ReactElement {
|
||||||
const addNewDisk = () => {
|
const addNewDisk = () => {
|
||||||
p.vm.disks.push({
|
p.vm.file_disks.push({
|
||||||
alloc_type: "Sparse",
|
format: "QCow2",
|
||||||
size: 10000,
|
size: 10000,
|
||||||
delete: false,
|
delete: false,
|
||||||
name: `disk${p.vm.disks.length}`,
|
name: `disk${p.vm.file_disks.length}`,
|
||||||
new: true,
|
new: true,
|
||||||
});
|
});
|
||||||
p.onChange?.();
|
p.onChange?.();
|
||||||
@ -38,7 +38,7 @@ export function VMDisksList(p: {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* disks list */}
|
{/* disks list */}
|
||||||
{p.vm.disks.map((d, num) => (
|
{p.vm.file_disks.map((d, num) => (
|
||||||
<DiskInfo
|
<DiskInfo
|
||||||
// eslint-disable-next-line react-x/no-array-index-key
|
// eslint-disable-next-line react-x/no-array-index-key
|
||||||
key={num}
|
key={num}
|
||||||
@ -46,7 +46,7 @@ export function VMDisksList(p: {
|
|||||||
disk={d}
|
disk={d}
|
||||||
onChange={p.onChange}
|
onChange={p.onChange}
|
||||||
removeFromList={() => {
|
removeFromList={() => {
|
||||||
p.vm.disks.splice(num, 1);
|
p.vm.file_disks.splice(num, 1);
|
||||||
p.onChange?.();
|
p.onChange?.();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -59,7 +59,7 @@ export function VMDisksList(p: {
|
|||||||
|
|
||||||
function DiskInfo(p: {
|
function DiskInfo(p: {
|
||||||
editable: boolean;
|
editable: boolean;
|
||||||
disk: VMDisk;
|
disk: VMFileDisk;
|
||||||
onChange?: () => void;
|
onChange?: () => void;
|
||||||
removeFromList: () => void;
|
removeFromList: () => void;
|
||||||
}): React.ReactElement {
|
}): React.ReactElement {
|
||||||
@ -126,25 +126,30 @@ function DiskInfo(p: {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
secondary={`${filesize(p.disk.size * 1000 * 1000)} - ${
|
secondary={`${filesize(p.disk.size * 1000 * 1000)} - ${
|
||||||
p.disk.alloc_type
|
p.disk.format
|
||||||
}`}
|
}${p.disk.format == "Raw" ? " - " + p.disk.alloc_type : ""}`}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper elevation={3} style={{ margin: "10px", padding: "10px" }}>
|
<Paper elevation={3} style={{ margin: "10px", padding: "10px" }}>
|
||||||
<TextInput
|
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
editable={true}
|
<TextInput
|
||||||
label="Disk name"
|
editable={true}
|
||||||
size={ServerApi.Config.constraints.disk_name_size}
|
label="Disk name"
|
||||||
checkValue={(v) => /^[a-zA-Z0-9]+$/.test(v)}
|
size={ServerApi.Config.constraints.disk_name_size}
|
||||||
value={p.disk.name}
|
checkValue={(v) => /^[a-zA-Z0-9]+$/.test(v)}
|
||||||
onValueChange={(v) => {
|
value={p.disk.name}
|
||||||
p.disk.name = v ?? "";
|
onValueChange={(v) => {
|
||||||
p.onChange?.();
|
p.disk.name = v ?? "";
|
||||||
}}
|
p.onChange?.();
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
<IconButton onClick={p.removeFromList}>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
editable={true}
|
editable={true}
|
||||||
@ -158,7 +163,21 @@ function DiskInfo(p: {
|
|||||||
type="number"
|
type="number"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
<SelectInput
|
||||||
|
editable={true}
|
||||||
|
label="Disk format"
|
||||||
|
options={[
|
||||||
|
{ label: "Raw file", value: "Raw" },
|
||||||
|
{ label: "QCow2", value: "QCow2" },
|
||||||
|
]}
|
||||||
|
value={p.disk.format}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
p.disk.format = v as any;
|
||||||
|
p.onChange?.();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{p.disk.format === "Raw" && (
|
||||||
<SelectInput
|
<SelectInput
|
||||||
editable={true}
|
editable={true}
|
||||||
label="File allocation type"
|
label="File allocation type"
|
||||||
@ -168,15 +187,11 @@ function DiskInfo(p: {
|
|||||||
]}
|
]}
|
||||||
value={p.disk.alloc_type}
|
value={p.disk.alloc_type}
|
||||||
onValueChange={(v) => {
|
onValueChange={(v) => {
|
||||||
p.disk.alloc_type = v as any;
|
if (p.disk.format === "Raw") p.disk.alloc_type = v as any;
|
||||||
p.onChange?.();
|
p.onChange?.();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
<IconButton onClick={p.removeFromList}>
|
|
||||||
<DeleteIcon />
|
|
||||||
</IconButton>
|
|
||||||
</div>
|
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import { TabsWidget } from "../TabsWidget";
|
|||||||
import { XMLAsyncWidget } from "../XMLWidget";
|
import { XMLAsyncWidget } from "../XMLWidget";
|
||||||
import { CheckboxInput } from "../forms/CheckboxInput";
|
import { CheckboxInput } from "../forms/CheckboxInput";
|
||||||
import { EditSection } from "../forms/EditSection";
|
import { EditSection } from "../forms/EditSection";
|
||||||
|
import { OEMStringFormWidget } from "../forms/OEMStringFormWidget";
|
||||||
import { ResAutostartInput } from "../forms/ResAutostartInput";
|
import { ResAutostartInput } from "../forms/ResAutostartInput";
|
||||||
import { SelectInput } from "../forms/SelectInput";
|
import { SelectInput } from "../forms/SelectInput";
|
||||||
import { TextInput } from "../forms/TextInput";
|
import { TextInput } from "../forms/TextInput";
|
||||||
@ -78,6 +79,7 @@ enum VMTab {
|
|||||||
General = 0,
|
General = 0,
|
||||||
Storage,
|
Storage,
|
||||||
Network,
|
Network,
|
||||||
|
Advanced,
|
||||||
XML,
|
XML,
|
||||||
Danger,
|
Danger,
|
||||||
}
|
}
|
||||||
@ -102,6 +104,8 @@ function VMDetailsInner(p: DetailsInnerProps): React.ReactElement {
|
|||||||
{ label: "General", value: VMTab.General, visible: true },
|
{ label: "General", value: VMTab.General, visible: true },
|
||||||
{ label: "Storage", value: VMTab.Storage, visible: true },
|
{ label: "Storage", value: VMTab.Storage, visible: true },
|
||||||
{ label: "Network", value: VMTab.Network, visible: true },
|
{ label: "Network", value: VMTab.Network, visible: true },
|
||||||
|
{ label: "Avanced", value: VMTab.Advanced, visible: true },
|
||||||
|
|
||||||
{
|
{
|
||||||
label: "XML",
|
label: "XML",
|
||||||
value: VMTab.XML,
|
value: VMTab.XML,
|
||||||
@ -119,6 +123,7 @@ function VMDetailsInner(p: DetailsInnerProps): React.ReactElement {
|
|||||||
{currTab === VMTab.General && <VMDetailsTabGeneral {...p} />}
|
{currTab === VMTab.General && <VMDetailsTabGeneral {...p} />}
|
||||||
{currTab === VMTab.Storage && <VMDetailsTabStorage {...p} />}
|
{currTab === VMTab.Storage && <VMDetailsTabStorage {...p} />}
|
||||||
{currTab === VMTab.Network && <VMDetailsTabNetwork {...p} />}
|
{currTab === VMTab.Network && <VMDetailsTabNetwork {...p} />}
|
||||||
|
{currTab === VMTab.Advanced && <VMDetailsTabAdvanced {...p} />}
|
||||||
{currTab === VMTab.XML && <VMDetailsTabXML {...p} />}
|
{currTab === VMTab.XML && <VMDetailsTabXML {...p} />}
|
||||||
{currTab === VMTab.Danger && <VMDetailsTabDanger {...p} />}
|
{currTab === VMTab.Danger && <VMDetailsTabDanger {...p} />}
|
||||||
</>
|
</>
|
||||||
@ -334,8 +339,8 @@ function VMDetailsTabGeneral(p: DetailsInnerProps): React.ReactElement {
|
|||||||
function VMDetailsTabStorage(p: DetailsInnerProps): React.ReactElement {
|
function VMDetailsTabStorage(p: DetailsInnerProps): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
{(p.editable || p.vm.disks.length > 0) && (
|
{(p.editable || p.vm.file_disks.length > 0) && (
|
||||||
<EditSection title="Disks storage">
|
<EditSection title="File disks storage">
|
||||||
<VMDisksList {...p} />
|
<VMDisksList {...p} />
|
||||||
</EditSection>
|
</EditSection>
|
||||||
)}
|
)}
|
||||||
@ -361,6 +366,15 @@ function VMDetailsTabNetwork(p: DetailsInnerProps): React.ReactElement {
|
|||||||
return <VMNetworksList {...p} />;
|
return <VMNetworksList {...p} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function VMDetailsTabAdvanced(p: DetailsInnerProps): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
{/* OEM strings */}
|
||||||
|
<OEMStringFormWidget {...p} />
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function VMDetailsTabXML(p: DetailsInnerProps): React.ReactElement {
|
function VMDetailsTabXML(p: DetailsInnerProps): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<XMLAsyncWidget
|
<XMLAsyncWidget
|
||||||
|
Loading…
x
Reference in New Issue
Block a user