Can see group VM screenshot
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing

This commit is contained in:
Pierre HUBERT 2024-12-06 18:57:28 +01:00
parent 7d7d9c5437
commit 957ead6521
3 changed files with 121 additions and 53 deletions

View File

@ -96,7 +96,7 @@ export class GroupApi {
/** /**
* Request a screenshot of the VM of group * 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 ( return (
await APIClient.exec({ await APIClient.exec({
method: "GET", method: "GET",

View File

@ -1,21 +1,33 @@
import { import {
Button,
Card, Card,
Dialog,
DialogActions,
DialogBody,
DialogContent,
DialogSurface,
DialogTitle,
DialogTrigger,
Table, Table,
TableBody, TableBody,
TableCell, TableCell,
TableCellActions,
TableCellLayout, TableCellLayout,
TableHeader, TableHeader,
TableHeaderCell, TableHeaderCell,
TableRow, TableRow,
Title3, Title3,
Tooltip,
} from "@fluentui/react-components"; } from "@fluentui/react-components";
import { Desktop24Regular } from "@fluentui/react-icons"; import { Desktop24Regular, ScreenshotRegular } from "@fluentui/react-icons";
import { filesize } from "filesize"; import { filesize } from "filesize";
import React from "react"; import React from "react";
import { GroupApi, GroupVMState } from "../api/GroupApi"; import { GroupApi, GroupVMState } from "../api/GroupApi";
import { Rights, VMGroup } from "../api/ServerApi"; import { Rights, VMGroup } from "../api/ServerApi";
import { VMInfo } from "../api/VMApi";
import { useToast } from "../hooks/providers/ToastProvider"; import { useToast } from "../hooks/providers/ToastProvider";
import { GroupVMAction } from "./GroupVMAction"; import { GroupVMAction } from "./GroupVMAction";
import { VMLiveScreenshot } from "./VMLiveScreenshot";
export function GroupsWidget(p: { rights: Rights }): React.ReactElement { export function GroupsWidget(p: { rights: Rights }): React.ReactElement {
return ( return (
@ -31,12 +43,17 @@ function GroupInfo(p: { group: VMGroup }): React.ReactElement {
const toast = useToast(); const toast = useToast();
const [state, setState] = React.useState<GroupVMState | undefined>(); const [state, setState] = React.useState<GroupVMState | undefined>();
const [screenshotVM, setScreenshotVM] = React.useState<VMInfo | undefined>();
const load = async () => { const load = async () => {
const newState = await GroupApi.State(p.group); const newState = await GroupApi.State(p.group);
if (state !== newState) setState(newState); if (state !== newState) setState(newState);
}; };
const screenshot = (vm: VMInfo) => {
setScreenshotVM(vm);
};
React.useEffect(() => { React.useEffect(() => {
const interval = setInterval(async () => { const interval = setInterval(async () => {
try { try {
@ -54,55 +71,99 @@ function GroupInfo(p: { group: VMGroup }): React.ReactElement {
}); });
return ( return (
<Card <>
style={{ <Card
margin: "50px 10px", style={{
display: "flex", margin: "50px 10px",
flexDirection: "column", display: "flex",
}} flexDirection: "column",
> }}
<div style={{ display: "flex", justifyContent: "space-between" }}> >
<Title3 style={{ marginLeft: "10px" }}>{p.group.id}</Title3> <div style={{ display: "flex", justifyContent: "space-between" }}>
<GroupVMAction group={p.group} /> <Title3 style={{ marginLeft: "10px" }}>{p.group.id}</Title3>
</div> <GroupVMAction group={p.group} />
<Table sortable> </div>
<TableHeader> <Table sortable>
<TableRow> <TableHeader>
<TableHeaderCell>VM</TableHeaderCell> <TableRow>
<TableHeaderCell>Resources</TableHeaderCell> <TableHeaderCell>VM</TableHeaderCell>
<TableHeaderCell>State</TableHeaderCell> <TableHeaderCell>Resources</TableHeaderCell>
<TableHeaderCell>Actions</TableHeaderCell> <TableHeaderCell>State</TableHeaderCell>
</TableRow> <TableHeaderCell>Actions</TableHeaderCell>
</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} &bull; RAM :{" "}
{filesize(item.memory * 1000 * 1000)} &bull; {item.number_vcpu}{" "}
vCPU
</TableCell>
<TableCell>{state?.[item.uuid] ?? ""}</TableCell>
<TableCell>
<GroupVMAction
group={p.group}
state={state?.[item.uuid]}
vm={item}
/>
</TableCell>
</TableRow> </TableRow>
))} </TableHeader>
</TableBody> <TableBody>
</Table> {p.group.vms.map((item) => (
</Card> <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} &bull; RAM :{" "}
{filesize(item.memory * 1000 * 1000)} &bull;{" "}
{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>
</>
); );
} }

View File

@ -1,8 +1,13 @@
import React from "react"; import React from "react";
import { GroupApi } from "../api/GroupApi";
import { VMGroup } from "../api/ServerApi";
import { VMApi, VMInfo } from "../api/VMApi"; import { VMApi, VMInfo } from "../api/VMApi";
import { useToast } from "../hooks/providers/ToastProvider"; 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 toast = useToast();
const [screenshotURL, setScreenshotURL] = React.useState< const [screenshotURL, setScreenshotURL] = React.useState<
@ -14,7 +19,9 @@ export function VMLiveScreenshot(p: { vm: VMInfo }): React.ReactElement {
React.useEffect(() => { React.useEffect(() => {
const refresh = async () => { const refresh = async () => {
try { 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); const u = URL.createObjectURL(screenshot);
setScreenshotURL(u); setScreenshotURL(u);
} catch (e) { } catch (e) {