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