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 = `
+
+| DPELP |
+Physical 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")}
+ >