1import { NodeType } from "@marzneshin/modules/nodes";
2import useWebSocket, { ReadyState } from "react-use-websocket";
3import { useAuth } from "@marzneshin/features/auth";
4import { joinPaths } from "@tanstack/react-router";
5import { useCallback, useEffect, useRef, useState } from "react";
6
7export const MAX_NUMBER_OF_LOGS = 500;
8
9export const getStatus = (status: string) => {
10 return {
11 [ReadyState.CONNECTING]: "connecting",
12 [ReadyState.OPEN]: "connected",
13 [ReadyState.CLOSING]: "closed",
14 [ReadyState.CLOSED]: "closed",
15 [ReadyState.UNINSTANTIATED]: "closed",
16 }[status];
17};
18
19export const getWebsocketUrl = (nodeId: number, backend: string) => {
20 try {
21 const baseURL = new URL(
22 import.meta.env.VITE_BASE_API.startsWith("/")
23 ? window.location.origin + import.meta.env.VITE_BASE_API
24 : import.meta.env.VITE_BASE_API,
25 );
26 const protocol = baseURL.protocol === "https:" ? "wss://" : "ws://";
27 return `${protocol}${joinPaths([baseURL.host + baseURL.pathname, `/nodes/${nodeId}/${backend}/logs`])}?interval=1&token=${useAuth.getState().getAuthToken()}`;
28 } catch (e) {
29 console.error("Unable to generate websocket url");
30 console.error(e);
31 return null;
32 }
33};
34
35export const useNodesLog = (node: NodeType, backend: string) => {
36 const [logs, setLogs] = useState<string[]>([]);
37 const logsDiv = useRef<HTMLDivElement | null>(null);
38 const scrollShouldStayOnEnd = useRef(true);
39
40 const updateLogs = useCallback(
41 (callback: (prevLogs: string[]) => string[]) => {
42 setLogs((prevLogs) => {
43 const newLogs = callback(prevLogs);
44 return newLogs.length > MAX_NUMBER_OF_LOGS
45 ? newLogs.slice(-MAX_NUMBER_OF_LOGS)
46 : newLogs;
47 });
48 },
49 [],
50 );
51
52 const { readyState } = useWebSocket(
53 node?.id ? getWebsocketUrl(node.id, backend) : "",
54 {
55 onMessage: (e: any) => {
56 updateLogs((prevLogs) => [...prevLogs, e.data]);
57 },
58 shouldReconnect: () => true,
59 reconnectAttempts: 10,
60 reconnectInterval: 1000,
61 },
62 );
63
64 useEffect(() => {
65 if (logsDiv.current && scrollShouldStayOnEnd.current) {
66 logsDiv.current.scrollTop = logsDiv.current.scrollHeight;
67 }
68 }, [logs]);
69
70 useEffect(() => {
71 return () => {
72 // Cleanup
73 setLogs([]);
74 };
75 }, []);
76
77 const status = getStatus(readyState.toString());
78
79 return { status, readyState, logs, logsDiv };
80};