diff --git a/virtweb_backend/src/controllers/server_controller.rs b/virtweb_backend/src/controllers/server_controller.rs index f42ffe5..ea56440 100644 --- a/virtweb_backend/src/controllers/server_controller.rs +++ b/virtweb_backend/src/controllers/server_controller.rs @@ -40,6 +40,7 @@ struct ServerConstraints { vnc_token_duration: u64, vm_name_size: LenConstraints, vm_title_size: LenConstraints, + group_id_size: LenConstraints, memory_size: LenConstraints, disk_name_size: LenConstraints, disk_size: LenConstraints, @@ -72,6 +73,7 @@ pub async fn static_config(local_auth: LocalAuthEnabled) -> impl Responder { vm_name_size: LenConstraints { min: 2, max: 50 }, vm_title_size: LenConstraints { min: 0, max: 50 }, + group_id_size: LenConstraints { min: 3, max: 50 }, memory_size: LenConstraints { min: constants::MIN_VM_MEMORY, max: constants::MAX_VM_MEMORY, diff --git a/virtweb_backend/src/main.rs b/virtweb_backend/src/main.rs index b6431c0..5797989 100644 --- a/virtweb_backend/src/main.rs +++ b/virtweb_backend/src/main.rs @@ -211,7 +211,7 @@ async fn main() -> std::io::Result<()> { ) .route("/api/vnc", web::get().to(vm_controller::vnc)) // Groups controller - .route("/api/groups/list", web::get().to(groups_controller::list)) + .route("/api/group/list", web::get().to(groups_controller::list)) // Network controller .route( "/api/network/create", diff --git a/virtweb_frontend/src/api/GroupApi.ts b/virtweb_frontend/src/api/GroupApi.ts new file mode 100644 index 0000000..dd1a340 --- /dev/null +++ b/virtweb_frontend/src/api/GroupApi.ts @@ -0,0 +1,15 @@ +import { APIClient } from "./ApiClient"; + +export class GroupApi { + /** + * Get the entire list of networks + */ + static async GetList(): Promise { + return ( + await APIClient.exec({ + method: "GET", + uri: "/group/list", + }) + ).data; + } +} diff --git a/virtweb_frontend/src/api/ServerApi.ts b/virtweb_frontend/src/api/ServerApi.ts index 51e90d1..757d5fb 100644 --- a/virtweb_frontend/src/api/ServerApi.ts +++ b/virtweb_frontend/src/api/ServerApi.ts @@ -16,6 +16,7 @@ export interface ServerConstraints { vnc_token_duration: number; vm_name_size: LenConstraint; vm_title_size: LenConstraint; + group_id_size: LenConstraint; memory_size: LenConstraint; disk_name_size: LenConstraint; disk_size: LenConstraint; diff --git a/virtweb_frontend/src/api/VMApi.ts b/virtweb_frontend/src/api/VMApi.ts index 9f58c46..d8bd614 100644 --- a/virtweb_frontend/src/api/VMApi.ts +++ b/virtweb_frontend/src/api/VMApi.ts @@ -63,6 +63,7 @@ interface VMInfoInterface { genid?: string; title?: string; description?: string; + group?: string; boot_type: "UEFI" | "UEFISecureBoot"; architecture: "i686" | "x86_64"; memory: number; @@ -80,6 +81,7 @@ export class VMInfo implements VMInfoInterface { genid?: string; title?: string; description?: string; + group?: string; boot_type: "UEFI" | "UEFISecureBoot"; architecture: "i686" | "x86_64"; number_vcpu: number; @@ -96,6 +98,7 @@ export class VMInfo implements VMInfoInterface { this.genid = int.genid; this.title = int.title; this.description = int.description; + this.group = int.group; this.boot_type = int.boot_type; this.architecture = int.architecture; this.number_vcpu = int.number_vcpu; diff --git a/virtweb_frontend/src/widgets/vms/VMDetails.tsx b/virtweb_frontend/src/widgets/vms/VMDetails.tsx index d685df0..883e87a 100644 --- a/virtweb_frontend/src/widgets/vms/VMDetails.tsx +++ b/virtweb_frontend/src/widgets/vms/VMDetails.tsx @@ -1,8 +1,11 @@ -import { Button } from "@mui/material"; +import AddIcon from "@mui/icons-material/Add"; +import ListIcon from "@mui/icons-material/List"; +import { Button, IconButton, Tooltip } from "@mui/material"; import Grid from "@mui/material/Grid2"; import React from "react"; import { useNavigate } from "react-router-dom"; import { validate as validateUUID } from "uuid"; +import { GroupApi } from "../../api/GroupApi"; import { IsoFile, IsoFilesApi } from "../../api/IsoFilesApi"; import { NWFilter, NWFilterApi } from "../../api/NWFilterApi"; import { NetworkApi, NetworkInfo } from "../../api/NetworksApi"; @@ -32,6 +35,7 @@ interface DetailsProps { } export function VMDetails(p: DetailsProps): React.ReactElement { + const [groupsList, setGroupsList] = React.useState(); const [isoList, setIsoList] = React.useState(); const [vcpuCombinations, setVCPUCombinations] = React.useState< number[] | any @@ -42,6 +46,7 @@ export function VMDetails(p: DetailsProps): React.ReactElement { >(); const load = async () => { + setGroupsList(await GroupApi.GetList()); setIsoList(await IsoFilesApi.GetList()); setVCPUCombinations(await ServerApi.NumberVCPUs()); setNetworksList(await NetworkApi.GetList()); @@ -55,6 +60,7 @@ export function VMDetails(p: DetailsProps): React.ReactElement { errMsg="Failed to load the list of ISO files" build={() => ( { @@ -175,6 +184,50 @@ function VMDetailsTabGeneral(p: DetailsInnerProps): React.ReactElement { }} multiline={true} /> + +
+ {addGroup ? ( + { + p.vm.group = v; + p.onChange?.(); + }} + size={ServerApi.Config.constraints.group_id_size} + /> + ) : ( + { + p.vm.group = v! as any; + p.onChange?.(); + }} + value={p.vm.group} + options={[ + { label: "None" }, + ...p.groupsList.map((g) => { + return { value: g, label: g }; + }), + ]} + /> + )} + {p.editable && ( + + setAddGroup(!addGroup)}> + {addGroup ? : } + + + )} +
{/* General section */}