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]);