Files
VirtWeb/virtweb_frontend/src/widgets/forms/NetNatConfiguration.tsx
Pierre HUBERT 9b14d62830
All checks were successful
continuous-integration/drone/push Build is passing
Update frontend dependencies
2024-10-30 21:47:22 +01:00

312 lines
9.1 KiB
TypeScript

import DeleteIcon from "@mui/icons-material/Delete";
import {
Alert,
Button,
Card,
CardActions,
CardContent,
IconButton,
Tooltip,
Typography,
} from "@mui/material";
import Grid from "@mui/material/Grid2";
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" && (
<>
{p.editable && (
<Alert severity="warning" style={{ margin: "10px 0px" }}>
Warning! All IP addresses may not be inferred on reboot due
to the fact that the network hook might be executed before
the network interfaces are fully configured. This might lead
to incomplete ports exposition!
</Alert>
)}
<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 size={{ 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>;
}