From 4369d722ef582edf3d3df1e377b42e4fd8b12c74 Mon Sep 17 00:00:00 2001 From: nguyentrungthat <80239428+nguentrungthat@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:45:09 +0700 Subject: [PATCH] Update form report summary fully tested --- BACKEND/app/services/line_connection.ts | 78 ++++++- BACKEND/providers/socket_io_provider.ts | 24 ++- FRONTEND/src/App.tsx | 10 + .../src/components/Modal/ModalTerminal.tsx | 204 +++++++++++++----- FRONTEND/src/untils/helper.ts | 107 +++++++++ FRONTEND/src/untils/types.ts | 1 + 6 files changed, 361 insertions(+), 63 deletions(-) diff --git a/BACKEND/app/services/line_connection.ts b/BACKEND/app/services/line_connection.ts index 8e0394d..5674302 100644 --- a/BACKEND/app/services/line_connection.ts +++ b/BACKEND/app/services/line_connection.ts @@ -76,6 +76,7 @@ interface LineConfig { ports: string[] runningScenario: string runningPhysical: boolean + listFeatureTested: string[] // history: string } @@ -133,8 +134,6 @@ export default class LineConnection { private outputPhysicalTest: string private outputLoadIosLicense: string | boolean private listDeviceIos: string[] - private debounceTimer: NodeJS.Timeout | null = null - private testingPortPoE: boolean constructor(config: LineConfig, socketIO: any, handleClearLine: () => void) { this.config = config @@ -164,8 +163,6 @@ export default class LineConnection { this.outputPhysicalTest = '' this.outputLoadIosLicense = '' this.listDeviceIos = [] - this.debounceTimer = null - this.testingPortPoE = false } /** * Connect to line with socket @@ -193,6 +190,8 @@ export default class LineConnection { lineNumber, status: 'connected', }) + this.config.listFeatureTested = [] + this.sendFeatureTested() this.checkLog() resolve() }, 1000) @@ -570,7 +569,10 @@ export default class LineConnection { `DPELP DATA line ${this.config.lineNumber} of ${this.config.stationName}:`, this.dataDPELP ) - + this.config.listFeatureTested = [ + ...new Set([...this.config.listFeatureTested, 'DPELP']), + ] + this.sendFeatureTested() // } if (this.config.latestScenario) this.config.latestScenario = { ...this.config.latestScenario, detectAI: detectLog } @@ -1088,7 +1090,6 @@ export default class LineConnection { this.config.runningPhysical = true this.config.runningScenario = 'Physical Test' const listPorts = await this.getPorts() - this.testingPortPoE = true this.socketIO.emit('running_scenario', { stationId: this.config.stationId, lineId: this.config.id, @@ -1107,7 +1108,7 @@ export default class LineConnection { this.config.inventory ) const interval = setInterval(async () => { - if (!this.config.runningPhysical) { + if (!this.config.runningPhysical || this.config.status !== 'connected') { clearInterval(interval) } else { this.flushLogBuffer() @@ -1146,7 +1147,6 @@ export default class LineConnection { this.physicalTest.resetTestedPorts() this.config.runningPhysical = false this.config.runningScenario = '' - this.testingPortPoE = false this.outputBuffer = '' this.outputScenario = '' this.outputPhysicalTest = '' @@ -1196,6 +1196,8 @@ export default class LineConnection { * Send report after done physical test */ async sendReportPhysicalTest() { + this.config.listFeatureTested = [...new Set([...this.config.listFeatureTested, 'PHYSICAL'])] + this.sendFeatureTested() const formReport = this.physicalTest.getFormReport() await sendMessageToMail( `[ATC] - [${this.config.stationName} - Line: ${this.config.lineNumber}] - Physical Ports Test`, @@ -1640,4 +1642,64 @@ ${log} } } } + + sendFeatureTested = async () => { + this.socketIO.emit('feature_tested', { + stationId: this.config.stationId, + lineId: this.config.id, + listFeatureTested: this.config.listFeatureTested, + }) + } + + sendReportSummary = async () => { + const portPhysical = Array.from(this.physicalTest.ports.values()) + const showVersion = this.config?.data?.find( + (d) => d.command?.trim()?.includes('show ver') || d.command?.trim()?.includes('sh ver') + ) + const dataShowVersion = + showVersion?.textfsm && showVersion?.textfsm?.[0] + ? showVersion?.textfsm?.[0] + : this.config?.inventory + + const showLicense = this.config?.data?.find( + (d) => d.command?.trim()?.includes('show lic') || d.command?.trim()?.includes('sh lic') + ) + const dataShowLic = + showLicense?.textfsm && Array.isArray(showLicense?.textfsm) ? showLicense?.textfsm : null + const issue = this.config?.latestScenario?.detectAI?.issue || [] + const body = ` + + + + + + + + +
DPELPPhysical Test
+ Model : ${this.config?.inventory?.pid ?? 'N/A'}
+ Serial Number : ${this.config?.inventory?.sn ?? 'N/A'}
+ MAC : ${dataShowVersion?.MAC_ADDRESS ?? ''}
+ IOS : ${dataShowVersion?.SOFTWARE_IMAGE ?? ''}
+ License : ${ + dataShowLic + ? dataShowLic + ?.filter((el) => el.LICENSE_TYPE === 'Permanent') + ?.map((v) => v.FEATURE) + ?.join(', ') + : '' + }
+ Summary : ${this.config?.latestScenario?.detectAI?.summary || ''}
+ Issue : ${issue?.length ? `
- ` + issue.join(`
- `) : 'No issues detected.'}

+
+ Total Ports : ${portPhysical?.length}
+ Ports Tested (UP) : ${portPhysical.filter((p) => p.tested).length}
+ Ports Missing : ${portPhysical.filter((p) => !p.tested).length}
+
` + + await sendMessageToMail( + `[ATC] - [${this.config.stationName} - Line: ${this.config.lineNumber}] - Summary Fully Tested`, + body + ) + } } diff --git a/BACKEND/providers/socket_io_provider.ts b/BACKEND/providers/socket_io_provider.ts index c10bf31..0f7ace0 100644 --- a/BACKEND/providers/socket_io_provider.ts +++ b/BACKEND/providers/socket_io_provider.ts @@ -629,7 +629,7 @@ export class WebSocketIo { titleAuto: `[${scenarioName || 'DPELP'}] - ${stationName} - ` + dataFormat, }) } catch (error) { - console.error(error) + console.error('Error sending wiki message:', error) } try { await sendMessageToMail( @@ -637,7 +637,7 @@ export class WebSocketIo { tableHTML ) } catch (error) { - console.error(error) + console.error('Error sending mail:', error) } try { await sendMessageToZulip( @@ -648,7 +648,7 @@ export class WebSocketIo { zulipMess ) } catch (error) { - console.error(error) + console.error('Error sending zulip message:', error) } } catch (error) { console.error(error) @@ -830,6 +830,23 @@ export class WebSocketIo { {} ) }) + + socket.on('send_summary_report', async (data) => { + const { stationId, lineId } = data + // Check station is active + const activeStation = await checkStationActive(stationId) + if (!activeStation) return + + await this.handleLineOperation( + io, + stationId, + [lineId], + async (lineCon) => { + lineCon.sendReportSummary() + }, + {} + ) + }) }) socketServer.listen(SOCKET_IO_PORT, () => { @@ -876,6 +893,7 @@ export class WebSocketIo { runningPhysical: false, runningScenario: '', latestScenario: latestScenario, + listFeatureTested: [], }, socket, async () => { diff --git a/FRONTEND/src/App.tsx b/FRONTEND/src/App.tsx index 024fadd..46ddf42 100644 --- a/FRONTEND/src/App.tsx +++ b/FRONTEND/src/App.tsx @@ -441,6 +441,15 @@ function App() { ); }); + socket?.on("feature_tested", (data) => { + if (data?.listFeatureTested && data?.listFeatureTested.length > 0) + updateValueLineStation( + data?.lineId, + { listFeatureTested: data?.listFeatureTested }, + data?.stationId + ); + }); + // ✅ cleanup on unmount or when socket changes return () => { socket.off("init"); @@ -459,6 +468,7 @@ function App() { socket.off("running_scenario"); socket.off("user_clear_terminal"); socket.off("test_port_physical"); + socket.off("feature_tested"); }; }, [socket, stations, selectedLine]); diff --git a/FRONTEND/src/components/Modal/ModalTerminal.tsx b/FRONTEND/src/components/Modal/ModalTerminal.tsx index 76f3b83..cd2a75f 100644 --- a/FRONTEND/src/components/Modal/ModalTerminal.tsx +++ b/FRONTEND/src/components/Modal/ModalTerminal.tsx @@ -10,6 +10,7 @@ import { Modal, ScrollArea, SimpleGrid, + Stepper, Tabs, Text, Textarea, @@ -53,6 +54,7 @@ import ModalSelectLicense from "./ModalSelectLicense"; import ModalRunScenario from "./ModalRunScenario"; import DrawerScenario from "./ModalScenario"; import AutoProgress from "../Components/AutoProgress"; +import { bodyDPELP } from "../../untils/helper"; const apiUrl = import.meta.env.VITE_BACKEND_URL; const INIT_TICKET = { @@ -117,11 +119,24 @@ const ModalTerminal = ({ const [openScenarioModal, setOpenScenarioModal] = useState(false); const [openDrawerScenario, setOpenDrawerScenario] = useState(false); const [isPhysicalTest, setIsPhysicalTest] = useState(false); + const [activeStep, setActiveStep] = useState(0); + const [activeTabs, setActiveTabs] = useState("general"); useEffect(() => { setIsPhysicalTest(line?.runningPhysical || false); }, [line?.runningPhysical]); + useEffect(() => { + if (line?.listFeatureTested && line?.listFeatureTested.length > 0) { + if (line?.listFeatureTested?.includes("DPELP")) setActiveStep(1); + if ( + line?.listFeatureTested?.includes("PHYSICAL") && + line?.listFeatureTested?.includes("DPELP") + ) + setActiveStep(2); + } else setActiveStep(0); + }, [opened, line?.listFeatureTested]); + useEffect(() => { if (opened && line?.tickets && line?.tickets?.length > 0) { const data = line?.tickets[0]; @@ -131,6 +146,7 @@ const ModalTerminal = ({ setLatestTicket(INIT_TICKET); setDataTicket(INIT_TICKET); setValueBaud(""); + setActiveStep(0); } }, [opened, line?.tickets]); @@ -520,70 +536,154 @@ const ModalTerminal = ({ ) socket?.emit("close_cli", { lineId: line?.id, - stationId: line?.station_id, + stationId: line?.station_id || line?.stationId, }); }} size={"100%"} style={{ position: "absolute", left: 0 }} title={ - - + + + +
+ {line?.userOpenCLI + ? (line?.userOpenCLI === user?.userName + ? "You are" + : line?.userOpenCLI + " is") + " using" + : "Terminal is used"} +
+
+ + {line?.connecting && line?.status !== "connected" && ( + + connecting... + + )} + {line?.runningScenario && ( + + Running {line?.runningScenario} + + )} + +
+
+ { + if (!line) return; + if (step === 0) { + const dpelp = scenarios?.find( + (el) => el.title.toUpperCase() === "DPELP" + ); + socket?.emit( + "run_scenario", + Object.assign(line, { + scenario: dpelp + ? dpelp + : { + id: 0, + is_reboot: 0, + title: "DPELP", + timeout: 360000, + body: JSON.stringify(bodyDPELP), + }, + }) + ); + socket?.emit("run_all_dpelp", { + lineIds: [line?.id], + stationName: stationItem?.name, + stationId: Number(stationItem?.id), + }); + setIsDisable(true); + setTimeout(() => { + setIsDisable(false); + }, 10000); + } + if (step === 1) { + setActiveTabs("physical"); + socket?.emit("run_physical_test", { + lineId: line?.id, + stationId: Number(stationItem?.id), + }); + setIsDisable(true); + setTimeout(() => { + setIsDisable(false); + }, 15000); + } + if (step === 2 && !isDisable) { + setIsDisable(true); + setTimeout(() => { + setIsDisable(false); + }, 5000); + socket?.emit("send_summary_report", { + lineId: line?.id, + stationId: line?.station_id || line?.stationId, + }); + } }} > -
- {line?.userOpenCLI - ? (line?.userOpenCLI === user?.userName - ? "You are" - : line?.userOpenCLI + " is") + " using" - : "Terminal is used"} -
-
- - {line?.connecting && line?.status !== "connected" && ( - - connecting... - - )} - {line?.runningScenario && ( - - Running {line?.runningScenario} - - )} - + + + + +
} > - + setActiveTabs(value || "general")} + >