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