This commit is contained in:
		@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										15
									
								
								virtweb_frontend/src/api/GroupApi.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								virtweb_frontend/src/api/GroupApi.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
import { APIClient } from "./ApiClient";
 | 
			
		||||
 | 
			
		||||
export class GroupApi {
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the entire list of networks
 | 
			
		||||
   */
 | 
			
		||||
  static async GetList(): Promise<string[]> {
 | 
			
		||||
    return (
 | 
			
		||||
      await APIClient.exec({
 | 
			
		||||
        method: "GET",
 | 
			
		||||
        uri: "/group/list",
 | 
			
		||||
      })
 | 
			
		||||
    ).data;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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<string[] | any>();
 | 
			
		||||
  const [isoList, setIsoList] = React.useState<IsoFile[] | any>();
 | 
			
		||||
  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={() => (
 | 
			
		||||
        <VMDetailsInner
 | 
			
		||||
          groupsList={groupsList}
 | 
			
		||||
          isoList={isoList}
 | 
			
		||||
          vcpuCombinations={vcpuCombinations}
 | 
			
		||||
          networksList={networksList}
 | 
			
		||||
@@ -75,6 +81,7 @@ enum VMTab {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DetailsInnerProps = DetailsProps & {
 | 
			
		||||
  groupsList: string[];
 | 
			
		||||
  isoList: IsoFile[];
 | 
			
		||||
  vcpuCombinations: number[];
 | 
			
		||||
  networksList: NetworkInfo[];
 | 
			
		||||
@@ -117,6 +124,8 @@ function VMDetailsInner(p: DetailsInnerProps): React.ReactElement {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function VMDetailsTabGeneral(p: DetailsInnerProps): React.ReactElement {
 | 
			
		||||
  const [addGroup, setAddGroup] = React.useState(false);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Grid container spacing={2}>
 | 
			
		||||
      {
 | 
			
		||||
@@ -175,6 +184,50 @@ function VMDetailsTabGeneral(p: DetailsInnerProps): React.ReactElement {
 | 
			
		||||
          }}
 | 
			
		||||
          multiline={true}
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
        <div style={{ display: "flex" }}>
 | 
			
		||||
          {addGroup ? (
 | 
			
		||||
            <TextInput
 | 
			
		||||
              label="Group"
 | 
			
		||||
              editable={p.editable}
 | 
			
		||||
              value={p.vm.group}
 | 
			
		||||
              onValueChange={(v) => {
 | 
			
		||||
                p.vm.group = v;
 | 
			
		||||
                p.onChange?.();
 | 
			
		||||
              }}
 | 
			
		||||
              size={ServerApi.Config.constraints.group_id_size}
 | 
			
		||||
            />
 | 
			
		||||
          ) : (
 | 
			
		||||
            <SelectInput
 | 
			
		||||
              editable={p.editable}
 | 
			
		||||
              label="Group"
 | 
			
		||||
              onValueChange={(v) => {
 | 
			
		||||
                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 && (
 | 
			
		||||
            <Tooltip
 | 
			
		||||
              title={
 | 
			
		||||
                addGroup
 | 
			
		||||
                  ? "Use an existing group"
 | 
			
		||||
                  : "Add a new group instead of using existing one"
 | 
			
		||||
              }
 | 
			
		||||
            >
 | 
			
		||||
              <IconButton onClick={() => setAddGroup(!addGroup)}>
 | 
			
		||||
                {addGroup ? <ListIcon /> : <AddIcon />}
 | 
			
		||||
              </IconButton>
 | 
			
		||||
            </Tooltip>
 | 
			
		||||
          )}
 | 
			
		||||
        </div>
 | 
			
		||||
      </EditSection>
 | 
			
		||||
 | 
			
		||||
      {/* General section */}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user