Add groups support #146
| @@ -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<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, | ||||
| } | ||||
|  | ||||
| #[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()), | ||||
|             }, | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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 ( | ||||
|     <AsyncWidget | ||||
| @@ -48,7 +53,7 @@ function AppInner(): React.ReactElement { | ||||
|  | ||||
| function AuthenticatedApp(): React.ReactElement { | ||||
|   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>(); | ||||
|  | ||||
| @@ -82,6 +87,13 @@ function AuthenticatedApp(): React.ReactElement { | ||||
|                 selectedValue={tab} | ||||
|                 onTabSelect={(_, d) => setTab(d.value as any)} | ||||
|               > | ||||
|                 <Tab | ||||
|                   value="group" | ||||
|                   icon={<AppListIcon />} | ||||
|                   disabled={rights!.groups.length === 0} | ||||
|                 > | ||||
|                   Groups | ||||
|                 </Tab> | ||||
|                 <Tab | ||||
|                   value="vm" | ||||
|                   icon={<DesktopIcon />} | ||||
| @@ -101,6 +113,7 @@ function AuthenticatedApp(): React.ReactElement { | ||||
|                 <MainMenu /> | ||||
|               </div> | ||||
|             </div> | ||||
|             {tab === "group" && <GroupsWidget rights={rights!} />} | ||||
|             {tab === "vm" && <VirtualMachinesWidget rights={rights!} />} | ||||
|             {tab === "info" && <SystemInfoWidget />} | ||||
|           </div> | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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" | ||||
|   | ||||
							
								
								
									
										5
									
								
								remote_frontend/src/widgets/GroupsWidget.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								remote_frontend/src/widgets/GroupsWidget.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| import { Rights } from "../api/ServerApi"; | ||||
|  | ||||
| export function GroupsWidget(p: { rights: Rights }): React.ReactElement { | ||||
|   return <p>TODO</p>; | ||||
| } | ||||
| @@ -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<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(); | ||||
|   if (!p.vm.can_screenshot || p.state !== "Running") { | ||||
|     return ( | ||||
|   | ||||
		Reference in New Issue
	
	Block a user