From 8b53875349b3c9b1ec8ed1b215872686357d1b70 Mon Sep 17 00:00:00 2001 From: Pierre Hubert Date: Wed, 6 Dec 2023 19:10:25 +0100 Subject: [PATCH] Can configure network autostart --- virtweb_frontend/src/api/NetworksApi.ts | 23 ++++++ virtweb_frontend/src/api/VMApi.ts | 8 +- .../src/widgets/forms/IPInput.tsx | 79 +++++++++++++++++++ ...tostartInput.tsx => ResAutostartInput.tsx} | 27 ++++--- .../src/widgets/forms/TextInput.tsx | 2 +- .../src/widgets/net/NetworkDetails.tsx | 13 ++- .../src/widgets/vms/VMDetails.tsx | 13 ++- 7 files changed, 143 insertions(+), 22 deletions(-) create mode 100644 virtweb_frontend/src/widgets/forms/IPInput.tsx rename virtweb_frontend/src/widgets/forms/{VMAutostartInput.tsx => ResAutostartInput.tsx} (67%) diff --git a/virtweb_frontend/src/api/NetworksApi.ts b/virtweb_frontend/src/api/NetworksApi.ts index 41f6b0e..044a9e3 100644 --- a/virtweb_frontend/src/api/NetworksApi.ts +++ b/virtweb_frontend/src/api/NetworksApi.ts @@ -95,6 +95,29 @@ export class NetworkApi { }); } + /** + * Check if autostart is enabled on a network + */ + static async IsAutostart(net: NetworkInfo): Promise { + return ( + await APIClient.exec({ + uri: `/network/${net.uuid}/autostart`, + method: "GET", + }) + ).data.autostart; + } + + /** + * Set autostart status of a network + */ + static async SetAutostart(net: NetworkInfo, enabled: boolean): Promise { + await APIClient.exec({ + uri: `/network/${net.uuid}/autostart`, + method: "PUT", + jsonData: { autostart: enabled }, + }); + } + /** * Update an existing network */ diff --git a/virtweb_frontend/src/api/VMApi.ts b/virtweb_frontend/src/api/VMApi.ts index 1039fb2..9daec0f 100644 --- a/virtweb_frontend/src/api/VMApi.ts +++ b/virtweb_frontend/src/api/VMApi.ts @@ -46,10 +46,10 @@ interface VMInfoInterface { export class VMInfo implements VMInfoInterface { name: string; - uuid?: string | undefined; - genid?: string | undefined; - title?: string | undefined; - description?: string | undefined; + uuid?: string; + genid?: string; + title?: string; + description?: string; boot_type: "UEFI" | "UEFISecureBoot"; architecture: "i686" | "x86_64"; memory: number; diff --git a/virtweb_frontend/src/widgets/forms/IPInput.tsx b/virtweb_frontend/src/widgets/forms/IPInput.tsx new file mode 100644 index 0000000..9a66bb9 --- /dev/null +++ b/virtweb_frontend/src/widgets/forms/IPInput.tsx @@ -0,0 +1,79 @@ +import { TextInput } from "./TextInput"; + +export function IPInput(p: { + label: string; + editable: boolean; + value?: string; + onValueChange?: (newVal: string | undefined) => void; + version: 4 | 6; +}): React.ReactElement { + const { onValueChange, ...props } = p; + return ( + { + onValueChange?.(p.version === 4 ? sanitizeIpV4(v) : sanitizeIpV6(v)); + }} + {...props} + /> + ); +} + +function sanitizeIpV4(s: string | undefined): string | undefined { + if (s === "" || s === undefined) return s; + + let split = s.split("."); + if (split.length > 4) split.splice(4); + + let needAnotherIteration = false; + + const res = split + .map((v) => { + if (v === "") return ""; + + const num = Number(v); + if (isNaN(num) || num < 0) return "0"; + if (num > 255) { + needAnotherIteration = true; + return v.slice(0, 2) + "." + v.slice(2); + } + return num.toString(); + }) + .join("."); + + return needAnotherIteration ? sanitizeIpV4(res) : res; +} + +function sanitizeIpV6(s: string | undefined): string | undefined { + if (s === "" || s === undefined) return s; + + const split = s.split(":"); + if (split.length > 8) split.splice(8); + + let needAnotherIteration = false; + + let res = split + .map((e) => { + if (e === "") return e; + + const num = parseInt(e, 16); + if (isNaN(num)) return "0"; + + let s = num.toString(16); + if (num > 0xffff) { + needAnotherIteration = true; + return s.slice(0, 4) + ":" + s.slice(4); + } + + return s; + }) + .join(":"); + + const firstIndex = res.indexOf("::"); + let nextIndex = res.lastIndexOf("::"); + while (nextIndex !== firstIndex) { + res = res.slice(0, nextIndex) + res.slice(nextIndex + 1); + nextIndex = res.lastIndexOf("::"); + } + + return needAnotherIteration ? sanitizeIpV6(res) : res; +} diff --git a/virtweb_frontend/src/widgets/forms/VMAutostartInput.tsx b/virtweb_frontend/src/widgets/forms/ResAutostartInput.tsx similarity index 67% rename from virtweb_frontend/src/widgets/forms/VMAutostartInput.tsx rename to virtweb_frontend/src/widgets/forms/ResAutostartInput.tsx index 5930dab..e7f7367 100644 --- a/virtweb_frontend/src/widgets/forms/VMAutostartInput.tsx +++ b/virtweb_frontend/src/widgets/forms/ResAutostartInput.tsx @@ -1,14 +1,15 @@ import { Alert, CircularProgress, Typography } from "@mui/material"; -import { VMApi, VMInfo } from "../../api/VMApi"; import { AsyncWidget } from "../AsyncWidget"; import React from "react"; import { CheckboxInput } from "./CheckboxInput"; import { useAlert } from "../../hooks/providers/AlertDialogProvider"; import { useSnackbar } from "../../hooks/providers/SnackbarProvider"; -export function VMAutostartInput(p: { +export function ResAutostartInput(p: { editable: boolean; - vm: VMInfo; + checkAutotostart: () => Promise; + setAutotostart: (enable: boolean) => Promise; + ressourceName: string; }): React.ReactElement { const alert = useAlert(); const snackbar = useSnackbar(); @@ -16,25 +17,25 @@ export function VMAutostartInput(p: { const [enabled, setEnabled] = React.useState(); const load = async () => { - setEnabled(await VMApi.IsAutostart(p.vm)); + setEnabled(await p.checkAutotostart()); }; const update = async (enabled: boolean) => { try { - await VMApi.SetAutostart(p.vm, enabled); - snackbar("Autostart status successfully updated!"); + await p.setAutotostart(enabled); + snackbar(`Autostart status of ${p.ressourceName} successfully updated!`); setEnabled(enabled); } catch (e) { console.error(e); - alert("Failed to update autostart status of the VM!"); + alert(`Failed to update autostart status of ${p.ressourceName}!`); } }; return ( ( Checking for autostart @@ -42,27 +43,29 @@ export function VMAutostartInput(p: { )} buildError={(e: string) => {e}} build={() => ( - )} /> ); } -function VMAutostartInputInner(p: { +function ResAutostartInputInner(p: { editable: boolean; enabled: boolean; setEnabled: (b: boolean) => void; + resName: string; }): React.ReactElement { return (
diff --git a/virtweb_frontend/src/widgets/forms/TextInput.tsx b/virtweb_frontend/src/widgets/forms/TextInput.tsx index ca3cc64..a83680a 100644 --- a/virtweb_frontend/src/widgets/forms/TextInput.tsx +++ b/virtweb_frontend/src/widgets/forms/TextInput.tsx @@ -16,7 +16,7 @@ export function TextInput(p: { maxRows?: number; type?: React.HTMLInputTypeAttribute; }): React.ReactElement { - if (((!p.editable && p.value) ?? "") === "") return <>; + if (!p.editable && (p.value ?? "") === "") return <>; let valueError = undefined; if (p.value && p.value.length > 0) { diff --git a/virtweb_frontend/src/widgets/net/NetworkDetails.tsx b/virtweb_frontend/src/widgets/net/NetworkDetails.tsx index e3cdedc..cab22a8 100644 --- a/virtweb_frontend/src/widgets/net/NetworkDetails.tsx +++ b/virtweb_frontend/src/widgets/net/NetworkDetails.tsx @@ -1,6 +1,6 @@ import { Checkbox, Grid } from "@mui/material"; import React from "react"; -import { IpConfig, NetworkInfo } from "../../api/NetworksApi"; +import { IpConfig, NetworkApi, NetworkInfo } from "../../api/NetworksApi"; import { ServerApi } from "../../api/ServerApi"; import { AsyncWidget } from "../AsyncWidget"; import { EditSection } from "../forms/EditSection"; @@ -9,6 +9,7 @@ import { SelectInput } from "../forms/SelectInput"; import { TextInput } from "../forms/TextInput"; import { useConfirm } from "../../hooks/providers/ConfirmDialogProvider"; import { CheckboxInput } from "../forms/CheckboxInput"; +import { ResAutostartInput } from "../forms/ResAutostartInput"; interface DetailsProps { net: NetworkInfo; @@ -75,8 +76,16 @@ function NetworkDetailsInner( }} multiline={true} /> + + {p.net.uuid && ( + NetworkApi.IsAutostart(p.net)} + setAutotostart={(e) => NetworkApi.SetAutostart(p.net, e)} + /> + )} - {/* TODO : autostart */} - {p.vm.uuid && } + {p.vm.uuid && ( + VMApi.IsAutostart(p.vm)} + setAutotostart={(e) => VMApi.SetAutostart(p.vm, e)} + /> + )} {/* Storage section */}