Show VM screenshot, if possible
This commit is contained in:
parent
31955ca738
commit
d63d3ecebc
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
42
remote_frontend/src/widgets/VMLiveScreenshot.tsx
Normal file
42
remote_frontend/src/widgets/VMLiveScreenshot.tsx
Normal 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" />
|
||||||
|
);
|
||||||
|
}
|
@ -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 () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user