This commit is contained in:
@ -337,7 +337,7 @@ export class LNVpsApi {
|
||||
}
|
||||
|
||||
async connect_terminal(id: number) {
|
||||
const u = `${this.url}/api/v1/console/${id}`;
|
||||
const u = `${this.url}/api/v1/vm/${id}/console`;
|
||||
const auth = await this.#auth_event(u, "GET");
|
||||
const ws = new WebSocket(
|
||||
`${u}?auth=${base64.encode(
|
||||
|
@ -17,6 +17,7 @@ import { VmBillingPage } from "./pages/vm-billing.tsx";
|
||||
import { VmGraphsPage } from "./pages/vm-graphs.tsx";
|
||||
import { NewsPage } from "./pages/news.tsx";
|
||||
import { NewsPost } from "./pages/news-post.tsx";
|
||||
import { VmConsolePage } from "./pages/vm-console.tsx";
|
||||
|
||||
const system = new NostrSystem({
|
||||
automaticOutboxModel: false,
|
||||
@ -65,6 +66,10 @@ const router = createBrowserRouter([
|
||||
path: "/vm/graphs",
|
||||
element: <VmGraphsPage />,
|
||||
},
|
||||
{
|
||||
path: "/vm/console",
|
||||
element: <VmConsolePage />,
|
||||
},
|
||||
{
|
||||
path: "/tos",
|
||||
element: <TosPage />,
|
||||
|
59
src/pages/vm-console.tsx
Normal file
59
src/pages/vm-console.tsx
Normal file
@ -0,0 +1,59 @@
|
||||
import { FitAddon } from "@xterm/addon-fit";
|
||||
import { Terminal } from "@xterm/xterm";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import useLogin from "../hooks/login";
|
||||
import { VmInstance } from "../api";
|
||||
import { WebglAddon } from "@xterm/addon-webgl";
|
||||
import { AttachAddon } from "@xterm/addon-attach";
|
||||
|
||||
const fit = new FitAddon();
|
||||
|
||||
export function VmConsolePage() {
|
||||
|
||||
const { state } = useLocation() as { state?: VmInstance };
|
||||
const login = useLogin();
|
||||
const [term, setTerm] = useState<Terminal>();
|
||||
const termRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
async function openTerminal() {
|
||||
if (!login?.api || !state) return;
|
||||
const ws = await login.api.connect_terminal(state.id);
|
||||
const te = new Terminal();
|
||||
const webgl = new WebglAddon();
|
||||
webgl.onContextLoss(() => {
|
||||
webgl.dispose();
|
||||
});
|
||||
te.loadAddon(webgl);
|
||||
te.loadAddon(fit);
|
||||
te.onResize(({ cols, rows }) => {
|
||||
//ws.send(`${cols}:${rows}`);
|
||||
});
|
||||
const attach = new AttachAddon(ws);
|
||||
attach.activate(te);
|
||||
setTerm((t) => {
|
||||
if (t) {
|
||||
t.dispose();
|
||||
}
|
||||
return te
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (term && termRef.current) {
|
||||
termRef.current.innerHTML = "";
|
||||
term.open(termRef.current);
|
||||
term.focus();
|
||||
fit.fit();
|
||||
}
|
||||
}, [termRef, term]);
|
||||
|
||||
useEffect(() => {
|
||||
openTerminal();
|
||||
}, []);
|
||||
|
||||
return <div className="flex flex-col gap-4">
|
||||
<div className="text-xl">VM #{state?.id} Terminal:</div>
|
||||
{term && <div className="border p-2" ref={termRef}></div>}
|
||||
</div>
|
||||
}
|
@ -4,16 +4,14 @@ import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { VmInstance, VmIpAssignment } from "../api";
|
||||
import VpsInstanceRow from "../components/vps-instance";
|
||||
import useLogin from "../hooks/login";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { AsyncButton } from "../components/button";
|
||||
import { Terminal } from "@xterm/xterm";
|
||||
import { FitAddon } from "@xterm/addon-fit";
|
||||
import { toEui64 } from "../utils";
|
||||
import { Icon } from "../components/icon";
|
||||
import Modal from "../components/modal";
|
||||
import SSHKeySelector from "../components/ssh-keys";
|
||||
|
||||
const fit = new FitAddon();
|
||||
|
||||
|
||||
export default function VmPage() {
|
||||
const location = useLocation() as { state?: VmInstance };
|
||||
@ -21,8 +19,6 @@ export default function VmPage() {
|
||||
const navigate = useNavigate();
|
||||
const [state, setState] = useState<VmInstance | undefined>(location?.state);
|
||||
|
||||
const [term] = useState<Terminal>();
|
||||
const termRef = useRef<HTMLDivElement | null>(null);
|
||||
const [editKey, setEditKey] = useState(false);
|
||||
const [editReverse, setEditReverse] = useState<VmIpAssignment>();
|
||||
const [error, setError] = useState<string>();
|
||||
@ -86,31 +82,6 @@ export default function VmPage() {
|
||||
);
|
||||
}
|
||||
|
||||
/*async function openTerminal() {
|
||||
if (!login?.api || !state) return;
|
||||
const ws = await login.api.connect_terminal(state.id);
|
||||
const te = new Terminal();
|
||||
const webgl = new WebglAddon();
|
||||
webgl.onContextLoss(() => {
|
||||
webgl.dispose();
|
||||
});
|
||||
te.loadAddon(webgl);
|
||||
te.loadAddon(fit);
|
||||
te.onResize(({ cols, rows }) => {
|
||||
ws.send(`${cols}:${rows}`);
|
||||
});
|
||||
const attach = new AttachAddon(ws);
|
||||
te.loadAddon(attach);
|
||||
setTerm(te);
|
||||
}*/
|
||||
|
||||
useEffect(() => {
|
||||
if (term && termRef.current) {
|
||||
term.open(termRef.current);
|
||||
term.focus();
|
||||
fit.fit();
|
||||
}
|
||||
}, [termRef, term, fit]);
|
||||
|
||||
useEffect(() => {
|
||||
const t = setInterval(() => reloadVmState(), 5000);
|
||||
@ -136,6 +107,9 @@ export default function VmPage() {
|
||||
</div>
|
||||
<hr />
|
||||
<div className="flex gap-4">
|
||||
<AsyncButton onClick={() => navigate("/vm/console", { state })}>
|
||||
Console
|
||||
</AsyncButton>
|
||||
<AsyncButton onClick={() => navigate("/vm/billing", { state })}>
|
||||
Billing
|
||||
</AsyncButton>
|
||||
@ -143,9 +117,7 @@ export default function VmPage() {
|
||||
Graphs
|
||||
</AsyncButton>
|
||||
</div>
|
||||
{/*
|
||||
{!term && <AsyncButton onClick={openTerminal}>Connect Terminal</AsyncButton>}
|
||||
{term && <div className="border p-2" ref={termRef}></div>}*/}
|
||||
|
||||
{editKey && (
|
||||
<Modal id="edit-ssh-key" onClose={() => setEditKey(false)}>
|
||||
<SSHKeySelector selectedKey={key} setSelectedKey={setKey} />
|
||||
|
Reference in New Issue
Block a user