Show VM screenshot
This commit is contained in:
parent
62364594c9
commit
3042bbdac6
@ -103,6 +103,7 @@ export class APIClient {
|
||||
});
|
||||
|
||||
// Process response
|
||||
// JSON response
|
||||
if (res.headers.get("content-type") === "application/json")
|
||||
data = await res.json();
|
||||
// Binary file
|
||||
@ -146,7 +147,7 @@ export class APIClient {
|
||||
data = await resInt.blob();
|
||||
}
|
||||
|
||||
// Do not track progress
|
||||
// Do not track progress (binary file)
|
||||
else data = await res.blob();
|
||||
|
||||
status = res.status;
|
||||
|
@ -136,6 +136,18 @@ export class VMApi {
|
||||
).data.state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a screenshot of a VM
|
||||
*/
|
||||
static async Screenshot(vm: VMInfo): Promise<Blob> {
|
||||
return (
|
||||
await APIClient.exec({
|
||||
uri: `/vm/${vm.uuid}/screenshot`,
|
||||
method: "GET",
|
||||
})
|
||||
).data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the VM
|
||||
*/
|
||||
|
@ -21,6 +21,7 @@ import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer";
|
||||
import { VMStatusWidget } from "../widgets/vms/VMStatusWidget";
|
||||
import { useSnackbar } from "../hooks/providers/SnackbarProvider";
|
||||
import { useConfirm } from "../hooks/providers/ConfirmDialogProvider";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
export function VMListRoute(): React.ReactElement {
|
||||
const [list, setList] = React.useState<VMInfo[] | undefined>();
|
||||
@ -66,6 +67,7 @@ function VMListWidget(p: {
|
||||
}): React.ReactElement {
|
||||
const confirm = useConfirm();
|
||||
const snackbar = useSnackbar();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const deleteVM = async (v: VMInfo) => {
|
||||
try {
|
||||
@ -110,8 +112,10 @@ function VMListWidget(p: {
|
||||
<TableBody>
|
||||
{p.list.map((row) => (
|
||||
<TableRow
|
||||
hover
|
||||
key={row.name}
|
||||
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
|
||||
onDoubleClick={() => navigate(row.ViewURL)}
|
||||
>
|
||||
<TableCell component="th" scope="row">
|
||||
{row.name}
|
||||
|
@ -50,7 +50,11 @@ function VMRouteBody(p: { vm: VMInfo }): React.ReactElement {
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<VMDetails vm={p.vm} editable={false} />
|
||||
<VMDetails
|
||||
vm={p.vm}
|
||||
editable={false}
|
||||
screenshot={p.vm.vnc_access && state === "Running"}
|
||||
/>
|
||||
</VirtWebRouteContainer>
|
||||
);
|
||||
}
|
||||
|
@ -6,14 +6,24 @@ import { VMInfo } from "../../api/VMApi";
|
||||
import { CheckboxInput } from "../forms/CheckboxInput";
|
||||
import { SelectInput } from "../forms/SelectInput";
|
||||
import { TextInput } from "../forms/TextInput";
|
||||
import { VMScreenshot } from "./VMScreenshot";
|
||||
|
||||
export function VMDetails(p: {
|
||||
vm: VMInfo;
|
||||
editable: boolean;
|
||||
onChange?: () => void;
|
||||
screenshot?: boolean;
|
||||
}): React.ReactElement {
|
||||
return (
|
||||
<Grid container spacing={2}>
|
||||
{
|
||||
/* Screenshot section */ p.screenshot && (
|
||||
<EditSection title="Screenshot">
|
||||
<VMScreenshot vm={p.vm} />
|
||||
</EditSection>
|
||||
)
|
||||
}
|
||||
|
||||
{/* Metadata section */}
|
||||
<EditSection title="Metadata">
|
||||
<TextInput
|
||||
|
42
virtweb_frontend/src/widgets/vms/VMScreenshot.tsx
Normal file
42
virtweb_frontend/src/widgets/vms/VMScreenshot.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import React from "react";
|
||||
import { VMApi, VMInfo } from "../../api/VMApi";
|
||||
import { useSnackbar } from "../../hooks/providers/SnackbarProvider";
|
||||
|
||||
export function VMScreenshot(p: { vm: VMInfo }): React.ReactElement {
|
||||
const snackbar = useSnackbar();
|
||||
|
||||
const [screenshotURL, setScreenshotURL] = React.useState<
|
||||
string | undefined
|
||||
>();
|
||||
|
||||
const int = React.useRef<NodeJS.Timer | 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);
|
||||
snackbar("Failed to get a screenshot of the VM!");
|
||||
}
|
||||
};
|
||||
|
||||
if (int.current === undefined) {
|
||||
refresh();
|
||||
int.current = setInterval(() => refresh(), 5000000);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (int.current !== undefined) {
|
||||
clearInterval(int.current);
|
||||
int.current = undefined;
|
||||
}
|
||||
};
|
||||
}, [p.vm, snackbar]);
|
||||
|
||||
return (
|
||||
<img src={screenshotURL} style={{ width: "100%" }} alt="VM screenshot" />
|
||||
);
|
||||
}
|
@ -3,7 +3,12 @@ import PlayArrowIcon from "@mui/icons-material/PlayArrow";
|
||||
import PowerSettingsNewIcon from "@mui/icons-material/PowerSettingsNew";
|
||||
import ReplayIcon from "@mui/icons-material/Replay";
|
||||
import StopIcon from "@mui/icons-material/Stop";
|
||||
import { CircularProgress, IconButton, Tooltip } from "@mui/material";
|
||||
import {
|
||||
CircularProgress,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import React from "react";
|
||||
import { VMApi, VMInfo, VMState } from "../../api/VMApi";
|
||||
import { useAlert } from "../../hooks/providers/AlertDialogProvider";
|
||||
@ -47,7 +52,7 @@ export function VMStatusWidget(p: {
|
||||
|
||||
return (
|
||||
<div style={{ display: "inline-flex" }}>
|
||||
{state}
|
||||
<Typography>{state}</Typography>
|
||||
|
||||
{/* Start VM */}
|
||||
<ActionButton
|
||||
|
Loading…
Reference in New Issue
Block a user