Show VM screenshot, if possible

This commit is contained in:
Pierre HUBERT 2024-05-06 19:39:21 +02:00
parent 31955ca738
commit d63d3ecebc
3 changed files with 86 additions and 2 deletions

View File

@ -86,4 +86,16 @@ export class VMApi {
static async ResetVM(vm: VMInfo): Promise<void> { static async ResetVM(vm: VMInfo): Promise<void> {
await APIClient.exec({ method: "GET", uri: `/vm/${vm.uiid}/reset` }); await APIClient.exec({ method: "GET", uri: `/vm/${vm.uiid}/reset` });
} }
/**
* Get a screenshot of a VM
*/
static async Screenshot(vm: VMInfo): Promise<Blob> {
return (
await APIClient.exec({
uri: `/vm/${vm.uiid}/screenshot`,
method: "GET",
})
).data;
}
} }

View File

@ -0,0 +1,42 @@
import React from "react";
import { VMApi, VMInfo } from "../api/VMApi";
import { useToast } from "../hooks/providers/ToastProvider";
export function VMLiveScreenshot(p: { vm: VMInfo }): React.ReactElement {
const toast = useToast();
const [screenshotURL, setScreenshotURL] = React.useState<
string | undefined
>();
const int = React.useRef<number | undefined>();
React.useEffect(() => {
const refresh = async () => {
try {
const screenshot = await VMApi.Screenshot(p.vm);
const u = URL.createObjectURL(screenshot);
setScreenshotURL(u);
} catch (e) {
console.error(e);
toast(p.vm.name, "Failed to get a screenshot of the VM!", "error");
}
};
if (int.current === undefined) {
refresh();
int.current = setInterval(() => refresh(), 5000);
}
return () => {
if (int.current !== undefined) {
clearInterval(int.current);
int.current = undefined;
}
};
}, [p.vm, toast]);
return (
<img src={screenshotURL} style={{ width: "100%" }} alt="VM screenshot" />
);
}

View File

@ -8,6 +8,8 @@ import {
CardPreview, CardPreview,
Spinner, Spinner,
Tooltip, Tooltip,
makeStyles,
typographyStyles,
} from "@fluentui/react-components"; } from "@fluentui/react-components";
import { import {
ArrowResetRegular, ArrowResetRegular,
@ -22,6 +24,11 @@ import { VMApi, VMInfo, VMState } from "../api/VMApi";
import { useToast } from "../hooks/providers/ToastProvider"; import { useToast } from "../hooks/providers/ToastProvider";
import { AsyncWidget } from "./AsyncWidget"; import { AsyncWidget } from "./AsyncWidget";
import { SectionContainer } from "./SectionContainer"; import { SectionContainer } from "./SectionContainer";
import { VMLiveScreenshot } from "./VMLiveScreenshot";
const useStyles = makeStyles({
body1Stronger: typographyStyles.body1Stronger,
});
export function VirtualMachinesWidget(): React.ReactElement { export function VirtualMachinesWidget(): React.ReactElement {
const [list, setList] = React.useState<VMInfo[] | undefined>(); const [list, setList] = React.useState<VMInfo[] | undefined>();
@ -86,13 +93,15 @@ function VMWidget(p: { vm: VMInfo }): React.ReactElement {
style={{ style={{
width: "400px", width: "400px",
maxWidth: "49%", maxWidth: "49%",
height: "250px", height: "350px",
margin: "10px", margin: "10px",
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
}} }}
> >
<CardPreview>TODO preview</CardPreview> <CardPreview style={{ height: "150px", display: "flex" }}>
<VMPreview {...p} state={state} />
</CardPreview>
<CardHeader <CardHeader
image={<DesktopRegular fontSize={32} />} image={<DesktopRegular fontSize={32} />}
@ -183,6 +192,26 @@ function VMWidget(p: { vm: VMInfo }): React.ReactElement {
); );
} }
function VMPreview(p: { vm: VMInfo; state?: VMState }): React.ReactElement {
const styles = useStyles();
if (!p.vm.can_screenshot || p.state !== "Running") {
return (
<div
style={{
flex: "1",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<span className={styles.body1Stronger}>{p.vm.name}</span>
</div>
);
}
return <VMLiveScreenshot {...p} />;
}
function VMAction(p: { function VMAction(p: {
vm: VMInfo; vm: VMInfo;
label: string; label: string;
@ -194,6 +223,7 @@ function VMAction(p: {
onClick: (vm: VMInfo) => Promise<void>; onClick: (vm: VMInfo) => Promise<void>;
}): React.ReactElement { }): React.ReactElement {
const toast = useToast(); const toast = useToast();
const [loading, setLoading] = React.useState(false); const [loading, setLoading] = React.useState(false);
const onClick = async () => { const onClick = async () => {