From 36d74380551310b3f3b46ad7718d1fbe75c14f65 Mon Sep 17 00:00:00 2001 From: nguyentrungthat <80239428+nguentrungthat@users.noreply.github.com> Date: Tue, 25 Nov 2025 11:32:07 +0700 Subject: [PATCH] Refactor log handling and improve output buffering Updated log file naming to include station name and IP, and refactored appendLog to use new format. Enhanced frontend output buffering for lines to reduce UI update frequency. Improved connection retry logic for switch connections and adjusted socket.io provider timeouts. Updated DrawerLogs format description and removed unnecessary state changes in TerminalXTerm. --- BACKEND/app/services/line_connection.ts | 55 ++++++++++------ BACKEND/app/services/switch_connection.ts | 15 ++++- BACKEND/app/ultils/helper.ts | 14 ++++- BACKEND/providers/socket_io_provider.ts | 15 ++--- FRONTEND/src/App.tsx | 76 ++++++++++++++++++++--- FRONTEND/src/components/DrawerLogs.tsx | 3 +- FRONTEND/src/components/TerminalXTerm.tsx | 2 - 7 files changed, 140 insertions(+), 40 deletions(-) diff --git a/BACKEND/app/services/line_connection.ts b/BACKEND/app/services/line_connection.ts index 24a7cef..8be4ea5 100644 --- a/BACKEND/app/services/line_connection.ts +++ b/BACKEND/app/services/line_connection.ts @@ -17,6 +17,8 @@ interface LineConfig { lineNumber: number ip: string stationId: number + stationName: string + stationIp: string apcName?: string outlet: number output: string @@ -36,8 +38,17 @@ interface LineConfig { textfsm: string }[] commands: string[] + // history: string } +/** HISTORY + * PID + * SN + * VID + * Timestamp + * Scenario + */ + interface User { userEmail: string userName: string @@ -81,7 +92,7 @@ export default class LineConnection { if (resolvedOrRejected) return resolvedOrRejected = true - console.log(`✅ Connected to line ${lineNumber} (${ip}:${port})`) + console.log(`[${Date.now()}] ✅ Connected to line ${lineNumber} (${ip}:${port})`) this.connecting = true setTimeout(() => { this.config.status = 'connected' @@ -135,8 +146,9 @@ export default class LineConnection { appendLog( cleanData(message), this.config.stationId, - this.config.lineNumber, - this.config.port + this.config.stationName, + this.config.stationIp, + this.config.lineNumber ) }) @@ -154,7 +166,7 @@ export default class LineConnection { }) this.client.on('close', () => { - console.log(`🔌 Line ${lineNumber} disconnected`) + console.log(`[${Date.now()}] 🔌 Line ${lineNumber} disconnected`) this.config.status = 'disconnected' // this.config.inventory = undefined this.socketIO.emit('line_disconnected', { @@ -178,8 +190,9 @@ export default class LineConnection { appendLog( cleanData(message), this.config.stationId, - this.config.lineNumber, - this.config.port + this.config.stationName, + this.config.stationIp, + this.config.lineNumber ) console.log(`⏳ Connection timeout line ${lineNumber}`) this.client.destroy() @@ -257,8 +270,9 @@ export default class LineConnection { appendLog( `\n\n---start-scenarios---${now}---${userName}---\n---scenario---${script?.title}---${now}---\n`, this.config.stationId, - this.config.lineNumber, - this.config.port + this.config.stationName, + this.config.stationIp, + this.config.lineNumber ) this.config.latestScenario = { name: script?.title, @@ -282,8 +296,9 @@ export default class LineConnection { appendLog( `\n---end-scenarios---${now}---${userName}---\n`, this.config.stationId, - this.config.lineNumber, - this.config.port + this.config.stationName, + this.config.stationIp, + this.config.lineNumber ) // reject(new Error('Script timeout')) }, script.timeout || 300000) @@ -303,8 +318,9 @@ export default class LineConnection { appendLog( `\n---end-scenarios---${now}---${userName}---\n`, this.config.stationId, - this.config.lineNumber, - this.config.port + this.config.stationName, + this.config.stationIp, + this.config.lineNumber ) const logScenarios = getLogWithTimeScenario(this.outputScenario, now) || '' @@ -342,8 +358,9 @@ export default class LineConnection { appendLog( `\n---send-command---"${step?.send ?? ''}"---${now}---\n`, this.config.stationId, - this.config.lineNumber, - this.config.port + this.config.stationName, + this.config.stationIp, + this.config.lineNumber ) let repeatCount = Number(step.repeat) || 1 const sendCommand = async () => { @@ -396,8 +413,9 @@ export default class LineConnection { appendLog( `\n-------${user.userName}-------\n`, this.config.stationId, - this.config.lineNumber, - this.config.port + this.config.stationName, + this.config.stationIp, + this.config.lineNumber ) } @@ -457,8 +475,9 @@ export default class LineConnection { appendLog( cleanData(data), this.config.stationId, - this.config.lineNumber, - this.config.port + this.config.stationName, + this.config.stationIp, + this.config.lineNumber ) }, }) diff --git a/BACKEND/app/services/switch_connection.ts b/BACKEND/app/services/switch_connection.ts index 8f5e4af..a136145 100644 --- a/BACKEND/app/services/switch_connection.ts +++ b/BACKEND/app/services/switch_connection.ts @@ -33,6 +33,7 @@ export default class SwitchController { public ports: PortInfo[] public portGroups: PortInfo[][] private isEnable: boolean + private retryConnect: number constructor({ host, port = 23, username, password, onData }: SwitchControllerOptions) { this.host = host @@ -47,6 +48,7 @@ export default class SwitchController { this.ports = [] this.portGroups = [] this.isEnable = false + this.retryConnect = 0 } private sleep(ms: number) { @@ -65,10 +67,17 @@ export default class SwitchController { } } - private _handleClose() { + private async _handleClose(err: boolean) { + console.log('[SWITCH CONNECTION CLOSE]', err) this.status = 'DISCONNECTED' this.isEnable = false this.onData(this.portGroups, this.status) + if (this.retryConnect <= 5) { + await this.sleep(15000) + console.log('Retry connect times', this.retryConnect) + this.retryConnect += 1 + await this.reconnect() + } } private _handleError(err: Error & { code?: string }) { @@ -113,8 +122,8 @@ export default class SwitchController { this.socket.on('data', (data) => this._handleData(data.toString())) resolve() }) - this.socket.on('close', () => { - this._handleClose() + this.socket.on('close', (e) => { + this._handleClose(e) resolve() }) this.socket.on('error', (err) => { diff --git a/BACKEND/app/ultils/helper.ts b/BACKEND/app/ultils/helper.ts index f32463c..cebae53 100644 --- a/BACKEND/app/ultils/helper.ts +++ b/BACKEND/app/ultils/helper.ts @@ -31,10 +31,20 @@ export function sleep(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)) } -export function appendLog(output: string, stationId: number, lineNumber: number, port: number) { +// 20250527-AUTO-Session.Station_1-13-192.168.171.9-2.log +// {DATE}-AUTO-Session.{Station name}-{Station ID}-{Station IP}-{Line number}.log +export function appendLog( + output: string, + stationId: number, + stationName: string, + stationIP: string, + lineNumber: number +) { const date = new Date().toISOString().slice(0, 10).replace(/-/g, '') // YYYYMMDD const logDir = path.join('storage', 'system_logs') - const logFile = path.join(logDir, `${date}-Station_${stationId}-Line_${lineNumber}_${port}.log`) + const logFile = path + .join(logDir, `${date}-AUTO-Session.${stationName}-${stationId}-${stationIP}-${lineNumber}.log`) + .replaceAll(' ', '_') // Ensure folder exists if (!fs.existsSync(logDir)) { diff --git a/BACKEND/providers/socket_io_provider.ts b/BACKEND/providers/socket_io_provider.ts index 4cdbea7..e46372b 100644 --- a/BACKEND/providers/socket_io_provider.ts +++ b/BACKEND/providers/socket_io_provider.ts @@ -148,7 +148,7 @@ export class WebSocketIo { lineIds, async (line) => command === 'spam_break' ? line.breakSpam() : line.writeCommand(command, userName), - { command, timeout: 120000 } + { command } ) }) @@ -162,7 +162,6 @@ export class WebSocketIo { async (line) => line.runScript(scenario, userName), { scenario, - timeout: scenario?.timeout ? Number(scenario.timeout) + 120000 : 300000, } ) }) @@ -203,7 +202,7 @@ export class WebSocketIo { stationId, [lineId], async (lineCon) => lineCon.writeCommand('\r\n', userName), - { command: '\r\n', timeout: 120000 } + { command: '\r\n' } ) } else { if (this.lineConnecting.includes(lineId)) return @@ -322,7 +321,7 @@ export class WebSocketIo { .andWhere('outlet', outletNumber) if (lines.length > 0) { const line = this.lineMap.get(lines[0].id) - if (line) this.setTimeoutConnect(lines[0].id, line, 300000) + if (line) this.setTimeoutConnect(lines[0].id, line) } const apcIp = (station as any)[`${apcName}_ip`] as string @@ -508,6 +507,8 @@ export class WebSocketIo { ip: station.ip, lineNumber: line.lineNumber, stationId: station.id, + stationName: station.name, + stationIp: station.ip, apcName: line.apcName, outlet: line.outlet, baud: line.baud, @@ -542,7 +543,7 @@ export class WebSocketIo { private setTimeoutConnect = ( lineId: number, lineConn: LineConnection | SwitchController, - timeout = 120000 + timeout = 28800000 // 8h = 8*60*60*1000 ) => { if (this.intervalMap[`${lineId}`]) { clearInterval(this.intervalMap[`${lineId}`]) @@ -576,7 +577,7 @@ export class WebSocketIo { // console.log(line?.config) if (line && line.config.status === 'connected') { this.lineConnecting = this.lineConnecting.filter((el) => el !== lineId) - this.setTimeoutConnect(lineId, line, options.timeout) + this.setTimeoutConnect(lineId, line) // await sleep(500) await action(line, options) } else { @@ -598,7 +599,7 @@ export class WebSocketIo { const lineReconnect = this.lineMap.get(lineId) if (lineReconnect) { - this.setTimeoutConnect(lineId, lineReconnect, options.timeout) + this.setTimeoutConnect(lineId, lineReconnect) await sleep(100) await action(lineReconnect, options) } diff --git a/FRONTEND/src/App.tsx b/FRONTEND/src/App.tsx index 8c1391b..010786d 100644 --- a/FRONTEND/src/App.tsx +++ b/FRONTEND/src/App.tsx @@ -4,7 +4,14 @@ import "@mantine/notifications/styles.css"; import "./App.css"; import classes from "./App.module.css"; -import { Suspense, useCallback, useEffect, useMemo, useState } from "react"; +import { + Suspense, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; import { Tabs, Text, @@ -79,6 +86,8 @@ function App() { const [isLogModalOpen, setIsLogModalOpen] = useState(false); const [expandedBottomBar, setExpandedBottomBar] = useState(true); const [activeTabBottom, setActiveTabBottom] = useState("command"); + const lineBuffersRef = useRef(new Map()); + const flushScheduledRef = useRef(false); const connectApcSwitch = (station: TStation) => { if (station?.apc_1_ip && station?.apc_1_port) { @@ -165,11 +174,20 @@ function App() { ); socket?.on("line_output", (data) => { - updateValueLineStation( - data?.lineId, - { netOutput: data.data, commands: data.commands || [] }, - data?.stationId - ); + const { lineId, data: text } = data; + // updateValueLineStation( + // data?.lineId, + // { netOutput: data.data, commands: data.commands || [] }, + // data?.stationId + // ); + + const buf = lineBuffersRef.current.get(lineId) || ""; + lineBuffersRef.current.set(lineId, buf + text); + + if (!flushScheduledRef.current) { + flushScheduledRef.current = true; + setTimeout(() => flushBuffers(), 50); + } }); socket?.on("line_error", (data) => { @@ -323,6 +341,28 @@ function App() { }; }, [socket, stations, selectedLine]); + const flushBuffers = useCallback(() => { + setStations((prev) => + prev.map((station) => ({ + ...station, + lines: station.lines.map((line) => { + const buffered = lineBuffersRef.current.get(line.id || 0); + if (!buffered) return line; // không có update + updateValueSelectedLine(line?.id || 0, { netOutput: buffered }); + return { + ...line, + netOutput: (line.netOutput || "") + buffered, + output: buffered, + loadingOutput: line.loadingOutput ? false : true, + }; + }), + })) + ); + // clear + lineBuffersRef.current.clear(); + flushScheduledRef.current = false; + }, []); + const updateValueLineStation = useCallback( (lineId: number, updates: Partial, stationId?: number) => { setStations((prevStations) => @@ -374,6 +414,29 @@ function App() { [] ); + const updateValueSelectedLine = useCallback( + (lineId: number, updates: Partial) => { + // Update selectedLine nếu nó đang được chọn + setSelectedLine((prevSelected) => { + if (!prevSelected || prevSelected.id !== lineId) return prevSelected; + + const isNetOutput = typeof updates?.netOutput !== "undefined"; + + return { + ...prevSelected, + ...updates, + ...(isNetOutput && { + netOutput: + (prevSelected.netOutput || "") + (updates.netOutput || ""), + output: updates.netOutput, + loadingOutput: prevSelected.loadingOutput ? false : true, + }), + }; + }); + }, + [] + ); + // const getLine = (lineId: number, stationId: number) => { // const station = stations?.find((sta) => sta.id === stationId); // if (station) { @@ -530,7 +593,6 @@ function App() { onChange={(id) => { if (selectedLines.length > 0) { selectedLines.forEach((el) => { - console.log(el?.userOpenCLI, user?.userName); if (el?.userOpenCLI === user?.userName) socket?.emit("close_cli", { lineId: el?.id, diff --git a/FRONTEND/src/components/DrawerLogs.tsx b/FRONTEND/src/components/DrawerLogs.tsx index cfcab39..1f64120 100644 --- a/FRONTEND/src/components/DrawerLogs.tsx +++ b/FRONTEND/src/components/DrawerLogs.tsx @@ -98,7 +98,8 @@ function DrawerLogs({
Format: - YYYYMMDD-Station_{`{id}`}-Line_{`{number}`}_{`{port}`} + YYYYMMDD-AUTO-Session.{`{Station name}`}-{`{Station ID}`}- + {`{Station IP}`}-{`{Line number}`} .log
diff --git a/FRONTEND/src/components/TerminalXTerm.tsx b/FRONTEND/src/components/TerminalXTerm.tsx index e060c43..0c7dacf 100644 --- a/FRONTEND/src/components/TerminalXTerm.tsx +++ b/FRONTEND/src/components/TerminalXTerm.tsx @@ -146,8 +146,6 @@ const TerminalCLI: React.FC = ({ setLoading(false); }, 500); if (fitRef.current) fitRef.current?.fit(); - } else { - setIsInit(false); } }, [cliOpened]);