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[] ports: string[]
runningScenario: string runningScenario: string
runningPhysical: boolean runningPhysical: boolean
listFeatureTested: string[]
// history: string // history: string
} }
@ -133,8 +134,6 @@ export default class LineConnection {
private outputPhysicalTest: string private outputPhysicalTest: string
private outputLoadIosLicense: string | boolean private outputLoadIosLicense: string | boolean
private listDeviceIos: string[] private listDeviceIos: string[]
private debounceTimer: NodeJS.Timeout | null = null
private testingPortPoE: boolean
constructor(config: LineConfig, socketIO: any, handleClearLine: () => void) { constructor(config: LineConfig, socketIO: any, handleClearLine: () => void) {
this.config = config this.config = config
@ -164,8 +163,6 @@ export default class LineConnection {
this.outputPhysicalTest = '' this.outputPhysicalTest = ''
this.outputLoadIosLicense = '' this.outputLoadIosLicense = ''
this.listDeviceIos = [] this.listDeviceIos = []
this.debounceTimer = null
this.testingPortPoE = false
} }
/** /**
* Connect to line with socket * Connect to line with socket
@ -193,6 +190,8 @@ export default class LineConnection {
lineNumber, lineNumber,
status: 'connected', status: 'connected',
}) })
this.config.listFeatureTested = []
this.sendFeatureTested()
this.checkLog() this.checkLog()
resolve() resolve()
}, 1000) }, 1000)
@ -570,7 +569,10 @@ export default class LineConnection {
`DPELP DATA line ${this.config.lineNumber} of ${this.config.stationName}:`, `DPELP DATA line ${this.config.lineNumber} of ${this.config.stationName}:`,
this.dataDPELP this.dataDPELP
) )
this.config.listFeatureTested = [
...new Set([...this.config.listFeatureTested, 'DPELP']),
]
this.sendFeatureTested()
// } // }
if (this.config.latestScenario) if (this.config.latestScenario)
this.config.latestScenario = { ...this.config.latestScenario, detectAI: detectLog } this.config.latestScenario = { ...this.config.latestScenario, detectAI: detectLog }
@ -1088,7 +1090,6 @@ export default class LineConnection {
this.config.runningPhysical = true this.config.runningPhysical = true
this.config.runningScenario = 'Physical Test' this.config.runningScenario = 'Physical Test'
const listPorts = await this.getPorts() const listPorts = await this.getPorts()
this.testingPortPoE = true
this.socketIO.emit('running_scenario', { this.socketIO.emit('running_scenario', {
stationId: this.config.stationId, stationId: this.config.stationId,
lineId: this.config.id, lineId: this.config.id,
@ -1107,7 +1108,7 @@ export default class LineConnection {
this.config.inventory this.config.inventory
) )
const interval = setInterval(async () => { const interval = setInterval(async () => {
if (!this.config.runningPhysical) { if (!this.config.runningPhysical || this.config.status !== 'connected') {
clearInterval(interval) clearInterval(interval)
} else { } else {
this.flushLogBuffer() this.flushLogBuffer()
@ -1146,7 +1147,6 @@ export default class LineConnection {
this.physicalTest.resetTestedPorts() this.physicalTest.resetTestedPorts()
this.config.runningPhysical = false this.config.runningPhysical = false
this.config.runningScenario = '' this.config.runningScenario = ''
this.testingPortPoE = false
this.outputBuffer = '' this.outputBuffer = ''
this.outputScenario = '' this.outputScenario = ''
this.outputPhysicalTest = '' this.outputPhysicalTest = ''
@ -1196,6 +1196,8 @@ export default class LineConnection {
* Send report after done physical test * Send report after done physical test
*/ */
async sendReportPhysicalTest() { async sendReportPhysicalTest() {
this.config.listFeatureTested = [...new Set([...this.config.listFeatureTested, 'PHYSICAL'])]
this.sendFeatureTested()
const formReport = this.physicalTest.getFormReport() const formReport = this.physicalTest.getFormReport()
await sendMessageToMail( await sendMessageToMail(
`[ATC] - [${this.config.stationName} - Line: ${this.config.lineNumber}] - Physical Ports Test`, `[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, titleAuto: `[${scenarioName || 'DPELP'}] - ${stationName} - ` + dataFormat,
}) })
} catch (error) { } catch (error) {
console.error(error) console.error('Error sending wiki message:', error)
} }
try { try {
await sendMessageToMail( await sendMessageToMail(
@ -637,7 +637,7 @@ export class WebSocketIo {
tableHTML tableHTML
) )
} catch (error) { } catch (error) {
console.error(error) console.error('Error sending mail:', error)
} }
try { try {
await sendMessageToZulip( await sendMessageToZulip(
@ -648,7 +648,7 @@ export class WebSocketIo {
zulipMess zulipMess
) )
} catch (error) { } catch (error) {
console.error(error) console.error('Error sending zulip message:', error)
} }
} catch (error) { } catch (error) {
console.error(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, () => { socketServer.listen(SOCKET_IO_PORT, () => {
@ -876,6 +893,7 @@ export class WebSocketIo {
runningPhysical: false, runningPhysical: false,
runningScenario: '', runningScenario: '',
latestScenario: latestScenario, latestScenario: latestScenario,
listFeatureTested: [],
}, },
socket, socket,
async () => { 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 // ✅ cleanup on unmount or when socket changes
return () => { return () => {
socket.off("init"); socket.off("init");
@ -459,6 +468,7 @@ function App() {
socket.off("running_scenario"); socket.off("running_scenario");
socket.off("user_clear_terminal"); socket.off("user_clear_terminal");
socket.off("test_port_physical"); socket.off("test_port_physical");
socket.off("feature_tested");
}; };
}, [socket, stations, selectedLine]); }, [socket, stations, selectedLine]);

View File

@ -10,6 +10,7 @@ import {
Modal, Modal,
ScrollArea, ScrollArea,
SimpleGrid, SimpleGrid,
Stepper,
Tabs, Tabs,
Text, Text,
Textarea, Textarea,
@ -53,6 +54,7 @@ import ModalSelectLicense from "./ModalSelectLicense";
import ModalRunScenario from "./ModalRunScenario"; import ModalRunScenario from "./ModalRunScenario";
import DrawerScenario from "./ModalScenario"; import DrawerScenario from "./ModalScenario";
import AutoProgress from "../Components/AutoProgress"; import AutoProgress from "../Components/AutoProgress";
import { bodyDPELP } from "../../untils/helper";
const apiUrl = import.meta.env.VITE_BACKEND_URL; const apiUrl = import.meta.env.VITE_BACKEND_URL;
const INIT_TICKET = { const INIT_TICKET = {
@ -117,11 +119,24 @@ const ModalTerminal = ({
const [openScenarioModal, setOpenScenarioModal] = useState<boolean>(false); const [openScenarioModal, setOpenScenarioModal] = useState<boolean>(false);
const [openDrawerScenario, setOpenDrawerScenario] = useState<boolean>(false); const [openDrawerScenario, setOpenDrawerScenario] = useState<boolean>(false);
const [isPhysicalTest, setIsPhysicalTest] = useState<boolean>(false); const [isPhysicalTest, setIsPhysicalTest] = useState<boolean>(false);
const [activeStep, setActiveStep] = useState(0);
const [activeTabs, setActiveTabs] = useState("general");
useEffect(() => { useEffect(() => {
setIsPhysicalTest(line?.runningPhysical || false); setIsPhysicalTest(line?.runningPhysical || false);
}, [line?.runningPhysical]); }, [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(() => { useEffect(() => {
if (opened && line?.tickets && line?.tickets?.length > 0) { if (opened && line?.tickets && line?.tickets?.length > 0) {
const data = line?.tickets[0]; const data = line?.tickets[0];
@ -131,6 +146,7 @@ const ModalTerminal = ({
setLatestTicket(INIT_TICKET); setLatestTicket(INIT_TICKET);
setDataTicket(INIT_TICKET); setDataTicket(INIT_TICKET);
setValueBaud(""); setValueBaud("");
setActiveStep(0);
} }
}, [opened, line?.tickets]); }, [opened, line?.tickets]);
@ -520,70 +536,154 @@ const ModalTerminal = ({
) )
socket?.emit("close_cli", { socket?.emit("close_cli", {
lineId: line?.id, lineId: line?.id,
stationId: line?.station_id, stationId: line?.station_id || line?.stationId,
}); });
}} }}
size={"100%"} size={"100%"}
style={{ position: "absolute", left: 0 }} style={{ position: "absolute", left: 0 }}
title={ title={
<Flex align={"center"} w={"400px"} justify={"space-between"}> <Flex justify={"space-between"} align={"center"} w={"100%"}>
<Box <Flex w={"30vw"}>
style={{ <Flex align={"center"} w={"400px"} justify={"space-between"}>
display: "flex", <Box
// justifyContent: "center", style={{
// width: "400px", display: "flex",
marginRight: "12px", // 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 <Stepper.Step
style={{ disabled={isDisable}
alignItems: "center", label="DPELP"
fontSize: "13px", description="Run DPELP Test"
color: "red", ></Stepper.Step>
display: "flex", <Stepper.Step
}} disabled={isDisable}
> label="Physical Test"
{line?.userOpenCLI description="Test physical ports"
? (line?.userOpenCLI === user?.userName ></Stepper.Step>
? "You are" <Stepper.Step
: line?.userOpenCLI + " is") + " using" disabled={isDisable}
: "Terminal is used"} label="Completed"
</div> description="Click to send report"
</Box> ></Stepper.Step>
<Flex> </Stepper>
{line?.connecting && line?.status !== "connected" && ( <Flex></Flex>
<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>
} }
> >
<Grid> <Grid>
<Grid.Col span={3}> <Grid.Col span={3}>
<Tabs defaultValue="general" h={"100%"}> <Tabs
defaultValue="general"
h={"100%"}
value={activeTabs}
onChange={(value) => setActiveTabs(value || "general")}
>
<Tabs.List> <Tabs.List>
<Tabs.Tab w={"50%"} value="general"> <Tabs.Tab w={"50%"} value="general">
<IconInfoCircle <IconInfoCircle

View File

@ -89,3 +89,110 @@ export function bytesToMB(bytes: number, decimal = 2): number {
export function bytesToKB(bytes: number, decimal = 2): number { export function bytesToKB(bytes: number, decimal = 2): number {
return Number((bytes / 1024).toFixed(decimal)); 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; loadingClearTerminal?: boolean;
runningPhysical?: boolean; runningPhysical?: boolean;
listPortsPhysical?: string[]; listPortsPhysical?: string[];
listFeatureTested?: string[];
}; };
export type TUser = { export type TUser = {