From c644e798c6e14b14b88b50d7c922a0306115e3ff Mon Sep 17 00:00:00 2001 From: nguyentrungthat <80239428+nguentrungthat@users.noreply.github.com> Date: Mon, 23 Feb 2026 11:20:56 +0700 Subject: [PATCH] Add summary-report debounce and small fixes Backend: Add debounceSendSummaryReport to LineConnection and snapshotting to debounce sendReportSummary (10 min) to avoid duplicate summary emails; adapt sendReportSummary to accept a snapshot and use snapshot data when available. Reset physicalTest and listFeatureTested on line disconnect and add initConfig helper. Improve PoE/SFP parsing (filter names with '/' and match 'unknown') and adjust report HTML (tested/missing counts, column counts). Add PS_INCOMPATIBLE log rule. Comment out automatic wiki/email send in socket provider. Physical test service: suppress getFormReport call on completion and include PoE/SFP breakdown in the HTML report counts. Frontend: fix terminal open logic to check userOpenCLI instead of userEmailOpenCLI and display device MAC in the terminal modal. --- BACKEND/app/services/line_connection.ts | 102 ++++++++++++++---- BACKEND/app/services/physical_test_service.ts | 4 +- BACKEND/app/ultils/helper.ts | 7 ++ BACKEND/providers/socket_io_provider.ts | 8 +- FRONTEND/src/App.tsx | 2 +- .../src/components/Modal/ModalTerminal.tsx | 10 ++ 6 files changed, 107 insertions(+), 26 deletions(-) diff --git a/BACKEND/app/services/line_connection.ts b/BACKEND/app/services/line_connection.ts index c05207d..c9d1ee8 100644 --- a/BACKEND/app/services/line_connection.ts +++ b/BACKEND/app/services/line_connection.ts @@ -138,6 +138,7 @@ export default class LineConnection { private debounceTimer: NodeJS.Timeout | null = null private testingPortPoE: boolean private outputTestingPortPoE: string + private debounceSendSummaryReport: NodeJS.Timeout | null = null constructor(config: LineConfig, socketIO: any, handleClearLine: () => void) { this.config = config @@ -168,6 +169,7 @@ export default class LineConnection { this.outputLoadIosLicense = '' this.listDeviceIos = [] this.debounceTimer = null + this.debounceSendSummaryReport = null this.testingPortPoE = false this.outputTestingPortPoE = '' } @@ -284,6 +286,8 @@ export default class LineConnection { console.log(`[${Date.now()}] πŸ”Œ Line ${lineNumber} disconnected`) this.config.status = 'disconnected' this.config.output += this.config.output + '[CLEAR_TERMINAL_SCROLL_BACK]' + this.config.listFeatureTested = [] + this.physicalTest = new PhysicalPortTest([]) // this.config.inventory = undefined this.socketIO.emit('line_disconnected', { stationId, @@ -588,6 +592,18 @@ export default class LineConnection { ...new Set([...this.config.listFeatureTested, 'DPELP']), ] this.sendFeatureTested() + + // 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, + } + this.debounceSendSummaryReport = setTimeout(() => { + this.sendReportSummary(snapshot) + }, 600000) // 10p debounce + // } if (this.config.latestScenario) this.config.latestScenario = { ...this.config.latestScenario, detectAI: detectLog } @@ -1218,15 +1234,16 @@ export default class LineConnection { const lines = statusOutput.split('\n') const ports = [] for (const line of lines) { - // Match: "Gi0/1 is up, line protocol is up" + // Match: "Gi1/0/1 auto off 0.0 n/a n/a 30.0 " const matchPoE = line.match(/^(\S+)\s+\S+\s+(on|off)/i) if (matchPoE) { const name = matchPoE[1] - ports.push(normalizeInterface(name)) + if (name.includes('/')) ports.push(normalizeInterface(name)) } // Match: "Gi0/15 notconnect 1 auto auto 1000BaseSX SFP" // Match: "Gi0/16 notconnect 1 auto auto Not Present" - const matchSFP = line.match(/^([A-Za-z0-9\/]+).*\b(SFP|Not Present)\b/i) + // Match: "Gi1/1/4 notconnect 1 auto auto unknown" + const matchSFP = line.match(/^([A-Za-z0-9\/]+).*\b(SFP|Not Present|unknown)\b/i) if (matchSFP) { const name = matchSFP[1] ports.push(normalizeInterface(name) + ' (SFP)') @@ -1656,6 +1673,9 @@ ${log} return '' } + /** + * Check config RAM and Flash, if higher config will send report + */ async checkConfigRam(mem: string, flash: string, pid: string, output: string) { const configRam = await detectConfigRamByModel(pid) if (configRam) { @@ -1687,6 +1707,9 @@ ${log} } } + /** + * Send list feature tested + */ sendFeatureTested = async () => { this.socketIO.emit('feature_tested', { stationId: this.config.stationId, @@ -1695,35 +1718,47 @@ ${log} }) } - sendReportSummary = async () => { - const portPhysical = Array.from(this.physicalTest.ports.values()) + /** + * Send summary of all report (DPELP, Physical Testing) + */ + sendReportSummary = async (snapshot?: { + snapConfig: LineConfig + snapPhysical: PhysicalPortTest + }) => { + if (this.debounceSendSummaryReport) clearTimeout(this.debounceSendSummaryReport) + const physicalTest = snapshot?.snapPhysical ? snapshot?.snapPhysical : this.physicalTest + const config = snapshot?.snapConfig ? snapshot?.snapConfig : this.config + const portPhysical = Array.from(physicalTest.ports.values()) const missing = portPhysical.filter((p) => !p.tested) const missingPoE = missing.filter((p) => !p.name.includes('SFP')) const missingSFP = missing.filter((p) => p.name.includes('SFP')) - const showVersion = this.config?.data?.find( + const tested = portPhysical.filter((p) => p.tested) + const testedPoE = tested.filter((p) => !p.name.includes('SFP')) + const testedSFP = tested.filter((p) => p.name.includes('SFP')) + const showVersion = 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 + : config?.inventory - const showLicense = this.config?.data?.find( + const showLicense = 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 summary = this.config?.latestScenario?.detectAI?.summary || '' + const issue = config?.latestScenario?.detectAI?.issue || [] + const summary = config?.latestScenario?.detectAI?.summary || '' const body = ` - + - @@ -1765,8 +1800,37 @@ ${log}
DPELPDPELP Physical Testing
- Model: ${this.config?.inventory?.pid ?? ''} ${this.config?.inventory?.vid ?? ''}
- Serial Number: ${this.config?.inventory?.sn ?? ''}
+
+ Model: ${config?.inventory?.pid ?? ''} ${config?.inventory?.vid ?? ''}
+ Serial Number: ${config?.inventory?.sn ?? ''}
MAC: ${dataShowVersion?.MAC_ADDRESS ?? ''}
IOS: ${dataShowVersion?.SOFTWARE_IMAGE ?? ''} ${dataShowVersion?.VERSION ?? ''}
MEM: ${dataShowVersion?.MEMORY ? convertFromKilobytesString(dataShowVersion?.MEMORY) : ''}
@@ -1741,14 +1776,14 @@ ${log}
Total Ports: ${portPhysical?.length}
- Ports Tested (Link UP): ${portPhysical.filter((p) => p.tested).length}
- Ports Missing/Down: ${portPhysical.filter((p) => !p.tested).length}
+ Ports Tested (Link UP): ${tested.length} (${testedPoE?.length} PoE, ${testedSFP?.length} SFP)
+ Ports Missing/Down: ${missing.length}
${ missingPoE?.length ? `
Ports Missing PoE
────────────────────────────────
-
${missingPoE.map((p) => this.physicalTest.normalizePortName(p.name)).join('
')}
+
${missingPoE.map((p) => physicalTest.normalizePortName(p.name)).join('
')}
` : '' } @@ -1757,7 +1792,7 @@ ${log} ? `
Ports Missing SFP
────────────────────────────────
-
${missingSFP.map((p) => this.physicalTest.normalizePortName(p.name)).join('
')}
` +
${missingSFP.map((p) => physicalTest.normalizePortName(p.name)).join('
')}
` : '' }
` await sendMessageToMail( - `[ATC] - [${this.config.stationName} - Line: ${this.config.lineNumber}] - Summary of Testing Results`, + `[ATC] - [${config.stationName} - Line: ${config.lineNumber}] - Summary of Testing Results`, body ) } + + /** + * Reset config information of line + */ + initConfig() { + this.config = { + id: 0, + port: 0, + lineNumber: 0, + ip: '', + stationId: 0, + stationName: '', + stationIp: '', + outlet: 0, + output: '', + status: '', + baud: 0, + openCLI: false, + userEmailOpenCLI: '', + userOpenCLI: '', + inventory: [], + data: [], + ports: [], + runningScenario: '', + runningPhysical: false, + listFeatureTested: [], + } + this.physicalTest = new PhysicalPortTest([]) + } } diff --git a/BACKEND/app/services/physical_test_service.ts b/BACKEND/app/services/physical_test_service.ts index acde15c..241536d 100644 --- a/BACKEND/app/services/physical_test_service.ts +++ b/BACKEND/app/services/physical_test_service.ts @@ -122,7 +122,7 @@ export class PhysicalPortTest { } onDone() { - this.getFormReport() + // this.getFormReport() // this.ports.clear() console.log('βœ… Physical Test DONE') } @@ -175,7 +175,7 @@ export class PhysicalPortTest { Total Ports: ${report.ports.length}
- Ports Tested (UP): ${tested.length}
+ Ports Tested (UP): ${tested.length} (${testedPoE?.length} PoE, ${testedSFP?.length} SFP)
Ports Missing: ${missing.length}
diff --git a/BACKEND/app/ultils/helper.ts b/BACKEND/app/ultils/helper.ts index 42efa7c..6600a84 100644 --- a/BACKEND/app/ultils/helper.ts +++ b/BACKEND/app/ultils/helper.ts @@ -510,6 +510,13 @@ export const RULES: LogRule[] = [ level: 'WARN', message: 'Hardware environment warning', }, + { + id: 'PS_INCOMPATIBLE', + category: 'HARDWARE', + match: /%PLATFORM_FEP-\d+-FRU_PS_INCOMPATIBLE/i, + level: 'FAIL', + message: 'Power supply incompatible', + }, // ERROR { id: 'MEMORY_ERROR', diff --git a/BACKEND/providers/socket_io_provider.ts b/BACKEND/providers/socket_io_provider.ts index 3dfbc0a..e221dfb 100644 --- a/BACKEND/providers/socket_io_provider.ts +++ b/BACKEND/providers/socket_io_provider.ts @@ -635,10 +635,10 @@ 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) } diff --git a/FRONTEND/src/App.tsx b/FRONTEND/src/App.tsx index 6d5fd07..62480b0 100644 --- a/FRONTEND/src/App.tsx +++ b/FRONTEND/src/App.tsx @@ -601,7 +601,7 @@ function App() { const openTerminal = (line: TLine) => { setOpenModalTerminal(true); const data = { ...line }; - if (!line.userEmailOpenCLI) { + if (!line.userOpenCLI) { data.cliOpened = true; data.userEmailOpenCLI = user?.email; data.userOpenCLI = user?.userName; diff --git a/FRONTEND/src/components/Modal/ModalTerminal.tsx b/FRONTEND/src/components/Modal/ModalTerminal.tsx index 576657a..1df4a13 100644 --- a/FRONTEND/src/components/Modal/ModalTerminal.tsx +++ b/FRONTEND/src/components/Modal/ModalTerminal.tsx @@ -923,6 +923,16 @@ const ModalTerminal = ({ : ""} + + + MAC: + + + {findDataShowVersion() + ? findDataShowVersion()?.MAC_ADDRESS || "" + : ""} + + License: