Can define network filters
This commit is contained in:
		@@ -1,4 +1,5 @@
 | 
			
		||||
use crate::libvirt_client::LibVirtClient;
 | 
			
		||||
use actix_http::StatusCode;
 | 
			
		||||
use actix_web::body::BoxBody;
 | 
			
		||||
use actix_web::{web, HttpResponse};
 | 
			
		||||
use std::error::Error;
 | 
			
		||||
@@ -32,8 +33,15 @@ impl Display 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> {
 | 
			
		||||
        log::error!("Error while processing request! {}", self);
 | 
			
		||||
 | 
			
		||||
        HttpResponse::InternalServerError().body("Failed to execute request!")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -112,10 +112,15 @@ pub async fn update(
 | 
			
		||||
    id: web::Path<SingleVMUUidReq>,
 | 
			
		||||
    req: web::Json<VMInfo>,
 | 
			
		||||
) -> 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}");
 | 
			
		||||
            return Ok(
 | 
			
		||||
                HttpResponse::BadRequest().json(format!("Failed to extract domain info! {e}"))
 | 
			
		||||
    })?;
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    domain.uuid = Some(id.uid);
 | 
			
		||||
    if let Err(e) = client.update_domain(req.0, domain).await {
 | 
			
		||||
 
 | 
			
		||||
@@ -63,6 +63,24 @@ pub struct NetIntModelXML {
 | 
			
		||||
    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)]
 | 
			
		||||
#[serde(rename = "interface")]
 | 
			
		||||
pub struct DomainNetInterfaceXML {
 | 
			
		||||
@@ -73,6 +91,8 @@ pub struct DomainNetInterfaceXML {
 | 
			
		||||
    pub source: Option<NetIntSourceXML>,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub model: Option<NetIntModelXML>,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub filterref: Option<NetIntfilterRefXML>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
 
 | 
			
		||||
@@ -24,11 +24,24 @@ pub enum VMArchitecture {
 | 
			
		||||
    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)]
 | 
			
		||||
pub struct Network {
 | 
			
		||||
    mac: String,
 | 
			
		||||
    #[serde(flatten)]
 | 
			
		||||
    r#type: NetworkType,
 | 
			
		||||
    mac: String,
 | 
			
		||||
    nwfilterref: Option<NWFilterRef>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
@@ -157,6 +170,67 @@ impl VMInfo {
 | 
			
		||||
            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
 | 
			
		||||
        for disk in &self.disks {
 | 
			
		||||
            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 {
 | 
			
		||||
            disk.check_config()?;
 | 
			
		||||
            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 {
 | 
			
		||||
            r#type: "kvm".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<_>, _>>()?,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
  type: "UserspaceSLIRPStack";
 | 
			
		||||
  mac: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface VMNetDefinedNetwork {
 | 
			
		||||
  type: "DefinedNetwork";
 | 
			
		||||
  mac: string;
 | 
			
		||||
  network: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -66,7 +66,7 @@ export function EditVMRoute(): React.ReactElement {
 | 
			
		||||
      navigate(v.ViewURL);
 | 
			
		||||
    } catch (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
 | 
			
		||||
 */
 | 
			
		||||
export function TextInput(p: {
 | 
			
		||||
  label: string;
 | 
			
		||||
  label?: string;
 | 
			
		||||
  editable: boolean;
 | 
			
		||||
  value?: string;
 | 
			
		||||
  onValueChange?: (newVal: string | undefined) => void;
 | 
			
		||||
@@ -15,6 +15,7 @@ export function TextInput(p: {
 | 
			
		||||
  minRows?: number;
 | 
			
		||||
  maxRows?: number;
 | 
			
		||||
  type?: React.HTMLInputTypeAttribute;
 | 
			
		||||
  style?: React.CSSProperties;
 | 
			
		||||
}): React.ReactElement {
 | 
			
		||||
  if (!p.editable && (p.value ?? "") === "") return <></>;
 | 
			
		||||
 | 
			
		||||
@@ -48,7 +49,7 @@ export function TextInput(p: {
 | 
			
		||||
        type: p.type,
 | 
			
		||||
      }}
 | 
			
		||||
      variant={"standard"}
 | 
			
		||||
      style={{ width: "100%", marginBottom: "15px" }}
 | 
			
		||||
      style={p.style ?? { width: "100%", marginBottom: "15px" }}
 | 
			
		||||
      multiline={p.multiline}
 | 
			
		||||
      minRows={p.minRows}
 | 
			
		||||
      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,
 | 
			
		||||
  Tooltip,
 | 
			
		||||
} 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 { useConfirm } from "../../hooks/providers/ConfirmDialogProvider";
 | 
			
		||||
import { SelectInput } from "./SelectInput";
 | 
			
		||||
import { NetworkInfo } from "../../api/NetworksApi";
 | 
			
		||||
import { randomMacAddress } from "../../utils/RandUtils";
 | 
			
		||||
import { ServerApi } from "../../api/ServerApi";
 | 
			
		||||
import { MACInput } from "./MACInput";
 | 
			
		||||
import { SelectInput } from "./SelectInput";
 | 
			
		||||
import { VMNetworkFilterParameters } from "./VMNetworkFilterParameters";
 | 
			
		||||
 | 
			
		||||
export function VMNetworksList(p: {
 | 
			
		||||
  vm: VMInfo;
 | 
			
		||||
  onChange?: () => void;
 | 
			
		||||
  editable: boolean;
 | 
			
		||||
  networksList: NetworkInfo[];
 | 
			
		||||
  networkFiltersList: NWFilter[];
 | 
			
		||||
}): React.ReactElement {
 | 
			
		||||
  const addNew = () => {
 | 
			
		||||
    p.vm.networks.push({
 | 
			
		||||
@@ -60,6 +63,7 @@ function NetworkInfoWidget(p: {
 | 
			
		||||
  onChange?: () => void;
 | 
			
		||||
  removeFromList: () => void;
 | 
			
		||||
  networksList: NetworkInfo[];
 | 
			
		||||
  networkFiltersList: NWFilter[];
 | 
			
		||||
}): React.ReactElement {
 | 
			
		||||
  const confirm = useConfirm();
 | 
			
		||||
  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>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@ import { VMScreenshot } from "./VMScreenshot";
 | 
			
		||||
import { ResAutostartInput } from "../forms/ResAutostartInput";
 | 
			
		||||
import { VMNetworksList } from "../forms/VMNetworksList";
 | 
			
		||||
import { NetworkApi, NetworkInfo } from "../../api/NetworksApi";
 | 
			
		||||
import { NWFilterApi, NWFilter } from "../../api/NWFilterApi";
 | 
			
		||||
 | 
			
		||||
interface DetailsProps {
 | 
			
		||||
  vm: VMInfo;
 | 
			
		||||
@@ -29,11 +30,15 @@ export function VMDetails(p: DetailsProps): React.ReactElement {
 | 
			
		||||
    number[] | any
 | 
			
		||||
  >();
 | 
			
		||||
  const [networksList, setNetworksList] = React.useState<NetworkInfo[] | any>();
 | 
			
		||||
  const [networkFiltersList, setNetworkFiltersList] = React.useState<
 | 
			
		||||
    NWFilter[] | any
 | 
			
		||||
  >();
 | 
			
		||||
 | 
			
		||||
  const load = async () => {
 | 
			
		||||
    setIsoList(await IsoFilesApi.GetList());
 | 
			
		||||
    setVCPUCombinations(await ServerApi.NumberVCPUs());
 | 
			
		||||
    setNetworksList(await NetworkApi.GetList());
 | 
			
		||||
    setNetworkFiltersList(await NWFilterApi.GetList());
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
@@ -46,6 +51,7 @@ export function VMDetails(p: DetailsProps): React.ReactElement {
 | 
			
		||||
          isoList={isoList}
 | 
			
		||||
          vcpuCombinations={vcpuCombinations}
 | 
			
		||||
          networksList={networksList}
 | 
			
		||||
          networkFiltersList={networkFiltersList}
 | 
			
		||||
          {...p}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
@@ -58,6 +64,7 @@ function VMDetailsInner(
 | 
			
		||||
    isoList: IsoFile[];
 | 
			
		||||
    vcpuCombinations: number[];
 | 
			
		||||
    networksList: NetworkInfo[];
 | 
			
		||||
    networkFiltersList: NWFilter[];
 | 
			
		||||
  }
 | 
			
		||||
): React.ReactElement {
 | 
			
		||||
  return (
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user