Managed to insert an ISO file
This commit is contained in:
		| @@ -60,8 +60,13 @@ pub struct ACPIXML {} | ||||
| #[derive(serde::Serialize, serde::Deserialize)] | ||||
| #[serde(rename = "devices")] | ||||
| pub struct DevicesXML { | ||||
|     /// Graphics (used for VNC | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub graphics: Option<GraphicsXML>, | ||||
|  | ||||
|     /// Disks (used for storage) | ||||
|     #[serde(default, rename = "disk")] | ||||
|     pub disks: Option<DiskXML>, // TODO : change to vec | ||||
| } | ||||
|  | ||||
| /// Screen information | ||||
| @@ -74,6 +79,74 @@ pub struct GraphicsXML { | ||||
|     pub socket: String, | ||||
| } | ||||
|  | ||||
| /// Disk information | ||||
| #[derive(serde::Serialize, serde::Deserialize)] | ||||
| #[serde(rename = "disk")] | ||||
| pub struct DiskXML { | ||||
|     #[serde(rename(serialize = "@type"))] | ||||
|     pub r#type: String, | ||||
|     #[serde(rename(serialize = "@device"))] | ||||
|     pub r#device: String, | ||||
|  | ||||
|     pub driver: DiskDriverXML, | ||||
|     pub source: DiskSourceXML, | ||||
|     pub target: DiskTargetXML, | ||||
|     pub readonly: DiskReadOnlyXML, | ||||
|     pub boot: DiskBootXML, | ||||
|     pub address: DiskAddressXML, | ||||
| } | ||||
|  | ||||
| #[derive(serde::Serialize, serde::Deserialize)] | ||||
| #[serde(rename = "driver")] | ||||
| pub struct DiskDriverXML { | ||||
|     #[serde(rename(serialize = "@name"))] | ||||
|     pub name: String, | ||||
|     #[serde(rename(serialize = "@type"))] | ||||
|     pub r#type: String, | ||||
| } | ||||
|  | ||||
| #[derive(serde::Serialize, serde::Deserialize)] | ||||
| #[serde(rename = "source")] | ||||
| pub struct DiskSourceXML { | ||||
|     #[serde(rename(serialize = "@file"))] | ||||
|     pub file: String, | ||||
| } | ||||
|  | ||||
| #[derive(serde::Serialize, serde::Deserialize)] | ||||
| #[serde(rename = "target")] | ||||
| pub struct DiskTargetXML { | ||||
|     #[serde(rename(serialize = "@dev"))] | ||||
|     pub dev: String, | ||||
|     #[serde(rename(serialize = "@bus"))] | ||||
|     pub bus: String, | ||||
| } | ||||
|  | ||||
| #[derive(serde::Serialize, serde::Deserialize)] | ||||
| #[serde(rename = "readonly")] | ||||
| pub struct DiskReadOnlyXML {} | ||||
|  | ||||
| #[derive(serde::Serialize, serde::Deserialize)] | ||||
| #[serde(rename = "boot")] | ||||
| pub struct DiskBootXML { | ||||
|     #[serde(rename(serialize = "@order"))] | ||||
|     pub order: String, | ||||
| } | ||||
|  | ||||
| #[derive(serde::Serialize, serde::Deserialize)] | ||||
| #[serde(rename = "address")] | ||||
| pub struct DiskAddressXML { | ||||
|     #[serde(rename(serialize = "@type"))] | ||||
|     pub r#type: String, | ||||
|     #[serde(rename(serialize = "@controller"))] | ||||
|     pub r#controller: String, | ||||
|     #[serde(rename(serialize = "@bus"))] | ||||
|     pub r#bus: String, | ||||
|     #[serde(rename(serialize = "@target"))] | ||||
|     pub r#target: String, | ||||
|     #[serde(rename(serialize = "@unit"))] | ||||
|     pub r#unit: String, | ||||
| } | ||||
|  | ||||
| /// Domain RAM information | ||||
| #[derive(serde::Serialize, serde::Deserialize)] | ||||
| #[serde(rename = "memory")] | ||||
|   | ||||
| @@ -1,10 +1,12 @@ | ||||
| use crate::app_config::AppConfig; | ||||
| use crate::constants; | ||||
| use crate::libvirt_lib_structures::{ | ||||
|     DevicesXML, DomainMemoryXML, DomainXML, DomainXMLUuid, FeaturesXML, GraphicsXML, OSLoaderXML, | ||||
|     OSTypeXML, ACPIXML, OSXML, | ||||
|     DevicesXML, DiskAddressXML, DiskBootXML, DiskDriverXML, DiskReadOnlyXML, DiskSourceXML, | ||||
|     DiskTargetXML, DiskXML, DomainMemoryXML, DomainXML, DomainXMLUuid, FeaturesXML, GraphicsXML, | ||||
|     OSLoaderXML, OSTypeXML, ACPIXML, OSXML, | ||||
| }; | ||||
| use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction; | ||||
| use crate::utils::files_utils; | ||||
| use lazy_regex::regex; | ||||
| use std::ops::{Div, Mul}; | ||||
|  | ||||
| @@ -70,8 +72,9 @@ pub struct VMInfo { | ||||
|     pub memory: usize, | ||||
|     /// Enable VNC access through admin console | ||||
|     pub vnc_access: bool, | ||||
|     /// Attach an ISO file | ||||
|     pub iso_file: Option<String>, | ||||
|     // TODO : storage | ||||
|     // TODO : iso | ||||
|     // TODO : autostart | ||||
|     // TODO : network interface | ||||
| } | ||||
| @@ -105,6 +108,47 @@ impl VMInfo { | ||||
|             return Err(StructureExtraction("VM memory is invalid!").into()); | ||||
|         } | ||||
|  | ||||
|         let mut disks = vec![]; | ||||
|  | ||||
|         if let Some(iso_file) = &self.iso_file { | ||||
|             if !files_utils::check_file_name(iso_file) { | ||||
|                 return Err(StructureExtraction("ISO filename is invalid!").into()); | ||||
|             } | ||||
|  | ||||
|             let path = AppConfig::get().iso_storage_path().join(iso_file); | ||||
|  | ||||
|             if !path.exists() { | ||||
|                 return Err(StructureExtraction("Specified ISO file does not exists!").into()); | ||||
|             } | ||||
|  | ||||
|             disks.push(DiskXML { | ||||
|                 r#type: "file".to_string(), | ||||
|                 device: "cdrom".to_string(), | ||||
|                 driver: DiskDriverXML { | ||||
|                     name: "qemu".to_string(), | ||||
|                     r#type: "raw".to_string(), | ||||
|                 }, | ||||
|                 source: DiskSourceXML { | ||||
|                     file: path.to_string_lossy().to_string(), | ||||
|                 }, | ||||
|                 target: DiskTargetXML { | ||||
|                     dev: "hdc".to_string(), | ||||
|                     bus: "ide".to_string(), | ||||
|                 }, | ||||
|                 readonly: DiskReadOnlyXML {}, | ||||
|                 boot: DiskBootXML { | ||||
|                     order: "1".to_string(), | ||||
|                 }, | ||||
|                 address: DiskAddressXML { | ||||
|                     r#type: "drive".to_string(), | ||||
|                     controller: "0".to_string(), | ||||
|                     bus: "1".to_string(), | ||||
|                     target: "0".to_string(), | ||||
|                     unit: "0".to_string(), | ||||
|                 }, | ||||
|             }) | ||||
|         } | ||||
|  | ||||
|         let vnc_graphics = match self.vnc_access { | ||||
|             true => Some(GraphicsXML { | ||||
|                 r#type: "vnc".to_string(), | ||||
| @@ -147,6 +191,7 @@ impl VMInfo { | ||||
|  | ||||
|             devices: DevicesXML { | ||||
|                 graphics: vnc_graphics, | ||||
|                 disks: disks.into_iter().next(), | ||||
|             }, | ||||
|  | ||||
|             memory: DomainMemoryXML { | ||||
| @@ -187,6 +232,12 @@ impl VMInfo { | ||||
|             }, | ||||
|             memory: convert_to_mb(&domain.memory.unit, domain.memory.memory)?, | ||||
|             vnc_access: domain.devices.graphics.is_some(), | ||||
|             iso_file: domain | ||||
|                 .devices | ||||
|                 .disks | ||||
|                 .iter() | ||||
|                 .find(|d| d.device == "cdrom") | ||||
|                 .map(|d| d.source.file.rsplit_once('/').unwrap().1.to_string()), | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -27,6 +27,7 @@ interface VMInfoInterface { | ||||
|   architecture: "i686" | "x86_64"; | ||||
|   memory: number; | ||||
|   vnc_access: boolean; | ||||
|   iso_file?: string; | ||||
| } | ||||
|  | ||||
| export class VMInfo implements VMInfoInterface { | ||||
| @@ -39,6 +40,7 @@ export class VMInfo implements VMInfoInterface { | ||||
|   architecture: "i686" | "x86_64"; | ||||
|   memory: number; | ||||
|   vnc_access: boolean; | ||||
|   iso_file?: string; | ||||
|  | ||||
|   constructor(int: VMInfoInterface) { | ||||
|     this.name = int.name; | ||||
| @@ -50,6 +52,7 @@ export class VMInfo implements VMInfoInterface { | ||||
|     this.architecture = int.architecture; | ||||
|     this.memory = int.memory; | ||||
|     this.vnc_access = int.vnc_access; | ||||
|     this.iso_file = int.iso_file; | ||||
|   } | ||||
|  | ||||
|   static NewEmpty(): VMInfo { | ||||
|   | ||||
| @@ -2,7 +2,7 @@ import { FormControl, InputLabel, MenuItem, Select } from "@mui/material"; | ||||
| import { TextInput } from "./TextInput"; | ||||
|  | ||||
| export interface SelectOption { | ||||
|   value: string; | ||||
|   value?: string; | ||||
|   label: string; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -7,13 +7,38 @@ import { CheckboxInput } from "../forms/CheckboxInput"; | ||||
| import { SelectInput } from "../forms/SelectInput"; | ||||
| import { TextInput } from "../forms/TextInput"; | ||||
| import { VMScreenshot } from "./VMScreenshot"; | ||||
| import { IsoFile, IsoFilesApi } from "../../api/IsoFilesApi"; | ||||
| import { AsyncWidget } from "../AsyncWidget"; | ||||
| import React from "react"; | ||||
| import { filesize } from "filesize"; | ||||
|  | ||||
| export function VMDetails(p: { | ||||
| interface DetailsProps { | ||||
|   vm: VMInfo; | ||||
|   editable: boolean; | ||||
|   onChange?: () => void; | ||||
|   screenshot?: boolean; | ||||
| }): React.ReactElement { | ||||
| } | ||||
|  | ||||
| export function VMDetails(p: DetailsProps): React.ReactElement { | ||||
|   const [list, setList] = React.useState<IsoFile[] | any>(); | ||||
|  | ||||
|   const load = async () => { | ||||
|     setList(await IsoFilesApi.GetList()); | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <AsyncWidget | ||||
|       loadKey={"1"} | ||||
|       load={load} | ||||
|       errMsg="Failed to load the list of ISO files" | ||||
|       build={() => <VMDetailsInner iso={list!} {...p} />} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
|  | ||||
| function VMDetailsInner( | ||||
|   p: DetailsProps & { iso: IsoFile[] } | ||||
| ): React.ReactElement { | ||||
|   return ( | ||||
|     <Grid container spacing={2}> | ||||
|       { | ||||
| @@ -128,6 +153,28 @@ export function VMDetails(p: { | ||||
|           }} | ||||
|         /> | ||||
|       </EditSection> | ||||
|  | ||||
|       {/* Storage section */} | ||||
|       <EditSection title="Storage"> | ||||
|         <SelectInput | ||||
|           label="ISO file" | ||||
|           editable={p.editable} | ||||
|           value={p.vm.iso_file} | ||||
|           onValueChange={(v) => { | ||||
|             p.vm.iso_file = v; | ||||
|             p.onChange?.(); | ||||
|           }} | ||||
|           options={[ | ||||
|             { label: "None", value: undefined }, | ||||
|             ...p.iso.map((i) => { | ||||
|               return { | ||||
|                 label: `${i.filename} ${filesize(i.size)}`, | ||||
|                 value: i.filename, | ||||
|               }; | ||||
|             }), | ||||
|           ]} | ||||
|         /> | ||||
|       </EditSection> | ||||
|     </Grid> | ||||
|   ); | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user