Update form report summary fully tested

This commit is contained in:
nguyentrungthat 2026-02-04 14:45:09 +07:00
parent 63b264304e
commit 4369d722ef
6 changed files with 361 additions and 63 deletions

View File

@ -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 = `<table cellpadding="6" cellspacing="0" border="1" style="margin-top: 10px; border-collapse: collapse; width: 100%">
<tr>
<td style="width: 50%; text-align: center;">DPELP</td>
<td style="text-align: center;">Physical Test</td>
</tr>
<tr>
<td style="width: 50%;">
Model : <b>${this.config?.inventory?.pid ?? 'N/A'}</b><br/>
Serial Number : <b>${this.config?.inventory?.sn ?? 'N/A'}</b><br/>
MAC : <b>${dataShowVersion?.MAC_ADDRESS ?? ''}</b><br/>
IOS : <b>${dataShowVersion?.SOFTWARE_IMAGE ?? ''}</b><br/>
License : <b>${
dataShowLic
? dataShowLic
?.filter((el) => el.LICENSE_TYPE === 'Permanent')
?.map((v) => v.FEATURE)
?.join(', ')
: ''
}</b><br/>
Summary : <b>${this.config?.latestScenario?.detectAI?.summary || ''}</b><br/>
Issue : <b>${issue?.length ? `<br>- ` + issue.join(`<br>- `) : 'No issues detected.'}</b><br/>
</td>
<td>
Total Ports : ${portPhysical?.length}<br/>
Ports Tested (UP) : <b style="color: #008000;">${portPhysical.filter((p) => p.tested).length}</b><br/>
Ports Missing : <b style="color: #ff0000;">${portPhysical.filter((p) => !p.tested).length}</b><br/>
</td>
</tr>
</table>`
await sendMessageToMail(
`[ATC] - [${this.config.stationName} - Line: ${this.config.lineNumber}] - Summary Fully Tested`,
body
)
}
}

View File

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

View File

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

View File

@ -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<boolean>(false);
const [openDrawerScenario, setOpenDrawerScenario] = useState<boolean>(false);
const [isPhysicalTest, setIsPhysicalTest] = useState<boolean>(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={
<Flex align={"center"} w={"400px"} justify={"space-between"}>
<Box
style={{
display: "flex",
// justifyContent: "center",
// width: "400px",
marginRight: "12px",
<Flex justify={"space-between"} align={"center"} w={"100%"}>
<Flex w={"30vw"}>
<Flex align={"center"} w={"400px"} justify={"space-between"}>
<Box
style={{
display: "flex",
// justifyContent: "center",
// width: "400px",
marginRight: "12px",
}}
>
<div
style={{
alignItems: "center",
fontSize: "13px",
color: "red",
display: "flex",
}}
>
{line?.userOpenCLI
? (line?.userOpenCLI === user?.userName
? "You are"
: line?.userOpenCLI + " is") + " using"
: "Terminal is used"}
</div>
</Box>
<Flex>
{line?.connecting && line?.status !== "connected" && (
<motion.div
style={{ fontSize: "12px", color: "red" }}
animate={{ opacity: [0.2, 1, 0.2] }}
transition={{
duration: 1.2,
repeat: Infinity,
ease: "easeInOut",
}}
>
connecting...
</motion.div>
)}
{line?.runningScenario && (
<motion.div
style={{ fontSize: "12px", color: "red" }}
animate={{ opacity: [0.2, 1, 0.2] }}
transition={{
duration: 1.2,
repeat: Infinity,
ease: "easeInOut",
}}
>
Running {line?.runningScenario}
</motion.div>
)}
</Flex>
</Flex>
</Flex>
<Stepper
w={600}
size="xs"
color={isDisable ? "gray" : "cyan"}
active={activeStep}
onStepClick={(step) => {
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,
});
}
}}
>
<div
style={{
alignItems: "center",
fontSize: "13px",
color: "red",
display: "flex",
}}
>
{line?.userOpenCLI
? (line?.userOpenCLI === user?.userName
? "You are"
: line?.userOpenCLI + " is") + " using"
: "Terminal is used"}
</div>
</Box>
<Flex>
{line?.connecting && line?.status !== "connected" && (
<motion.div
style={{ fontSize: "12px", color: "red" }}
animate={{ opacity: [0.2, 1, 0.2] }}
transition={{
duration: 1.2,
repeat: Infinity,
ease: "easeInOut",
}}
>
connecting...
</motion.div>
)}
{line?.runningScenario && (
<motion.div
style={{ fontSize: "12px", color: "red" }}
animate={{ opacity: [0.2, 1, 0.2] }}
transition={{
duration: 1.2,
repeat: Infinity,
ease: "easeInOut",
}}
>
Running {line?.runningScenario}
</motion.div>
)}
</Flex>
<Stepper.Step
disabled={isDisable}
label="DPELP"
description="Run DPELP Test"
></Stepper.Step>
<Stepper.Step
disabled={isDisable}
label="Physical Test"
description="Test physical ports"
></Stepper.Step>
<Stepper.Step
disabled={isDisable}
label="Completed"
description="Click to send report"
></Stepper.Step>
</Stepper>
<Flex></Flex>
</Flex>
}
>
<Grid>
<Grid.Col span={3}>
<Tabs defaultValue="general" h={"100%"}>
<Tabs
defaultValue="general"
h={"100%"}
value={activeTabs}
onChange={(value) => setActiveTabs(value || "general")}
>
<Tabs.List>
<Tabs.Tab w={"50%"} value="general">
<IconInfoCircle

View File

@ -89,3 +89,110 @@ export function bytesToMB(bytes: number, decimal = 2): number {
export function bytesToKB(bytes: number, decimal = 2): number {
return Number((bytes / 1024).toFixed(decimal));
}
export const bodyDPELP = [
{
expect: "",
send: "",
delay: "1",
repeat: "1",
note: "",
},
{
expect: "",
send: " no",
delay: "1",
repeat: "1",
note: "",
},
{
expect: "",
send: "\r\n",
delay: "1",
repeat: "1",
note: "",
},
{
expect: "",
send: "\r\n",
delay: "2",
repeat: "1",
note: "",
},
// {
// expect: "",
// send: " terminal length 0",
// delay: "3",
// repeat: "1",
// note: "",
// },
{
expect: "",
send: "enable",
delay: "3",
repeat: "1",
note: "",
},
{
expect: "",
send: "show inventory",
delay: "3",
repeat: "1",
note: "",
},
{
expect: "",
send: "show version | include License",
delay: "1",
repeat: "1",
note: "",
},
{
expect: "",
send: "show version",
delay: "1",
repeat: "1",
note: "",
},
{
expect: "",
send: "show diag",
delay: "3",
repeat: "1",
note: "",
},
{
expect: "",
send: "show post",
delay: "3",
repeat: "1",
note: "",
},
{
expect: "",
send: "show env all",
delay: "3",
repeat: "1",
note: "",
},
{
expect: "",
send: "show license",
delay: "3",
repeat: "1",
note: "",
},
{
expect: "",
send: "show log",
delay: "3",
repeat: "1",
note: "",
},
{
expect: "",
send: "show platform",
delay: "3",
repeat: "1",
note: "",
},
];

View File

@ -107,6 +107,7 @@ export type TLine = {
loadingClearTerminal?: boolean;
runningPhysical?: boolean;
listPortsPhysical?: string[];
listFeatureTested?: string[];
};
export type TUser = {