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={} /> + } + />