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,
 | 
					    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
 | 
				
			||||||
@@ -247,15 +247,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)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -428,7 +434,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()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,7 +80,7 @@ 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;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -88,7 +98,7 @@ 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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -105,7 +115,7 @@ 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;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -119,7 +129,7 @@ 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,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@@ -194,8 +204,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;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -334,8 +334,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>
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user