Can configure network NAT settings from UI
This commit is contained in:
parent
71e22bc328
commit
f82925dbcb
@ -44,6 +44,9 @@ pub const DISK_SIZE_MIN: usize = 100;
|
|||||||
/// Disk size max (MB)
|
/// Disk size max (MB)
|
||||||
pub const DISK_SIZE_MAX: usize = 1000 * 1000 * 2;
|
pub const DISK_SIZE_MAX: usize = 1000 * 1000 * 2;
|
||||||
|
|
||||||
|
/// Net nat entry comment max size
|
||||||
|
pub const NET_NAT_COMMENT_MAX_SIZE: usize = 250;
|
||||||
|
|
||||||
/// Network mac address default prefix
|
/// Network mac address default prefix
|
||||||
pub const NET_MAC_ADDR_PREFIX: &str = "52:54:00";
|
pub const NET_MAC_ADDR_PREFIX: &str = "52:54:00";
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ struct ServerConstraints {
|
|||||||
disk_size: LenConstraints,
|
disk_size: LenConstraints,
|
||||||
net_name_size: LenConstraints,
|
net_name_size: LenConstraints,
|
||||||
net_title_size: LenConstraints,
|
net_title_size: LenConstraints,
|
||||||
|
net_nat_comment_size: LenConstraints,
|
||||||
dhcp_reservation_host_name: LenConstraints,
|
dhcp_reservation_host_name: LenConstraints,
|
||||||
nwfilter_name_size: LenConstraints,
|
nwfilter_name_size: LenConstraints,
|
||||||
nwfilter_comment_size: LenConstraints,
|
nwfilter_comment_size: LenConstraints,
|
||||||
@ -81,6 +82,10 @@ pub async fn static_config(local_auth: LocalAuthEnabled) -> impl Responder {
|
|||||||
|
|
||||||
net_name_size: LenConstraints { min: 2, max: 50 },
|
net_name_size: LenConstraints { min: 2, max: 50 },
|
||||||
net_title_size: LenConstraints { min: 0, max: 50 },
|
net_title_size: LenConstraints { min: 0, max: 50 },
|
||||||
|
net_nat_comment_size: LenConstraints {
|
||||||
|
min: 0,
|
||||||
|
max: constants::NET_NAT_COMMENT_MAX_SIZE,
|
||||||
|
},
|
||||||
|
|
||||||
dhcp_reservation_host_name: LenConstraints { min: 2, max: 250 },
|
dhcp_reservation_host_name: LenConstraints { min: 2, max: 250 },
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::constants;
|
||||||
use crate::utils::net_utils;
|
use crate::utils::net_utils;
|
||||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||||
|
|
||||||
@ -64,6 +65,12 @@ impl<IPv> Nat<IPv> {
|
|||||||
return Err(NatDefError::InvalidNatDef("Invalid guest port!").into());
|
return Err(NatDefError::InvalidNatDef("Invalid guest port!").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(comment) = &self.comment {
|
||||||
|
if comment.len() > constants::NET_NAT_COMMENT_MAX_SIZE {
|
||||||
|
return Err(NatDefError::InvalidNatDef("Comment is too large!").into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,10 +13,28 @@ export interface DHCPConfig {
|
|||||||
hosts: DHCPHost[];
|
hosts: DHCPHost[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type NatSource =
|
||||||
|
| { type: "interface"; name: string }
|
||||||
|
| { type: "ip"; ip: string };
|
||||||
|
|
||||||
|
export type NatHostPort =
|
||||||
|
| { type: "single"; port: number }
|
||||||
|
| { type: "range"; start: number; end: number };
|
||||||
|
|
||||||
|
export interface NatEntry {
|
||||||
|
protocol: "TCP" | "UDP" | "Both";
|
||||||
|
host_addr: NatSource;
|
||||||
|
host_port: NatHostPort;
|
||||||
|
guest_addr: string;
|
||||||
|
guest_port: number;
|
||||||
|
comment?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IpConfig {
|
export interface IpConfig {
|
||||||
bridge_address: string;
|
bridge_address: string;
|
||||||
prefix: number;
|
prefix: number;
|
||||||
dhcp?: DHCPConfig;
|
dhcp?: DHCPConfig;
|
||||||
|
nat?: NatEntry[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NetworkInfo {
|
export interface NetworkInfo {
|
||||||
|
@ -21,6 +21,7 @@ export interface ServerConstraints {
|
|||||||
disk_size: LenConstraint;
|
disk_size: LenConstraint;
|
||||||
net_name_size: LenConstraint;
|
net_name_size: LenConstraint;
|
||||||
net_title_size: LenConstraint;
|
net_title_size: LenConstraint;
|
||||||
|
net_nat_comment_size: LenConstraint;
|
||||||
dhcp_reservation_host_name: LenConstraint;
|
dhcp_reservation_host_name: LenConstraint;
|
||||||
nwfilter_name_size: LenConstraint;
|
nwfilter_name_size: LenConstraint;
|
||||||
nwfilter_comment_size: LenConstraint;
|
nwfilter_comment_size: LenConstraint;
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
ListItemText,
|
ListItemText,
|
||||||
Paper,
|
Paper,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
|
Typography,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { DHCPConfig, DHCPHost } from "../../api/NetworksApi";
|
import { DHCPConfig, DHCPHost } from "../../api/NetworksApi";
|
||||||
import { ServerApi } from "../../api/ServerApi";
|
import { ServerApi } from "../../api/ServerApi";
|
||||||
@ -54,6 +55,11 @@ export function NetDHCPHostReservations(p: {
|
|||||||
</Grid>
|
</Grid>
|
||||||
))}
|
))}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
{p.dhcp.hosts.length === 0 && (
|
||||||
|
<Typography style={{ textAlign: "center" }}>
|
||||||
|
You have not set any DHCP host reservations.
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
{p.editable && (
|
{p.editable && (
|
||||||
<Button onClick={addHost}>Add new host reservation</Button>
|
<Button onClick={addHost}>Add new host reservation</Button>
|
||||||
)}
|
)}
|
||||||
|
301
virtweb_frontend/src/widgets/forms/NetNatConfiguration.tsx
Normal file
301
virtweb_frontend/src/widgets/forms/NetNatConfiguration.tsx
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
import DeleteIcon from "@mui/icons-material/Delete";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
CardActions,
|
||||||
|
CardContent,
|
||||||
|
Grid,
|
||||||
|
IconButton,
|
||||||
|
Tooltip,
|
||||||
|
Typography,
|
||||||
|
} from "@mui/material";
|
||||||
|
import React, { PropsWithChildren } from "react";
|
||||||
|
import { NatEntry } from "../../api/NetworksApi";
|
||||||
|
import { ServerApi } from "../../api/ServerApi";
|
||||||
|
import { useConfirm } from "../../hooks/providers/ConfirmDialogProvider";
|
||||||
|
import { IPInput } from "./IPInput";
|
||||||
|
import { PortInput } from "./PortInput";
|
||||||
|
import { RadioGroupInput } from "./RadioGroupInput";
|
||||||
|
import { SelectInput } from "./SelectInput";
|
||||||
|
import { TextInput } from "./TextInput";
|
||||||
|
|
||||||
|
export function NetNatConfiguration(p: {
|
||||||
|
editable: boolean;
|
||||||
|
nat: NatEntry[];
|
||||||
|
nicsList: string[];
|
||||||
|
onChange?: (nat: NatEntry[]) => void;
|
||||||
|
version: 4 | 6;
|
||||||
|
}): React.ReactElement {
|
||||||
|
const confirm = useConfirm();
|
||||||
|
|
||||||
|
const addEntry = () => {
|
||||||
|
p.nat.push({
|
||||||
|
host_addr: {
|
||||||
|
type: "ip",
|
||||||
|
ip: p.version === 4 ? "10.0.0.1" : "fd00::",
|
||||||
|
},
|
||||||
|
host_port: { type: "single", port: 80 },
|
||||||
|
guest_addr: p.version === 4 ? "10.0.0.100" : "fd00::",
|
||||||
|
guest_port: 10,
|
||||||
|
protocol: "TCP",
|
||||||
|
});
|
||||||
|
p.onChange?.(p.nat);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDelete = async (idx: number) => {
|
||||||
|
if (!(await confirm("Do you really want to delete this entry?"))) return;
|
||||||
|
|
||||||
|
p.nat.splice(idx, 1);
|
||||||
|
p.onChange?.(p.nat);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{p.nat.map((e, num) => (
|
||||||
|
<NatEntryForm
|
||||||
|
key={num}
|
||||||
|
{...p}
|
||||||
|
entry={e}
|
||||||
|
onChange={() => p.onChange?.(p.nat)}
|
||||||
|
onDelete={() => onDelete(num)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{p.nat.length === 0 && (
|
||||||
|
<Typography style={{ textAlign: "center" }}>
|
||||||
|
You have not set any NAT entry yet.
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{p.editable && <Button onClick={addEntry}>Add a new entry</Button>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function NatEntryForm(p: {
|
||||||
|
editable: boolean;
|
||||||
|
version: 4 | 6;
|
||||||
|
entry: NatEntry;
|
||||||
|
onChange?: () => void;
|
||||||
|
onDelete: () => void;
|
||||||
|
nicsList: string[];
|
||||||
|
}): React.ReactElement {
|
||||||
|
const guestPortEnd =
|
||||||
|
p.entry.host_port.type === "range"
|
||||||
|
? p.entry.host_port.end - p.entry.host_port.start + p.entry.guest_port
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card style={{ margin: "30px" }} elevation={3}>
|
||||||
|
<CardContent>
|
||||||
|
<Grid container>
|
||||||
|
<NATEntryProp>
|
||||||
|
<SelectInput
|
||||||
|
{...p}
|
||||||
|
label="Protocol"
|
||||||
|
options={[
|
||||||
|
{ value: "TCP" },
|
||||||
|
{ value: "UDP" },
|
||||||
|
{ label: "TCP & UDP", value: "Both" },
|
||||||
|
]}
|
||||||
|
value={p.entry.protocol}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
p.entry.protocol = v as any;
|
||||||
|
p.onChange?.();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</NATEntryProp>
|
||||||
|
<NATEntryProp>
|
||||||
|
<TextInput
|
||||||
|
{...p}
|
||||||
|
label="Comment"
|
||||||
|
value={p.entry.comment}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
p.entry.comment = v;
|
||||||
|
p.onChange?.();
|
||||||
|
}}
|
||||||
|
size={ServerApi.Config.constraints.net_nat_comment_size}
|
||||||
|
/>
|
||||||
|
</NATEntryProp>
|
||||||
|
|
||||||
|
{/* Host conf */}
|
||||||
|
<NATEntryProp label="Host configuration">
|
||||||
|
<SelectInput
|
||||||
|
{...p}
|
||||||
|
label="Host address specification"
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
label: "Specific IP",
|
||||||
|
value: "ip",
|
||||||
|
description: "Use a pre-defined IP address",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Network interface",
|
||||||
|
value: "interface",
|
||||||
|
description:
|
||||||
|
"Use active IP addresses on the selected network interface during network startup to determine host adddress",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
value={p.entry.host_addr.type}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
p.entry.host_addr.type = v as any;
|
||||||
|
p.onChange?.();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{p.entry.host_addr.type === "ip" && (
|
||||||
|
<IPInput
|
||||||
|
{...p}
|
||||||
|
label="Host IP address"
|
||||||
|
value={p.entry.host_addr.ip}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
if (p.entry.host_addr.type === "ip")
|
||||||
|
p.entry.host_addr.ip = v!;
|
||||||
|
p.onChange?.();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{p.entry.host_addr.type === "interface" && (
|
||||||
|
<SelectInput
|
||||||
|
{...p}
|
||||||
|
label="Network interface"
|
||||||
|
value={p.entry.host_addr.name}
|
||||||
|
options={p.nicsList.map((n) => {
|
||||||
|
return {
|
||||||
|
value: n,
|
||||||
|
};
|
||||||
|
})}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
if (p.entry.host_addr.type === "interface")
|
||||||
|
p.entry.host_addr.name = v!;
|
||||||
|
p.onChange?.();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</NATEntryProp>
|
||||||
|
|
||||||
|
<NATEntryProp label="Target guest configuration">
|
||||||
|
<IPInput
|
||||||
|
{...p}
|
||||||
|
label="Guest address"
|
||||||
|
value={p.entry.guest_addr}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
p.entry.guest_addr = v!;
|
||||||
|
p.onChange?.();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</NATEntryProp>
|
||||||
|
|
||||||
|
<NATEntryProp>
|
||||||
|
<RadioGroupInput
|
||||||
|
{...p}
|
||||||
|
options={[
|
||||||
|
{ label: "Single port", value: "single" },
|
||||||
|
{ label: "Range of ports", value: "range" },
|
||||||
|
]}
|
||||||
|
value={p.entry.host_port.type}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
p.entry.host_port.type = v as any;
|
||||||
|
p.onChange?.();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{p.entry.host_port.type === "single" && (
|
||||||
|
<PortInput
|
||||||
|
{...p}
|
||||||
|
label="Host port"
|
||||||
|
value={p.entry.host_port.port}
|
||||||
|
onChange={(v) => {
|
||||||
|
if (p.entry.host_port.type === "single")
|
||||||
|
p.entry.host_port.port = v!;
|
||||||
|
p.onChange?.();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{p.entry.host_port.type === "range" && (
|
||||||
|
<div style={{ display: "flex" }}>
|
||||||
|
<PortInput
|
||||||
|
{...p}
|
||||||
|
label="Host port start"
|
||||||
|
value={p.entry.host_port.start}
|
||||||
|
onChange={(v) => {
|
||||||
|
if (p.entry.host_port.type === "range")
|
||||||
|
p.entry.host_port.start = v!;
|
||||||
|
p.onChange?.();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<PortSpacer />
|
||||||
|
<PortInput
|
||||||
|
{...p}
|
||||||
|
label="Host port end"
|
||||||
|
value={p.entry.host_port.end}
|
||||||
|
onChange={(v) => {
|
||||||
|
if (p.entry.host_port.type === "range")
|
||||||
|
p.entry.host_port.end = v!;
|
||||||
|
p.onChange?.();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</NATEntryProp>
|
||||||
|
|
||||||
|
<NATEntryProp>
|
||||||
|
<div style={{ display: "flex", height: "100%", alignItems: "end" }}>
|
||||||
|
<PortInput
|
||||||
|
{...p}
|
||||||
|
label={`Guest port ${guestPortEnd ? "start" : ""}`}
|
||||||
|
value={p.entry.guest_port}
|
||||||
|
onChange={(v) => {
|
||||||
|
p.entry.guest_port = v!;
|
||||||
|
p.onChange?.();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{guestPortEnd && <PortSpacer />}
|
||||||
|
{guestPortEnd && (
|
||||||
|
<PortInput
|
||||||
|
editable={false}
|
||||||
|
label={`Guest port end`}
|
||||||
|
value={guestPortEnd}
|
||||||
|
onChange={(v) => {
|
||||||
|
p.entry.guest_port = v!;
|
||||||
|
p.onChange?.();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</NATEntryProp>
|
||||||
|
</Grid>
|
||||||
|
</CardContent>
|
||||||
|
<CardActions>
|
||||||
|
{p.editable && (
|
||||||
|
<Tooltip title="Remove the entry">
|
||||||
|
<IconButton color="error" onClick={p.onDelete}>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</CardActions>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function NATEntryProp(
|
||||||
|
p: PropsWithChildren<{ label?: string }>
|
||||||
|
): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<Grid item sm={12} md={6} style={{ padding: "20px" }}>
|
||||||
|
{p.label && (
|
||||||
|
<Typography variant="h6" style={{ marginBottom: "10px" }}>
|
||||||
|
{p.label}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
{p.children}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PortSpacer(): React.ReactElement {
|
||||||
|
return <span style={{ width: "20px" }}></span>;
|
||||||
|
}
|
@ -14,6 +14,7 @@ export function PortInput(p: {
|
|||||||
onValueChange={(v) => {
|
onValueChange={(v) => {
|
||||||
p.onChange?.(sanitizePort(v));
|
p.onChange?.(sanitizePort(v));
|
||||||
}}
|
}}
|
||||||
|
checkValue={(v) => Number(v) <= 65535}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
40
virtweb_frontend/src/widgets/forms/RadioGroupInput.tsx
Normal file
40
virtweb_frontend/src/widgets/forms/RadioGroupInput.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import {
|
||||||
|
RadioGroup,
|
||||||
|
FormControlLabel,
|
||||||
|
Radio,
|
||||||
|
FormControl,
|
||||||
|
FormLabel,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
|
interface RadioGroupOption {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RadioGroupInput(p: {
|
||||||
|
editable: boolean;
|
||||||
|
label?: string;
|
||||||
|
options: RadioGroupOption[];
|
||||||
|
value: string;
|
||||||
|
onValueChange: (v: string) => void;
|
||||||
|
}): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<FormControl>
|
||||||
|
{p.label && <FormLabel>{p.label}</FormLabel>}
|
||||||
|
<RadioGroup
|
||||||
|
row
|
||||||
|
value={p.value}
|
||||||
|
onChange={(_ev, v) => p.onValueChange?.(v)}
|
||||||
|
>
|
||||||
|
{p.options.map((o) => (
|
||||||
|
<FormControlLabel
|
||||||
|
disabled={!p.editable}
|
||||||
|
value={o.value}
|
||||||
|
control={<Radio />}
|
||||||
|
label={o.label}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</RadioGroup>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}
|
@ -9,7 +9,7 @@ import { TextInput } from "./TextInput";
|
|||||||
|
|
||||||
export interface SelectOption {
|
export interface SelectOption {
|
||||||
value?: string;
|
value?: string;
|
||||||
label: string;
|
label?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,9 +23,10 @@ export function SelectInput(p: {
|
|||||||
if (!p.editable && !p.value) return <></>;
|
if (!p.editable && !p.value) return <></>;
|
||||||
|
|
||||||
if (!p.editable) {
|
if (!p.editable) {
|
||||||
const value = p.options.find((o) => o.value === p.value)?.label;
|
const value = p.options.find((o) => o.value === p.value)?.label ?? p.value;
|
||||||
return <TextInput label={p.label} editable={p.editable} value={value} />;
|
return <TextInput label={p.label} editable={p.editable} value={value} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl fullWidth variant="standard" style={{ marginBottom: "15px" }}>
|
<FormControl fullWidth variant="standard" style={{ marginBottom: "15px" }}>
|
||||||
<InputLabel>{p.label}</InputLabel>
|
<InputLabel>{p.label}</InputLabel>
|
||||||
@ -41,7 +42,7 @@ export function SelectInput(p: {
|
|||||||
style={{ fontStyle: e.value === undefined ? "italic" : undefined }}
|
style={{ fontStyle: e.value === undefined ? "italic" : undefined }}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
{e.label}
|
{e.label ?? e.value}
|
||||||
{e.description && (
|
{e.description && (
|
||||||
<Typography
|
<Typography
|
||||||
component={"div"}
|
component={"div"}
|
||||||
|
@ -8,13 +8,14 @@ import { useConfirm } from "../../hooks/providers/ConfirmDialogProvider";
|
|||||||
import { useSnackbar } from "../../hooks/providers/SnackbarProvider";
|
import { useSnackbar } from "../../hooks/providers/SnackbarProvider";
|
||||||
import { AsyncWidget } from "../AsyncWidget";
|
import { AsyncWidget } from "../AsyncWidget";
|
||||||
import { TabsWidget } from "../TabsWidget";
|
import { TabsWidget } from "../TabsWidget";
|
||||||
|
import { XMLAsyncWidget } from "../XMLWidget";
|
||||||
import { EditSection } from "../forms/EditSection";
|
import { EditSection } from "../forms/EditSection";
|
||||||
import { IPInput } from "../forms/IPInput";
|
import { IPInput } from "../forms/IPInput";
|
||||||
|
import { NetDHCPHostReservations } from "../forms/NetDHCPHostReservations";
|
||||||
|
import { NetNatConfiguration } from "../forms/NetNatConfiguration";
|
||||||
import { ResAutostartInput } from "../forms/ResAutostartInput";
|
import { ResAutostartInput } from "../forms/ResAutostartInput";
|
||||||
import { SelectInput } from "../forms/SelectInput";
|
import { SelectInput } from "../forms/SelectInput";
|
||||||
import { TextInput } from "../forms/TextInput";
|
import { TextInput } from "../forms/TextInput";
|
||||||
import { NetDHCPHostReservations } from "../forms/NetDHCPHostReservations";
|
|
||||||
import { XMLAsyncWidget } from "../XMLWidget";
|
|
||||||
|
|
||||||
interface DetailsProps {
|
interface DetailsProps {
|
||||||
net: NetworkInfo;
|
net: NetworkInfo;
|
||||||
@ -223,7 +224,7 @@ function NetworkDetailsTabGeneral(p: DetailsInnerProps): React.ReactElement {
|
|||||||
function NetworkDetailsTabIPv4(p: DetailsInnerProps): React.ReactElement {
|
function NetworkDetailsTabIPv4(p: DetailsInnerProps): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<IPSection
|
<IPSection
|
||||||
editable={p.editable}
|
{...p}
|
||||||
config={p.net.ip_v4}
|
config={p.net.ip_v4}
|
||||||
onChange={(c) => {
|
onChange={(c) => {
|
||||||
p.net.ip_v4 = c;
|
p.net.ip_v4 = c;
|
||||||
@ -237,7 +238,7 @@ function NetworkDetailsTabIPv4(p: DetailsInnerProps): React.ReactElement {
|
|||||||
function NetworkDetailsTabIPv6(p: DetailsInnerProps): React.ReactElement {
|
function NetworkDetailsTabIPv6(p: DetailsInnerProps): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<IPSection
|
<IPSection
|
||||||
editable={p.editable}
|
{...p}
|
||||||
config={p.net.ip_v6}
|
config={p.net.ip_v6}
|
||||||
onChange={(c) => {
|
onChange={(c) => {
|
||||||
p.net.ip_v6 = c;
|
p.net.ip_v6 = c;
|
||||||
@ -253,6 +254,7 @@ function IPSection(p: {
|
|||||||
config?: IpConfig;
|
config?: IpConfig;
|
||||||
onChange: (c: IpConfig | undefined) => void;
|
onChange: (c: IpConfig | undefined) => void;
|
||||||
version: 4 | 6;
|
version: 4 | 6;
|
||||||
|
nicsList: string[];
|
||||||
}): React.ReactElement {
|
}): React.ReactElement {
|
||||||
const confirm = useConfirm();
|
const confirm = useConfirm();
|
||||||
|
|
||||||
@ -260,7 +262,7 @@ function IPSection(p: {
|
|||||||
if (!!p.config) {
|
if (!!p.config) {
|
||||||
if (
|
if (
|
||||||
!(await confirm(
|
!(await confirm(
|
||||||
`Do you really want to disable IPv${p.version} on this network?`
|
`Do you really want to disable IPv${p.version} on this network? Specific configuration will be deleted!`
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
@ -275,8 +277,8 @@ function IPSection(p: {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleDHCP = (v: boolean) => {
|
const toggleDHCP = async (v: boolean) => {
|
||||||
if (v)
|
if (v) {
|
||||||
p.config!.dhcp =
|
p.config!.dhcp =
|
||||||
p.version === 4
|
p.version === 4
|
||||||
? {
|
? {
|
||||||
@ -285,7 +287,32 @@ function IPSection(p: {
|
|||||||
hosts: [],
|
hosts: [],
|
||||||
}
|
}
|
||||||
: { start: "fd00::100", end: "fd00::f00", hosts: [] };
|
: { start: "fd00::100", end: "fd00::f00", hosts: [] };
|
||||||
else p.config!.dhcp = undefined;
|
} else {
|
||||||
|
if (
|
||||||
|
!(await confirm(
|
||||||
|
`Do you really want to disable DHCPv${p.version} on this network? Specific configuration will be deleted!`
|
||||||
|
))
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
p.config!.dhcp = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.onChange?.(p.config);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleNAT = async (v: boolean) => {
|
||||||
|
if (v) {
|
||||||
|
p.config!.nat = [];
|
||||||
|
} else {
|
||||||
|
if (
|
||||||
|
(p.config?.nat?.length ?? 0 > 0) &&
|
||||||
|
!(await confirm(
|
||||||
|
`Do you really want to disable NAT port forwarding on this network? Specific configuration will be deleted!`
|
||||||
|
))
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
p.config!.nat = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
p.onChange?.(p.config);
|
p.onChange?.(p.config);
|
||||||
};
|
};
|
||||||
@ -384,6 +411,31 @@ function IPSection(p: {
|
|||||||
/>
|
/>
|
||||||
</EditSection>
|
</EditSection>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{p.config && (p.editable || p.config.nat) && (
|
||||||
|
<EditSection
|
||||||
|
title={`NAT v${p.version} ports redirection`}
|
||||||
|
fullWidth
|
||||||
|
actions={
|
||||||
|
<Checkbox
|
||||||
|
disabled={!p.editable}
|
||||||
|
checked={!!p.config.nat}
|
||||||
|
onChange={(_ev, val) => toggleNAT(val)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{p.config.nat && (
|
||||||
|
<NetNatConfiguration
|
||||||
|
{...p}
|
||||||
|
nat={p.config.nat}
|
||||||
|
onChange={(n) => {
|
||||||
|
p.config!.nat = n;
|
||||||
|
p.onChange?.(p.config);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</EditSection>
|
||||||
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user