Add groups support #146
| @@ -96,7 +96,7 @@ export class GroupApi { | ||||
|   /** | ||||
|    * Request a screenshot of the VM of group | ||||
|    */ | ||||
|   static async ScreenshotVM(g: VMGroup, vm?: VMInfo): Promise<TreatmentResult> { | ||||
|   static async ScreenshotVM(g: VMGroup, vm?: VMInfo): Promise<Blob> { | ||||
|     return ( | ||||
|       await APIClient.exec({ | ||||
|         method: "GET", | ||||
|   | ||||
| @@ -1,21 +1,33 @@ | ||||
| import { | ||||
|   Button, | ||||
|   Card, | ||||
|   Dialog, | ||||
|   DialogActions, | ||||
|   DialogBody, | ||||
|   DialogContent, | ||||
|   DialogSurface, | ||||
|   DialogTitle, | ||||
|   DialogTrigger, | ||||
|   Table, | ||||
|   TableBody, | ||||
|   TableCell, | ||||
|   TableCellActions, | ||||
|   TableCellLayout, | ||||
|   TableHeader, | ||||
|   TableHeaderCell, | ||||
|   TableRow, | ||||
|   Title3, | ||||
|   Tooltip, | ||||
| } from "@fluentui/react-components"; | ||||
| import { Desktop24Regular } from "@fluentui/react-icons"; | ||||
| import { Desktop24Regular, ScreenshotRegular } from "@fluentui/react-icons"; | ||||
| import { filesize } from "filesize"; | ||||
| import React from "react"; | ||||
| import { GroupApi, GroupVMState } from "../api/GroupApi"; | ||||
| import { Rights, VMGroup } from "../api/ServerApi"; | ||||
| import { VMInfo } from "../api/VMApi"; | ||||
| import { useToast } from "../hooks/providers/ToastProvider"; | ||||
| import { GroupVMAction } from "./GroupVMAction"; | ||||
| import { VMLiveScreenshot } from "./VMLiveScreenshot"; | ||||
|  | ||||
| export function GroupsWidget(p: { rights: Rights }): React.ReactElement { | ||||
|   return ( | ||||
| @@ -31,12 +43,17 @@ function GroupInfo(p: { group: VMGroup }): React.ReactElement { | ||||
|   const toast = useToast(); | ||||
|  | ||||
|   const [state, setState] = React.useState<GroupVMState | undefined>(); | ||||
|   const [screenshotVM, setScreenshotVM] = React.useState<VMInfo | undefined>(); | ||||
|  | ||||
|   const load = async () => { | ||||
|     const newState = await GroupApi.State(p.group); | ||||
|     if (state !== newState) setState(newState); | ||||
|   }; | ||||
|  | ||||
|   const screenshot = (vm: VMInfo) => { | ||||
|     setScreenshotVM(vm); | ||||
|   }; | ||||
|  | ||||
|   React.useEffect(() => { | ||||
|     const interval = setInterval(async () => { | ||||
|       try { | ||||
| @@ -54,55 +71,99 @@ function GroupInfo(p: { group: VMGroup }): React.ReactElement { | ||||
|   }); | ||||
|  | ||||
|   return ( | ||||
|     <Card | ||||
|       style={{ | ||||
|         margin: "50px 10px", | ||||
|         display: "flex", | ||||
|         flexDirection: "column", | ||||
|       }} | ||||
|     > | ||||
|       <div style={{ display: "flex", justifyContent: "space-between" }}> | ||||
|         <Title3 style={{ marginLeft: "10px" }}>{p.group.id}</Title3> | ||||
|         <GroupVMAction group={p.group} /> | ||||
|       </div> | ||||
|       <Table sortable> | ||||
|         <TableHeader> | ||||
|           <TableRow> | ||||
|             <TableHeaderCell>VM</TableHeaderCell> | ||||
|             <TableHeaderCell>Resources</TableHeaderCell> | ||||
|             <TableHeaderCell>State</TableHeaderCell> | ||||
|             <TableHeaderCell>Actions</TableHeaderCell> | ||||
|           </TableRow> | ||||
|         </TableHeader> | ||||
|         <TableBody> | ||||
|           {p.group.vms.map((item) => ( | ||||
|             <TableRow key={item.uuid}> | ||||
|               <TableCell> | ||||
|                 <TableCellLayout | ||||
|                   media={<Desktop24Regular />} | ||||
|                   appearance="primary" | ||||
|                   description={item.description} | ||||
|                 > | ||||
|                   {item.name} | ||||
|                 </TableCellLayout> | ||||
|               </TableCell> | ||||
|               <TableCell> | ||||
|                 {item.architecture} • RAM :{" "} | ||||
|                 {filesize(item.memory * 1000 * 1000)} • {item.number_vcpu}{" "} | ||||
|                 vCPU | ||||
|               </TableCell> | ||||
|               <TableCell>{state?.[item.uuid] ?? ""}</TableCell> | ||||
|               <TableCell> | ||||
|                 <GroupVMAction | ||||
|                   group={p.group} | ||||
|                   state={state?.[item.uuid]} | ||||
|                   vm={item} | ||||
|                 /> | ||||
|               </TableCell> | ||||
|     <> | ||||
|       <Card | ||||
|         style={{ | ||||
|           margin: "50px 10px", | ||||
|           display: "flex", | ||||
|           flexDirection: "column", | ||||
|         }} | ||||
|       > | ||||
|         <div style={{ display: "flex", justifyContent: "space-between" }}> | ||||
|           <Title3 style={{ marginLeft: "10px" }}>{p.group.id}</Title3> | ||||
|           <GroupVMAction group={p.group} /> | ||||
|         </div> | ||||
|         <Table sortable> | ||||
|           <TableHeader> | ||||
|             <TableRow> | ||||
|               <TableHeaderCell>VM</TableHeaderCell> | ||||
|               <TableHeaderCell>Resources</TableHeaderCell> | ||||
|               <TableHeaderCell>State</TableHeaderCell> | ||||
|               <TableHeaderCell>Actions</TableHeaderCell> | ||||
|             </TableRow> | ||||
|           ))} | ||||
|         </TableBody> | ||||
|       </Table> | ||||
|     </Card> | ||||
|           </TableHeader> | ||||
|           <TableBody> | ||||
|             {p.group.vms.map((item) => ( | ||||
|               <TableRow key={item.uuid}> | ||||
|                 <TableCell> | ||||
|                   <TableCellLayout | ||||
|                     media={<Desktop24Regular />} | ||||
|                     appearance="primary" | ||||
|                     description={item.description} | ||||
|                   > | ||||
|                     {item.name} | ||||
|                   </TableCellLayout> | ||||
|                   <TableCellActions> | ||||
|                     {state?.[item.uuid] === "Running" && ( | ||||
|                       <Tooltip | ||||
|                         relationship="description" | ||||
|                         content={"Take a screenshot of the VM screen"} | ||||
|                         withArrow | ||||
|                       > | ||||
|                         <Button | ||||
|                           icon={<ScreenshotRegular />} | ||||
|                           appearance="subtle" | ||||
|                           aria-label="Edit" | ||||
|                           disabled={!p.group.can_screenshot} | ||||
|                           onClick={() => screenshot(item)} | ||||
|                         /> | ||||
|                       </Tooltip> | ||||
|                     )} | ||||
|                   </TableCellActions> | ||||
|                 </TableCell> | ||||
|                 <TableCell> | ||||
|                   {item.architecture} • RAM :{" "} | ||||
|                   {filesize(item.memory * 1000 * 1000)} •{" "} | ||||
|                   {item.number_vcpu} vCPU | ||||
|                 </TableCell> | ||||
|                 <TableCell>{state?.[item.uuid] ?? ""}</TableCell> | ||||
|                 <TableCell> | ||||
|                   <GroupVMAction | ||||
|                     group={p.group} | ||||
|                     state={state?.[item.uuid]} | ||||
|                     vm={item} | ||||
|                   /> | ||||
|                 </TableCell> | ||||
|               </TableRow> | ||||
|             ))} | ||||
|           </TableBody> | ||||
|         </Table> | ||||
|       </Card> | ||||
|       <Dialog | ||||
|         open={!!screenshotVM} | ||||
|         onOpenChange={(_event, _data) => { | ||||
|           if (!screenshotVM) setScreenshotVM(undefined); | ||||
|         }} | ||||
|       > | ||||
|         <DialogSurface> | ||||
|           <DialogBody> | ||||
|             <DialogTitle>{screenshotVM?.name} screen</DialogTitle> | ||||
|             <DialogContent> | ||||
|               <VMLiveScreenshot vm={screenshotVM!} group={p.group} /> | ||||
|             </DialogContent> | ||||
|             <DialogActions> | ||||
|               <DialogTrigger disableButtonEnhancement> | ||||
|                 <Button | ||||
|                   appearance="secondary" | ||||
|                   onClick={() => setScreenshotVM(undefined)} | ||||
|                 > | ||||
|                   Close | ||||
|                 </Button> | ||||
|               </DialogTrigger> | ||||
|             </DialogActions> | ||||
|           </DialogBody> | ||||
|         </DialogSurface> | ||||
|       </Dialog> | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
|   | ||||
| @@ -1,8 +1,13 @@ | ||||
| import React from "react"; | ||||
| import { GroupApi } from "../api/GroupApi"; | ||||
| import { VMGroup } from "../api/ServerApi"; | ||||
| import { VMApi, VMInfo } from "../api/VMApi"; | ||||
| import { useToast } from "../hooks/providers/ToastProvider"; | ||||
|  | ||||
| export function VMLiveScreenshot(p: { vm: VMInfo }): React.ReactElement { | ||||
| export function VMLiveScreenshot(p: { | ||||
|   vm: VMInfo; | ||||
|   group?: VMGroup; | ||||
| }): React.ReactElement { | ||||
|   const toast = useToast(); | ||||
|  | ||||
|   const [screenshotURL, setScreenshotURL] = React.useState< | ||||
| @@ -14,7 +19,9 @@ export function VMLiveScreenshot(p: { vm: VMInfo }): React.ReactElement { | ||||
|   React.useEffect(() => { | ||||
|     const refresh = async () => { | ||||
|       try { | ||||
|         const screenshot = await VMApi.Screenshot(p.vm); | ||||
|         const screenshot = p.group | ||||
|           ? await GroupApi.ScreenshotVM(p.group, p.vm) | ||||
|           : await VMApi.Screenshot(p.vm); | ||||
|         const u = URL.createObjectURL(screenshot); | ||||
|         setScreenshotURL(u); | ||||
|       } catch (e) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user