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.
This commit is contained in:
nguyentrungthat 2025-11-25 11:32:07 +07:00
parent dc47636c96
commit 36d7438055
7 changed files with 140 additions and 40 deletions

View File

@ -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
)
},
})

View File

@ -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) => {

View File

@ -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)) {

View File

@ -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)
}

View File

@ -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<string>("command");
const lineBuffersRef = useRef(new Map<number, string>());
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<TLine>, stationId?: number) => {
setStations((prevStations) =>
@ -374,6 +414,29 @@ function App() {
[]
);
const updateValueSelectedLine = useCallback(
(lineId: number, updates: Partial<TLine>) => {
// 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,

View File

@ -98,7 +98,8 @@ function DrawerLogs({
<div>
Format:
<i style={{ marginLeft: "4px" }}>
YYYYMMDD-Station_{`{id}`}-Line_{`{number}`}_{`{port}`}
YYYYMMDD-AUTO-Session.{`{Station name}`}-{`{Station ID}`}-
{`{Station IP}`}-{`{Line number}`}
.log
</i>
</div>

View File

@ -146,8 +146,6 @@ const TerminalCLI: React.FC<TerminalCLIProps> = ({
setLoading(false);
}, 500);
if (fitRef.current) fitRef.current?.fit();
} else {
setIsInit(false);
}
}, [cliOpened]);