434 lines
11 KiB
TypeScript
434 lines
11 KiB
TypeScript
import { Button, Checkbox, Grid } from "@mui/material";
|
|
import React from "react";
|
|
import { useNavigate } from "react-router-dom";
|
|
import { IpConfig, NetworkApi, NetworkInfo } from "../../api/NetworksApi";
|
|
import { ServerApi } from "../../api/ServerApi";
|
|
import { useAlert } from "../../hooks/providers/AlertDialogProvider";
|
|
import { useConfirm } from "../../hooks/providers/ConfirmDialogProvider";
|
|
import { useSnackbar } from "../../hooks/providers/SnackbarProvider";
|
|
import { AsyncWidget } from "../AsyncWidget";
|
|
import { TabsWidget } from "../TabsWidget";
|
|
import { EditSection } from "../forms/EditSection";
|
|
import { IPInput } from "../forms/IPInput";
|
|
import { ResAutostartInput } from "../forms/ResAutostartInput";
|
|
import { SelectInput } from "../forms/SelectInput";
|
|
import { TextInput } from "../forms/TextInput";
|
|
import { NetDHCPHostReservations } from "../forms/NetDHCPHostReservations";
|
|
import { XMLAsyncWidget } from "../XMLWidget";
|
|
|
|
interface DetailsProps {
|
|
net: NetworkInfo;
|
|
editable: boolean;
|
|
onChange?: () => void;
|
|
}
|
|
|
|
export function NetworkDetails(p: DetailsProps): React.ReactElement {
|
|
const [nicsList, setNicsList] = React.useState<string[] | any>();
|
|
|
|
const load = async () => {
|
|
setNicsList(await ServerApi.GetNetworksList());
|
|
};
|
|
|
|
return (
|
|
<AsyncWidget
|
|
loadKey={"1"}
|
|
load={load}
|
|
errMsg="Failed to load the list of host network cards!"
|
|
build={() => <NetworkDetailsInner nicsList={nicsList} {...p} />}
|
|
/>
|
|
);
|
|
}
|
|
|
|
enum NetTab {
|
|
General = 0,
|
|
IPv4,
|
|
IPv6,
|
|
XML,
|
|
Danger,
|
|
}
|
|
|
|
type DetailsInnerProps = DetailsProps & { nicsList: string[] };
|
|
|
|
function NetworkDetailsInner(p: DetailsInnerProps): React.ReactElement {
|
|
const [currTab, setCurrTab] = React.useState(NetTab.General);
|
|
|
|
return (
|
|
<>
|
|
<TabsWidget
|
|
currTab={currTab}
|
|
onTabChange={setCurrTab}
|
|
options={[
|
|
{ label: "General", value: NetTab.General, visible: true },
|
|
{
|
|
label: "IPv4",
|
|
value: NetTab.IPv4,
|
|
visible: p.editable || !!p.net.ip_v4,
|
|
},
|
|
{
|
|
label: "IPv6",
|
|
value: NetTab.IPv6,
|
|
visible: p.editable || !!p.net.ip_v6,
|
|
},
|
|
{
|
|
label: "XML",
|
|
value: NetTab.XML,
|
|
visible: !p.editable,
|
|
},
|
|
{
|
|
label: "Danger zone",
|
|
value: NetTab.Danger,
|
|
color: "red",
|
|
visible: !p.editable,
|
|
},
|
|
]}
|
|
/>
|
|
|
|
{currTab === NetTab.General && <NetworkDetailsTabGeneral {...p} />}
|
|
{currTab === NetTab.IPv4 && <NetworkDetailsTabIPv4 {...p} />}
|
|
{currTab === NetTab.IPv6 && <NetworkDetailsTabIPv6 {...p} />}
|
|
{currTab === NetTab.XML && <NetworkDetailsTabXML {...p} />}
|
|
{currTab === NetTab.Danger && <NetworkDetailsTabDanger {...p} />}
|
|
</>
|
|
);
|
|
}
|
|
|
|
function NetworkDetailsTabGeneral(p: DetailsInnerProps): React.ReactElement {
|
|
return (
|
|
<Grid container spacing={2}>
|
|
{/* Metadata section */}
|
|
<EditSection title="Metadata">
|
|
<TextInput
|
|
label="Name"
|
|
editable={p.editable && !p.net.uuid}
|
|
value={p.net.name}
|
|
onValueChange={(v) => {
|
|
p.net.name = v ?? "";
|
|
p.onChange?.();
|
|
}}
|
|
checkValue={(v) => /^[a-zA-Z0-9]+$/.test(v)}
|
|
size={ServerApi.Config.constraints.net_name_size}
|
|
/>
|
|
|
|
<TextInput label="UUID" editable={false} value={p.net.uuid} />
|
|
|
|
<TextInput
|
|
label="Title"
|
|
editable={p.editable}
|
|
value={p.net.title}
|
|
onValueChange={(v) => {
|
|
p.net.title = v;
|
|
p.onChange?.();
|
|
}}
|
|
size={ServerApi.Config.constraints.net_title_size}
|
|
/>
|
|
|
|
<TextInput
|
|
label="Description"
|
|
editable={p.editable}
|
|
value={p.net.description}
|
|
onValueChange={(v) => {
|
|
p.net.description = v;
|
|
p.onChange?.();
|
|
}}
|
|
multiline={true}
|
|
/>
|
|
|
|
{p.net.uuid && (
|
|
<ResAutostartInput
|
|
editable={p.editable}
|
|
ressourceName="Network"
|
|
checkAutotostart={() => NetworkApi.IsAutostart(p.net)}
|
|
setAutotostart={(e) => NetworkApi.SetAutostart(p.net, e)}
|
|
/>
|
|
)}
|
|
</EditSection>
|
|
|
|
<EditSection title="General settings">
|
|
<SelectInput
|
|
editable={p.editable}
|
|
label="Forward mode"
|
|
onValueChange={(v) => {
|
|
p.net.forward_mode = v as any;
|
|
p.onChange?.();
|
|
}}
|
|
value={p.net.forward_mode}
|
|
options={[
|
|
{
|
|
label: "NAT",
|
|
value: "NAT",
|
|
},
|
|
|
|
{
|
|
label: "Isolated network",
|
|
value: "Isolated",
|
|
},
|
|
]}
|
|
/>
|
|
|
|
{p.net.forward_mode === "NAT" && (
|
|
<SelectInput
|
|
editable={p.editable}
|
|
label="Network output device"
|
|
onValueChange={(v) => {
|
|
p.net.device = v;
|
|
p.onChange?.();
|
|
}}
|
|
value={p.net.device}
|
|
options={[
|
|
{ label: "Default interface", value: undefined },
|
|
...p.nicsList.map((d) => {
|
|
return { label: d, value: d };
|
|
}),
|
|
]}
|
|
/>
|
|
)}
|
|
|
|
<TextInput
|
|
editable={p.editable}
|
|
label="Network bridge name"
|
|
onValueChange={(v) => {
|
|
p.net.bridge_name = v;
|
|
p.onChange?.();
|
|
}}
|
|
value={p.net.bridge_name}
|
|
checkValue={(v) => v === "" || /^[a-zA-Z0-9]+$/.test(v)}
|
|
/>
|
|
|
|
<IPInput
|
|
editable={p.editable}
|
|
label="DNS server to use"
|
|
value={p.net.dns_server}
|
|
onValueChange={(v) => {
|
|
p.net.dns_server = v;
|
|
p.onChange?.();
|
|
}}
|
|
version={4}
|
|
/>
|
|
|
|
<TextInput
|
|
label="Domain"
|
|
editable={p.editable}
|
|
value={p.net.domain}
|
|
onValueChange={(v) => {
|
|
p.net.domain = v;
|
|
p.onChange?.();
|
|
}}
|
|
multiline={true}
|
|
/>
|
|
</EditSection>
|
|
</Grid>
|
|
);
|
|
}
|
|
|
|
function NetworkDetailsTabIPv4(p: DetailsInnerProps): React.ReactElement {
|
|
return (
|
|
<IPSection
|
|
editable={p.editable}
|
|
config={p.net.ip_v4}
|
|
onChange={(c) => {
|
|
p.net.ip_v4 = c;
|
|
p.onChange?.();
|
|
}}
|
|
version={4}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function NetworkDetailsTabIPv6(p: DetailsInnerProps): React.ReactElement {
|
|
return (
|
|
<IPSection
|
|
editable={p.editable}
|
|
config={p.net.ip_v6}
|
|
onChange={(c) => {
|
|
p.net.ip_v6 = c;
|
|
p.onChange?.();
|
|
}}
|
|
version={6}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function IPSection(p: {
|
|
editable: boolean;
|
|
config?: IpConfig;
|
|
onChange: (c: IpConfig | undefined) => void;
|
|
version: 4 | 6;
|
|
}): React.ReactElement {
|
|
const confirm = useConfirm();
|
|
|
|
const toggleNetwork = async () => {
|
|
if (!!p.config) {
|
|
if (
|
|
!(await confirm(
|
|
`Do you really want to disable IPv${p.version} on this network?`
|
|
))
|
|
)
|
|
return;
|
|
|
|
p.onChange?.(undefined);
|
|
return;
|
|
}
|
|
|
|
p.onChange?.({
|
|
bridge_address: p.version === 4 ? "192.168.1.1" : "fd00::1",
|
|
prefix: p.version === 4 ? 24 : 8,
|
|
});
|
|
};
|
|
|
|
const toggleDHCP = (v: boolean) => {
|
|
if (v)
|
|
p.config!.dhcp =
|
|
p.version === 4
|
|
? {
|
|
start: "192.168.1.100",
|
|
end: "192.168.1.200",
|
|
hosts: [],
|
|
}
|
|
: { start: "fd00::100", end: "fd00::f00", hosts: [] };
|
|
else p.config!.dhcp = undefined;
|
|
|
|
p.onChange?.(p.config);
|
|
};
|
|
|
|
if (!p.config && !p.editable) return <></>;
|
|
|
|
return (
|
|
<Grid container spacing={2}>
|
|
<EditSection
|
|
title={`IPv${p.version} network`}
|
|
actions={
|
|
<Checkbox
|
|
disabled={!p.editable}
|
|
checked={!!p.config}
|
|
onChange={toggleNetwork}
|
|
/>
|
|
}
|
|
>
|
|
{p.config && (
|
|
<>
|
|
<IPInput
|
|
editable={p.editable}
|
|
label="Bridge address"
|
|
version={p.version}
|
|
value={p.config?.bridge_address}
|
|
onValueChange={(v) => {
|
|
p.config!.bridge_address = v ?? "";
|
|
p.onChange?.(p.config);
|
|
}}
|
|
/>
|
|
|
|
<TextInput
|
|
label="Prefix"
|
|
editable={p.editable}
|
|
value={p.config.prefix.toString()}
|
|
type="number"
|
|
onValueChange={(v) => {
|
|
p.config!.prefix = Number(v);
|
|
p.onChange?.(p.config);
|
|
}}
|
|
size={
|
|
p.version === 4 ? { min: 0, max: 32 } : { min: 0, max: 128 }
|
|
}
|
|
/>
|
|
</>
|
|
)}
|
|
</EditSection>
|
|
|
|
{p.config && (p.editable || p.config.dhcp) && (
|
|
<EditSection
|
|
title={`DHCP v${p.version}`}
|
|
actions={
|
|
<Checkbox
|
|
disabled={!p.editable}
|
|
checked={!!p.config.dhcp}
|
|
onChange={(_ev, val) => toggleDHCP(val)}
|
|
/>
|
|
}
|
|
>
|
|
{p.config.dhcp && (
|
|
<>
|
|
<IPInput
|
|
label="DHCP allocation start"
|
|
editable={p.editable}
|
|
version={p.version}
|
|
value={p.config.dhcp.start}
|
|
onValueChange={(v) => {
|
|
p.config!.dhcp!.start = v!;
|
|
p.onChange(p.config);
|
|
}}
|
|
/>
|
|
<IPInput
|
|
label="DHCP allocation end"
|
|
editable={p.editable}
|
|
version={p.version}
|
|
value={p.config.dhcp.end}
|
|
onValueChange={(v) => {
|
|
p.config!.dhcp!.end = v!;
|
|
p.onChange(p.config);
|
|
}}
|
|
/>
|
|
</>
|
|
)}
|
|
</EditSection>
|
|
)}
|
|
|
|
{p.config?.dhcp && (p.editable || p.config.dhcp.hosts.length > 0) && (
|
|
<EditSection title="DHCP hosts reservations" fullWidth>
|
|
<NetDHCPHostReservations
|
|
{...p}
|
|
dhcp={p.config.dhcp}
|
|
onChange={(d) => {
|
|
p.config!.dhcp = d;
|
|
p.onChange?.(p.config);
|
|
}}
|
|
/>
|
|
</EditSection>
|
|
)}
|
|
</Grid>
|
|
);
|
|
}
|
|
|
|
function NetworkDetailsTabXML(p: DetailsInnerProps): React.ReactElement {
|
|
return (
|
|
<XMLAsyncWidget
|
|
errMsg="Failed to load network XML definition!"
|
|
identifier={p.net.uuid!}
|
|
load={() => NetworkApi.GetSingleXML(p.net.uuid!)}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function NetworkDetailsTabDanger(p: DetailsInnerProps): React.ReactElement {
|
|
const confirm = useConfirm();
|
|
const snackbar = useSnackbar();
|
|
const alert = useAlert();
|
|
const navigate = useNavigate();
|
|
|
|
const requestDelete = async () => {
|
|
try {
|
|
if (
|
|
!(await confirm(
|
|
"Do you really want to delete this network?",
|
|
`Delete network ${p.net.name}`,
|
|
"Delete"
|
|
))
|
|
)
|
|
return;
|
|
|
|
await NetworkApi.Delete(p.net);
|
|
|
|
navigate("/net");
|
|
snackbar("The network was successfully deleted!");
|
|
} catch (e) {
|
|
console.error(e);
|
|
alert(`Failed to delete the network!\n${e}`);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Button color="error" onClick={requestDelete}>
|
|
Delete this network
|
|
</Button>
|
|
);
|
|
}
|