Create frames for network filters management

This commit is contained in:
Pierre HUBERT 2024-01-03 19:20:37 +01:00
parent 22f5acd0ff
commit c880c5e6bb
7 changed files with 397 additions and 14 deletions

View File

@ -27,6 +27,11 @@ import { BaseLoginPage } from "./widgets/BaseLoginPage";
import { ViewNetworkRoute } from "./routes/ViewNetworkRoute"; import { ViewNetworkRoute } from "./routes/ViewNetworkRoute";
import { HomeRoute } from "./routes/HomeRoute"; import { HomeRoute } from "./routes/HomeRoute";
import { NetworkFiltersListRoute } from "./routes/NetworkFiltersListRoute"; import { NetworkFiltersListRoute } from "./routes/NetworkFiltersListRoute";
import { ViewNWFilterRoute } from "./routes/ViewNWFilterRoute";
import {
CreateNWFilterRoute,
EditNWFilterRoute,
} from "./routes/EditNWFilterRoute";
interface AuthContext { interface AuthContext {
signedIn: boolean; signedIn: boolean;
@ -63,6 +68,9 @@ export function App() {
<Route path="net/:uuid/edit" element={<EditNetworkRoute />} /> <Route path="net/:uuid/edit" element={<EditNetworkRoute />} />
<Route path="nwfilter" element={<NetworkFiltersListRoute />} /> <Route path="nwfilter" element={<NetworkFiltersListRoute />} />
<Route path="nwfilter/new" element={<CreateNWFilterRoute />} />
<Route path="nwfilter/:uuid" element={<ViewNWFilterRoute />} />
<Route path="nwfilter/:uuid/edit" element={<EditNWFilterRoute />} />
<Route path="sysinfo" element={<SysInfoRoute />} /> <Route path="sysinfo" element={<SysInfoRoute />} />
<Route path="*" element={<NotFoundRoute />} /> <Route path="*" element={<NotFoundRoute />} />

View File

@ -1,4 +1,5 @@
import { APIClient } from "./ApiClient"; import { APIClient } from "./ApiClient";
import { ServerApi } from "./ServerApi";
export interface NWFilterChain { export interface NWFilterChain {
protocol: string; protocol: string;
@ -18,8 +19,91 @@ export interface NWFSMac {
comment?: string; comment?: string;
} }
// TODO : complete export interface NWFSArpOrRARP {
export type NWFSelector = NWFSAll | NWFSMac; srcmacaddr?: string;
srcmacmask?: string;
dstmacaddr?: string;
dstmacmask?: string;
arpsrcipaddr?: string;
arpsrcipmask?: number;
arpdstipaddr?: string;
arpdstipmask?: number;
comment?: string;
}
export type NWFSArp = NWFSArpOrRARP & {
type: "arp";
};
export type NWFSRArp = NWFSArpOrRARP & {
type: "rarp";
};
export interface NWFSIPBase {
srcmacaddr?: string;
srcmacmask?: string;
dstmacaddr?: string;
dstmacmask?: string;
srcipaddr?: string;
srcipmask?: number;
dstipaddr?: string;
dstipmask?: number;
comment?: string;
}
export type NFWSIPv4 = NWFSIPBase & { type: "ipv4" };
export type NFWSIPv6 = NWFSIPBase & { type: "ipv6" };
export type Layer4State =
| "NEW"
| "ESTABLISHED"
| "RELATED"
| "INVALID"
| "NONE";
export interface NWFSLayer4Base {
srcmacaddr?: string;
srcipaddr?: string;
srcipmask?: number;
dstipaddr?: string;
dstipmask?: number;
srcipfrom?: string;
srcipto?: string;
dstipfrom?: string;
dstipto?: string;
srcportstart?: number;
srcportend?: number;
dstportstart?: number;
dstportend?: number;
state?: Layer4State;
comment?: string;
}
export type NFWSTCPv4 = NWFSLayer4Base & { type: "tcp" };
export type NFWSUDPv4 = NWFSLayer4Base & { type: "udp" };
export type NFWSSCTPv4 = NWFSLayer4Base & { type: "sctp" };
export type NFWSICMPv4 = NWFSLayer4Base & { type: "icmp" };
export type NFWSTCPv6 = NWFSLayer4Base & { type: "tcpipv6" };
export type NFWSUDPv6 = NWFSLayer4Base & { type: "udpipv6" };
export type NFWSSCTPv6 = NWFSLayer4Base & { type: "sctpipv6" };
export type NFWSICMPv6 = NWFSLayer4Base & { type: "icmpipv6" };
export type NWFSelector =
| NWFSAll
| NWFSMac
| NWFSArp
| NWFSRArp
| NFWSIPv4
| NFWSIPv6
| NFWSTCPv4
| NFWSUDPv4
| NFWSSCTPv4
| NFWSICMPv4
| NFWSTCPv6
| NFWSUDPv6
| NFWSSCTPv6
| NFWSICMPv6;
export interface NWFilterRule { export interface NWFilterRule {
action: "drop" | "reject" | "accept" | "return" | "continue"; action: "drop" | "reject" | "accept" | "return" | "continue";
@ -41,6 +125,10 @@ export function NWFilterURL(n: NWFilter, edit: boolean = false): string {
return `/nwfilter/${n.uuid}${edit ? "/edit" : ""}`; return `/nwfilter/${n.uuid}${edit ? "/edit" : ""}`;
} }
export function NWFilterIsBuiltin(n: NWFilter): boolean {
return ServerApi.Config.builtin_nwfilter_rules.includes(n.name);
}
export class NWFilterApi { export class NWFilterApi {
/** /**
* Get the entire list of networks * Get the entire list of networks
@ -57,4 +145,64 @@ export class NWFilterApi {
return list; return list;
} }
/**
* Get the information about a single network filter
*/
static async GetSingle(uuid: string): Promise<NWFilter> {
return (
await APIClient.exec({
method: "GET",
uri: `/nwfilter/${uuid}`,
})
).data;
}
/**
* Get the source XML configuration of a network filter for debugging purposes
*/
static async GetSingleXML(uuid: string): Promise<string> {
return (
await APIClient.exec({
uri: `/nwfilter/${uuid}/src`,
method: "GET",
})
).data;
}
/**
* Create a new network filter
*/
static async Create(n: NWFilter): Promise<{ uid: string }> {
return (
await APIClient.exec({
method: "POST",
uri: "/nwfilter/create",
jsonData: n,
})
).data;
}
/**
* Update an existing network filter
*/
static async Update(n: NWFilter): Promise<{ uid: string }> {
return (
await APIClient.exec({
method: "PUT",
uri: `/nwfilter/${n.uuid}`,
jsonData: n,
})
).data;
}
/**
* Delete a network filter
*/
static async Delete(n: NWFilter): Promise<void> {
await APIClient.exec({
method: "DELETE",
uri: `/nwfilter/${n.uuid}`,
});
}
} }

View File

@ -160,12 +160,10 @@ export class NetworkApi {
/** /**
* Delete a network * Delete a network
*/ */
static async Delete(n: NetworkInfo): Promise<NetworkInfo[]> { static async Delete(n: NetworkInfo): Promise<void> {
return ( await APIClient.exec({
await APIClient.exec({ method: "DELETE",
method: "DELETE", uri: `/network/${n.uuid}`,
uri: `/network/${n.uuid}`, });
})
).data;
} }
} }

View File

@ -0,0 +1,150 @@
import { Button } from "@mui/material";
import React from "react";
import { useNavigate, useParams } from "react-router-dom";
import { useAlert } from "../hooks/providers/AlertDialogProvider";
import { useLoadingMessage } from "../hooks/providers/LoadingMessageProvider";
import { useSnackbar } from "../hooks/providers/SnackbarProvider";
import { AsyncWidget } from "../widgets/AsyncWidget";
import { ConfigImportExportButtons } from "../widgets/ConfigImportExportButtons";
import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer";
import { NWFilter, NWFilterApi, NWFilterURL } from "../api/NWFilterApi";
import { NWFilterDetails } from "../widgets/nwfilter/NWFilterDetails";
export function CreateNWFilterRoute(): React.ReactElement {
const alert = useAlert();
const snackbar = useSnackbar();
const navigate = useNavigate();
const [nwfilter, setNWFilter] = React.useState<NWFilter>({
name: "my-filter",
join_filters: [],
rules: [],
});
const createNWFilter = async (n: NWFilter) => {
try {
const res = await NWFilterApi.Create(n);
snackbar("The network filter was successfully created!");
navigate(`/nwfilter/${res.uid}`);
} catch (e) {
console.error(e);
alert(`Failed to create network filter!\n${e}`);
}
};
return (
<EditNetworkFilterRouteInner
nwfilter={nwfilter}
creating={true}
onCancel={() => navigate("/nwfilter")}
onSave={createNWFilter}
onReplace={setNWFilter}
/>
);
}
export function EditNWFilterRoute(): React.ReactElement {
const alert = useAlert();
const snackbar = useSnackbar();
const { uuid } = useParams();
const navigate = useNavigate();
const [nwfilter, setNWFilter] = React.useState<NWFilter | undefined>();
const load = async () => {
setNWFilter(await NWFilterApi.GetSingle(uuid!));
};
const updateNetworkFilter = async (n: NWFilter) => {
try {
await NWFilterApi.Update(n);
snackbar("The network filter was successfully updated!");
navigate(NWFilterURL(nwfilter!));
} catch (e) {
console.error(e);
alert(`Failed to update network filter!\n${e}`);
}
};
return (
<AsyncWidget
loadKey={uuid}
ready={nwfilter !== undefined}
errMsg="Failed to fetch network filter information!"
load={load}
build={() => (
<EditNetworkFilterRouteInner
nwfilter={nwfilter!}
creating={false}
onCancel={() => navigate(`/nwfilter/${uuid}`)}
onSave={updateNetworkFilter}
onReplace={setNWFilter}
/>
)}
/>
);
}
function EditNetworkFilterRouteInner(p: {
nwfilter: NWFilter;
creating: boolean;
onCancel: () => void;
onSave: (vm: NWFilter) => Promise<void>;
onReplace: (vm: NWFilter) => void;
}): React.ReactElement {
const loadingMessage = useLoadingMessage();
const [changed, setChanged] = React.useState(false);
const [, updateState] = React.useState<any>();
const forceUpdate = React.useCallback(() => updateState({}), []);
const valueChanged = () => {
setChanged(true);
forceUpdate();
};
const save = async () => {
loadingMessage.show("Saving network filter configuration...");
await p.onSave(p.nwfilter);
loadingMessage.hide();
};
return (
<VirtWebRouteContainer
label={p.creating ? "Create a Network Filter" : "Edit Network Filter"}
actions={
<span>
<ConfigImportExportButtons
currentConf={p.nwfilter}
filename={`nwfilter-${p.nwfilter.name}.json`}
importConf={(c) => {
p.onReplace(c);
valueChanged();
}}
/>
{changed && (
<Button
variant="contained"
onClick={save}
style={{ marginRight: "10px" }}
>
{p.creating ? "Create" : "Save"}
</Button>
)}
<Button onClick={p.onCancel} variant="outlined">
Cancel
</Button>
</span>
}
>
<NWFilterDetails
nwfilter={p.nwfilter}
editable={true}
onChange={valueChanged}
/>
</VirtWebRouteContainer>
);
}

View File

@ -15,7 +15,12 @@ import {
} from "@mui/material"; } from "@mui/material";
import React from "react"; import React from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { NWFilter, NWFilterApi, NWFilterURL } from "../api/NWFilterApi"; import {
NWFilter,
NWFilterApi,
NWFilterIsBuiltin,
NWFilterURL,
} from "../api/NWFilterApi";
import { AsyncWidget } from "../widgets/AsyncWidget"; import { AsyncWidget } from "../widgets/AsyncWidget";
import { RouterLink } from "../widgets/RouterLink"; import { RouterLink } from "../widgets/RouterLink";
import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer"; import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer";
@ -61,10 +66,7 @@ function NetworkFiltersListRouteInner(p: {
const onlyBuiltin = visibleFilters === VisibleFilters.Builtin; const onlyBuiltin = visibleFilters === VisibleFilters.Builtin;
return p.list.filter( return p.list.filter((f) => NWFilterIsBuiltin(f) === onlyBuiltin);
(f) =>
ServerApi.Config.builtin_nwfilter_rules.includes(f.name) === onlyBuiltin
);
}, [visibleFilters]); }, [visibleFilters]);
return ( return (

View File

@ -0,0 +1,65 @@
import { Button } from "@mui/material";
import React from "react";
import { useNavigate, useParams } from "react-router-dom";
import {
NWFilter,
NWFilterApi,
NWFilterIsBuiltin,
NWFilterURL,
} from "../api/NWFilterApi";
import { AsyncWidget } from "../widgets/AsyncWidget";
import { ConfigImportExportButtons } from "../widgets/ConfigImportExportButtons";
import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer";
import { NWFilterDetails } from "../widgets/nwfilter/NWFilterDetails";
export function ViewNWFilterRoute() {
const { uuid } = useParams();
const [nwfilter, setNWFilter] = React.useState<NWFilter | undefined>();
const load = async () => {
setNWFilter(await NWFilterApi.GetSingle(uuid!));
};
return (
<AsyncWidget
loadKey={uuid}
ready={nwfilter !== undefined}
errMsg="Failed to fetch network filter information!"
load={load}
build={() => <ViewNetworkFilterRouteInner nwfilter={nwfilter!} />}
/>
);
}
function ViewNetworkFilterRouteInner(p: {
nwfilter: NWFilter;
}): React.ReactElement {
const navigate = useNavigate();
return (
<VirtWebRouteContainer
label={`Network filter ${p.nwfilter.name}`}
actions={
<span style={{ display: "flex", alignItems: "center" }}>
<ConfigImportExportButtons
filename={`nwfilter-${p.nwfilter.name}.json`}
currentConf={p.nwfilter}
/>
{!NWFilterIsBuiltin(p.nwfilter) && (
<Button
variant="contained"
style={{ marginLeft: "15px" }}
onClick={() => navigate(NWFilterURL(p.nwfilter, true))}
>
Edit
</Button>
)}
</span>
}
>
<NWFilterDetails nwfilter={p.nwfilter} editable={false} />
</VirtWebRouteContainer>
);
}

View File

@ -0,0 +1,12 @@
import { ReactElement } from "react";
import { NWFilter } from "../../api/NWFilterApi";
interface DetailsProps {
nwfilter: NWFilter;
editable: boolean;
onChange?: () => void;
}
export function NWFilterDetails(p: DetailsProps): ReactElement {
return <>todo</>;
}