VirtWeb/virtweb_frontend/src/widgets/net/NetworkDetails.tsx

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