Ready to implement groups web ui integration
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing

This commit is contained in:
Pierre HUBERT 2024-12-03 21:49:53 +01:00
parent acb9baee23
commit d243022810
7 changed files with 77 additions and 39 deletions

View File

@ -2,7 +2,7 @@ use crate::app_config::AppConfig;
use crate::controllers::HttpResult; use crate::controllers::HttpResult;
use crate::extractors::auth_extractor::AuthExtractor; use crate::extractors::auth_extractor::AuthExtractor;
use crate::virtweb_client; use crate::virtweb_client;
use crate::virtweb_client::{GroupID, VMInfo}; use crate::virtweb_client::{GroupID, VMCaps, VMInfo};
use actix_web::HttpResponse; use actix_web::HttpResponse;
#[derive(serde::Serialize)] #[derive(serde::Serialize)]
@ -29,28 +29,16 @@ pub struct Rights {
pub struct GroupInfo { pub struct GroupInfo {
id: GroupID, id: GroupID,
vms: Vec<VMInfo>, vms: Vec<VMInfo>,
can_get_state: bool, #[serde(flatten)]
can_start: bool, caps: VMCaps,
can_shutdown: bool,
can_kill: bool,
can_reset: bool,
can_suspend: bool,
can_resume: bool,
can_screenshot: bool,
} }
#[derive(Debug, serde::Serialize)] #[derive(Debug, serde::Serialize)]
pub struct VMInfoAndCaps { pub struct VMInfoAndCaps {
#[serde(flatten)] #[serde(flatten)]
info: VMInfo, info: VMInfo,
can_get_state: bool, #[serde(flatten)]
can_start: bool, caps: VMCaps,
can_shutdown: bool,
can_kill: bool,
can_reset: bool,
can_suspend: bool,
can_resume: bool,
can_screenshot: bool,
} }
pub async fn rights() -> HttpResult { pub async fn rights() -> HttpResult {
@ -68,14 +56,16 @@ pub async fn rights() -> HttpResult {
res.groups.push(GroupInfo { res.groups.push(GroupInfo {
id: g.clone(), id: g.clone(),
vms: group_vms, vms: group_vms,
can_get_state: rights.is_route_allowed("GET", &g.route_vm_state(None)), caps: VMCaps {
can_start: rights.is_route_allowed("GET", &g.route_vm_start(None)), can_get_state: rights.is_route_allowed("GET", &g.route_vm_state(None)),
can_shutdown: rights.is_route_allowed("GET", &g.route_vm_shutdown(None)), can_start: rights.is_route_allowed("GET", &g.route_vm_start(None)),
can_kill: rights.is_route_allowed("GET", &g.route_vm_kill(None)), can_shutdown: rights.is_route_allowed("GET", &g.route_vm_shutdown(None)),
can_reset: rights.is_route_allowed("GET", &g.route_vm_reset(None)), can_kill: rights.is_route_allowed("GET", &g.route_vm_kill(None)),
can_suspend: rights.is_route_allowed("GET", &g.route_vm_suspend(None)), can_reset: rights.is_route_allowed("GET", &g.route_vm_reset(None)),
can_resume: rights.is_route_allowed("GET", &g.route_vm_resume(None)), can_suspend: rights.is_route_allowed("GET", &g.route_vm_suspend(None)),
can_screenshot: rights.is_route_allowed("GET", &g.route_vm_screenshot(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 { res.vms.push(VMInfoAndCaps {
info: vm_info, info: vm_info,
can_get_state: rights.is_route_allowed("GET", &v.route_state()), caps: VMCaps {
can_start: rights.is_route_allowed("GET", &v.route_start()), can_get_state: rights.is_route_allowed("GET", &v.route_state()),
can_shutdown: rights.is_route_allowed("GET", &v.route_shutdown()), can_start: rights.is_route_allowed("GET", &v.route_start()),
can_kill: rights.is_route_allowed("GET", &v.route_kill()), can_shutdown: rights.is_route_allowed("GET", &v.route_shutdown()),
can_reset: rights.is_route_allowed("GET", &v.route_reset()), can_kill: rights.is_route_allowed("GET", &v.route_kill()),
can_suspend: rights.is_route_allowed("GET", &v.route_suspend()), can_reset: rights.is_route_allowed("GET", &v.route_reset()),
can_resume: rights.is_route_allowed("GET", &v.route_resume()), can_suspend: rights.is_route_allowed("GET", &v.route_suspend()),
can_screenshot: rights.is_route_allowed("GET", &v.route_screenshot()), can_resume: rights.is_route_allowed("GET", &v.route_resume()),
can_screenshot: rights.is_route_allowed("GET", &v.route_screenshot()),
},
}) })
} }

View File

@ -176,6 +176,18 @@ pub struct VMInfo {
pub number_vcpu: usize, 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)] #[derive(serde::Deserialize, serde::Serialize, Debug)]
pub struct VMState { pub struct VMState {
pub state: String, pub state: String,

View File

@ -5,6 +5,8 @@ import {
typographyStyles, typographyStyles,
} from "@fluentui/react-components"; } from "@fluentui/react-components";
import { import {
AppsListDetailFilled,
AppsListDetailRegular,
DesktopFilled, DesktopFilled,
DesktopRegular, DesktopRegular,
InfoFilled, InfoFilled,
@ -18,6 +20,7 @@ import { AsyncWidget } from "./widgets/AsyncWidget";
import { MainMenu } from "./widgets/MainMenu"; import { MainMenu } from "./widgets/MainMenu";
import { SystemInfoWidget } from "./widgets/SystemInfoWidget"; import { SystemInfoWidget } from "./widgets/SystemInfoWidget";
import { VirtualMachinesWidget } from "./widgets/VirtualMachinesWidget"; import { VirtualMachinesWidget } from "./widgets/VirtualMachinesWidget";
import { GroupsWidget } from "./widgets/GroupsWidget";
const useStyles = makeStyles({ const useStyles = makeStyles({
title: typographyStyles.title2, title: typographyStyles.title2,
@ -27,6 +30,8 @@ const InfoIcon = bundleIcon(InfoFilled, InfoRegular);
const DesktopIcon = bundleIcon(DesktopFilled, DesktopRegular); const DesktopIcon = bundleIcon(DesktopFilled, DesktopRegular);
const AppListIcon = bundleIcon(AppsListDetailFilled, AppsListDetailRegular);
export function App() { export function App() {
return ( return (
<AsyncWidget <AsyncWidget
@ -48,7 +53,7 @@ function AppInner(): React.ReactElement {
function AuthenticatedApp(): React.ReactElement { function AuthenticatedApp(): React.ReactElement {
const styles = useStyles(); const styles = useStyles();
const [tab, setTab] = React.useState<"vm" | "info">("vm"); const [tab, setTab] = React.useState<"group" | "vm" | "info">("group");
const [rights, setRights] = React.useState<Rights | undefined>(); const [rights, setRights] = React.useState<Rights | undefined>();
@ -82,6 +87,13 @@ function AuthenticatedApp(): React.ReactElement {
selectedValue={tab} selectedValue={tab}
onTabSelect={(_, d) => setTab(d.value as any)} onTabSelect={(_, d) => setTab(d.value as any)}
> >
<Tab
value="group"
icon={<AppListIcon />}
disabled={rights!.groups.length === 0}
>
Groups
</Tab>
<Tab <Tab
value="vm" value="vm"
icon={<DesktopIcon />} icon={<DesktopIcon />}
@ -101,6 +113,7 @@ function AuthenticatedApp(): React.ReactElement {
<MainMenu /> <MainMenu />
</div> </div>
</div> </div>
{tab === "group" && <GroupsWidget rights={rights!} />}
{tab === "vm" && <VirtualMachinesWidget rights={rights!} />} {tab === "vm" && <VirtualMachinesWidget rights={rights!} />}
{tab === "info" && <SystemInfoWidget />} {tab === "info" && <SystemInfoWidget />}
</div> </div>

View File

@ -1,5 +1,5 @@
import { APIClient } from "./ApiClient"; import { APIClient } from "./ApiClient";
import { VMInfo } from "./VMApi"; import { VMCaps, VMInfo, VMInfoAndCaps } from "./VMApi";
export interface ServerConfig { export interface ServerConfig {
authenticated: boolean; authenticated: boolean;
@ -7,10 +7,18 @@ export interface ServerConfig {
} }
export interface Rights { export interface Rights {
vms: VMInfo[]; groups: VMGroup[];
vms: VMInfoAndCaps[];
sys_info: boolean; sys_info: boolean;
} }
export type VMGroup = VMGroupInfo & VMCaps;
export interface VMGroupInfo {
id: string;
vms: VMInfo[];
}
let config: ServerConfig | null = null; let config: ServerConfig | null = null;
export class ServerApi { export class ServerApi {

View File

@ -7,6 +7,9 @@ export interface VMInfo {
architecture: string; architecture: string;
memory: number; memory: number;
number_vcpu: number; number_vcpu: number;
}
export interface VMCaps {
can_get_state: boolean; can_get_state: boolean;
can_start: boolean; can_start: boolean;
can_shutdown: boolean; can_shutdown: boolean;
@ -17,6 +20,8 @@ export interface VMInfo {
can_screenshot: boolean; can_screenshot: boolean;
} }
export type VMInfoAndCaps = VMInfo & VMCaps;
export type VMState = export type VMState =
| "NoState" | "NoState"
| "Running" | "Running"

View File

@ -0,0 +1,5 @@
import { Rights } from "../api/ServerApi";
export function GroupsWidget(p: { rights: Rights }): React.ReactElement {
return <p>TODO</p>;
}

View File

@ -22,7 +22,7 @@ import {
import { filesize } from "filesize"; import { filesize } from "filesize";
import React from "react"; import React from "react";
import { Rights } from "../api/ServerApi"; 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 { useConfirm } from "../hooks/providers/ConfirmDialogProvider";
import { useToast } from "../hooks/providers/ToastProvider"; import { useToast } from "../hooks/providers/ToastProvider";
import { VMLiveScreenshot } from "./VMLiveScreenshot"; 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 toast = useToast();
const [state, setState] = React.useState<VMState | undefined>(); const [state, setState] = React.useState<VMState | undefined>();
@ -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(); const styles = useStyles();
if (!p.vm.can_screenshot || p.state !== "Running") { if (!p.vm.can_screenshot || p.state !== "Running") {
return ( return (