All checks were successful
continuous-integration/drone/push Build is passing
156 lines
4.0 KiB
TypeScript
156 lines
4.0 KiB
TypeScript
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
|
import FullscreenIcon from "@mui/icons-material/Fullscreen";
|
|
import FullscreenExitIcon from "@mui/icons-material/FullscreenExit";
|
|
import KeyboardAltIcon from "@mui/icons-material/KeyboardAlt";
|
|
import { IconButton, Tooltip } from "@mui/material";
|
|
import React, { useEffect } from "react";
|
|
import { useNavigate, useParams } from "react-router-dom";
|
|
import { VncScreen, VncScreenHandle } from "react-vnc";
|
|
import { ServerApi } from "../api/ServerApi";
|
|
import { VMApi, VMInfo } from "../api/VMApi";
|
|
import { useSnackbar } from "../hooks/providers/SnackbarProvider";
|
|
import { time } from "../utils/DateUtils";
|
|
import { AsyncWidget } from "../widgets/AsyncWidget";
|
|
|
|
interface VNCTokenInfo {
|
|
url: string;
|
|
expire: number;
|
|
}
|
|
|
|
export function VNCRoute(): React.ReactElement {
|
|
const { uuid } = useParams();
|
|
|
|
const [vm, setVM] = React.useState<VMInfo | undefined>();
|
|
|
|
const load = async () => {
|
|
setVM(await VMApi.GetSingle(uuid!));
|
|
};
|
|
|
|
return (
|
|
<AsyncWidget
|
|
loadKey={uuid}
|
|
load={load}
|
|
errMsg="Failed to load VM information!"
|
|
build={() => <VNCInner vm={vm!} />}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function VNCInner(p: { vm: VMInfo }): React.ReactElement {
|
|
const snackbar = useSnackbar();
|
|
const navigate = useNavigate();
|
|
|
|
const [token, setToken] = React.useState<VNCTokenInfo | undefined>();
|
|
const [counter, setCounter] = React.useState(1);
|
|
const [connected, setConnected] = React.useState(false);
|
|
|
|
const vncRef = React.useRef<HTMLDivElement>(null);
|
|
const vncScreenRef = React.useRef<VncScreenHandle>(null);
|
|
|
|
const connect = async (force: boolean) => {
|
|
try {
|
|
if (force) setCounter(counter + 1);
|
|
|
|
// Check if getting new time is useless
|
|
if ((token?.expire ?? 0) > time()) return;
|
|
|
|
setToken(undefined);
|
|
|
|
const u = await VMApi.OneShotVNCURL(p.vm);
|
|
console.info("VNC URL", u);
|
|
|
|
if (!token)
|
|
setToken({
|
|
expire: time() + ServerApi.Config.constraints.vnc_token_duration,
|
|
url: u,
|
|
});
|
|
} catch (e) {
|
|
console.error(e);
|
|
snackbar("Failed to initialize VNC connection!");
|
|
}
|
|
};
|
|
|
|
const disconnected = () => {
|
|
setConnected(false);
|
|
connect(true);
|
|
};
|
|
|
|
const goBack = () => {
|
|
navigate(p.vm.ViewURL);
|
|
};
|
|
|
|
const goFullScreen = () => {
|
|
if (vncRef.current) vncRef.current.requestFullscreen();
|
|
};
|
|
|
|
const exitFullScreen = () => {
|
|
document.exitFullscreen();
|
|
};
|
|
|
|
useEffect(() => {
|
|
connect(false);
|
|
|
|
if (vncRef.current) {
|
|
vncRef.current.onfullscreenchange = () => {
|
|
setCounter(counter + 1);
|
|
};
|
|
}
|
|
});
|
|
|
|
if (token === undefined)
|
|
return <p>Please wait, connecting to the machine...</p>;
|
|
|
|
return (
|
|
<div ref={vncRef} style={{ display: "flex" }}>
|
|
{/* Controls */}
|
|
<div style={{ display: "flex", flexDirection: "column" }}>
|
|
<IconButton onClick={goBack}>
|
|
<ArrowBackIcon />
|
|
</IconButton>
|
|
|
|
{/* Toggle fullscreen */}
|
|
{!document.fullscreenElement ? (
|
|
<IconButton onClick={goFullScreen}>
|
|
<FullscreenIcon />
|
|
</IconButton>
|
|
) : (
|
|
<IconButton onClick={exitFullScreen}>
|
|
<FullscreenExitIcon />
|
|
</IconButton>
|
|
)}
|
|
|
|
{/* Keystrokes */}
|
|
{connected && (
|
|
<Tooltip title="Send Ctrl+Alt+Del">
|
|
<IconButton onClick={() => vncScreenRef.current?.sendCtrlAltDel()}>
|
|
<KeyboardAltIcon />
|
|
</IconButton>
|
|
</Tooltip>
|
|
)}
|
|
</div>
|
|
|
|
{/* Screen */}
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
height: "100%",
|
|
}}
|
|
>
|
|
<VncScreen
|
|
ref={vncScreenRef}
|
|
url={token.url}
|
|
onDisconnect={() => {
|
|
console.info("VNC disconnected " + token.url);
|
|
disconnected();
|
|
}}
|
|
onConnect={() => {
|
|
setConnected(true);
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|