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>;
}