From d243022810e1bb67365f15c0480bdbf21f10e012 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Tue, 3 Dec 2024 21:49:53 +0100 Subject: [PATCH] Ready to implement groups web ui integration --- .../src/controllers/server_controller.rs | 58 ++++++++----------- remote_backend/src/virtweb_client.rs | 12 ++++ remote_frontend/src/App.tsx | 15 ++++- remote_frontend/src/api/ServerApi.ts | 12 +++- remote_frontend/src/api/VMApi.ts | 5 ++ remote_frontend/src/widgets/GroupsWidget.tsx | 5 ++ .../src/widgets/VirtualMachinesWidget.tsx | 9 ++- 7 files changed, 77 insertions(+), 39 deletions(-) create mode 100644 remote_frontend/src/widgets/GroupsWidget.tsx diff --git a/remote_backend/src/controllers/server_controller.rs b/remote_backend/src/controllers/server_controller.rs index d84d51f..962dc05 100644 --- a/remote_backend/src/controllers/server_controller.rs +++ b/remote_backend/src/controllers/server_controller.rs @@ -2,7 +2,7 @@ use crate::app_config::AppConfig; use crate::controllers::HttpResult; use crate::extractors::auth_extractor::AuthExtractor; use crate::virtweb_client; -use crate::virtweb_client::{GroupID, VMInfo}; +use crate::virtweb_client::{GroupID, VMCaps, VMInfo}; use actix_web::HttpResponse; #[derive(serde::Serialize)] @@ -29,28 +29,16 @@ pub struct Rights { pub struct GroupInfo { id: GroupID, vms: Vec, - can_get_state: bool, - can_start: bool, - can_shutdown: bool, - can_kill: bool, - can_reset: bool, - can_suspend: bool, - can_resume: bool, - can_screenshot: bool, + #[serde(flatten)] + caps: VMCaps, } #[derive(Debug, serde::Serialize)] pub struct VMInfoAndCaps { #[serde(flatten)] info: VMInfo, - can_get_state: bool, - can_start: bool, - can_shutdown: bool, - can_kill: bool, - can_reset: bool, - can_suspend: bool, - can_resume: bool, - can_screenshot: bool, + #[serde(flatten)] + caps: VMCaps, } pub async fn rights() -> HttpResult { @@ -68,14 +56,16 @@ pub async fn rights() -> HttpResult { res.groups.push(GroupInfo { id: g.clone(), vms: group_vms, - can_get_state: rights.is_route_allowed("GET", &g.route_vm_state(None)), - can_start: rights.is_route_allowed("GET", &g.route_vm_start(None)), - can_shutdown: rights.is_route_allowed("GET", &g.route_vm_shutdown(None)), - can_kill: rights.is_route_allowed("GET", &g.route_vm_kill(None)), - can_reset: rights.is_route_allowed("GET", &g.route_vm_reset(None)), - can_suspend: rights.is_route_allowed("GET", &g.route_vm_suspend(None)), - can_resume: rights.is_route_allowed("GET", &g.route_vm_resume(None)), - can_screenshot: rights.is_route_allowed("GET", &g.route_vm_screenshot(None)), + caps: VMCaps { + can_get_state: rights.is_route_allowed("GET", &g.route_vm_state(None)), + can_start: rights.is_route_allowed("GET", &g.route_vm_start(None)), + can_shutdown: rights.is_route_allowed("GET", &g.route_vm_shutdown(None)), + can_kill: rights.is_route_allowed("GET", &g.route_vm_kill(None)), + can_reset: rights.is_route_allowed("GET", &g.route_vm_reset(None)), + can_suspend: rights.is_route_allowed("GET", &g.route_vm_suspend(None)), + can_resume: rights.is_route_allowed("GET", &g.route_vm_resume(None)), + can_screenshot: rights.is_route_allowed("GET", &g.route_vm_screenshot(None)), + }, }) } @@ -84,14 +74,16 @@ pub async fn rights() -> HttpResult { res.vms.push(VMInfoAndCaps { info: vm_info, - can_get_state: rights.is_route_allowed("GET", &v.route_state()), - can_start: rights.is_route_allowed("GET", &v.route_start()), - can_shutdown: rights.is_route_allowed("GET", &v.route_shutdown()), - can_kill: rights.is_route_allowed("GET", &v.route_kill()), - can_reset: rights.is_route_allowed("GET", &v.route_reset()), - can_suspend: rights.is_route_allowed("GET", &v.route_suspend()), - can_resume: rights.is_route_allowed("GET", &v.route_resume()), - can_screenshot: rights.is_route_allowed("GET", &v.route_screenshot()), + caps: VMCaps { + can_get_state: rights.is_route_allowed("GET", &v.route_state()), + can_start: rights.is_route_allowed("GET", &v.route_start()), + can_shutdown: rights.is_route_allowed("GET", &v.route_shutdown()), + can_kill: rights.is_route_allowed("GET", &v.route_kill()), + can_reset: rights.is_route_allowed("GET", &v.route_reset()), + can_suspend: rights.is_route_allowed("GET", &v.route_suspend()), + can_resume: rights.is_route_allowed("GET", &v.route_resume()), + can_screenshot: rights.is_route_allowed("GET", &v.route_screenshot()), + }, }) } diff --git a/remote_backend/src/virtweb_client.rs b/remote_backend/src/virtweb_client.rs index 9821ad4..10748f9 100644 --- a/remote_backend/src/virtweb_client.rs +++ b/remote_backend/src/virtweb_client.rs @@ -176,6 +176,18 @@ pub struct VMInfo { pub number_vcpu: usize, } +#[derive(serde::Deserialize, serde::Serialize, Debug)] +pub struct VMCaps { + pub can_get_state: bool, + pub can_start: bool, + pub can_shutdown: bool, + pub can_kill: bool, + pub can_reset: bool, + pub can_suspend: bool, + pub can_resume: bool, + pub can_screenshot: bool, +} + #[derive(serde::Deserialize, serde::Serialize, Debug)] pub struct VMState { pub state: String, diff --git a/remote_frontend/src/App.tsx b/remote_frontend/src/App.tsx index c65fc8f..a8951d7 100644 --- a/remote_frontend/src/App.tsx +++ b/remote_frontend/src/App.tsx @@ -5,6 +5,8 @@ import { typographyStyles, } from "@fluentui/react-components"; import { + AppsListDetailFilled, + AppsListDetailRegular, DesktopFilled, DesktopRegular, InfoFilled, @@ -18,6 +20,7 @@ import { AsyncWidget } from "./widgets/AsyncWidget"; import { MainMenu } from "./widgets/MainMenu"; import { SystemInfoWidget } from "./widgets/SystemInfoWidget"; import { VirtualMachinesWidget } from "./widgets/VirtualMachinesWidget"; +import { GroupsWidget } from "./widgets/GroupsWidget"; const useStyles = makeStyles({ title: typographyStyles.title2, @@ -27,6 +30,8 @@ const InfoIcon = bundleIcon(InfoFilled, InfoRegular); const DesktopIcon = bundleIcon(DesktopFilled, DesktopRegular); +const AppListIcon = bundleIcon(AppsListDetailFilled, AppsListDetailRegular); + export function App() { return ( ("vm"); + const [tab, setTab] = React.useState<"group" | "vm" | "info">("group"); const [rights, setRights] = React.useState(); @@ -82,6 +87,13 @@ function AuthenticatedApp(): React.ReactElement { selectedValue={tab} onTabSelect={(_, d) => setTab(d.value as any)} > + } + disabled={rights!.groups.length === 0} + > + Groups + } @@ -101,6 +113,7 @@ function AuthenticatedApp(): React.ReactElement { + {tab === "group" && } {tab === "vm" && } {tab === "info" && } diff --git a/remote_frontend/src/api/ServerApi.ts b/remote_frontend/src/api/ServerApi.ts index ad6b441..7c5abb6 100644 --- a/remote_frontend/src/api/ServerApi.ts +++ b/remote_frontend/src/api/ServerApi.ts @@ -1,5 +1,5 @@ import { APIClient } from "./ApiClient"; -import { VMInfo } from "./VMApi"; +import { VMCaps, VMInfo, VMInfoAndCaps } from "./VMApi"; export interface ServerConfig { authenticated: boolean; @@ -7,10 +7,18 @@ export interface ServerConfig { } export interface Rights { - vms: VMInfo[]; + groups: VMGroup[]; + vms: VMInfoAndCaps[]; sys_info: boolean; } +export type VMGroup = VMGroupInfo & VMCaps; + +export interface VMGroupInfo { + id: string; + vms: VMInfo[]; +} + let config: ServerConfig | null = null; export class ServerApi { diff --git a/remote_frontend/src/api/VMApi.ts b/remote_frontend/src/api/VMApi.ts index 16fcace..42902be 100644 --- a/remote_frontend/src/api/VMApi.ts +++ b/remote_frontend/src/api/VMApi.ts @@ -7,6 +7,9 @@ export interface VMInfo { architecture: string; memory: number; number_vcpu: number; +} + +export interface VMCaps { can_get_state: boolean; can_start: boolean; can_shutdown: boolean; @@ -17,6 +20,8 @@ export interface VMInfo { can_screenshot: boolean; } +export type VMInfoAndCaps = VMInfo & VMCaps; + export type VMState = | "NoState" | "Running" diff --git a/remote_frontend/src/widgets/GroupsWidget.tsx b/remote_frontend/src/widgets/GroupsWidget.tsx new file mode 100644 index 0000000..4f553e7 --- /dev/null +++ b/remote_frontend/src/widgets/GroupsWidget.tsx @@ -0,0 +1,5 @@ +import { Rights } from "../api/ServerApi"; + +export function GroupsWidget(p: { rights: Rights }): React.ReactElement { + return

TODO

; +} diff --git a/remote_frontend/src/widgets/VirtualMachinesWidget.tsx b/remote_frontend/src/widgets/VirtualMachinesWidget.tsx index 7fb047d..b0c78ca 100644 --- a/remote_frontend/src/widgets/VirtualMachinesWidget.tsx +++ b/remote_frontend/src/widgets/VirtualMachinesWidget.tsx @@ -22,7 +22,7 @@ import { import { filesize } from "filesize"; import React from "react"; import { Rights } from "../api/ServerApi"; -import { VMApi, VMInfo, VMState } from "../api/VMApi"; +import { VMApi, VMInfo, VMInfoAndCaps, VMState } from "../api/VMApi"; import { useConfirm } from "../hooks/providers/ConfirmDialogProvider"; import { useToast } from "../hooks/providers/ToastProvider"; import { VMLiveScreenshot } from "./VMLiveScreenshot"; @@ -54,7 +54,7 @@ export function VirtualMachinesWidget(p: { ); } -function VMWidget(p: { vm: VMInfo }): React.ReactElement { +function VMWidget(p: { vm: VMInfoAndCaps }): React.ReactElement { const toast = useToast(); const [state, setState] = React.useState(); @@ -189,7 +189,10 @@ function VMWidget(p: { vm: VMInfo }): React.ReactElement { ); } -function VMPreview(p: { vm: VMInfo; state?: VMState }): React.ReactElement { +function VMPreview(p: { + vm: VMInfoAndCaps; + state?: VMState; +}): React.ReactElement { const styles = useStyles(); if (!p.vm.can_screenshot || p.state !== "Running") { return (