Merge pull request 'Refactor log handling and improve output buffering' (#5) from that into main
Reviewed-on: #5
This commit is contained in:
commit
55954d0bda
|
|
@ -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
|
||||
)
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -146,8 +146,6 @@ const TerminalCLI: React.FC<TerminalCLIProps> = ({
|
|||
setLoading(false);
|
||||
}, 500);
|
||||
if (fitRef.current) fitRef.current?.fit();
|
||||
} else {
|
||||
setIsInit(false);
|
||||
}
|
||||
}, [cliOpened]);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue