From c032a29cddef1ed5184f2fca822b728704c52d25 Mon Sep 17 00:00:00 2001 From: nguyentrungthat <80239428+nguentrungthat@users.noreply.github.com> Date: Tue, 10 Mar 2026 15:31:43 +0700 Subject: [PATCH] Update flow physical test va confirm skip test --- BACKEND/app/services/line_connection.ts | 57 ++++- BACKEND/app/services/physical_test_service.ts | 6 +- BACKEND/providers/socket_io_provider.ts | 37 +-- FRONTEND/src/App.tsx | 31 ++- FRONTEND/src/components/BottomToolBar.tsx | 116 ++-------- FRONTEND/src/components/CardLine.tsx | 66 ++++-- .../Modal/ModalConfirmSkipTestPort.tsx | 161 +++++++++++++ .../src/components/Modal/ModalTerminal.tsx | 218 ++++++++++-------- FRONTEND/src/untils/types.ts | 2 + 9 files changed, 462 insertions(+), 232 deletions(-) create mode 100644 FRONTEND/src/components/Modal/ModalConfirmSkipTestPort.tsx diff --git a/BACKEND/app/services/line_connection.ts b/BACKEND/app/services/line_connection.ts index 8cb9e3e..4bd9cdc 100644 --- a/BACKEND/app/services/line_connection.ts +++ b/BACKEND/app/services/line_connection.ts @@ -28,7 +28,7 @@ import path from 'node:path' import axios from 'axios' import redis from '@adonisjs/redis/services/main' import Line from '#models/line' -import { ErrorRow, TestResult } from '../ultils/types.js' +import { CustomSocket, ErrorRow, TestResult } from '../ultils/types.js' import momentTZ from 'moment-timezone' import { PhysicalPortTest } from './physical_test_service.js' import Station from '#models/station' @@ -79,6 +79,8 @@ interface LineConfig { runningPhysical: boolean listFeatureTested: string[] isReady: boolean + isSkipPhysical?: boolean + reasonSkipPhysical?: string // history: string } @@ -201,6 +203,8 @@ export default class LineConnection { status: 'connected', }) this.config.listFeatureTested = [] + this.config.isSkipPhysical = false + this.config.reasonSkipPhysical = '' this.sendFeatureTested() this.checkLog() resolve() @@ -296,6 +300,8 @@ export default class LineConnection { this.config.status = 'disconnected' this.config.output += this.config.output + '[CLEAR_TERMINAL_SCROLL_BACK]' this.config.listFeatureTested = [] + this.config.isSkipPhysical = false + this.config.reasonSkipPhysical = '' this.config.latestScenario = undefined this.physicalTest = new PhysicalPortTest([]) this.config.isReady = false @@ -1110,7 +1116,9 @@ ${data.issues?.length ? `- ` + data.issues.join(`\n- `) : ''} *****[Physical]***** Total Ports: ${portPhysical?.length} Ports Tested (Link UP): ${tested.length} (${testedPoE?.length} PoE, ${testedSFP?.length} SFP) -Ports Missing/Down: ${missing.length}\n\n` +Ports Missing/Down: ${missing.length} +${this.config.reasonSkipPhysical ? `***User skip test ports:\n- ${this.config.reasonSkipPhysical}` : ''} +\n` await updateNoteToERP(sn, note) } @@ -1173,6 +1181,8 @@ Ports Missing/Down: ${missing.length}\n\n` }) if (listPorts.length === 0) { this.config.listFeatureTested = [...new Set([...this.config.listFeatureTested, 'PHYSICAL'])] + this.config.isSkipPhysical = true + this.config.reasonSkipPhysical = '' this.sendFeatureTested() console.log('End physical test') this.endTesting() @@ -1301,15 +1311,28 @@ Ports Missing/Down: ${missing.length}\n\n` /** * Send report after done physical test */ - async sendReportPhysicalTest() { + async sendReportPhysicalTest(reason?: string) { this.config.listFeatureTested = [...new Set([...this.config.listFeatureTested, 'PHYSICAL'])] + if (reason) { + this.config.isSkipPhysical = true + this.config.reasonSkipPhysical = reason + } this.sendFeatureTested() // Set timeout send report - this.setTimeoutSendSummaryReport(30000) - const formReport = this.physicalTest.getFormReport() + if ( + this.config.listFeatureTested?.includes('PHYSICAL') && + this.config.listFeatureTested?.includes('DPELP') + ) + this.setTimeoutSendSummaryReport(30000) + const formReport = this.physicalTest.getFormReport(this.config.inventory) + const reasonSkipPhysical = reason + ? `User Skip Test Port
+ ────────────────────────────────
+ ${reason}` + : '' await sendMessageToMail( `[ATC] - [${this.config.stationName} - Line: ${this.config.lineNumber}] - Physical Ports Test`, - formReport + formReport + reasonSkipPhysical ) } @@ -1762,6 +1785,8 @@ ${log} stationId: this.config.stationId, lineId: this.config.id, listFeatureTested: this.config.listFeatureTested, + isSkipPhysical: this.config.isSkipPhysical, + reasonSkipPhysical: this.config.reasonSkipPhysical, }) } @@ -1771,6 +1796,7 @@ ${log} sendReportSummary = async (snapshot?: { snapConfig: LineConfig snapPhysical: PhysicalPortTest + reason: string }) => { if (this.debounceSendSummaryReport) clearTimeout(this.debounceSendSummaryReport) const physicalTest = snapshot?.snapPhysical ? snapshot?.snapPhysical : this.physicalTest @@ -1797,6 +1823,13 @@ ${log} showLicense?.textfsm && Array.isArray(showLicense?.textfsm) ? showLicense?.textfsm : null const issue = config?.latestScenario?.detectAI?.issue || [] const summary = config?.latestScenario?.detectAI?.summary || '' + const reasonSkipPhysical = + snapshot?.reason || this.config.reasonSkipPhysical + ? `
User Skip Test Port
+ ────────────────────────────────
+ ${snapshot?.reason || this.config.reasonSkipPhysical}` + : '' + const body = ` @@ -1842,6 +1875,7 @@ ${log}
${missingSFP.map((p) => physicalTest.normalizePortName(p.name)).join('
')}
` : '' } + ${reasonSkipPhysical}
DPELP
` @@ -1883,16 +1917,21 @@ ${log} this.physicalTest = new PhysicalPortTest([]) } - setTimeoutSendSummaryReport(timeout: number) { + setTimeoutSendSummaryReport(timeout: number, reason?: string) { // Debounce send summary report if (this.debounceSendSummaryReport) clearTimeout(this.debounceSendSummaryReport) // Snapshot toàn bộ data tại thời điểm này const snapshot = { snapConfig: this.config, snapPhysical: this.physicalTest, + reason: reason || '', } this.debounceSendSummaryReport = setTimeout(() => { - this.config.listFeatureTested = ['DPELP', 'PHYSICAL'] + if (!this.config.listFeatureTested?.includes('PHYSICAL')) { + this.config.isSkipPhysical = true + this.config.reasonSkipPhysical = '' + } + this.config.listFeatureTested = ['DPELP', 'PHYSICAL', 'SUMMARY'] this.sendFeatureTested() this.sendReportSummary(snapshot) }, timeout) @@ -1900,6 +1939,8 @@ ${log} resetDPELP() { this.config.listFeatureTested = [] + this.config.isSkipPhysical = false + this.config.reasonSkipPhysical = '' this.dataDPELP = '' this.sendFeatureTested() console.log('Reset DPELP data and features', this.config.id, this.config.listFeatureTested) diff --git a/BACKEND/app/services/physical_test_service.ts b/BACKEND/app/services/physical_test_service.ts index 241536d..db25a9a 100644 --- a/BACKEND/app/services/physical_test_service.ts +++ b/BACKEND/app/services/physical_test_service.ts @@ -127,11 +127,11 @@ export class PhysicalPortTest { console.log('✅ Physical Test DONE') } - getFormReport() { + getFormReport(inventory?: any) { const report: PhysicalTestReport = { device: { - model: this?.inventory?.pid || '', - serial: this?.inventory?.sn || '', + model: this?.inventory?.pid || inventory?.pid || '', + serial: this?.inventory?.sn || inventory?.sn || '', }, startTime: this.startTime, endTime: new Date(), diff --git a/BACKEND/providers/socket_io_provider.ts b/BACKEND/providers/socket_io_provider.ts index 19ac293..2845c80 100644 --- a/BACKEND/providers/socket_io_provider.ts +++ b/BACKEND/providers/socket_io_provider.ts @@ -611,6 +611,7 @@ export class WebSocketIo { const stationId = data.stationId || '' const scenarioName = data.scenarioName || '' const skipTestPorts = data.skipTestPorts || false + const reasonSkipPhysical = data.reasonSkipPhysical || false const station = await Station.find(stationId) // Check station is active const activeStation = await checkStationActive(stationId) @@ -625,6 +626,8 @@ export class WebSocketIo { line.config.listFeatureTested = [ ...new Set([...line.config.listFeatureTested, 'PHYSICAL']), ] + line.config.isSkipPhysical = true + line.config.reasonSkipPhysical = reasonSkipPhysical } else if (line.config.status === 'connected') { console.log('Reset list feature tested for line', lineId) line.resetDPELP() @@ -653,21 +656,21 @@ export class WebSocketIo { console.error('Error sending wiki message:', error) } try { - // await sendMessageToMail( - // `[${scenarioName || 'DPELP'}] - ${stationName} - ${dataFormat}`, - // tableHTML - // ) + await sendMessageToMail( + `[${scenarioName || 'DPELP'}] - ${stationName} - ${dataFormat}`, + tableHTML + ) } catch (error) { console.error('Error sending mail:', error) } try { - await sendMessageToZulip( - 'stream', - 'ATC_Report', - station.name, - `\n\n---\n**[${scenarioName || 'DPELP'}] - ${stationName} - ${dataFormat}**\n\n` + - zulipMess - ) + // await sendMessageToZulip( + // 'stream', + // 'ATC_Report', + // station.name, + // `\n\n---\n**[${scenarioName || 'DPELP'}] - ${stationName} - ${dataFormat}**\n\n` + + // zulipMess + // ) } catch (error) { console.error('Error sending zulip message:', error) } @@ -707,7 +710,7 @@ export class WebSocketIo { }) socket.on('end_run_physical_test', async (data) => { - const { stationId, lineId } = data + const { stationId, lineId, reasonSkipPhysical } = data // Check station is active const activeStation = await checkStationActive(stationId) if (!activeStation) return @@ -717,7 +720,7 @@ export class WebSocketIo { stationId, [lineId], async (lineCon) => { - await lineCon.sendReportPhysicalTest() + await lineCon.sendReportPhysicalTest(reasonSkipPhysical) lineCon.endTesting() }, {} @@ -887,7 +890,8 @@ export class WebSocketIo { output = '', inventory: string = '', latestScenario?: any, - data?: any + data?: any, + reasonSkipPhysical?: string ) { try { for (const line of lines) { @@ -916,6 +920,8 @@ export class WebSocketIo { latestScenario: latestScenario, listFeatureTested: [], isReady: false, + reasonSkipPhysical: reasonSkipPhysical, + isSkipPhysical: reasonSkipPhysical ? true : false, }, socket, async () => { @@ -1001,7 +1007,8 @@ export class WebSocketIo { line?.config?.output || '', line?.config?.inventory || '', line?.config?.latestScenario || undefined, - line?.config?.data || [] + line?.config?.data || [], + line?.config?.reasonSkipPhysical || '' ) this.lineConnecting = this.lineConnecting.filter((el) => el !== lineId) diff --git a/FRONTEND/src/App.tsx b/FRONTEND/src/App.tsx index 1a74947..edb8f61 100644 --- a/FRONTEND/src/App.tsx +++ b/FRONTEND/src/App.tsx @@ -55,6 +55,7 @@ import PageLogin from "./components/Authentication/LoginPage"; import DraggableTabs from "./components/DragTabs"; import { isJsonString } from "./untils/helper"; import BottomToolBar from "./components/BottomToolBar"; +import ModalConfirmSkipTestPort from "./components/Modal/ModalConfirmSkipTestPort"; // import ModalConfirmRunScenario from "./components/Modal/ModalConfirmRunScenario"; const apiUrl = import.meta.env.VITE_BACKEND_URL; @@ -109,6 +110,7 @@ function App() { const [listIos, setListIos] = useState([]); const [listLicense, setListLicense] = useState([]); const [isLoading, setIsLoading] = useState(true); + const [linesConfirmSkipPort, setLinesConfirmSkipPort] = useState([]); const connectApcSwitch = (station: TStation) => { if (!station?.is_active) return; @@ -468,12 +470,21 @@ function App() { }); socket?.on("feature_tested", (data) => { - if (data?.listFeatureTested) + if (data?.listFeatureTested) { updateValueLineStation( data?.lineId, - { listFeatureTested: data?.listFeatureTested }, + { + listFeatureTested: data?.listFeatureTested, + isSkipPhysical: data?.isSkipPhysical, + reasonSkipPhysical: data?.reasonSkipPhysical, + }, data?.stationId ); + if (data?.isSkipPhysical && !data?.reasonSkipPhysical) { + const valueLine = findLineByLineId(data?.lineId, data?.stationId); + if (valueLine) setLinesConfirmSkipPort((pre) => [...pre, valueLine]); + } + } }); // ✅ cleanup on unmount or when socket changes @@ -649,6 +660,13 @@ function App() { } }, [expandedBottomBar]); + const findLineByLineId = (lineId: number, stationId?: number) => { + const valueStation = stations.find((el) => el.id === stationId); + if (!valueStation || !stationId) return null; + const valueLine = valueStation?.lines?.find((el) => el.id === lineId); + return valueLine; + }; + return ( {/* el.id === Number(activeTab))} scenarios={scenarios} /> */} + + el.id === Number(activeTab))} + /> ); } diff --git a/FRONTEND/src/components/BottomToolBar.tsx b/FRONTEND/src/components/BottomToolBar.tsx index 7527802..61032c3 100644 --- a/FRONTEND/src/components/BottomToolBar.tsx +++ b/FRONTEND/src/components/BottomToolBar.tsx @@ -5,7 +5,6 @@ import { CloseButton, Flex, Grid, - Menu, ScrollArea, Tabs, Text, @@ -26,11 +25,7 @@ import { DrawerAPCControl, DrawerSwitchControl } from "./Drawer/DrawerControl"; import DrawerScenario from "./Modal/ModalScenario"; import { isJsonString } from "../untils/helper"; import { motion } from "motion/react"; -import { - IconCaretDown, - IconCaretRight, - IconCaretUp, -} from "@tabler/icons-react"; +import { IconCaretDown, IconCaretUp } from "@tabler/icons-react"; import InputHistory from "./InputHistory"; import ModalRunScenario from "./Modal/ModalRunScenario"; @@ -365,94 +360,27 @@ const BottomToolBar = ({ gap={"xs"} wrap={"wrap"} > - - - - - - - el.title.toUpperCase() === "DPELP" - )} - onClick={() => { - if ( - selectedLines.length > 0 - // && - // selectedLines.length === station?.lines?.length - ) { - socket?.emit("run_all_dpelp", { - lineIds: selectedLines.map( - (line) => line.id - ), - stationName: station.name, - stationId: station.id, - }); - } - setIsDisable(true); - setTimeout(() => { - setIsDisable(false); - }, 5000); - }} - text="Run Test Ports after DPELP" - /> - el.title.toUpperCase() === "DPELP" - )} - onClick={() => { - if ( - selectedLines.length > 0 - // && - // selectedLines.length === station?.lines?.length - ) { - socket?.emit("run_all_dpelp", { - lineIds: selectedLines.map( - (line) => line.id - ), - stationName: station.name, - stationId: station.id, - skipTestPorts: true, - }); - } - setIsDisable(true); - setTimeout(() => { - setIsDisable(false); - }, 5000); - }} - text="Skip Test Ports" - color="yellow" - /> - - - + el.title.toUpperCase() === "DPELP" + )} + onClick={() => { + if (selectedLines.length > 0) { + socket?.emit("run_all_dpelp", { + lineIds: selectedLines.map((line) => line.id), + stationName: station.name, + stationId: station.id, + }); + } + setIsDisable(true); + setTimeout(() => { + setIsDisable(false); + }, 5000); + }} + /> + + + ))} + + + + ); +} diff --git a/FRONTEND/src/components/Modal/ModalTerminal.tsx b/FRONTEND/src/components/Modal/ModalTerminal.tsx index 9a0f3de..d5bdc62 100644 --- a/FRONTEND/src/components/Modal/ModalTerminal.tsx +++ b/FRONTEND/src/components/Modal/ModalTerminal.tsx @@ -81,6 +81,8 @@ const ModalTerminal = ({ listBrands, listCategories, setScenarios, + setLinesConfirmSkipPort, + linesConfirmSkipPort, }: { opened: boolean; onClose: () => void; @@ -96,6 +98,8 @@ const ModalTerminal = ({ listBrands: TBrands[]; listCategories: TCategories[]; setScenarios: (value: React.SetStateAction) => void; + setLinesConfirmSkipPort: (value: React.SetStateAction) => void; + linesConfirmSkipPort: TLine[]; }) => { const user = useMemo(() => { return localStorage.getItem("user") && @@ -134,6 +138,12 @@ const ModalTerminal = ({ line?.listFeatureTested?.includes("DPELP") ) setActiveStep(2); + if ( + line?.listFeatureTested?.includes("PHYSICAL") && + line?.listFeatureTested?.includes("DPELP") && + line?.listFeatureTested?.includes("SUMMARY") + ) + setActiveStep(3); } else setActiveStep(0); }, [opened, line?.listFeatureTested]); @@ -537,6 +547,7 @@ const ModalTerminal = ({ return; } if (openSelectIos) return; + if (linesConfirmSkipPort.length) return; onClose(); if ( line?.userOpenCLI === user?.userName && @@ -685,7 +696,31 @@ const ModalTerminal = ({ > + + Physical Test + + + ) : ( + "Physical Test" + ) + } description="Test physical ports" > {isPhysicalTest && line?.ports ? line.ports.map((port, i) => ( - - {normalizePortName(port)} + + + {normalizePortName(port)} + {port?.includes("SFP") ? ( + )) : null} @@ -1265,31 +1301,58 @@ const ModalTerminal = ({ Reset {isPhysicalTest ? ( - + + + + ) : ( - - - - el.title.toUpperCase() === "DPELP" - )} - onClick={() => { - socket?.emit("run_all_dpelp", { - lineIds: [line?.id], - stationName: stationItem?.name, - stationId: Number(stationItem?.id), - }); - setIsDisable(true); - setTimeout(() => { - setIsDisable(false); - }, 10000); - }} - text="Run Test Ports after DPELP" - /> - el.title.toUpperCase() === "DPELP" - )} - onClick={() => { - socket?.emit("run_all_dpelp", { - lineIds: [line?.id], - stationName: stationItem?.name, - stationId: Number(stationItem?.id), - skipTestPorts: true, - }); - setIsDisable(true); - setTimeout(() => { - setIsDisable(false); - }, 10000); - }} - text="Skip Test Ports" - color="yellow" - /> - - - + 0 + ? true + : false) + } + onClick={() => { + socket?.emit("run_all_dpelp", { + lineIds: [line?.id], + stationName: stationItem?.name, + stationId: Number(stationItem?.id), + }); + setIsDisable(true); + setTimeout(() => { + setIsDisable(false); + }, 10000); + }} + dataDPELP={scenarios?.find( + (el) => el.title.toUpperCase() === "DPELP" + )} + />