feat: console progress
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
2025-03-24 13:04:55 +00:00
parent 1f38e22053
commit c0b7836ce3
4 changed files with 71 additions and 35 deletions

View File

@ -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(

View File

@ -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
View 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>
}

View File

@ -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} />