Updates
This commit is contained in:
parent
f26227c7a8
commit
921375c53d
113
src/components/general/ttyd-iframe.tsx
Normal file
113
src/components/general/ttyd-iframe.tsx
Normal file
@ -0,0 +1,113 @@
|
||||
import Border from "@/twui/components/elements/Border";
|
||||
import Loading from "@/twui/components/elements/Loading";
|
||||
import LucideIcon from "@/twui/components/elements/lucide-icon";
|
||||
import useStatus from "@/twui/components/hooks/useStatus";
|
||||
import Button from "@/twui/components/layout/Button";
|
||||
import Center from "@/twui/components/layout/Center";
|
||||
import Row from "@/twui/components/layout/Row";
|
||||
import Span from "@/twui/components/layout/Span";
|
||||
import Stack from "@/twui/components/layout/Stack";
|
||||
import {
|
||||
ComponentProps,
|
||||
DetailedHTMLProps,
|
||||
Fragment,
|
||||
IframeHTMLAttributes,
|
||||
ReactNode,
|
||||
} from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
type Props = Omit<
|
||||
DetailedHTMLProps<
|
||||
IframeHTMLAttributes<HTMLIFrameElement>,
|
||||
HTMLIFrameElement
|
||||
>,
|
||||
"title"
|
||||
> & {
|
||||
url: string;
|
||||
wrapperProps?: ComponentProps<typeof Border>;
|
||||
title?: string | ReactNode;
|
||||
};
|
||||
|
||||
export default function TtydIframe({
|
||||
url,
|
||||
wrapperProps,
|
||||
title,
|
||||
...props
|
||||
}: Props) {
|
||||
const { loading, setLoading } = useStatus();
|
||||
|
||||
return (
|
||||
<Border
|
||||
{...wrapperProps}
|
||||
className={twMerge("p-0", wrapperProps?.className)}
|
||||
>
|
||||
<Stack className="gap-0">
|
||||
<Row className="p-4 w-full justify-between">
|
||||
<Row>
|
||||
{title ? (
|
||||
<Fragment>
|
||||
<Span size="small" variant="faded">
|
||||
{title}
|
||||
</Span>
|
||||
<LucideIcon
|
||||
name="ChevronRight"
|
||||
size={15}
|
||||
opacity={0.3}
|
||||
/>
|
||||
</Fragment>
|
||||
) : null}
|
||||
|
||||
<Span size="small" variant="faded">
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
className="dotted-link"
|
||||
>
|
||||
{url}
|
||||
</a>
|
||||
</Span>
|
||||
</Row>
|
||||
<Row>
|
||||
<Button
|
||||
title="Open Full Screen"
|
||||
variant="ghost"
|
||||
className="p-1!"
|
||||
onClick={() => {
|
||||
window.open(url, "__blank");
|
||||
}}
|
||||
>
|
||||
<LucideIcon name="ArrowUpRight" size={20} />
|
||||
</Button>
|
||||
<Button
|
||||
title="Refresh Iframe"
|
||||
variant="ghost"
|
||||
className="p-1!"
|
||||
loading={loading}
|
||||
onClick={() => {
|
||||
setLoading(true);
|
||||
setTimeout(() => {
|
||||
setLoading(false);
|
||||
}, 2000);
|
||||
}}
|
||||
loadingProps={{ size: "smaller" }}
|
||||
>
|
||||
<LucideIcon name="RotateCcw" size={18} />
|
||||
</Button>
|
||||
</Row>
|
||||
</Row>
|
||||
<hr />
|
||||
{loading ? (
|
||||
<Center className="w-full p-10 h-[400px]">
|
||||
<Loading />
|
||||
</Center>
|
||||
) : (
|
||||
<iframe
|
||||
{...props}
|
||||
src={url}
|
||||
className={twMerge("w-full h-[400px]", props.className)}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</Border>
|
||||
);
|
||||
}
|
||||
@ -1,15 +1,18 @@
|
||||
import Stack from "@/twui/components/layout/Stack";
|
||||
import { useContext, useEffect, useRef } from "react";
|
||||
import { RefObject, useContext, useEffect, useRef, useState } from "react";
|
||||
import { AppContext } from "@/src/pages/_app";
|
||||
import {
|
||||
NormalizedServerObject,
|
||||
ParsedDeploymentServiceConfig,
|
||||
TtydInfoObject,
|
||||
WebSocketDataType,
|
||||
} from "@/src/types";
|
||||
import useIntersectionObserver from "@/twui/components/hooks/useIntersectionObserver";
|
||||
import Center from "@/twui/components/layout/Center";
|
||||
import Loading from "@/twui/components/elements/Loading";
|
||||
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 useIntersectionObserver from "@/twui/components/hooks/useIntersectionObserver";
|
||||
|
||||
type Props = {
|
||||
service: ParsedDeploymentServiceConfig;
|
||||
@ -19,23 +22,97 @@ type Props = {
|
||||
export default function ServiceClusterServerViews({ service, server }: Props) {
|
||||
const { pageProps, ws } = useContext(AppContext);
|
||||
|
||||
const viewRef = useRef<HTMLDivElement>(undefined);
|
||||
|
||||
const { data } = useWebSocketEventHandler<WebSocketDataType>();
|
||||
const { isIntersecting } = useIntersectionObserver({ elementRef: viewRef });
|
||||
|
||||
const [ttydLogs, setTtydLogs] = useState<TtydInfoObject>();
|
||||
|
||||
const WsReqSentRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ws?.socket) {
|
||||
if (!ws?.socket || WsReqSentRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
ws.sendData({
|
||||
event: "client:ping",
|
||||
event: "client:service-server-logs",
|
||||
server,
|
||||
service,
|
||||
service: _.omit(service, ["servers"]),
|
||||
});
|
||||
|
||||
WsReqSentRef.current = true;
|
||||
}, [ws]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("data", data);
|
||||
if (ttydLogs) return;
|
||||
|
||||
if (
|
||||
data?.event == "server:service-server-logs" &&
|
||||
data?.ttyd &&
|
||||
data.server?.private_ip == server.private_ip
|
||||
) {
|
||||
setTimeout(() => {
|
||||
setTtydLogs(data.ttyd);
|
||||
}, 2000);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
return <Stack className="gap-0 w-full"></Stack>;
|
||||
console.log("isIntersecting", isIntersecting);
|
||||
console.log("ttydLogs", ttydLogs);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ttydLogs?.port) return;
|
||||
|
||||
if (!isIntersecting) {
|
||||
ws.sendData({
|
||||
event: "client:kill-port",
|
||||
server,
|
||||
service: _.omit(service, ["servers"]),
|
||||
port: ttydLogs.port,
|
||||
});
|
||||
}
|
||||
}, [isIntersecting]);
|
||||
|
||||
const dev_logs_url = ttydLogs?.port
|
||||
? `http://localhost:${ttydLogs.port}`
|
||||
: undefined;
|
||||
|
||||
const title = (
|
||||
<>
|
||||
{service.service_name} service <code>{server.private_ip}</code> Logs
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack className="gap-0 w-full" componentRef={viewRef as any}>
|
||||
{isIntersecting && ttydLogs?.url && ttydLogs.port ? (
|
||||
<Stack className="gap-0">
|
||||
{/* {dev_logs_url ? (
|
||||
<TtydIframe
|
||||
url={dev_logs_url}
|
||||
title={title}
|
||||
wrapperProps={{
|
||||
className: "border-none",
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<hr /> */}
|
||||
<TtydIframe
|
||||
url={ttydLogs?.url}
|
||||
title={title}
|
||||
wrapperProps={{
|
||||
className: "border-none",
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
) : (
|
||||
<Center className="p-10 h-[400px]">
|
||||
<Loading />
|
||||
</Center>
|
||||
)}
|
||||
<hr />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@ -36,6 +36,9 @@ export default async function defaultAdminProps({
|
||||
const deployment = grabTurboCiConfig();
|
||||
const deployment_id = readFileSync(TURBOCI_DEPLOYMENT_ID_FILE, "utf-8");
|
||||
|
||||
const host = process.env.HOST || null;
|
||||
const ws_url = `${host?.replace(/^http/, "ws")}/ws`;
|
||||
|
||||
const service = query.service_name
|
||||
? deployment.services.find(
|
||||
(srv) => srv.service_name == query.service_name,
|
||||
@ -98,6 +101,8 @@ export default async function defaultAdminProps({
|
||||
deployment_id,
|
||||
service,
|
||||
children_services,
|
||||
ws_url,
|
||||
host,
|
||||
};
|
||||
|
||||
let finalProps = _.merge(props, propsFnProps, defaultPageProps);
|
||||
|
||||
72
src/functions/ttyd/grab-ttyd-service-info.ts
Normal file
72
src/functions/ttyd/grab-ttyd-service-info.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import { AppData } from "@/src/data/app-data";
|
||||
import {
|
||||
NormalizedServerObject,
|
||||
ParsedDeploymentServiceConfig,
|
||||
PrivateServerTtydParadigms,
|
||||
TtydInfoObject,
|
||||
User,
|
||||
} from "@/src/types";
|
||||
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 { exec } from "child_process";
|
||||
|
||||
const { TURBOCI_SSH_KEY_FILE } = grabDirNames();
|
||||
|
||||
type Params = {
|
||||
user: User;
|
||||
service?: Omit<ParsedDeploymentServiceConfig, "servers">;
|
||||
server?: NormalizedServerObject;
|
||||
paradigm: (typeof PrivateServerTtydParadigms)[number]["name"];
|
||||
};
|
||||
|
||||
export default async function grabTtydServerInfo({
|
||||
server,
|
||||
service,
|
||||
user,
|
||||
paradigm,
|
||||
}: Params): Promise<TtydInfoObject> {
|
||||
const first_log = service?.logs?.[0];
|
||||
const final_first_log =
|
||||
typeof first_log == "string" ? first_log : first_log?.cmd;
|
||||
|
||||
if (paradigm == "logs" && !final_first_log) {
|
||||
throw new Error(`Service Doesn't have logs.`);
|
||||
}
|
||||
|
||||
const available_port = getNextAvailablePort();
|
||||
|
||||
let url = `${process.env.HOST}/ttyd/${available_port}`;
|
||||
|
||||
let cmd = ``;
|
||||
|
||||
if (paradigm == "logs") {
|
||||
cmd += ` ${grabSSHPrefix()}`;
|
||||
} else {
|
||||
cmd += ` ssh -i ${TURBOCI_SSH_KEY_FILE}`;
|
||||
}
|
||||
|
||||
cmd += ` root@${server?.private_ip}`;
|
||||
|
||||
if (final_first_log) {
|
||||
cmd += ` ${final_first_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 });
|
||||
|
||||
connected_user_data.child_processes.push(ttyd_exec);
|
||||
connected_user_data.ports.push(available_port);
|
||||
|
||||
return { port: available_port, url };
|
||||
}
|
||||
@ -2,9 +2,11 @@ import React, { useEffect } from "react";
|
||||
import { PagePropsType, ToastType, WebSocketDataType } from "../types";
|
||||
import useWebSocket from "@/twui/components/hooks/useWebSocket";
|
||||
import useStatus from "@/twui/components/hooks/useStatus";
|
||||
import useWebSocketEventHandler from "@/twui/components/hooks/useWebSocketEventHandler";
|
||||
import { date } from "zod";
|
||||
|
||||
export default function useAppInit(pageProps: PagePropsType) {
|
||||
const wsURL = process.env.NEXT_PUBLIC_WEBSOCKET_URL || "";
|
||||
const wsURL = pageProps.ws_url || "";
|
||||
|
||||
const { user } = pageProps;
|
||||
|
||||
@ -22,6 +24,32 @@ export default function useAppInit(pageProps: PagePropsType) {
|
||||
|
||||
const ws = { socket, sendData };
|
||||
|
||||
const { data } = useWebSocketEventHandler<WebSocketDataType>();
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.event == "server:error") {
|
||||
setToast({
|
||||
toastOpen: true,
|
||||
toastMessage: data.message,
|
||||
toastStyle: "error",
|
||||
});
|
||||
}
|
||||
if (data?.event == "server:update") {
|
||||
setToast({
|
||||
toastOpen: true,
|
||||
toastMessage: data.message,
|
||||
toastStyle: "normal",
|
||||
});
|
||||
}
|
||||
if (data?.event == "server:success") {
|
||||
setToast({
|
||||
toastOpen: true,
|
||||
toastMessage: data.message,
|
||||
toastStyle: "success",
|
||||
});
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
return {
|
||||
socket,
|
||||
sendData,
|
||||
|
||||
@ -5,6 +5,8 @@ import Stack from "@/twui/components/layout/Stack";
|
||||
import { PropsWithChildren } from "react";
|
||||
import { AdminAsideLinks } from "./(data)/links";
|
||||
import Header from "./header";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Spacer from "@/twui/components/layout/Spacer";
|
||||
|
||||
type Props = PropsWithChildren & {};
|
||||
|
||||
@ -25,8 +27,17 @@ export default function Layout({ children }: Props) {
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack className="grid-cell col-span-6 xl:col-span-5 gap-0">
|
||||
<Stack
|
||||
className={twMerge(
|
||||
"grid-cell col-span-6 xl:col-span-5 gap-0",
|
||||
"overflow-auto pb-[200px]",
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
<div
|
||||
className="h-[400px] w-full block"
|
||||
style={{ height: "400px" }}
|
||||
/>
|
||||
</Stack>
|
||||
</div>
|
||||
</Main>
|
||||
|
||||
@ -3,6 +3,7 @@ import type { AppProps } from "next/app";
|
||||
import { createContext } from "react";
|
||||
import { PagePropsType, TurboCIAdminAppContextType } from "../types";
|
||||
import useAppInit from "../hooks/use-app-init";
|
||||
import Toast from "@/twui/components/elements/Toast";
|
||||
|
||||
export const AppContext = createContext<TurboCIAdminAppContextType>(
|
||||
{} as TurboCIAdminAppContextType,
|
||||
@ -11,9 +12,21 @@ export const AppContext = createContext<TurboCIAdminAppContextType>(
|
||||
export default function App({ Component, pageProps }: AppProps<PagePropsType>) {
|
||||
const init = useAppInit(pageProps);
|
||||
|
||||
const { toast, setToast } = init;
|
||||
|
||||
return (
|
||||
<AppContext.Provider value={{ ...init }}>
|
||||
<Component {...pageProps} />
|
||||
<Toast
|
||||
open={toast.toastOpen}
|
||||
closeDispatch={(open) => {
|
||||
setToast((prev) => ({ ...prev, toastOpen: false }));
|
||||
}}
|
||||
color={toast.toastStyle}
|
||||
closeDelay={toast.closeDelay}
|
||||
>
|
||||
{toast.toastMessage}
|
||||
</Toast>
|
||||
</AppContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ app.prepare().then(() => {
|
||||
return;
|
||||
}
|
||||
|
||||
const full_href = `${process.env.NEXT_PUBLIC_HOST}${req.url}`;
|
||||
const full_href = `${process.env.HOST}${req.url}`;
|
||||
|
||||
const url = new URL(full_href);
|
||||
|
||||
|
||||
@ -119,3 +119,7 @@ code {
|
||||
hr {
|
||||
@apply border-foreground-light/10 dark:border-foreground-dark/10;
|
||||
}
|
||||
|
||||
.dotted-link {
|
||||
@apply border-b border-dashed border-foreground-light/40 dark:border-foreground-dark/40;
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { ToastStyles } from "@/twui/components/elements/Toast";
|
||||
import { DATASQUIREL_LoggedInUser } from "@moduletrace/datasquirel/dist/package-shared/types";
|
||||
import useAppInit from "../hooks/use-app-init";
|
||||
import { ServerWebSocket } from "bun";
|
||||
import { ChildProcess } from "child_process";
|
||||
|
||||
export type User = DATASQUIREL_LoggedInUser & {};
|
||||
|
||||
@ -119,8 +121,15 @@ export type TCIConfigServiceConfig = {
|
||||
* Commoands to Run on first run
|
||||
*/
|
||||
init?: string[];
|
||||
logs?: TCIConfigServiceConfigLog[];
|
||||
};
|
||||
|
||||
export type TCIConfigServiceConfigLog =
|
||||
| string
|
||||
| {
|
||||
cmd: string;
|
||||
};
|
||||
|
||||
export type TCIConfigServiceHealthcheck = {
|
||||
cmd: string;
|
||||
test: string;
|
||||
@ -183,6 +192,8 @@ export type PagePropsType = {
|
||||
deployment_id?: string | null;
|
||||
service?: ParsedDeploymentServiceConfig | null;
|
||||
children_services?: ParsedDeploymentServiceConfig[] | null;
|
||||
ws_url?: string | null;
|
||||
host?: string | null;
|
||||
};
|
||||
|
||||
export type APIReqObject = {
|
||||
@ -219,6 +230,8 @@ export type TurboCIAdminAppContextType = ReturnType<typeof useAppInit>;
|
||||
|
||||
export const WebSocketEvents = [
|
||||
"client:ping",
|
||||
"client:service-server-logs",
|
||||
"client:kill-port",
|
||||
|
||||
"server:ping",
|
||||
"server:error",
|
||||
@ -226,13 +239,23 @@ export const WebSocketEvents = [
|
||||
"server:ready",
|
||||
"server:success",
|
||||
"server:update",
|
||||
"server:service-server-logs",
|
||||
"server:killed-port",
|
||||
] as const;
|
||||
|
||||
export type WebSocketDataType = {
|
||||
event: (typeof WebSocketEvents)[number];
|
||||
message?: string;
|
||||
service?: ParsedDeploymentServiceConfig;
|
||||
service?: Omit<ParsedDeploymentServiceConfig, "servers">;
|
||||
server?: NormalizedServerObject;
|
||||
ttyd?: TtydInfoObject;
|
||||
port?: string | number;
|
||||
};
|
||||
|
||||
export type WebSocketMessageParam = {
|
||||
ws: ServerWebSocket<WebSocketData>;
|
||||
message?: string | Buffer;
|
||||
data?: WebSocketDataType;
|
||||
};
|
||||
|
||||
export type WebSocketType = {
|
||||
@ -248,3 +271,23 @@ export type ToastType = {
|
||||
toastOpen: boolean;
|
||||
closeDelay?: number;
|
||||
};
|
||||
|
||||
export type TtydInfoObject = {
|
||||
url: string;
|
||||
port: number;
|
||||
};
|
||||
|
||||
export const PrivateServerTtydParadigms = [
|
||||
{
|
||||
name: "logs",
|
||||
},
|
||||
{
|
||||
name: "terminal",
|
||||
},
|
||||
] as const;
|
||||
|
||||
export type WebSocketConnectedUserData = {
|
||||
user: User;
|
||||
child_processes: ChildProcess[];
|
||||
ports: (string | number)[];
|
||||
};
|
||||
|
||||
@ -4,14 +4,19 @@ type Params = {
|
||||
cmd: string;
|
||||
cwd?: string;
|
||||
flags?: string[];
|
||||
port: number;
|
||||
};
|
||||
|
||||
export default function grabTtydCmd({ cmd: ttydCmd, cwd, flags }: Params) {
|
||||
const port = 8080;
|
||||
|
||||
export default function grabTtydCmd({
|
||||
cmd: ttydCmd,
|
||||
cwd,
|
||||
flags,
|
||||
port,
|
||||
}: Params) {
|
||||
let cmd = ``;
|
||||
cmd += `${AppData["TerminalBinName"]}`;
|
||||
cmd += ` --writable --max-clients 1`;
|
||||
cmd += ` --writable`;
|
||||
// cmd += ` --max-clients 1`;
|
||||
cmd += ` --client-option 'theme={"background":"#0c0e11"}'`;
|
||||
cmd += ` --client-option fontSize=14`;
|
||||
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
import { User } from "@/src/types";
|
||||
|
||||
type Params = {
|
||||
user: User;
|
||||
};
|
||||
|
||||
export default function grabConnectedWebsocketUserdataIndex({ user }: Params) {
|
||||
const user_index = global.WEBSOCKET_CONNECTED_USERS_DATA.findIndex(
|
||||
(u) => u.user.id == user.id,
|
||||
);
|
||||
|
||||
if (typeof user_index == "number" && user_index >= 0) {
|
||||
return user_index;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
21
src/websocket/(utils)/grab-connected-websocket-user-data.ts
Normal file
21
src/websocket/(utils)/grab-connected-websocket-user-data.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { User } from "@/src/types";
|
||||
import grabConnectedWebsocketUserdataIndex from "./grab-connected-websocket-user-data-index";
|
||||
|
||||
type Params = {
|
||||
user: User;
|
||||
};
|
||||
|
||||
export default function grabConnectedWebsocketUserdata({ user }: Params) {
|
||||
const connected_user_data_index = grabConnectedWebsocketUserdataIndex({
|
||||
user,
|
||||
});
|
||||
|
||||
if (typeof connected_user_data_index !== "number") {
|
||||
throw new Error(`User Connection Data not found!`);
|
||||
}
|
||||
|
||||
const connected_user_data =
|
||||
global.WEBSOCKET_CONNECTED_USERS_DATA[connected_user_data_index];
|
||||
|
||||
return connected_user_data;
|
||||
}
|
||||
20
src/websocket/(utils)/kill-all-ttyd-ports.ts
Normal file
20
src/websocket/(utils)/kill-all-ttyd-ports.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { AppData } from "@/src/data/app-data";
|
||||
import { execSync, ExecSyncOptions } from "child_process";
|
||||
|
||||
type Params = {
|
||||
exec_options?: ExecSyncOptions;
|
||||
};
|
||||
|
||||
export default async function killAllTtydPorts(params?: Params) {
|
||||
const start_port = AppData["DynamicPortStart"];
|
||||
const end_port = start_port + 100;
|
||||
const kill_ports_cmd = `ss -tlnp | awk 'NR>1 {split($4, a, ":"); port=a[length(a)]; if (port >= ${start_port} && port <= ${end_port}) print $6}' | grep -oP 'pid=\\K[0-9]+' | xargs -r kill -9`;
|
||||
|
||||
execSync(kill_ports_cmd, {
|
||||
...params?.exec_options,
|
||||
});
|
||||
}
|
||||
|
||||
// sudo ss -tlnp | awk 'NR>1 {split($4, a, ":"); port=a[length(a)]; if (port >= 4700 && port <= 4800) print $6}' | grep -oP 'pid=\K[0-9]+' | xargs -r sudo kill -9
|
||||
|
||||
// ss -tlnp | awk 'NR>1 {split($4, a, ":"); port=a[length(a)]; if (port >= 4700 && port <= 4800) print $6}' | grep -oP 'pid=\K[0-9]+' | xargs -r sudo kill -9
|
||||
14
src/websocket/(utils)/kill-port.ts
Normal file
14
src/websocket/(utils)/kill-port.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { execSync, ExecSyncOptions } from "child_process";
|
||||
|
||||
type Params = {
|
||||
port: string | number;
|
||||
exec_options?: ExecSyncOptions;
|
||||
};
|
||||
|
||||
export default async function killPort({ port, exec_options }: Params) {
|
||||
const kill_ports_cmd = `ss -tlnp | awk -v p=${port} 'NR>1 {split($4,a,":"); if(a[length(a)]==p) print $6}' | grep -oP 'pid=\K[0-9]+' | xargs -r kill -9`;
|
||||
|
||||
execSync(kill_ports_cmd, {
|
||||
...exec_options,
|
||||
});
|
||||
}
|
||||
26
src/websocket/events/client-kill-port.ts
Normal file
26
src/websocket/events/client-kill-port.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { WebSocketMessageParam } from "@/src/types";
|
||||
import sendData from "../(utils)/send-data";
|
||||
import sendError from "../(utils)/send-error";
|
||||
import grabConnectedWebsocketUserdata from "../(utils)/grab-connected-websocket-user-data";
|
||||
|
||||
export default async function socketClientKillPort({
|
||||
ws,
|
||||
data,
|
||||
}: WebSocketMessageParam) {
|
||||
try {
|
||||
const user = ws.data.user;
|
||||
const service = data?.service;
|
||||
const server = data?.server;
|
||||
const port = data?.port;
|
||||
|
||||
const connected_user_data = grabConnectedWebsocketUserdata({ user });
|
||||
|
||||
console.log("connected_user_data", connected_user_data);
|
||||
|
||||
sendData(ws, {
|
||||
event: "server:killed-port",
|
||||
});
|
||||
} catch (error: any) {
|
||||
sendError(ws, "Service Server Logs Error! " + error.message);
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,18 @@
|
||||
import { WebSocketMessageParam } from "@/src/types";
|
||||
import sendData from "../(utils)/send-data";
|
||||
import sendError from "../(utils)/send-error";
|
||||
import { WebSocketMessageParam } from "../socket-message";
|
||||
import grabConnectedWebsocketUserdata from "../(utils)/grab-connected-websocket-user-data";
|
||||
|
||||
export default async function socketClientPing({
|
||||
ws,
|
||||
data,
|
||||
}: WebSocketMessageParam) {
|
||||
try {
|
||||
const user = ws.data.user;
|
||||
const connected_user_data = grabConnectedWebsocketUserdata({ user });
|
||||
|
||||
console.log("connected_user_data", connected_user_data);
|
||||
|
||||
sendData(ws, {
|
||||
event: "server:ping",
|
||||
});
|
||||
|
||||
32
src/websocket/events/client-service-server-logs.ts
Normal file
32
src/websocket/events/client-service-server-logs.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { WebSocketMessageParam } from "@/src/types";
|
||||
import sendData from "../(utils)/send-data";
|
||||
import sendError from "../(utils)/send-error";
|
||||
import grabTtydServerInfo from "@/src/functions/ttyd/grab-ttyd-service-info";
|
||||
|
||||
export default async function socketClientServiceServerLogs({
|
||||
ws,
|
||||
data,
|
||||
}: WebSocketMessageParam) {
|
||||
try {
|
||||
const user = ws.data.user;
|
||||
const service = data?.service;
|
||||
const server = data?.server;
|
||||
|
||||
const ttyd = await grabTtydServerInfo({
|
||||
server,
|
||||
service,
|
||||
user,
|
||||
paradigm: "logs",
|
||||
});
|
||||
|
||||
console.log("ttyd", ttyd);
|
||||
|
||||
sendData(ws, {
|
||||
event: "server:service-server-logs",
|
||||
ttyd,
|
||||
server,
|
||||
});
|
||||
} catch (error: any) {
|
||||
sendError(ws, "Service Server Logs Error! " + error.message);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,14 @@
|
||||
import { WebSocketData } from "../types";
|
||||
import { WebSocketConnectedUserData, WebSocketData } from "../types";
|
||||
import socketClose from "./socket-close";
|
||||
import socketInit from "./socket-init";
|
||||
import socketMessage from "./socket-message";
|
||||
import socketOpen from "./socket-open";
|
||||
|
||||
declare global {
|
||||
var WEBSOCKET_CONNECTED_USERS_DATA: WebSocketConnectedUserData[];
|
||||
}
|
||||
|
||||
global.WEBSOCKET_CONNECTED_USERS_DATA = [];
|
||||
|
||||
const server = Bun.serve<WebSocketData>({
|
||||
async fetch(req, server) {
|
||||
@ -27,12 +35,10 @@ const server = Bun.serve<WebSocketData>({
|
||||
}
|
||||
},
|
||||
async open(ws) {
|
||||
const user = ws.data.user;
|
||||
console.log(`Web Socket Opened by ${user.first_name}`);
|
||||
await socketOpen({ ws });
|
||||
},
|
||||
async close(ws, code, message) {
|
||||
const user = ws.data.user;
|
||||
console.log(`Socket Closed by ${user.first_name}`);
|
||||
await socketClose({ ws });
|
||||
},
|
||||
idleTimeout: 600,
|
||||
maxPayloadLength: 1024 * 1024 * 10,
|
||||
|
||||
33
src/websocket/socket-close.ts
Normal file
33
src/websocket/socket-close.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { ServerWebSocket } from "bun";
|
||||
import { WebSocketData } from "../types";
|
||||
import killAllTtydPorts from "./(utils)/kill-all-ttyd-ports";
|
||||
|
||||
type Param = {
|
||||
ws: ServerWebSocket<WebSocketData>;
|
||||
};
|
||||
|
||||
export default async function socketClose({ ws }: Param) {
|
||||
const user = ws.data.user;
|
||||
console.log(`Web Socket Closed by ${user.first_name}`);
|
||||
|
||||
const existing_connected_user_data =
|
||||
global.WEBSOCKET_CONNECTED_USERS_DATA.find((u) => u.user.id == user.id);
|
||||
|
||||
if (existing_connected_user_data) {
|
||||
for (
|
||||
let i = 0;
|
||||
i < existing_connected_user_data.child_processes.length;
|
||||
i++
|
||||
) {
|
||||
const child_process =
|
||||
existing_connected_user_data.child_processes[i];
|
||||
child_process.kill();
|
||||
}
|
||||
|
||||
existing_connected_user_data.child_processes = [];
|
||||
}
|
||||
|
||||
killAllTtydPorts({
|
||||
exec_options: { stdio: "inherit" },
|
||||
});
|
||||
}
|
||||
@ -1,20 +1,20 @@
|
||||
import { ServerWebSocket } from "bun";
|
||||
import { WebSocketData, WebSocketDataType } from "../types";
|
||||
import {
|
||||
WebSocketData,
|
||||
WebSocketDataType,
|
||||
WebSocketMessageParam,
|
||||
} from "../types";
|
||||
import { EJSON } from "../exports/client-exports";
|
||||
import socketClientPing from "./events/client-ping";
|
||||
import debugLog from "@moduletrace/datasquirel/dist/package-shared/utils/logging/debug-log";
|
||||
import socketClientServiceServerLogs from "./events/client-service-server-logs";
|
||||
import socketClientKillPort from "./events/client-kill-port";
|
||||
|
||||
type Param = {
|
||||
ws: ServerWebSocket<WebSocketData>;
|
||||
message: string | Buffer;
|
||||
};
|
||||
|
||||
export type WebSocketMessageParam = {
|
||||
ws: ServerWebSocket<WebSocketData>;
|
||||
message?: string | Buffer;
|
||||
data?: WebSocketDataType;
|
||||
};
|
||||
|
||||
export default async function socketMessage({ ws, message }: Param) {
|
||||
const user = ws.data.user;
|
||||
const data = EJSON.parse(message.toString()) as
|
||||
@ -39,6 +39,22 @@ export default async function socketMessage({ ws, message }: Param) {
|
||||
});
|
||||
await socketClientPing(websocketMessageParams);
|
||||
break;
|
||||
case "client:service-server-logs":
|
||||
debugLog({
|
||||
log: `${userRef} Getting Service Server Logs ...`,
|
||||
addTime: true,
|
||||
label,
|
||||
});
|
||||
await socketClientServiceServerLogs(websocketMessageParams);
|
||||
break;
|
||||
case "client:kill-port":
|
||||
debugLog({
|
||||
log: `${userRef} Killing Port ${data.port} ...`,
|
||||
addTime: true,
|
||||
label,
|
||||
});
|
||||
await socketClientKillPort(websocketMessageParams);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
22
src/websocket/socket-open.ts
Normal file
22
src/websocket/socket-open.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { ServerWebSocket } from "bun";
|
||||
import { WebSocketData } from "../types";
|
||||
|
||||
type Param = {
|
||||
ws: ServerWebSocket<WebSocketData>;
|
||||
};
|
||||
|
||||
export default async function socketOpen({ ws }: Param) {
|
||||
const user = ws.data.user;
|
||||
console.log(`Web Socket Opened by ${user.first_name}`);
|
||||
|
||||
const existing_connected_user_data =
|
||||
global.WEBSOCKET_CONNECTED_USERS_DATA.find((u) => u.user.id == user.id);
|
||||
|
||||
if (!existing_connected_user_data) {
|
||||
global.WEBSOCKET_CONNECTED_USERS_DATA.push({
|
||||
user,
|
||||
child_processes: [],
|
||||
ports: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user