This commit is contained in:
2026-03-14 08:49:12 +01:00
parent 971695c6c2
commit c5bbe1361a
52 changed files with 363 additions and 80 deletions
@@ -0,0 +1,117 @@
import Stack from "@/twui/components/layout/Stack";
import { useContext, useEffect, useRef, useState } from "react";
import { AppContext } from "@/src/pages/_app";
import { TtydInfoObject, WebSocketDataType } from "@/src/types";
import useWebSocketEventHandler from "@/twui/components/hooks/useWebSocketEventHandler";
import _ from "lodash";
import Loading from "@/twui/components/elements/Loading";
import Center from "@/twui/components/layout/Center";
import TtydIframe from "@/src/components/general/ttyd-iframe";
import useStatus from "@/twui/components/hooks/useStatus";
type Props = {
paradigm: "shell" | "pm2-logs";
};
export default function RelayShellView({ paradigm }: Props) {
const { pageProps, ws } = useContext(AppContext);
const { user } = pageProps;
const username = user.super_admin ? "root" : user.username;
const cmd =
paradigm == "pm2-logs"
? "pm2 logs"
: user.super_admin
? "bash"
: username
? `su - ${username} -c "bash"`
: undefined;
const { data } = useWebSocketEventHandler<WebSocketDataType>();
const [ttyd, setTtyd] = useState<TtydInfoObject>();
const { refresh, setRefresh } = useStatus();
const WsReqSentRef = useRef(false);
function sendKillPort() {
if (ttyd?.port) {
ws.sendData({
event: "client:kill-port",
port: ttyd.port,
});
}
}
useEffect(() => {
if (!ws?.socket || WsReqSentRef.current) {
return;
}
ws.sendData({
event:
paradigm == "pm2-logs"
? "client:relay-pm2-logs"
: "client:relay-shell",
cmd,
});
WsReqSentRef.current = true;
return function () {
sendKillPort();
};
}, [ws, refresh]);
useEffect(() => {
if (WsReqSentRef.current) {
sendKillPort();
setTtyd(undefined);
WsReqSentRef.current = false;
setRefresh((prev) => prev + 1);
}
}, []);
useEffect(() => {
if (ttyd) return;
if (
(data?.event == "server:relay-shell" ||
data?.event == "server:relay-pm2-logs") &&
data?.ttyd
) {
setTimeout(() => {
setTtyd(
data.ttyd
? {
...data.ttyd,
href: `${window.location.origin}${data.ttyd.url}`,
}
: undefined,
);
}, 2000);
}
}, [data]);
return (
<Stack className="gap-0 w-full">
{ttyd?.url && ttyd.port ? (
<Stack className="gap-0">
<TtydIframe
url={ttyd?.url}
title={`Relay Shell`}
wrapperProps={{
className: "border-none",
}}
/>
</Stack>
) : (
<Center className="p-10 h-[460px]">
<Loading />
</Center>
)}
<hr />
</Stack>
);
}
@@ -0,0 +1,22 @@
import { AppContext } from "@/src/pages/_app";
import Stack from "@/twui/components/layout/Stack";
import { useContext } from "react";
import RelayShellView from "../(partials)/shell-view";
export default function RelayShellViewsSection() {
const { pageProps } = useContext(AppContext);
const { user } = pageProps;
return (
<Stack className="w-full nested-grid-frame xl:grid-cols-2">
<Stack className="grid-cell w-full">
<RelayShellView paradigm="shell" />
</Stack>
{user.super_admin ? (
<Stack className="grid-cell w-full">
<RelayShellView paradigm="pm2-logs" />
</Stack>
) : null}
</Stack>
);
}
@@ -0,0 +1,20 @@
import { Fragment, useContext } from "react";
import { AppContext } from "@/src/pages/_app";
import Divider from "@/twui/components/layout/Divider";
import AdminHero from "@/src/components/general/admin/hero";
import RelayShellViewsSection from "./(sections)/shell-views";
export default function Main() {
const { pageProps } = useContext(AppContext);
return (
<Fragment>
<AdminHero
title={`Shell`}
description={<>Access the relay server shell</>}
/>
<Divider />
<RelayShellViewsSection />
</Fragment>
);
}
+48
View File
@@ -0,0 +1,48 @@
import {
NSQLITE_TURBOCI_ADMIN_USERS_PORTS,
NSQLiteTables,
} from "@/src/db/types";
import { TtydInfoObject, User } from "@/src/types";
import getNextAvailablePort from "@/src/utils/grab-next-available-port";
import grabTtydCmd from "@/src/utils/grab-ttyd-cmd";
import grabConnectedWebsocketUserdata from "@/src/websocket/(utils)/grab-connected-websocket-user-data";
import BunSQLite from "@moduletrace/bun-sqlite";
import { exec } from "child_process";
type Params = {
user: User;
cmd: string;
};
export default async function grabTtydInfo({
user,
cmd,
}: Params): Promise<TtydInfoObject> {
const available_port = await getNextAvailablePort();
let url = `/ttyd/${available_port}`;
const ttyd_cmd = grabTtydCmd({
cmd,
port: available_port,
});
const ttyd_exec = exec(ttyd_cmd.cmd);
await Bun.sleep(2000);
const connected_user_data = grabConnectedWebsocketUserdata({ user });
await BunSQLite.insert<
NSQLITE_TURBOCI_ADMIN_USERS_PORTS,
(typeof NSQLiteTables)[number]
>({
data: [{ user_id: user.id, port: available_port }],
table: "users_ports",
});
connected_user_data.child_processes.push(ttyd_exec);
connected_user_data.ports.push(available_port);
return { port: available_port, url };
}
+2 -44
View File
@@ -1,21 +1,10 @@
import {
NSQLITE_TURBOCI_ADMIN_USERS_PORTS,
NSQLiteTables,
} from "@/src/db/types";
import { PrivateServerTtydParadigms, TtydInfoObject, User } from "@/src/types";
import {
NormalizedServerObject,
ParsedDeploymentServiceConfig,
} from "@/src/types/turboci";
import grabDirNames from "@/src/utils/grab-dir-names";
import getNextAvailablePort from "@/src/utils/grab-next-available-port";
import grabSSHPrefix from "@/src/utils/grab-ssh-prefix";
import grabTtydCmd from "@/src/utils/grab-ttyd-cmd";
import grabConnectedWebsocketUserdata from "@/src/websocket/(utils)/grab-connected-websocket-user-data";
import BunSQLite from "@moduletrace/bun-sqlite";
import { exec } from "child_process";
const { TURBOCI_SSH_KEY_FILE } = grabDirNames();
import grabTtydInfo from "./grab-ttyd-info";
type Params = {
user: User;
@@ -44,46 +33,15 @@ export default async function grabTtydServerInfo({
);
}
const available_port = await getNextAvailablePort();
let url = `/ttyd/${available_port}`;
let cmd = ``;
cmd += ` ${grabSSHPrefix()}`;
// if (paradigm == "logs") {
// } else {
// cmd += ` ssh -i ${TURBOCI_SSH_KEY_FILE}`;
// }
cmd += ` root@${server?.private_ip}`;
if (paradigm == "logs" && final_log) {
cmd += ` ${final_log}`;
}
const ttyd_cmd = grabTtydCmd({
cmd,
port: available_port,
});
const ttyd_exec = exec(ttyd_cmd.cmd);
await Bun.sleep(2000);
const connected_user_data = grabConnectedWebsocketUserdata({ user });
await BunSQLite.insert<
NSQLITE_TURBOCI_ADMIN_USERS_PORTS,
(typeof NSQLiteTables)[number]
>({
data: [{ user_id: user.id, port: available_port }],
table: "users_ports",
});
connected_user_data.child_processes.push(ttyd_exec);
connected_user_data.ports.push(available_port);
return { port: available_port, url };
return await grabTtydInfo({ cmd, user });
}
+9
View File
@@ -24,10 +24,19 @@ export function AdminAsideLinks({ user }: Params) {
url: "/admin/users",
}
: undefined,
{
title: "Shell",
url: "/admin/shell",
},
{
title: "Settings",
url: "/admin/settings",
},
{ divider: true },
{
title: "Logout",
url: "/auth/logout",
},
];
return links;
+18
View File
@@ -0,0 +1,18 @@
import Main from "@/src/components/pages/admin/shell";
import defaultAdminProps from "@/src/functions/pages/admin/default-admin-props";
import Layout from "@/src/layouts/admin";
import { GetServerSideProps } from "next";
export default function AdminDeploymentUsers() {
return (
<Layout>
<Main />
</Layout>
);
}
export const getServerSideProps: GetServerSideProps = async (ctx) => {
return await defaultAdminProps({
ctx,
});
};
+4
View File
@@ -79,6 +79,8 @@ export const WebSocketEvents = [
"client:kill-port",
"client:kill-all-ports",
"client:service-server-shell",
"client:relay-shell",
"client:relay-pm2-logs",
"server:ping",
"server:error",
@@ -90,6 +92,8 @@ export const WebSocketEvents = [
"server:killed-port",
"server:killed-all-ports",
"server:service-server-shell",
"server:relay-shell",
"server:relay-pm2-logs",
] as const;
export type WebSocketDataType = {
@@ -0,0 +1,22 @@
import { WebSocketMessageParam } from "@/src/types";
import sendData from "../(utils)/send-data";
import sendError from "../(utils)/send-error";
import grabTtydInfo from "@/src/functions/ttyd/grab-ttyd-info";
export default async function socketClientRelayPm2Logs({
ws,
data,
}: WebSocketMessageParam) {
try {
const user = ws.data.user;
const ttyd = await grabTtydInfo({ cmd: `pm2 logs`, user });
sendData(ws, {
event: "server:relay-pm2-logs",
ttyd,
});
} catch (error: any) {
sendError(ws, "Relay Shell Error! " + error.message);
}
}
@@ -0,0 +1,27 @@
import { WebSocketMessageParam } from "@/src/types";
import sendData from "../(utils)/send-data";
import sendError from "../(utils)/send-error";
import grabTtydInfo from "@/src/functions/ttyd/grab-ttyd-info";
export default async function socketClientRelayShell({
ws,
data,
}: WebSocketMessageParam) {
try {
const user = ws.data.user;
const cmd = data?.cmd;
if (!cmd) {
throw new Error(`No Command Passed`);
}
const ttyd = await grabTtydInfo({ cmd, user });
sendData(ws, {
event: "server:relay-shell",
ttyd,
});
} catch (error: any) {
sendError(ws, "Relay Shell Error! " + error.message);
}
}
+24
View File
@@ -11,6 +11,8 @@ import socketClientServiceServerLogs from "./events/client-service-server-logs";
import socketClientKillPort from "./events/client-kill-port";
import socketClientServiceServerShell from "./events/client-service-server-shell";
import socketClientKillAllPorts from "./events/client-kill-all-ports";
import socketClientRelayShell from "./events/client-relay-shell";
import socketClientRelayPm2Logs from "./events/client-relay-pm2-logs";
type Param = {
ws: ServerWebSocket<WebSocketData>;
@@ -41,6 +43,7 @@ export default async function socketMessage({ ws, message }: Param) {
});
await socketClientPing(websocketMessageParams);
break;
case "client:service-server-logs":
debugLog({
log: `${userRef} Getting Service Server Logs ...`,
@@ -49,6 +52,7 @@ export default async function socketMessage({ ws, message }: Param) {
});
await socketClientServiceServerLogs(websocketMessageParams);
break;
case "client:service-server-shell":
debugLog({
log: `${userRef} Getting Service Server Shell ...`,
@@ -57,6 +61,25 @@ export default async function socketMessage({ ws, message }: Param) {
});
await socketClientServiceServerShell(websocketMessageParams);
break;
case "client:relay-shell":
debugLog({
log: `${userRef} Getting Relay Server Shell ...`,
addTime: true,
label,
});
await socketClientRelayShell(websocketMessageParams);
break;
case "client:relay-pm2-logs":
debugLog({
log: `${userRef} Getting Relay pm2 Logs ...`,
addTime: true,
label,
});
await socketClientRelayPm2Logs(websocketMessageParams);
break;
case "client:kill-port":
debugLog({
log: `${userRef} Killing Port ${data.port} ...`,
@@ -65,6 +88,7 @@ export default async function socketMessage({ ws, message }: Param) {
});
await socketClientKillPort(websocketMessageParams);
break;
case "client:kill-all-ports":
debugLog({
log: `${userRef} Killing All Ports ...`,