diff --git a/src/api.ts b/src/api.ts index 4919d72..26d21f8 100644 --- a/src/api.ts +++ b/src/api.ts @@ -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( diff --git a/src/main.tsx b/src/main.tsx index 2050d6c..4c2318e 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -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: , }, + { + path: "/vm/console", + element: , + }, { path: "/tos", element: , diff --git a/src/pages/vm-console.tsx b/src/pages/vm-console.tsx new file mode 100644 index 0000000..1d84723 --- /dev/null +++ b/src/pages/vm-console.tsx @@ -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(); + const termRef = useRef(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
+
VM #{state?.id} Terminal:
+ {term &&
} +
+} \ No newline at end of file diff --git a/src/pages/vm.tsx b/src/pages/vm.tsx index 8e1f16f..06213d0 100644 --- a/src/pages/vm.tsx +++ b/src/pages/vm.tsx @@ -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(location?.state); - const [term] = useState(); - const termRef = useRef(null); const [editKey, setEditKey] = useState(false); const [editReverse, setEditReverse] = useState(); const [error, setError] = useState(); @@ -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() {
+ navigate("/vm/console", { state })}> + Console + navigate("/vm/billing", { state })}> Billing @@ -143,9 +117,7 @@ export default function VmPage() { Graphs
- {/* - {!term && Connect Terminal} - {term &&
}*/} + {editKey && ( setEditKey(false)}>