Can create NAT networks
This commit is contained in:
		@@ -107,17 +107,24 @@ impl Handler<DefineDomainReq> for LibVirtActor {
 | 
				
			|||||||
    type Result = anyhow::Result<XMLUuid>;
 | 
					    type Result = anyhow::Result<XMLUuid>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn handle(&mut self, mut msg: DefineDomainReq, _ctx: &mut Self::Context) -> Self::Result {
 | 
					    fn handle(&mut self, mut msg: DefineDomainReq, _ctx: &mut Self::Context) -> Self::Result {
 | 
				
			||||||
        // A issue with the disks definition serialization needs them to be serialized aside
 | 
					        // A issue with the disks & network interface definition serialization needs them to be serialized aside
 | 
				
			||||||
        let mut disks_xml = Vec::with_capacity(msg.0.devices.disks.len());
 | 
					        let mut devices_xml = Vec::with_capacity(msg.0.devices.disks.len());
 | 
				
			||||||
        for disk in msg.0.devices.disks {
 | 
					        for disk in msg.0.devices.disks {
 | 
				
			||||||
            let disk_xml = serde_xml_rs::to_string(&disk)?;
 | 
					            let disk_xml = serde_xml_rs::to_string(&disk)?;
 | 
				
			||||||
            let start_offset = disk_xml.find("<disk").unwrap();
 | 
					            let start_offset = disk_xml.find("<disk").unwrap();
 | 
				
			||||||
            disks_xml.push(disk_xml[start_offset..].to_string());
 | 
					            devices_xml.push(disk_xml[start_offset..].to_string());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        for network in msg.0.devices.net_interfaces {
 | 
				
			||||||
 | 
					            let network_xml = serde_xml_rs::to_string(&network)?;
 | 
				
			||||||
 | 
					            let start_offset = network_xml.find("<interface").unwrap();
 | 
				
			||||||
 | 
					            devices_xml.push(network_xml[start_offset..].to_string());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        msg.0.devices.disks = vec![];
 | 
					        msg.0.devices.disks = vec![];
 | 
				
			||||||
 | 
					        msg.0.devices.net_interfaces = vec![];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let mut xml = serde_xml_rs::to_string(&msg.0)?;
 | 
					        let mut xml = serde_xml_rs::to_string(&msg.0)?;
 | 
				
			||||||
        let disks_xml = disks_xml.join("\n");
 | 
					        let disks_xml = devices_xml.join("\n");
 | 
				
			||||||
        xml = xml.replacen("<devices>", &format!("<devices>{disks_xml}"), 1);
 | 
					        xml = xml.replacen("<devices>", &format!("<devices>{disks_xml}"), 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        log::debug!("Define domain:\n{}", xml);
 | 
					        log::debug!("Define domain:\n{}", xml);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -63,6 +63,13 @@ pub struct FeaturesXML {
 | 
				
			|||||||
#[serde(rename = "acpi")]
 | 
					#[serde(rename = "acpi")]
 | 
				
			||||||
pub struct ACPIXML {}
 | 
					pub struct ACPIXML {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
 | 
					#[serde(rename = "interface")]
 | 
				
			||||||
 | 
					pub struct DomainNetInterfaceXML {
 | 
				
			||||||
 | 
					    #[serde(rename(serialize = "@type"))]
 | 
				
			||||||
 | 
					    pub r#type: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Devices information
 | 
					/// Devices information
 | 
				
			||||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
					#[derive(serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "devices")]
 | 
					#[serde(rename = "devices")]
 | 
				
			||||||
@@ -74,6 +81,10 @@ pub struct DevicesXML {
 | 
				
			|||||||
    /// Disks (used for storage)
 | 
					    /// Disks (used for storage)
 | 
				
			||||||
    #[serde(default, rename = "disk", skip_serializing_if = "Vec::is_empty")]
 | 
					    #[serde(default, rename = "disk", skip_serializing_if = "Vec::is_empty")]
 | 
				
			||||||
    pub disks: Vec<DiskXML>,
 | 
					    pub disks: Vec<DiskXML>,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Networks cards
 | 
				
			||||||
 | 
					    #[serde(default, rename = "interface", skip_serializing_if = "Vec::is_empty")]
 | 
				
			||||||
 | 
					    pub net_interfaces: Vec<DomainNetInterfaceXML>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Screen information
 | 
					/// Screen information
 | 
				
			||||||
@@ -276,7 +287,7 @@ pub struct NetworkDNSXML {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/// Network DNS information
 | 
					/// Network DNS information
 | 
				
			||||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
					#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
				
			||||||
#[serde(rename = "fowarder")]
 | 
					#[serde(rename = "forwarder")]
 | 
				
			||||||
pub struct NetworkDNSForwarderXML {
 | 
					pub struct NetworkDNSForwarderXML {
 | 
				
			||||||
    /// Address of the DNS server
 | 
					    /// Address of the DNS server
 | 
				
			||||||
    #[serde(rename(serialize = "@addr"))]
 | 
					    #[serde(rename(serialize = "@addr"))]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,10 +2,10 @@ use crate::app_config::AppConfig;
 | 
				
			|||||||
use crate::constants;
 | 
					use crate::constants;
 | 
				
			||||||
use crate::libvirt_lib_structures::{
 | 
					use crate::libvirt_lib_structures::{
 | 
				
			||||||
    DevicesXML, DiskBootXML, DiskDriverXML, DiskReadOnlyXML, DiskSourceXML, DiskTargetXML, DiskXML,
 | 
					    DevicesXML, DiskBootXML, DiskDriverXML, DiskReadOnlyXML, DiskSourceXML, DiskTargetXML, DiskXML,
 | 
				
			||||||
    DomainCPUTopology, DomainCPUXML, DomainMemoryXML, DomainVCPUXML, DomainXML, FeaturesXML,
 | 
					    DomainCPUTopology, DomainCPUXML, DomainMemoryXML, DomainNetInterfaceXML, DomainVCPUXML,
 | 
				
			||||||
    GraphicsXML, NetworkDHCPRangeXML, NetworkDHCPXML, NetworkDNSForwarderXML, NetworkDNSXML,
 | 
					    DomainXML, FeaturesXML, GraphicsXML, NetworkDHCPRangeXML, NetworkDHCPXML,
 | 
				
			||||||
    NetworkDomainXML, NetworkForwardXML, NetworkIPXML, NetworkXML, OSLoaderXML, OSTypeXML, XMLUuid,
 | 
					    NetworkDNSForwarderXML, NetworkDNSXML, NetworkDomainXML, NetworkForwardXML, NetworkIPXML,
 | 
				
			||||||
    ACPIXML, OSXML,
 | 
					    NetworkXML, OSLoaderXML, OSTypeXML, XMLUuid, ACPIXML, OSXML,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction;
 | 
					use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction;
 | 
				
			||||||
use crate::utils::disks_utils::Disk;
 | 
					use crate::utils::disks_utils::Disk;
 | 
				
			||||||
@@ -64,6 +64,13 @@ pub enum VMArchitecture {
 | 
				
			|||||||
    X86_64,
 | 
					    X86_64,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
 | 
					#[serde(tag = "type")]
 | 
				
			||||||
 | 
					pub enum Network {
 | 
				
			||||||
 | 
					    UserspaceSLIRPStack,
 | 
				
			||||||
 | 
					    // TODO : complete network types
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
					#[derive(serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
pub struct VMInfo {
 | 
					pub struct VMInfo {
 | 
				
			||||||
    /// VM name (alphanumeric characters only)
 | 
					    /// VM name (alphanumeric characters only)
 | 
				
			||||||
@@ -84,7 +91,8 @@ pub struct VMInfo {
 | 
				
			|||||||
    pub iso_file: Option<String>,
 | 
					    pub iso_file: Option<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
 | 
					    /// 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<Disk>,
 | 
					    pub disks: Vec<Disk>,
 | 
				
			||||||
    // TODO : network interfaces
 | 
					    /// Network cards
 | 
				
			||||||
 | 
					    pub networks: Vec<Network>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl VMInfo {
 | 
					impl VMInfo {
 | 
				
			||||||
@@ -213,6 +221,15 @@ impl VMInfo {
 | 
				
			|||||||
            })
 | 
					            })
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let mut networks = vec![];
 | 
				
			||||||
 | 
					        for n in self.networks {
 | 
				
			||||||
 | 
					            networks.push(match n {
 | 
				
			||||||
 | 
					                Network::UserspaceSLIRPStack => DomainNetInterfaceXML {
 | 
				
			||||||
 | 
					                    r#type: "user".to_string(),
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(DomainXML {
 | 
					        Ok(DomainXML {
 | 
				
			||||||
            r#type: "kvm".to_string(),
 | 
					            r#type: "kvm".to_string(),
 | 
				
			||||||
            name: self.name,
 | 
					            name: self.name,
 | 
				
			||||||
@@ -245,6 +262,7 @@ impl VMInfo {
 | 
				
			|||||||
            devices: DevicesXML {
 | 
					            devices: DevicesXML {
 | 
				
			||||||
                graphics: vnc_graphics,
 | 
					                graphics: vnc_graphics,
 | 
				
			||||||
                disks,
 | 
					                disks,
 | 
				
			||||||
 | 
					                net_interfaces: networks,
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            memory: DomainMemoryXML {
 | 
					            memory: DomainMemoryXML {
 | 
				
			||||||
@@ -319,6 +337,18 @@ impl VMInfo {
 | 
				
			|||||||
                .filter(|d| d.device == "disk")
 | 
					                .filter(|d| d.device == "disk")
 | 
				
			||||||
                .map(|d| Disk::load_from_file(&d.source.file).unwrap())
 | 
					                .map(|d| Disk::load_from_file(&d.source.file).unwrap())
 | 
				
			||||||
                .collect(),
 | 
					                .collect(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            networks: domain
 | 
				
			||||||
 | 
					                .devices
 | 
				
			||||||
 | 
					                .net_interfaces
 | 
				
			||||||
 | 
					                .iter()
 | 
				
			||||||
 | 
					                .map(|d| match d.r#type.as_str() {
 | 
				
			||||||
 | 
					                    "user" => Ok(Network::UserspaceSLIRPStack),
 | 
				
			||||||
 | 
					                    a => Err(LibVirtStructError::DomainExtraction(format!(
 | 
				
			||||||
 | 
					                        "Unknown network interface type: {a}! "
 | 
				
			||||||
 | 
					                    ))),
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .collect::<Result<Vec<_>, _>>()?,
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,6 +30,12 @@ export interface VMDisk {
 | 
				
			|||||||
  deleteType?: "keepfile" | "deletefile";
 | 
					  deleteType?: "keepfile" | "deletefile";
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type VMNetInterface = VMNetUserspaceSLIRPStack;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface VMNetUserspaceSLIRPStack {
 | 
				
			||||||
 | 
					  type: "UserspaceSLIRPStack";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface VMInfoInterface {
 | 
					interface VMInfoInterface {
 | 
				
			||||||
  name: string;
 | 
					  name: string;
 | 
				
			||||||
  uuid?: string;
 | 
					  uuid?: string;
 | 
				
			||||||
@@ -43,6 +49,7 @@ interface VMInfoInterface {
 | 
				
			|||||||
  vnc_access: boolean;
 | 
					  vnc_access: boolean;
 | 
				
			||||||
  iso_file?: string;
 | 
					  iso_file?: string;
 | 
				
			||||||
  disks: VMDisk[];
 | 
					  disks: VMDisk[];
 | 
				
			||||||
 | 
					  networks: VMNetInterface[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class VMInfo implements VMInfoInterface {
 | 
					export class VMInfo implements VMInfoInterface {
 | 
				
			||||||
@@ -58,6 +65,7 @@ export class VMInfo implements VMInfoInterface {
 | 
				
			|||||||
  vnc_access: boolean;
 | 
					  vnc_access: boolean;
 | 
				
			||||||
  iso_file?: string;
 | 
					  iso_file?: string;
 | 
				
			||||||
  disks: VMDisk[];
 | 
					  disks: VMDisk[];
 | 
				
			||||||
 | 
					  networks: VMNetUserspaceSLIRPStack[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(int: VMInfoInterface) {
 | 
					  constructor(int: VMInfoInterface) {
 | 
				
			||||||
    this.name = int.name;
 | 
					    this.name = int.name;
 | 
				
			||||||
@@ -72,6 +80,7 @@ export class VMInfo implements VMInfoInterface {
 | 
				
			|||||||
    this.vnc_access = int.vnc_access;
 | 
					    this.vnc_access = int.vnc_access;
 | 
				
			||||||
    this.iso_file = int.iso_file;
 | 
					    this.iso_file = int.iso_file;
 | 
				
			||||||
    this.disks = int.disks;
 | 
					    this.disks = int.disks;
 | 
				
			||||||
 | 
					    this.networks = int.networks;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static NewEmpty(): VMInfo {
 | 
					  static NewEmpty(): VMInfo {
 | 
				
			||||||
@@ -83,6 +92,7 @@ export class VMInfo implements VMInfoInterface {
 | 
				
			|||||||
      number_vcpu: 1,
 | 
					      number_vcpu: 1,
 | 
				
			||||||
      vnc_access: true,
 | 
					      vnc_access: true,
 | 
				
			||||||
      disks: [],
 | 
					      disks: [],
 | 
				
			||||||
 | 
					      networks: [],
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,16 @@
 | 
				
			|||||||
import { FormControl, InputLabel, MenuItem, Select } from "@mui/material";
 | 
					import {
 | 
				
			||||||
 | 
					  FormControl,
 | 
				
			||||||
 | 
					  InputLabel,
 | 
				
			||||||
 | 
					  MenuItem,
 | 
				
			||||||
 | 
					  Select,
 | 
				
			||||||
 | 
					  Typography,
 | 
				
			||||||
 | 
					} from "@mui/material";
 | 
				
			||||||
import { TextInput } from "./TextInput";
 | 
					import { TextInput } from "./TextInput";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface SelectOption {
 | 
					export interface SelectOption {
 | 
				
			||||||
  value?: string;
 | 
					  value?: string;
 | 
				
			||||||
  label: string;
 | 
					  label: string;
 | 
				
			||||||
 | 
					  description?: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function SelectInput(p: {
 | 
					export function SelectInput(p: {
 | 
				
			||||||
@@ -33,7 +40,18 @@ export function SelectInput(p: {
 | 
				
			|||||||
            value={e.value}
 | 
					            value={e.value}
 | 
				
			||||||
            style={{ fontStyle: e.value === undefined ? "italic" : undefined }}
 | 
					            style={{ fontStyle: e.value === undefined ? "italic" : undefined }}
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
            {e.label}
 | 
					            <div>
 | 
				
			||||||
 | 
					              {e.label}
 | 
				
			||||||
 | 
					              {e.description && (
 | 
				
			||||||
 | 
					                <Typography
 | 
				
			||||||
 | 
					                  component={"div"}
 | 
				
			||||||
 | 
					                  variant="caption"
 | 
				
			||||||
 | 
					                  style={{ whiteSpace: "normal" }}
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                  {e.description}
 | 
				
			||||||
 | 
					                </Typography>
 | 
				
			||||||
 | 
					              )}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
          </MenuItem>
 | 
					          </MenuItem>
 | 
				
			||||||
        ))}
 | 
					        ))}
 | 
				
			||||||
      </Select>
 | 
					      </Select>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										116
									
								
								virtweb_frontend/src/widgets/forms/VMNetworksList.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								virtweb_frontend/src/widgets/forms/VMNetworksList.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,116 @@
 | 
				
			|||||||
 | 
					import { mdiNetworkOutline } from "@mdi/js";
 | 
				
			||||||
 | 
					import Icon from "@mdi/react";
 | 
				
			||||||
 | 
					import DeleteIcon from "@mui/icons-material/Delete";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Avatar,
 | 
				
			||||||
 | 
					  Button,
 | 
				
			||||||
 | 
					  IconButton,
 | 
				
			||||||
 | 
					  ListItem,
 | 
				
			||||||
 | 
					  ListItemAvatar,
 | 
				
			||||||
 | 
					  ListItemText,
 | 
				
			||||||
 | 
					  Tooltip,
 | 
				
			||||||
 | 
					} from "@mui/material";
 | 
				
			||||||
 | 
					import { VMInfo, VMNetInterface } from "../../api/VMApi";
 | 
				
			||||||
 | 
					import { useConfirm } from "../../hooks/providers/ConfirmDialogProvider";
 | 
				
			||||||
 | 
					import { SelectInput } from "./SelectInput";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function VMNetworksList(p: {
 | 
				
			||||||
 | 
					  vm: VMInfo;
 | 
				
			||||||
 | 
					  onChange?: () => void;
 | 
				
			||||||
 | 
					  editable: boolean;
 | 
				
			||||||
 | 
					}): React.ReactElement {
 | 
				
			||||||
 | 
					  const addNew = () => {
 | 
				
			||||||
 | 
					    p.vm.networks.push({ type: "UserspaceSLIRPStack" });
 | 
				
			||||||
 | 
					    p.onChange?.();
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      {/* networks list */}
 | 
				
			||||||
 | 
					      {p.vm.networks.map((n, num) => (
 | 
				
			||||||
 | 
					        <NetworkInfo
 | 
				
			||||||
 | 
					          key={num}
 | 
				
			||||||
 | 
					          editable={p.editable}
 | 
				
			||||||
 | 
					          network={n}
 | 
				
			||||||
 | 
					          onChange={p.onChange}
 | 
				
			||||||
 | 
					          removeFromList={() => {
 | 
				
			||||||
 | 
					            p.vm.networks.splice(num, 1);
 | 
				
			||||||
 | 
					            p.onChange?.();
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      ))}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {p.editable && (
 | 
				
			||||||
 | 
					        <Button onClick={addNew}>Add a new network interface</Button>
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function NetworkInfo(p: {
 | 
				
			||||||
 | 
					  editable: boolean;
 | 
				
			||||||
 | 
					  network: VMNetInterface;
 | 
				
			||||||
 | 
					  onChange?: () => void;
 | 
				
			||||||
 | 
					  removeFromList: () => void;
 | 
				
			||||||
 | 
					}): React.ReactElement {
 | 
				
			||||||
 | 
					  const confirm = useConfirm();
 | 
				
			||||||
 | 
					  const deleteNetwork = async () => {
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					      !(await confirm("Do you really want to remove this network interface?"))
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    p.removeFromList();
 | 
				
			||||||
 | 
					    p.onChange?.();
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					      <ListItem
 | 
				
			||||||
 | 
					        secondaryAction={
 | 
				
			||||||
 | 
					          p.editable && (
 | 
				
			||||||
 | 
					            <IconButton
 | 
				
			||||||
 | 
					              edge="end"
 | 
				
			||||||
 | 
					              aria-label="remove network"
 | 
				
			||||||
 | 
					              onClick={deleteNetwork}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              <Tooltip title="Remove network">
 | 
				
			||||||
 | 
					                <DeleteIcon />
 | 
				
			||||||
 | 
					              </Tooltip>
 | 
				
			||||||
 | 
					            </IconButton>
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <ListItemAvatar>
 | 
				
			||||||
 | 
					          <Avatar>
 | 
				
			||||||
 | 
					            <Icon path={mdiNetworkOutline} />
 | 
				
			||||||
 | 
					          </Avatar>
 | 
				
			||||||
 | 
					        </ListItemAvatar>
 | 
				
			||||||
 | 
					        <ListItemText
 | 
				
			||||||
 | 
					          primary={
 | 
				
			||||||
 | 
					            p.editable ? (
 | 
				
			||||||
 | 
					              <SelectInput
 | 
				
			||||||
 | 
					                label=""
 | 
				
			||||||
 | 
					                editable
 | 
				
			||||||
 | 
					                value={p.network.type}
 | 
				
			||||||
 | 
					                onValueChange={(v) => {
 | 
				
			||||||
 | 
					                  p.network.type = v as any;
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					                options={[
 | 
				
			||||||
 | 
					                  {
 | 
				
			||||||
 | 
					                    label: "Userspace SLIRP stack",
 | 
				
			||||||
 | 
					                    value: "UserspaceSLIRPStack",
 | 
				
			||||||
 | 
					                    description:
 | 
				
			||||||
 | 
					                      "Provides a virtual LAN with NAT to the outside world. The virtual network has DHCP & DNS services",
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					                ]}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            ) : (
 | 
				
			||||||
 | 
					              p.network.type
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </ListItem>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -13,6 +13,7 @@ import { VMDisksList } from "../forms/VMDisksList";
 | 
				
			|||||||
import { VMSelectIsoInput } from "../forms/VMSelectIsoInput";
 | 
					import { VMSelectIsoInput } from "../forms/VMSelectIsoInput";
 | 
				
			||||||
import { VMScreenshot } from "./VMScreenshot";
 | 
					import { VMScreenshot } from "./VMScreenshot";
 | 
				
			||||||
import { ResAutostartInput } from "../forms/ResAutostartInput";
 | 
					import { ResAutostartInput } from "../forms/ResAutostartInput";
 | 
				
			||||||
 | 
					import { VMNetworksList } from "../forms/VMNetworksList";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface DetailsProps {
 | 
					interface DetailsProps {
 | 
				
			||||||
  vm: VMInfo;
 | 
					  vm: VMInfo;
 | 
				
			||||||
@@ -202,6 +203,11 @@ function VMDetailsInner(
 | 
				
			|||||||
        />
 | 
					        />
 | 
				
			||||||
        <VMDisksList vm={p.vm} editable={p.editable} onChange={p.onChange} />
 | 
					        <VMDisksList vm={p.vm} editable={p.editable} onChange={p.onChange} />
 | 
				
			||||||
      </EditSection>
 | 
					      </EditSection>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {/* Networks section */}
 | 
				
			||||||
 | 
					      <EditSection title="Networks">
 | 
				
			||||||
 | 
					        <VMNetworksList vm={p.vm} editable={p.editable} onChange={p.onChange} />
 | 
				
			||||||
 | 
					      </EditSection>
 | 
				
			||||||
    </Grid>
 | 
					    </Grid>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user