Add support for QCow2 file format in web ui
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is passing
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			This commit is contained in:
		@@ -77,8 +77,8 @@ pub struct VMInfo {
 | 
			
		||||
    pub vnc_access: bool,
 | 
			
		||||
    /// Attach ISO file(s)
 | 
			
		||||
    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
 | 
			
		||||
    pub disks: Vec<FileDisk>,
 | 
			
		||||
    /// 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 file_disks: Vec<FileDisk>,
 | 
			
		||||
    /// Network cards
 | 
			
		||||
    pub networks: Vec<Network>,
 | 
			
		||||
    /// Add a TPM v2.0 module
 | 
			
		||||
@@ -247,15 +247,21 @@ impl VMInfo {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Check disks name for duplicates
 | 
			
		||||
        for disk in &self.disks {
 | 
			
		||||
            if self.disks.iter().filter(|d| d.name == disk.name).count() > 1 {
 | 
			
		||||
        for disk in &self.file_disks {
 | 
			
		||||
            if self
 | 
			
		||||
                .file_disks
 | 
			
		||||
                .iter()
 | 
			
		||||
                .filter(|d| d.name == disk.name)
 | 
			
		||||
                .count()
 | 
			
		||||
                > 1
 | 
			
		||||
            {
 | 
			
		||||
                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
 | 
			
		||||
        // bad user input
 | 
			
		||||
        for disk in &self.disks {
 | 
			
		||||
        for disk in &self.file_disks {
 | 
			
		||||
            disk.check_config()?;
 | 
			
		||||
            disk.apply_config(uuid)?;
 | 
			
		||||
 | 
			
		||||
@@ -428,7 +434,7 @@ impl VMInfo {
 | 
			
		||||
                .map(|d| d.source.file.rsplit_once('/').unwrap().1.to_string())
 | 
			
		||||
                .collect(),
 | 
			
		||||
 | 
			
		||||
            disks: domain
 | 
			
		||||
            file_disks: domain
 | 
			
		||||
                .devices
 | 
			
		||||
                .disks
 | 
			
		||||
                .iter()
 | 
			
		||||
 
 | 
			
		||||
@@ -17,12 +17,11 @@ export type VMState =
 | 
			
		||||
  | "PowerManagementSuspended"
 | 
			
		||||
  | "Other";
 | 
			
		||||
 | 
			
		||||
export type DiskAllocType = "Sparse" | "Fixed";
 | 
			
		||||
export type VMFileDisk = BaseFileVMDisk & (RawVMDisk | QCow2Disk);
 | 
			
		||||
 | 
			
		||||
export interface VMDisk {
 | 
			
		||||
export interface BaseFileVMDisk {
 | 
			
		||||
  size: number;
 | 
			
		||||
  name: string;
 | 
			
		||||
  alloc_type: DiskAllocType;
 | 
			
		||||
  delete: boolean;
 | 
			
		||||
 | 
			
		||||
  // application attribute
 | 
			
		||||
@@ -30,6 +29,17 @@ export interface VMDisk {
 | 
			
		||||
  deleteType?: "keepfile" | "deletefile";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type DiskAllocType = "Sparse" | "Fixed";
 | 
			
		||||
 | 
			
		||||
interface RawVMDisk {
 | 
			
		||||
  format: "Raw";
 | 
			
		||||
  alloc_type: DiskAllocType;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface QCow2Disk {
 | 
			
		||||
  format: "QCow2";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface VMNetInterfaceFilterParams {
 | 
			
		||||
  name: string;
 | 
			
		||||
  value: string;
 | 
			
		||||
@@ -70,7 +80,7 @@ interface VMInfoInterface {
 | 
			
		||||
  number_vcpu: number;
 | 
			
		||||
  vnc_access: boolean;
 | 
			
		||||
  iso_files: string[];
 | 
			
		||||
  disks: VMDisk[];
 | 
			
		||||
  file_disks: VMFileDisk[];
 | 
			
		||||
  networks: VMNetInterface[];
 | 
			
		||||
  tpm_module: boolean;
 | 
			
		||||
}
 | 
			
		||||
@@ -88,7 +98,7 @@ export class VMInfo implements VMInfoInterface {
 | 
			
		||||
  memory: number;
 | 
			
		||||
  vnc_access: boolean;
 | 
			
		||||
  iso_files: string[];
 | 
			
		||||
  disks: VMDisk[];
 | 
			
		||||
  file_disks: VMFileDisk[];
 | 
			
		||||
  networks: VMNetInterface[];
 | 
			
		||||
  tpm_module: boolean;
 | 
			
		||||
 | 
			
		||||
@@ -105,7 +115,7 @@ export class VMInfo implements VMInfoInterface {
 | 
			
		||||
    this.memory = int.memory;
 | 
			
		||||
    this.vnc_access = int.vnc_access;
 | 
			
		||||
    this.iso_files = int.iso_files;
 | 
			
		||||
    this.disks = int.disks;
 | 
			
		||||
    this.file_disks = int.file_disks;
 | 
			
		||||
    this.networks = int.networks;
 | 
			
		||||
    this.tpm_module = int.tpm_module;
 | 
			
		||||
  }
 | 
			
		||||
@@ -119,7 +129,7 @@ export class VMInfo implements VMInfoInterface {
 | 
			
		||||
      number_vcpu: 1,
 | 
			
		||||
      vnc_access: true,
 | 
			
		||||
      iso_files: [],
 | 
			
		||||
      disks: [],
 | 
			
		||||
      file_disks: [],
 | 
			
		||||
      networks: [],
 | 
			
		||||
      tpm_module: true,
 | 
			
		||||
    });
 | 
			
		||||
@@ -194,8 +204,8 @@ export class VMApi {
 | 
			
		||||
   */
 | 
			
		||||
  static async UpdateSingle(vm: VMInfo): Promise<VMInfo> {
 | 
			
		||||
    // Process disks list, looking for removal
 | 
			
		||||
    vm.disks = vm.disks.filter((d) => d.deleteType !== "keepfile");
 | 
			
		||||
    vm.disks.forEach((d) => {
 | 
			
		||||
    vm.file_disks = vm.file_disks.filter((d) => d.deleteType !== "keepfile");
 | 
			
		||||
    vm.file_disks.forEach((d) => {
 | 
			
		||||
      if (d.deleteType === "deletefile") d.delete = true;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ import {
 | 
			
		||||
} from "@mui/material";
 | 
			
		||||
import { filesize } from "filesize";
 | 
			
		||||
import { ServerApi } from "../../api/ServerApi";
 | 
			
		||||
import { VMDisk, VMInfo } from "../../api/VMApi";
 | 
			
		||||
import { VMFileDisk, VMInfo } from "../../api/VMApi";
 | 
			
		||||
import { useConfirm } from "../../hooks/providers/ConfirmDialogProvider";
 | 
			
		||||
import { SelectInput } from "./SelectInput";
 | 
			
		||||
import { TextInput } from "./TextInput";
 | 
			
		||||
@@ -25,11 +25,11 @@ export function VMDisksList(p: {
 | 
			
		||||
  editable: boolean;
 | 
			
		||||
}): React.ReactElement {
 | 
			
		||||
  const addNewDisk = () => {
 | 
			
		||||
    p.vm.disks.push({
 | 
			
		||||
      alloc_type: "Sparse",
 | 
			
		||||
    p.vm.file_disks.push({
 | 
			
		||||
      format: "QCow2",
 | 
			
		||||
      size: 10000,
 | 
			
		||||
      delete: false,
 | 
			
		||||
      name: `disk${p.vm.disks.length}`,
 | 
			
		||||
      name: `disk${p.vm.file_disks.length}`,
 | 
			
		||||
      new: true,
 | 
			
		||||
    });
 | 
			
		||||
    p.onChange?.();
 | 
			
		||||
@@ -38,7 +38,7 @@ export function VMDisksList(p: {
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      {/* disks list */}
 | 
			
		||||
      {p.vm.disks.map((d, num) => (
 | 
			
		||||
      {p.vm.file_disks.map((d, num) => (
 | 
			
		||||
        <DiskInfo
 | 
			
		||||
          // eslint-disable-next-line react-x/no-array-index-key
 | 
			
		||||
          key={num}
 | 
			
		||||
@@ -46,7 +46,7 @@ export function VMDisksList(p: {
 | 
			
		||||
          disk={d}
 | 
			
		||||
          onChange={p.onChange}
 | 
			
		||||
          removeFromList={() => {
 | 
			
		||||
            p.vm.disks.splice(num, 1);
 | 
			
		||||
            p.vm.file_disks.splice(num, 1);
 | 
			
		||||
            p.onChange?.();
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
@@ -59,7 +59,7 @@ export function VMDisksList(p: {
 | 
			
		||||
 | 
			
		||||
function DiskInfo(p: {
 | 
			
		||||
  editable: boolean;
 | 
			
		||||
  disk: VMDisk;
 | 
			
		||||
  disk: VMFileDisk;
 | 
			
		||||
  onChange?: () => void;
 | 
			
		||||
  removeFromList: () => void;
 | 
			
		||||
}): React.ReactElement {
 | 
			
		||||
@@ -126,14 +126,15 @@ function DiskInfo(p: {
 | 
			
		||||
            </>
 | 
			
		||||
          }
 | 
			
		||||
          secondary={`${filesize(p.disk.size * 1000 * 1000)} - ${
 | 
			
		||||
            p.disk.alloc_type
 | 
			
		||||
          }`}
 | 
			
		||||
            p.disk.format
 | 
			
		||||
          }${p.disk.format == "Raw" ? " - " + p.disk.alloc_type : ""}`}
 | 
			
		||||
        />
 | 
			
		||||
      </ListItem>
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Paper elevation={3} style={{ margin: "10px", padding: "10px" }}>
 | 
			
		||||
      <div style={{ display: "flex", justifyContent: "space-between" }}>
 | 
			
		||||
        <TextInput
 | 
			
		||||
          editable={true}
 | 
			
		||||
          label="Disk name"
 | 
			
		||||
@@ -145,6 +146,10 @@ function DiskInfo(p: {
 | 
			
		||||
            p.onChange?.();
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <IconButton onClick={p.removeFromList}>
 | 
			
		||||
          <DeleteIcon />
 | 
			
		||||
        </IconButton>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <TextInput
 | 
			
		||||
        editable={true}
 | 
			
		||||
@@ -158,7 +163,21 @@ function DiskInfo(p: {
 | 
			
		||||
        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
 | 
			
		||||
          editable={true}
 | 
			
		||||
          label="File allocation type"
 | 
			
		||||
@@ -168,15 +187,11 @@ function DiskInfo(p: {
 | 
			
		||||
          ]}
 | 
			
		||||
          value={p.disk.alloc_type}
 | 
			
		||||
          onValueChange={(v) => {
 | 
			
		||||
            p.disk.alloc_type = v as any;
 | 
			
		||||
            if (p.disk.format === "Raw") p.disk.alloc_type = v as any;
 | 
			
		||||
            p.onChange?.();
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
        <IconButton onClick={p.removeFromList}>
 | 
			
		||||
          <DeleteIcon />
 | 
			
		||||
        </IconButton>
 | 
			
		||||
      </div>
 | 
			
		||||
      )}
 | 
			
		||||
    </Paper>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -334,8 +334,8 @@ function VMDetailsTabGeneral(p: DetailsInnerProps): React.ReactElement {
 | 
			
		||||
function VMDetailsTabStorage(p: DetailsInnerProps): React.ReactElement {
 | 
			
		||||
  return (
 | 
			
		||||
    <Grid container spacing={2}>
 | 
			
		||||
      {(p.editable || p.vm.disks.length > 0) && (
 | 
			
		||||
        <EditSection title="Disks storage">
 | 
			
		||||
      {(p.editable || p.vm.file_disks.length > 0) && (
 | 
			
		||||
        <EditSection title="File disks storage">
 | 
			
		||||
          <VMDisksList {...p} />
 | 
			
		||||
        </EditSection>
 | 
			
		||||
      )}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user