mirror of
https://github.com/khairul169/vaulterm.git
synced 2026-09-15 17:03:30 +07:00
feat: add pve vnc session
This commit is contained in:
@@ -1,32 +1,45 @@
|
||||
import React from "react";
|
||||
import Terminal from "./terminal";
|
||||
import { BASE_WS_URL } from "@/lib/api";
|
||||
import VNCViewer from "./vncviewer";
|
||||
|
||||
type SSHSessionProps = {
|
||||
type: "ssh";
|
||||
options: {
|
||||
params: {
|
||||
serverId: string;
|
||||
};
|
||||
};
|
||||
|
||||
type Props = SSHSessionProps;
|
||||
type PVESessionProps = {
|
||||
type: "pve";
|
||||
params: {
|
||||
client: "vnc" | "xtermjs";
|
||||
serverId: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type InteractiveSessionProps = SSHSessionProps | PVESessionProps;
|
||||
|
||||
const InteractiveSession = ({ type, params }: InteractiveSessionProps) => {
|
||||
const query = new URLSearchParams({
|
||||
...params,
|
||||
});
|
||||
|
||||
const InteractiveSession = ({ type, options }: Props) => {
|
||||
switch (type) {
|
||||
case "ssh":
|
||||
const params = new URLSearchParams({
|
||||
serverId: options.serverId,
|
||||
token: "token",
|
||||
});
|
||||
return (
|
||||
<Terminal client="xtermjs" wsUrl={`${BASE_WS_URL}/ws/ssh?${params}`} />
|
||||
return <Terminal wsUrl={`${BASE_WS_URL}/ws/ssh?${query}`} />;
|
||||
|
||||
case "pve":
|
||||
const url = `${BASE_WS_URL}/ws/pve?${query}`;
|
||||
return params.client === "vnc" ? (
|
||||
<VNCViewer url={url} />
|
||||
) : (
|
||||
<Terminal wsUrl={url} />
|
||||
);
|
||||
|
||||
default:
|
||||
throw new Error("Unknown interactive session type");
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default InteractiveSession;
|
||||
|
||||
@@ -27,13 +27,13 @@ const Keys = {
|
||||
};
|
||||
|
||||
type XTermJsProps = {
|
||||
client: "xtermjs";
|
||||
client?: "xtermjs";
|
||||
wsUrl: string;
|
||||
};
|
||||
|
||||
type TerminalProps = ComponentPropsWithoutRef<typeof View> & XTermJsProps;
|
||||
|
||||
const Terminal = ({ client, style, ...props }: TerminalProps) => {
|
||||
const Terminal = ({ client = "xtermjs", style, ...props }: TerminalProps) => {
|
||||
const xtermRef = React.useRef<XTermRef>(null);
|
||||
|
||||
const send = (data: string) => {
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
"use dom";
|
||||
|
||||
import React, { useEffect, useRef } from "react";
|
||||
|
||||
type VNCViewerProps = {
|
||||
url: string;
|
||||
};
|
||||
|
||||
const VNCViewer = ({ ...props }: VNCViewerProps) => {
|
||||
const screenRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let clean = () => {};
|
||||
|
||||
(async function () {
|
||||
// Dynamically load noVNC library
|
||||
const { default: RFB } = await import("@novnc/novnc/lib/rfb");
|
||||
|
||||
const rfb = new RFB(screenRef.current!, props.url);
|
||||
rfb.scaleViewport = true;
|
||||
|
||||
// @ts-ignore
|
||||
const ws: WebSocket = rfb._sock._websocket;
|
||||
let password: string | null = null;
|
||||
|
||||
const onConnect = () => {
|
||||
// console.log("Connected");
|
||||
};
|
||||
|
||||
const onDisconnect = () => {
|
||||
// console.log("Disconnected");
|
||||
};
|
||||
|
||||
const onMessage = (e: MessageEvent) => {
|
||||
const msg = String(e.data);
|
||||
|
||||
// Capture password from server
|
||||
if (msg.startsWith("\x01")) {
|
||||
password = msg.substring(1);
|
||||
ws.removeEventListener("message", onMessage);
|
||||
}
|
||||
};
|
||||
|
||||
const onCredentialsRequired = () => {
|
||||
rfb.sendCredentials({
|
||||
password: password || "",
|
||||
username: "",
|
||||
target: "",
|
||||
});
|
||||
password = null;
|
||||
};
|
||||
|
||||
// const onDesktopName = (e: CustomEvent<{ name: string }>) => {
|
||||
// console.log("Desktop name:", e.detail.name);
|
||||
// };
|
||||
|
||||
ws.addEventListener("message", onMessage);
|
||||
rfb.addEventListener("connect", onConnect);
|
||||
rfb.addEventListener("disconnect", onDisconnect);
|
||||
rfb.addEventListener("credentialsrequired", onCredentialsRequired);
|
||||
// rfb.addEventListener("desktopname", onDesktopName);
|
||||
|
||||
// Hack: trigger scale update on visibility change
|
||||
const observer = new ResizeObserver(([entry]) => {
|
||||
if (entry.contentRect.width > 0 && rfb.scaleViewport) {
|
||||
(rfb as any)._updateScale();
|
||||
}
|
||||
});
|
||||
observer.observe(screenRef.current!);
|
||||
|
||||
clean = () => {
|
||||
ws.removeEventListener("message", onMessage);
|
||||
rfb.disconnect();
|
||||
rfb.removeEventListener("connect", onConnect);
|
||||
rfb.removeEventListener("disconnect", onDisconnect);
|
||||
rfb.removeEventListener("credentialsrequired", onCredentialsRequired);
|
||||
// rfb.removeEventListener("desktopname", onDesktopName);
|
||||
observer.disconnect();
|
||||
};
|
||||
})();
|
||||
|
||||
return () => {
|
||||
clean();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <div ref={screenRef} style={{ width: "100%", height: "100vh" }}></div>;
|
||||
};
|
||||
|
||||
export default VNCViewer;
|
||||
@@ -1,10 +1,11 @@
|
||||
"use dom";
|
||||
|
||||
import React, { CSSProperties, FC, forwardRef, useEffect, useRef } from "react";
|
||||
import React, { CSSProperties, forwardRef, useEffect, useRef } from "react";
|
||||
import {
|
||||
DOMImperativeFactory,
|
||||
DOMProps,
|
||||
useDOMImperativeHandle,
|
||||
IS_DOM,
|
||||
} from "expo/dom";
|
||||
import { Terminal as XTerm } from "@xterm/xterm";
|
||||
import "@xterm/xterm/css/xterm.css";
|
||||
@@ -19,9 +20,6 @@ type XTermJsProps = {
|
||||
wsUrl: string;
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
const IS_DOM = typeof ReactNativeWebView !== "undefined";
|
||||
|
||||
export interface XTermRef extends DOMImperativeFactory {
|
||||
send: (...args: JSONValue[]) => void;
|
||||
}
|
||||
@@ -89,6 +87,15 @@ const XTermJs = forwardRef<XTermRef, XTermJsProps>((props, ref) => {
|
||||
ws.addEventListener("close", onClose);
|
||||
xterm.onResize(resizeTerminal);
|
||||
window.addEventListener("resize", onResize);
|
||||
|
||||
// Hack: trigger scale update on visibility change
|
||||
const observer = new ResizeObserver(([entry]) => {
|
||||
if (entry.contentRect.width > 0) {
|
||||
fitAddon.fit();
|
||||
}
|
||||
});
|
||||
observer.observe(containerRef.current!);
|
||||
|
||||
onLoad?.();
|
||||
|
||||
return () => {
|
||||
@@ -100,6 +107,8 @@ const XTermJs = forwardRef<XTermRef, XTermJsProps>((props, ref) => {
|
||||
ws.removeEventListener("open", onOpen);
|
||||
ws.removeEventListener("close", onClose);
|
||||
window.removeEventListener("resize", onResize);
|
||||
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [wsUrl]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user