diff --git a/virtweb_frontend/src/App.tsx b/virtweb_frontend/src/App.tsx
index ad1961c..ed5628b 100644
--- a/virtweb_frontend/src/App.tsx
+++ b/virtweb_frontend/src/App.tsx
@@ -19,6 +19,7 @@ import { VMListRoute } from "./routes/VMListRoute";
import { CreateVMRoute, EditVMRoute } from "./routes/EditVMRoute";
import { VMRoute } from "./routes/VMRoute";
import { VNCRoute } from "./routes/VNCRoute";
+import { NetworksListRoute } from "./routes/NetworksListRoute";
interface AuthContext {
signedIn: boolean;
@@ -47,6 +48,8 @@ export function App() {
} />
} />
+ } />
+
} />
} />
diff --git a/virtweb_frontend/src/api/NetworksApi.ts b/virtweb_frontend/src/api/NetworksApi.ts
new file mode 100644
index 0000000..11bc291
--- /dev/null
+++ b/virtweb_frontend/src/api/NetworksApi.ts
@@ -0,0 +1,50 @@
+import { APIClient } from "./ApiClient";
+
+export interface IpConfig {
+ bridge_address: string;
+ prefix: number;
+ dhcp_range?: [string, string];
+}
+
+export interface NetworkInfo {
+ name: string;
+ uuid: string;
+ title?: string;
+ description?: string;
+ forward_mode: "NAT" | "Isolated";
+ device?: string;
+ dns_server?: string;
+ domain?: string;
+ ip_v4?: IpConfig;
+ ip_v6?: IpConfig;
+}
+
+export function NetworkURL(n: NetworkInfo, edit: boolean = false): string {
+ return `/net/${n.uuid}${edit ? "/edit" : ""}`;
+}
+
+export class NetworkApi {
+ /**
+ * Get the entire list of networks
+ */
+ static async GetList(): Promise {
+ return (
+ await APIClient.exec({
+ method: "GET",
+ uri: "/network/list",
+ })
+ ).data;
+ }
+
+ /**
+ * Delete a network
+ */
+ static async Delete(n: NetworkInfo): Promise {
+ return (
+ await APIClient.exec({
+ method: "DELETE",
+ uri: `/network/${n.uuid}`,
+ })
+ ).data;
+ }
+}
diff --git a/virtweb_frontend/src/routes/NetworksListRoute.tsx b/virtweb_frontend/src/routes/NetworksListRoute.tsx
new file mode 100644
index 0000000..fccf43c
--- /dev/null
+++ b/virtweb_frontend/src/routes/NetworksListRoute.tsx
@@ -0,0 +1,133 @@
+import DeleteIcon from "@mui/icons-material/Delete";
+import VisibilityIcon from "@mui/icons-material/Visibility";
+import {
+ Button,
+ IconButton,
+ Paper,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ Typography,
+} from "@mui/material";
+import React from "react";
+import { NetworkApi, NetworkInfo, NetworkURL } from "../api/NetworksApi";
+import { AsyncWidget } from "../widgets/AsyncWidget";
+import { RouterLink } from "../widgets/RouterLink";
+import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer";
+import { useConfirm } from "../hooks/providers/ConfirmDialogProvider";
+import { useSnackbar } from "../hooks/providers/SnackbarProvider";
+import { useAlert } from "../hooks/providers/AlertDialogProvider";
+
+export function NetworksListRoute(): React.ReactElement {
+ const confirm = useConfirm();
+ const snackbar = useSnackbar();
+ const alert = useAlert();
+
+ const [list, setList] = React.useState();
+
+ const [count, setCount] = React.useState(1);
+
+ const load = async () => {
+ setList(await NetworkApi.GetList());
+ };
+
+ const reload = () => {
+ setList(undefined);
+ setCount(count + 1);
+ };
+
+ const requestDelete = async (n: NetworkInfo) => {
+ try {
+ if (
+ !(await confirm(
+ "Do you really want to delete this network?",
+ `Delete network ${n.name}`,
+ "Delete"
+ ))
+ )
+ return;
+
+ await NetworkApi.Delete(n);
+ reload();
+ snackbar("The network was successfully deleted!");
+ } catch (e) {
+ console.error(e);
+ alert("Failed to delete the network!");
+ }
+ };
+
+ return (
+ (
+
+ )}
+ />
+ );
+}
+
+function NetworksListRouteInner(p: {
+ list: NetworkInfo[];
+ onRequestDelete: (n: NetworkInfo) => void;
+}): React.ReactElement {
+ return (
+
+
+
+ }
+ >
+
+
+
+
+ Name
+ Description
+ Network type
+ IP
+ Actions
+
+
+
+ {p.list.map((t) => {
+ return (
+
+ {t.name}
+
+ {t.description ?? (
+
+ None
+
+ )}
+
+ {t.forward_mode}
+
+ {t.ip_v4 && "IPv4"} {t.ip_v6 && "IPv6"}
+
+
+
+
+
+
+
+ p.onRequestDelete(t)}>
+
+
+
+
+ );
+ })}
+
+
+
+
+ );
+}
diff --git a/virtweb_frontend/src/widgets/BaseAuthenticatedPage.tsx b/virtweb_frontend/src/widgets/BaseAuthenticatedPage.tsx
index 9197967..48f1263 100644
--- a/virtweb_frontend/src/widgets/BaseAuthenticatedPage.tsx
+++ b/virtweb_frontend/src/widgets/BaseAuthenticatedPage.tsx
@@ -1,4 +1,11 @@
-import { mdiBoxShadow, mdiDisc, mdiHome, mdiInformation } from "@mdi/js";
+import {
+ mdiBoxShadow,
+ mdiDisc,
+ mdiHome,
+ mdiInformation,
+ mdiLan,
+ mdiNetwork,
+} from "@mdi/js";
import Icon from "@mdi/react";
import {
Box,
@@ -49,6 +56,11 @@ export function BaseAuthenticatedPage(): React.ReactElement {
uri="/vms"
icon={}
/>
+ }
+ />