Can define network filters
This commit is contained in:
parent
2b145ebeff
commit
d4ef389852
@ -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() {
|
||||||
log::error!("Failed to extract domain info! {e}");
|
Ok(d) => d,
|
||||||
HttpResponse::BadRequest().json(format!("Failed to extract domain info! {e}"))
|
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);
|
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 (
|
||||||
|
Loading…
Reference in New Issue
Block a user