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 = `
| DPELP |
@@ -1842,6 +1875,7 @@ ${log}
${missingSFP.map((p) => physicalTest.normalizePortName(p.name)).join('
')}
`
: ''
}
+ ${reasonSkipPhysical}
`
@@ -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) {
+ socket?.emit("run_all_dpelp", {
+ lineIds: selectedLines.map((line) => line.id),
+ stationName: station.name,
+ stationId: station.id,
+ });
+ }
+ setIsDisable(true);
+ setTimeout(() => {
+ setIsDisable(false);
+ }, 5000);
+ }}
+ />
{isPhysicalTest ? (
- {
- socket?.emit("end_run_physical_test", {
- lineId: line?.id,
- stationId: Number(stationItem?.id),
- });
- // setListPortsPhysical([]);
- setIsDisable(true);
- setTimeout(() => {
- setIsDisable(false);
- }, 5000);
- }}
- >
- Done/End
-
+
+ {
+ if (line)
+ setLinesConfirmSkipPort((pre) => [...pre, line]);
+ setIsDisable(true);
+ setTimeout(() => {
+ setIsDisable(false);
+ }, 5000);
+ }}
+ >
+ Skip test ports
+
+ {
+ socket?.emit("end_run_physical_test", {
+ lineId: line?.id,
+ stationId: Number(stationItem?.id),
+ });
+ // setListPortsPhysical([]);
+ setIsDisable(true);
+ setTimeout(() => {
+ setIsDisable(false);
+ }, 5000);
+ }}
+ >
+ Done/End
+
+
) : (
0
+ ? true
+ : false)
+ }
fw={400}
variant="filled"
// color="green"
@@ -1395,67 +1458,30 @@ const ModalTerminal = ({
-
+ 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"
+ )}
+ />