Can define network filters
This commit is contained in:
		@@ -1,4 +1,5 @@
 | 
				
			|||||||
use crate::libvirt_client::LibVirtClient;
 | 
					use crate::libvirt_client::LibVirtClient;
 | 
				
			||||||
 | 
					use actix_http::StatusCode;
 | 
				
			||||||
use actix_web::body::BoxBody;
 | 
					use actix_web::body::BoxBody;
 | 
				
			||||||
use actix_web::{web, HttpResponse};
 | 
					use actix_web::{web, HttpResponse};
 | 
				
			||||||
use std::error::Error;
 | 
					use std::error::Error;
 | 
				
			||||||
@@ -32,8 +33,15 @@ impl Display for HttpErr {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl actix_web::error::ResponseError for HttpErr {
 | 
					impl actix_web::error::ResponseError for HttpErr {
 | 
				
			||||||
 | 
					    fn status_code(&self) -> StatusCode {
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            HttpErr::Err(_) => StatusCode::INTERNAL_SERVER_ERROR,
 | 
				
			||||||
 | 
					            HttpErr::HTTPResponse(r) => r.status(),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    fn error_response(&self) -> HttpResponse<BoxBody> {
 | 
					    fn error_response(&self) -> HttpResponse<BoxBody> {
 | 
				
			||||||
        log::error!("Error while processing request! {}", self);
 | 
					        log::error!("Error while processing request! {}", self);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        HttpResponse::InternalServerError().body("Failed to execute request!")
 | 
					        HttpResponse::InternalServerError().body("Failed to execute request!")
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -112,10 +112,15 @@ pub async fn update(
 | 
				
			|||||||
    id: web::Path<SingleVMUUidReq>,
 | 
					    id: web::Path<SingleVMUUidReq>,
 | 
				
			||||||
    req: web::Json<VMInfo>,
 | 
					    req: web::Json<VMInfo>,
 | 
				
			||||||
) -> HttpResult {
 | 
					) -> HttpResult {
 | 
				
			||||||
    let mut domain = req.0.as_tomain().map_err(|e| {
 | 
					    let mut domain = match req.0.as_tomain() {
 | 
				
			||||||
 | 
					        Ok(d) => d,
 | 
				
			||||||
 | 
					        Err(e) => {
 | 
				
			||||||
            log::error!("Failed to extract domain info! {e}");
 | 
					            log::error!("Failed to extract domain info! {e}");
 | 
				
			||||||
 | 
					            return Ok(
 | 
				
			||||||
                HttpResponse::BadRequest().json(format!("Failed to extract domain info! {e}"))
 | 
					                HttpResponse::BadRequest().json(format!("Failed to extract domain info! {e}"))
 | 
				
			||||||
    })?;
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    domain.uuid = Some(id.uid);
 | 
					    domain.uuid = Some(id.uid);
 | 
				
			||||||
    if let Err(e) = client.update_domain(req.0, domain).await {
 | 
					    if let Err(e) = client.update_domain(req.0, domain).await {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -63,6 +63,24 @@ pub struct NetIntModelXML {
 | 
				
			|||||||
    pub r#type: String,
 | 
					    pub r#type: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
 | 
					#[serde(rename = "filterref")]
 | 
				
			||||||
 | 
					pub struct NetIntFilterParameterXML {
 | 
				
			||||||
 | 
					    #[serde(rename = "@name")]
 | 
				
			||||||
 | 
					    pub name: String,
 | 
				
			||||||
 | 
					    #[serde(rename = "@value")]
 | 
				
			||||||
 | 
					    pub value: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
 | 
					#[serde(rename = "filterref")]
 | 
				
			||||||
 | 
					pub struct NetIntfilterRefXML {
 | 
				
			||||||
 | 
					    #[serde(rename = "@filter")]
 | 
				
			||||||
 | 
					    pub filter: String,
 | 
				
			||||||
 | 
					    #[serde(rename = "parameter", default)]
 | 
				
			||||||
 | 
					    pub parameters: Vec<NetIntFilterParameterXML>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
					#[derive(serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
#[serde(rename = "interface")]
 | 
					#[serde(rename = "interface")]
 | 
				
			||||||
pub struct DomainNetInterfaceXML {
 | 
					pub struct DomainNetInterfaceXML {
 | 
				
			||||||
@@ -73,6 +91,8 @@ pub struct DomainNetInterfaceXML {
 | 
				
			|||||||
    pub source: Option<NetIntSourceXML>,
 | 
					    pub source: Option<NetIntSourceXML>,
 | 
				
			||||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
					    #[serde(skip_serializing_if = "Option::is_none")]
 | 
				
			||||||
    pub model: Option<NetIntModelXML>,
 | 
					    pub model: Option<NetIntModelXML>,
 | 
				
			||||||
 | 
					    #[serde(skip_serializing_if = "Option::is_none")]
 | 
				
			||||||
 | 
					    pub filterref: Option<NetIntfilterRefXML>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
					#[derive(serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,11 +24,24 @@ pub enum VMArchitecture {
 | 
				
			|||||||
    X86_64,
 | 
					    X86_64,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
 | 
					pub struct NWFilterParam {
 | 
				
			||||||
 | 
					    name: String,
 | 
				
			||||||
 | 
					    value: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
 | 
					pub struct NWFilterRef {
 | 
				
			||||||
 | 
					    name: String,
 | 
				
			||||||
 | 
					    parameters: Vec<NWFilterParam>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
					#[derive(serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
pub struct Network {
 | 
					pub struct Network {
 | 
				
			||||||
    mac: String,
 | 
					 | 
				
			||||||
    #[serde(flatten)]
 | 
					    #[serde(flatten)]
 | 
				
			||||||
    r#type: NetworkType,
 | 
					    r#type: NetworkType,
 | 
				
			||||||
 | 
					    mac: String,
 | 
				
			||||||
 | 
					    nwfilterref: Option<NWFilterRef>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
					#[derive(serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
@@ -157,6 +170,67 @@ impl VMInfo {
 | 
				
			|||||||
            false => (None, None),
 | 
					            false => (None, None),
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Process network card
 | 
				
			||||||
 | 
					        let mut networks = vec![];
 | 
				
			||||||
 | 
					        for n in &self.networks {
 | 
				
			||||||
 | 
					            let mac = NetMacAddress {
 | 
				
			||||||
 | 
					                address: n.mac.to_string(),
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let model = Some(NetIntModelXML {
 | 
				
			||||||
 | 
					                r#type: "virtio".to_string(),
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let filterref = if let Some(n) = &n.nwfilterref {
 | 
				
			||||||
 | 
					                if !regex!("^[a-zA-Z0-9\\_\\-]+$").is_match(&n.name) {
 | 
				
			||||||
 | 
					                    log::error!("Filter ref name {} is invalid", n.name);
 | 
				
			||||||
 | 
					                    return Err(StructureExtraction("Network filter ref name is invalid!").into());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                for p in &n.parameters {
 | 
				
			||||||
 | 
					                    if !regex!("^[a-zA-Z0-9_-]+$").is_match(&p.name) {
 | 
				
			||||||
 | 
					                        return Err(StructureExtraction(
 | 
				
			||||||
 | 
					                            "Network filter ref parameter name is invalid!",
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                        .into());
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Some(NetIntfilterRefXML {
 | 
				
			||||||
 | 
					                    filter: n.name.to_string(),
 | 
				
			||||||
 | 
					                    parameters: n
 | 
				
			||||||
 | 
					                        .parameters
 | 
				
			||||||
 | 
					                        .iter()
 | 
				
			||||||
 | 
					                        .map(|f| NetIntFilterParameterXML {
 | 
				
			||||||
 | 
					                            name: f.name.to_string(),
 | 
				
			||||||
 | 
					                            value: f.value.to_string(),
 | 
				
			||||||
 | 
					                        })
 | 
				
			||||||
 | 
					                        .collect(),
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                None
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            networks.push(match &n.r#type {
 | 
				
			||||||
 | 
					                NetworkType::UserspaceSLIRPStack => DomainNetInterfaceXML {
 | 
				
			||||||
 | 
					                    mac,
 | 
				
			||||||
 | 
					                    r#type: "user".to_string(),
 | 
				
			||||||
 | 
					                    source: None,
 | 
				
			||||||
 | 
					                    model,
 | 
				
			||||||
 | 
					                    filterref,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                NetworkType::DefinedNetwork { network } => DomainNetInterfaceXML {
 | 
				
			||||||
 | 
					                    mac,
 | 
				
			||||||
 | 
					                    r#type: "network".to_string(),
 | 
				
			||||||
 | 
					                    source: Some(NetIntSourceXML {
 | 
				
			||||||
 | 
					                        network: network.to_string(),
 | 
				
			||||||
 | 
					                    }),
 | 
				
			||||||
 | 
					                    model,
 | 
				
			||||||
 | 
					                    filterref,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Check disks name for duplicates
 | 
					        // Check disks name for duplicates
 | 
				
			||||||
        for disk in &self.disks {
 | 
					        for disk in &self.disks {
 | 
				
			||||||
            if self.disks.iter().filter(|d| d.name == disk.name).count() > 1 {
 | 
					            if self.disks.iter().filter(|d| d.name == disk.name).count() > 1 {
 | 
				
			||||||
@@ -164,7 +238,8 @@ impl VMInfo {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Apply disks configuration
 | 
					        // 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.disks {
 | 
				
			||||||
            disk.check_config()?;
 | 
					            disk.check_config()?;
 | 
				
			||||||
            disk.apply_config(uuid)?;
 | 
					            disk.apply_config(uuid)?;
 | 
				
			||||||
@@ -199,34 +274,6 @@ impl VMInfo {
 | 
				
			|||||||
            })
 | 
					            })
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let mut networks = vec![];
 | 
					 | 
				
			||||||
        for n in &self.networks {
 | 
					 | 
				
			||||||
            networks.push(match &n.r#type {
 | 
					 | 
				
			||||||
                NetworkType::UserspaceSLIRPStack => DomainNetInterfaceXML {
 | 
					 | 
				
			||||||
                    mac: NetMacAddress {
 | 
					 | 
				
			||||||
                        address: n.mac.to_string(),
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    r#type: "user".to_string(),
 | 
					 | 
				
			||||||
                    source: None,
 | 
					 | 
				
			||||||
                    model: Some(NetIntModelXML {
 | 
					 | 
				
			||||||
                        r#type: "virtio".to_string(),
 | 
					 | 
				
			||||||
                    }),
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                NetworkType::DefinedNetwork { network } => DomainNetInterfaceXML {
 | 
					 | 
				
			||||||
                    mac: NetMacAddress {
 | 
					 | 
				
			||||||
                        address: n.mac.to_string(),
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    r#type: "network".to_string(),
 | 
					 | 
				
			||||||
                    source: Some(NetIntSourceXML {
 | 
					 | 
				
			||||||
                        network: network.to_string(),
 | 
					 | 
				
			||||||
                    }),
 | 
					 | 
				
			||||||
                    model: Some(NetIntModelXML {
 | 
					 | 
				
			||||||
                        r#type: "virtio".to_string(),
 | 
					 | 
				
			||||||
                    }),
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Ok(DomainXML {
 | 
					        Ok(DomainXML {
 | 
				
			||||||
            r#type: "kvm".to_string(),
 | 
					            r#type: "kvm".to_string(),
 | 
				
			||||||
            name: self.name.to_string(),
 | 
					            name: self.name.to_string(),
 | 
				
			||||||
@@ -376,6 +423,17 @@ impl VMInfo {
 | 
				
			|||||||
                                )));
 | 
					                                )));
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        },
 | 
					                        },
 | 
				
			||||||
 | 
					                        nwfilterref: d.filterref.as_ref().map(|f| NWFilterRef {
 | 
				
			||||||
 | 
					                            name: f.filter.to_string(),
 | 
				
			||||||
 | 
					                            parameters: f
 | 
				
			||||||
 | 
					                                .parameters
 | 
				
			||||||
 | 
					                                .iter()
 | 
				
			||||||
 | 
					                                .map(|p| NWFilterParam {
 | 
				
			||||||
 | 
					                                    name: p.name.to_string(),
 | 
				
			||||||
 | 
					                                    value: p.value.to_string(),
 | 
				
			||||||
 | 
					                                })
 | 
				
			||||||
 | 
					                                .collect(),
 | 
				
			||||||
 | 
					                        }),
 | 
				
			||||||
                    })
 | 
					                    })
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
                .collect::<Result<Vec<_>, _>>()?,
 | 
					                .collect::<Result<Vec<_>, _>>()?,
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										56
									
								
								virtweb_frontend/src/api/NWFilterApi.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								virtweb_frontend/src/api/NWFilterApi.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					import { APIClient } from "./ApiClient";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface NWFilterChain {
 | 
				
			||||||
 | 
					  protocol: string;
 | 
				
			||||||
 | 
					  suffix?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface NWFSAll {
 | 
				
			||||||
 | 
					  type: "all";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface NWFSMac {
 | 
				
			||||||
 | 
					  type: "mac";
 | 
				
			||||||
 | 
					  src_mac_addr?: string;
 | 
				
			||||||
 | 
					  src_mac_mask?: string;
 | 
				
			||||||
 | 
					  dst_mac_addr?: string;
 | 
				
			||||||
 | 
					  dst_mac_mask?: string;
 | 
				
			||||||
 | 
					  comment?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO : complete
 | 
				
			||||||
 | 
					export type NWFSelector = NWFSAll | NWFSMac;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface NWFilterRule {
 | 
				
			||||||
 | 
					  action: "drop" | "reject" | "accept" | "return" | "continue";
 | 
				
			||||||
 | 
					  direction: "in" | "out" | "inout";
 | 
				
			||||||
 | 
					  priority?: number;
 | 
				
			||||||
 | 
					  selectors: NWFSelector[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface NWFilter {
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					  chain?: NWFilterChain;
 | 
				
			||||||
 | 
					  priority?: number;
 | 
				
			||||||
 | 
					  uuid?: string;
 | 
				
			||||||
 | 
					  join_filters: string[];
 | 
				
			||||||
 | 
					  rules: NWFilterRule[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class NWFilterApi {
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Get the entire list of networks
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  static async GetList(): Promise<NWFilter[]> {
 | 
				
			||||||
 | 
					    const list: NWFilter[] = (
 | 
				
			||||||
 | 
					      await APIClient.exec({
 | 
				
			||||||
 | 
					        method: "GET",
 | 
				
			||||||
 | 
					        uri: "/nwfilter/list",
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    ).data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    list.sort((a, b) => a.name.localeCompare(b.name));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return list;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -30,16 +30,30 @@ export interface VMDisk {
 | 
				
			|||||||
  deleteType?: "keepfile" | "deletefile";
 | 
					  deleteType?: "keepfile" | "deletefile";
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type VMNetInterface = VMNetUserspaceSLIRPStack | VMNetDefinedNetwork;
 | 
					export interface VMNetInterfaceFilterParams {
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					  value: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface VMNetInterfaceFilter {
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					  parameters: VMNetInterfaceFilterParams[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type VMNetInterface = (VMNetUserspaceSLIRPStack | VMNetDefinedNetwork) &
 | 
				
			||||||
 | 
					  VMNetInterfaceBase;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface VMNetInterfaceBase {
 | 
				
			||||||
 | 
					  mac: string;
 | 
				
			||||||
 | 
					  nwfilterref?: VMNetInterfaceFilter;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface VMNetUserspaceSLIRPStack {
 | 
					export interface VMNetUserspaceSLIRPStack {
 | 
				
			||||||
  type: "UserspaceSLIRPStack";
 | 
					  type: "UserspaceSLIRPStack";
 | 
				
			||||||
  mac: string;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface VMNetDefinedNetwork {
 | 
					export interface VMNetDefinedNetwork {
 | 
				
			||||||
  type: "DefinedNetwork";
 | 
					  type: "DefinedNetwork";
 | 
				
			||||||
  mac: string;
 | 
					 | 
				
			||||||
  network: string;
 | 
					  network: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -66,7 +66,7 @@ export function EditVMRoute(): React.ReactElement {
 | 
				
			|||||||
      navigate(v.ViewURL);
 | 
					      navigate(v.ViewURL);
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
      console.error(e);
 | 
					      console.error(e);
 | 
				
			||||||
      alert("Failed to update VM info!");
 | 
					      alert(`Failed to update VM info!\n${e}`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,7 @@ import { LenConstraint } from "../../api/ServerApi";
 | 
				
			|||||||
 * Couple / Member property edition
 | 
					 * Couple / Member property edition
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function TextInput(p: {
 | 
					export function TextInput(p: {
 | 
				
			||||||
  label: string;
 | 
					  label?: string;
 | 
				
			||||||
  editable: boolean;
 | 
					  editable: boolean;
 | 
				
			||||||
  value?: string;
 | 
					  value?: string;
 | 
				
			||||||
  onValueChange?: (newVal: string | undefined) => void;
 | 
					  onValueChange?: (newVal: string | undefined) => void;
 | 
				
			||||||
@@ -15,6 +15,7 @@ export function TextInput(p: {
 | 
				
			|||||||
  minRows?: number;
 | 
					  minRows?: number;
 | 
				
			||||||
  maxRows?: number;
 | 
					  maxRows?: number;
 | 
				
			||||||
  type?: React.HTMLInputTypeAttribute;
 | 
					  type?: React.HTMLInputTypeAttribute;
 | 
				
			||||||
 | 
					  style?: React.CSSProperties;
 | 
				
			||||||
}): React.ReactElement {
 | 
					}): React.ReactElement {
 | 
				
			||||||
  if (!p.editable && (p.value ?? "") === "") return <></>;
 | 
					  if (!p.editable && (p.value ?? "") === "") return <></>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -48,7 +49,7 @@ export function TextInput(p: {
 | 
				
			|||||||
        type: p.type,
 | 
					        type: p.type,
 | 
				
			||||||
      }}
 | 
					      }}
 | 
				
			||||||
      variant={"standard"}
 | 
					      variant={"standard"}
 | 
				
			||||||
      style={{ width: "100%", marginBottom: "15px" }}
 | 
					      style={p.style ?? { width: "100%", marginBottom: "15px" }}
 | 
				
			||||||
      multiline={p.multiline}
 | 
					      multiline={p.multiline}
 | 
				
			||||||
      minRows={p.minRows}
 | 
					      minRows={p.minRows}
 | 
				
			||||||
      maxRows={p.maxRows}
 | 
					      maxRows={p.maxRows}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,97 @@
 | 
				
			|||||||
 | 
					import DeleteIcon from "@mui/icons-material/Delete";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Button,
 | 
				
			||||||
 | 
					  IconButton,
 | 
				
			||||||
 | 
					  Paper,
 | 
				
			||||||
 | 
					  Table,
 | 
				
			||||||
 | 
					  TableBody,
 | 
				
			||||||
 | 
					  TableCell,
 | 
				
			||||||
 | 
					  TableContainer,
 | 
				
			||||||
 | 
					  TableHead,
 | 
				
			||||||
 | 
					  TableRow,
 | 
				
			||||||
 | 
					  Tooltip,
 | 
				
			||||||
 | 
					} from "@mui/material";
 | 
				
			||||||
 | 
					import { VMNetInterfaceFilter } from "../../api/VMApi";
 | 
				
			||||||
 | 
					import { TextInput } from "./TextInput";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function VMNetworkFilterParameters(p: {
 | 
				
			||||||
 | 
					  editable: boolean;
 | 
				
			||||||
 | 
					  filterref: VMNetInterfaceFilter;
 | 
				
			||||||
 | 
					  onChange?: () => void;
 | 
				
			||||||
 | 
					}): React.ReactElement {
 | 
				
			||||||
 | 
					  if (!p.editable && p.filterref.parameters.length === 0) return <></>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const addParameter = () => {
 | 
				
			||||||
 | 
					    p.filterref.parameters.push({ name: "", value: "" });
 | 
				
			||||||
 | 
					    p.onChange?.();
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      {p.filterref.parameters.length > 0 && (
 | 
				
			||||||
 | 
					        <TableContainer component={Paper}>
 | 
				
			||||||
 | 
					          <Table size="small" aria-label="nwfilter parameters">
 | 
				
			||||||
 | 
					            <TableHead>
 | 
				
			||||||
 | 
					              <TableRow>
 | 
				
			||||||
 | 
					                <TableCell>Name</TableCell>
 | 
				
			||||||
 | 
					                <TableCell>Value</TableCell>
 | 
				
			||||||
 | 
					                {p.editable && <TableCell></TableCell>}
 | 
				
			||||||
 | 
					              </TableRow>
 | 
				
			||||||
 | 
					            </TableHead>
 | 
				
			||||||
 | 
					            <TableBody>
 | 
				
			||||||
 | 
					              {p.filterref.parameters.map((row, index) => (
 | 
				
			||||||
 | 
					                <TableRow
 | 
				
			||||||
 | 
					                  key={index}
 | 
				
			||||||
 | 
					                  sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                  <TableCell
 | 
				
			||||||
 | 
					                    component="th"
 | 
				
			||||||
 | 
					                    scope="row"
 | 
				
			||||||
 | 
					                    style={{ padding: "0px 5px" }}
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                    <TextInput
 | 
				
			||||||
 | 
					                      editable={p.editable}
 | 
				
			||||||
 | 
					                      value={row.name}
 | 
				
			||||||
 | 
					                      onValueChange={(v) => {
 | 
				
			||||||
 | 
					                        row.name = v ?? "";
 | 
				
			||||||
 | 
					                        p.onChange?.();
 | 
				
			||||||
 | 
					                      }}
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                  </TableCell>
 | 
				
			||||||
 | 
					                  <TableCell scope="row" style={{ padding: "0px 5px" }}>
 | 
				
			||||||
 | 
					                    <TextInput
 | 
				
			||||||
 | 
					                      editable={p.editable}
 | 
				
			||||||
 | 
					                      value={row.value}
 | 
				
			||||||
 | 
					                      onValueChange={(v) => {
 | 
				
			||||||
 | 
					                        row.value = v ?? "";
 | 
				
			||||||
 | 
					                        p.onChange?.();
 | 
				
			||||||
 | 
					                      }}
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                  </TableCell>
 | 
				
			||||||
 | 
					                  {p.editable && (
 | 
				
			||||||
 | 
					                    <TableCell style={{ padding: "0px" }}>
 | 
				
			||||||
 | 
					                      <IconButton
 | 
				
			||||||
 | 
					                        onClick={() => {
 | 
				
			||||||
 | 
					                          p.filterref.parameters.splice(index, 1);
 | 
				
			||||||
 | 
					                          p.onChange?.();
 | 
				
			||||||
 | 
					                        }}
 | 
				
			||||||
 | 
					                      >
 | 
				
			||||||
 | 
					                        <Tooltip title="Remove parameter">
 | 
				
			||||||
 | 
					                          <DeleteIcon />
 | 
				
			||||||
 | 
					                        </Tooltip>
 | 
				
			||||||
 | 
					                      </IconButton>
 | 
				
			||||||
 | 
					                    </TableCell>
 | 
				
			||||||
 | 
					                  )}
 | 
				
			||||||
 | 
					                </TableRow>
 | 
				
			||||||
 | 
					              ))}
 | 
				
			||||||
 | 
					            </TableBody>
 | 
				
			||||||
 | 
					          </Table>
 | 
				
			||||||
 | 
					        </TableContainer>
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {p.editable && (
 | 
				
			||||||
 | 
					        <Button onClick={addParameter}>Add a filter ref parameter</Button>
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -10,19 +10,22 @@ import {
 | 
				
			|||||||
  ListItemText,
 | 
					  ListItemText,
 | 
				
			||||||
  Tooltip,
 | 
					  Tooltip,
 | 
				
			||||||
} from "@mui/material";
 | 
					} from "@mui/material";
 | 
				
			||||||
 | 
					import { NWFilter } from "../../api/NWFilterApi";
 | 
				
			||||||
 | 
					import { NetworkInfo } from "../../api/NetworksApi";
 | 
				
			||||||
 | 
					import { ServerApi } from "../../api/ServerApi";
 | 
				
			||||||
import { VMInfo, VMNetInterface } from "../../api/VMApi";
 | 
					import { VMInfo, VMNetInterface } from "../../api/VMApi";
 | 
				
			||||||
import { useConfirm } from "../../hooks/providers/ConfirmDialogProvider";
 | 
					import { useConfirm } from "../../hooks/providers/ConfirmDialogProvider";
 | 
				
			||||||
import { SelectInput } from "./SelectInput";
 | 
					 | 
				
			||||||
import { NetworkInfo } from "../../api/NetworksApi";
 | 
					 | 
				
			||||||
import { randomMacAddress } from "../../utils/RandUtils";
 | 
					import { randomMacAddress } from "../../utils/RandUtils";
 | 
				
			||||||
import { ServerApi } from "../../api/ServerApi";
 | 
					 | 
				
			||||||
import { MACInput } from "./MACInput";
 | 
					import { MACInput } from "./MACInput";
 | 
				
			||||||
 | 
					import { SelectInput } from "./SelectInput";
 | 
				
			||||||
 | 
					import { VMNetworkFilterParameters } from "./VMNetworkFilterParameters";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function VMNetworksList(p: {
 | 
					export function VMNetworksList(p: {
 | 
				
			||||||
  vm: VMInfo;
 | 
					  vm: VMInfo;
 | 
				
			||||||
  onChange?: () => void;
 | 
					  onChange?: () => void;
 | 
				
			||||||
  editable: boolean;
 | 
					  editable: boolean;
 | 
				
			||||||
  networksList: NetworkInfo[];
 | 
					  networksList: NetworkInfo[];
 | 
				
			||||||
 | 
					  networkFiltersList: NWFilter[];
 | 
				
			||||||
}): React.ReactElement {
 | 
					}): React.ReactElement {
 | 
				
			||||||
  const addNew = () => {
 | 
					  const addNew = () => {
 | 
				
			||||||
    p.vm.networks.push({
 | 
					    p.vm.networks.push({
 | 
				
			||||||
@@ -60,6 +63,7 @@ function NetworkInfoWidget(p: {
 | 
				
			|||||||
  onChange?: () => void;
 | 
					  onChange?: () => void;
 | 
				
			||||||
  removeFromList: () => void;
 | 
					  removeFromList: () => void;
 | 
				
			||||||
  networksList: NetworkInfo[];
 | 
					  networksList: NetworkInfo[];
 | 
				
			||||||
 | 
					  networkFiltersList: NWFilter[];
 | 
				
			||||||
}): React.ReactElement {
 | 
					}): React.ReactElement {
 | 
				
			||||||
  const confirm = useConfirm();
 | 
					  const confirm = useConfirm();
 | 
				
			||||||
  const deleteNetwork = async () => {
 | 
					  const deleteNetwork = async () => {
 | 
				
			||||||
@@ -160,6 +164,42 @@ function NetworkInfoWidget(p: {
 | 
				
			|||||||
            }}
 | 
					            }}
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
        )}
 | 
					        )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {/* Network Filter */}
 | 
				
			||||||
 | 
					        <SelectInput
 | 
				
			||||||
 | 
					          editable={p.editable}
 | 
				
			||||||
 | 
					          label="Network filter"
 | 
				
			||||||
 | 
					          value={p.network.nwfilterref?.name}
 | 
				
			||||||
 | 
					          onValueChange={(v) => {
 | 
				
			||||||
 | 
					            if (v && !p.network.nwfilterref) {
 | 
				
			||||||
 | 
					              p.network.nwfilterref = { name: v, parameters: [] };
 | 
				
			||||||
 | 
					            } else if (v) {
 | 
				
			||||||
 | 
					              p.network.nwfilterref!.name = v;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              p.network.nwfilterref = undefined;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            p.onChange?.();
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					          options={[
 | 
				
			||||||
 | 
					            { label: "No network filer", value: undefined },
 | 
				
			||||||
 | 
					            ...p.networkFiltersList.map((v) => {
 | 
				
			||||||
 | 
					              return {
 | 
				
			||||||
 | 
					                value: v.name,
 | 
				
			||||||
 | 
					                label: `${v.name} (${v.chain?.protocol ?? "unspecified"})`,
 | 
				
			||||||
 | 
					                description: `${v.rules.length} rules - ${v.join_filters.length} joint filters`,
 | 
				
			||||||
 | 
					              };
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					          ]}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {p.network.nwfilterref && (
 | 
				
			||||||
 | 
					          <div style={{ margin: "10px" }}>
 | 
				
			||||||
 | 
					            <VMNetworkFilterParameters
 | 
				
			||||||
 | 
					              filterref={p.network.nwfilterref}
 | 
				
			||||||
 | 
					              {...p}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </>
 | 
					    </>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,6 +15,7 @@ import { VMScreenshot } from "./VMScreenshot";
 | 
				
			|||||||
import { ResAutostartInput } from "../forms/ResAutostartInput";
 | 
					import { ResAutostartInput } from "../forms/ResAutostartInput";
 | 
				
			||||||
import { VMNetworksList } from "../forms/VMNetworksList";
 | 
					import { VMNetworksList } from "../forms/VMNetworksList";
 | 
				
			||||||
import { NetworkApi, NetworkInfo } from "../../api/NetworksApi";
 | 
					import { NetworkApi, NetworkInfo } from "../../api/NetworksApi";
 | 
				
			||||||
 | 
					import { NWFilterApi, NWFilter } from "../../api/NWFilterApi";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface DetailsProps {
 | 
					interface DetailsProps {
 | 
				
			||||||
  vm: VMInfo;
 | 
					  vm: VMInfo;
 | 
				
			||||||
@@ -29,11 +30,15 @@ export function VMDetails(p: DetailsProps): React.ReactElement {
 | 
				
			|||||||
    number[] | any
 | 
					    number[] | any
 | 
				
			||||||
  >();
 | 
					  >();
 | 
				
			||||||
  const [networksList, setNetworksList] = React.useState<NetworkInfo[] | any>();
 | 
					  const [networksList, setNetworksList] = React.useState<NetworkInfo[] | any>();
 | 
				
			||||||
 | 
					  const [networkFiltersList, setNetworkFiltersList] = React.useState<
 | 
				
			||||||
 | 
					    NWFilter[] | any
 | 
				
			||||||
 | 
					  >();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const load = async () => {
 | 
					  const load = async () => {
 | 
				
			||||||
    setIsoList(await IsoFilesApi.GetList());
 | 
					    setIsoList(await IsoFilesApi.GetList());
 | 
				
			||||||
    setVCPUCombinations(await ServerApi.NumberVCPUs());
 | 
					    setVCPUCombinations(await ServerApi.NumberVCPUs());
 | 
				
			||||||
    setNetworksList(await NetworkApi.GetList());
 | 
					    setNetworksList(await NetworkApi.GetList());
 | 
				
			||||||
 | 
					    setNetworkFiltersList(await NWFilterApi.GetList());
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
@@ -46,6 +51,7 @@ export function VMDetails(p: DetailsProps): React.ReactElement {
 | 
				
			|||||||
          isoList={isoList}
 | 
					          isoList={isoList}
 | 
				
			||||||
          vcpuCombinations={vcpuCombinations}
 | 
					          vcpuCombinations={vcpuCombinations}
 | 
				
			||||||
          networksList={networksList}
 | 
					          networksList={networksList}
 | 
				
			||||||
 | 
					          networkFiltersList={networkFiltersList}
 | 
				
			||||||
          {...p}
 | 
					          {...p}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
@@ -58,6 +64,7 @@ function VMDetailsInner(
 | 
				
			|||||||
    isoList: IsoFile[];
 | 
					    isoList: IsoFile[];
 | 
				
			||||||
    vcpuCombinations: number[];
 | 
					    vcpuCombinations: number[];
 | 
				
			||||||
    networksList: NetworkInfo[];
 | 
					    networksList: NetworkInfo[];
 | 
				
			||||||
 | 
					    networkFiltersList: NWFilter[];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
): React.ReactElement {
 | 
					): React.ReactElement {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user