1import { NodeType } from "@marzneshin/features/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) => {
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
27 return (
28 (baseURL.protocol === 'https:' ? 'wss://' : 'ws://') +
29 joinPaths([baseURL.host + baseURL.pathname, `/nodes/${nodeId}/logs`]) +
30 '?interval=1&token=' +
31 useAuth.getState().getAuthToken()
32 );
33 } catch (e) {
34 console.error('Unable to generate websocket url');
35 console.error(e);
36 return null;
37 }
38};
39
40
41export const useNodesLog = (node: NodeType) => {
42 const [logs, setLogs] = useState<string[]>([]);
43 const logsDiv = useRef<HTMLDivElement | null>(null);
44 const scrollShouldStayOnEnd = useRef(true);
45
46 const updateLogs = useCallback((callback: (prevLogs: string[]) => string[]) => {
47 setLogs(prevLogs => {
48 const newLogs = callback(prevLogs);
49 return newLogs.length > MAX_NUMBER_OF_LOGS ? newLogs.slice(-MAX_NUMBER_OF_LOGS) : newLogs;
50 });
51 }, []);
52
53 const { readyState } = useWebSocket(node?.id ? getWebsocketUrl(node.id) : '', {
54 onMessage: (e: any) => {
55 updateLogs(prevLogs => [...prevLogs, e.data]);
56 },
57 shouldReconnect: () => true,
58 reconnectAttempts: 10,
59 reconnectInterval: 1000,
60 });
61
62 useEffect(() => {
63 if (logsDiv.current && scrollShouldStayOnEnd.current) {
64 logsDiv.current.scrollTop = logsDiv.current.scrollHeight;
65 }
66 }, [logs]);
67
68 useEffect(() => {
69 return () => {
70 // Cleanup
71 setLogs([]);
72 };
73 }, []);
74
75 const status = getStatus(readyState.toString());
76
77 return { status, readyState, logs, logsDiv };
78};