301 lines
8.5 KiB
TypeScript
301 lines
8.5 KiB
TypeScript
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_ip: {
|
|
type: "ip",
|
|
ip: p.version === 4 ? "10.0.0.1" : "fd00::",
|
|
},
|
|
host_port: { type: "single", port: 80 },
|
|
guest_ip: 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 IP 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_ip.type}
|
|
onValueChange={(v) => {
|
|
p.entry.host_ip.type = v as any;
|
|
p.onChange?.();
|
|
}}
|
|
/>
|
|
|
|
{p.entry.host_ip.type === "ip" && (
|
|
<IPInput
|
|
{...p}
|
|
label="Host IP address"
|
|
value={p.entry.host_ip.ip}
|
|
onValueChange={(v) => {
|
|
if (p.entry.host_ip.type === "ip") p.entry.host_ip.ip = v!;
|
|
p.onChange?.();
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
{p.entry.host_ip.type === "interface" && (
|
|
<SelectInput
|
|
{...p}
|
|
label="Network interface"
|
|
value={p.entry.host_ip.name}
|
|
options={p.nicsList.map((n) => {
|
|
return {
|
|
value: n,
|
|
};
|
|
})}
|
|
onValueChange={(v) => {
|
|
if (p.entry.host_ip.type === "interface")
|
|
p.entry.host_ip.name = v!;
|
|
p.onChange?.();
|
|
}}
|
|
/>
|
|
)}
|
|
</NATEntryProp>
|
|
|
|
<NATEntryProp label="Target guest configuration">
|
|
<IPInput
|
|
{...p}
|
|
label="Guest IP"
|
|
value={p.entry.guest_ip}
|
|
onValueChange={(v) => {
|
|
p.entry.guest_ip = 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>;
|
|
}
|