From 95601a02cb406260d99a242fe161c27c32c5cff0 Mon Sep 17 00:00:00 2001 From: nguyentrungthat <80239428+nguentrungthat@users.noreply.github.com> Date: Fri, 8 May 2026 16:41:32 +0700 Subject: [PATCH] Update --- BACKEND/app/services/line_connection.ts | 475 +++++++++++++++++++++++- BACKEND/app/ultils/helper.ts | 14 +- BACKEND/providers/socket_io_provider.ts | 22 +- 3 files changed, 492 insertions(+), 19 deletions(-) diff --git a/BACKEND/app/services/line_connection.ts b/BACKEND/app/services/line_connection.ts index 78a39f9..1aad6c9 100644 --- a/BACKEND/app/services/line_connection.ts +++ b/BACKEND/app/services/line_connection.ts @@ -145,6 +145,7 @@ export default class LineConnection { private debounceSendSummaryReport: NodeJS.Timeout | null = null private isPingToServer: boolean private outputPingToServer: string + private outputTestLog: string constructor(config: LineConfig, socketIO: any, handleClearLine: () => void) { this.config = config @@ -180,6 +181,7 @@ export default class LineConnection { this.outputTestingPortPoE = '' this.isPingToServer = false this.outputPingToServer = '' + this.outputTestLog = '' } /** * Connect to line with socket @@ -225,6 +227,7 @@ export default class LineConnection { this.waitingScenario = true this.outputBuffer += message this.outputScenario += message + this.outputTestLog += message if (!this.config.inventory) this.outputInventory = this.outputInventory.slice(-3000) + message } @@ -1895,6 +1898,474 @@ Ports Missing/Down: ${missing.length}\n\n` }) } + /** + * Send summary report using the new "Equipment Receiving & Testing Report" template. + * Email-safe HTML: table-based layout, inline styles, no external CSS or web fonts. + */ + sendReportSummaryV2 = async (snapshot?: { + snapConfig: LineConfig + snapPhysical: PhysicalPortTest + reason: string + outputTestLog: string + }) => { + 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 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 totalPoE = testedPoE.length + missingPoE.length + const totalSFP = testedSFP.length + missingSFP.length + + const showVersion = config?.data?.find( + (d) => d.command?.trim()?.includes('show ver') || d.command?.trim()?.includes('sh ver') + ) + const dataShowVersion = + showVersion?.textfsm && (showVersion?.textfsm as any)?.[0] + ? (showVersion?.textfsm as any)?.[0] + : config?.inventory + + 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 as any[]) + : null + + const issues: string[] = config?.latestScenario?.detectAI?.issue || [] + const skipReason = this.config.reasonSkipPhysical || snapshot?.reason || '' + const isSkipped = typeof skipReason === 'string' && skipReason.trim().length > 0 + + const verdictPass = missing.length === 0 && issues.length === 0 && !isSkipped + const verdictLabel = verdictPass ? 'PASSED' : 'NEEDS REVIEW' + const verdictMsg = verdictPass + ? 'All tests passed — Ready for deployment' + : 'Issues detected — review required before deployment' + const verdictBg = verdictPass ? '#ecfdf5' : '#fef2f2' + const verdictBd = verdictPass ? '#a7f3d0' : '#fecaca' + const verdictTx = verdictPass ? '#065f46' : '#991b1b' + + const reportId = `RPT-${config.stationId}-L${config.lineNumber}-${Date.now().toString().slice(-6)}` + const reportDate = momentTZ() + .tz(process.env.TIME_ZONE || 'UTC') + .format('DD MMM YYYY HH:mm') + + const memText = dataShowVersion?.MEMORY + ? convertFromKilobytesString(dataShowVersion.MEMORY) + : '—' + const flashText = dataShowVersion?.USB_FLASH + ? convertFromKilobytesString(dataShowVersion.USB_FLASH) + : '—' + + // ---- Template-fallback values (use file's hardcoded content when no real data) ---- + const productName = 'Cisco Catalyst 9300-48P-A' + const productPN = escapeHtml(String(config?.inventory?.pid || 'C9300-48P-A')) + const productSN = escapeHtml(String(config?.inventory?.sn || 'FCW2425L0KP')) + const productVid = escapeHtml(String(config?.inventory?.vid || 'V02')) + const iosVersion = escapeHtml(String(dataShowVersion?.VERSION || '17.09.04a')) + const memDisplay = escapeHtml(memText !== '—' ? memText : '8 GB') + const flashDisplay = escapeHtml(flashText !== '—' ? flashText : '16 GB') + + // AI issue rows (one per real AI issue, fall back to file's hardcoded row when none) + const aiIssueRowsHtml = + issues.length > 0 + ? issues + .slice(0, 1) + .map( + (issue) => + `
★ AI${escapeHtml(issue)}Investigate
` + ) + .join('') + : `
★ AIPotential intermittent power instability. PSU #1 POST logs show 3 retries before handshake.Investigate
` + + // License boxes (real licenses if available, else file's hardcoded boxes) + const licenseBoxesHtml = + dataShowLic && dataShowLic.length > 0 + ? dataShowLic + .map( + (l: any) => + `
${escapeHtml(String(l.FEATURE || ''))}
${escapeHtml(String(l.LICENSE_TYPE || ''))}${l.STATUS ? ' · ' + escapeHtml(String(l.STATUS)) : ''}
` + ) + .join('') + : `
Network Advantage
Permanent · Smart License: Active
DNA Premier
Evaluation · 85 days remaining
` + + // Port stat values (real numbers if any port data, else file's defaults) + const hasPortData = portPhysical.length > 0 + const poeText = hasPortData ? `${testedPoE.length}/${totalPoE}` : '48/48' + const sfpText = hasPortData ? `${testedSFP.length}/${totalSFP}` : '4/4' + const poeColor = + !hasPortData || (totalPoE > 0 && testedPoE.length === totalPoE) ? '#10b981' : '#f59e0b' + const sfpColor = + !hasPortData || (totalSFP > 0 && testedSFP.length === totalSFP) ? '#10b981' : '#f59e0b' + + // Missing-port detail blocks (only when there is something to show) + const missingParts: string[] = [] + if (missingPoE.length) { + missingParts.push( + `
Missing PoE (${missingPoE.length}):
${missingPoE.map((p) => escapeHtml(physicalTest.normalizePortName(p.name))).join(', ')}
` + ) + } + if (missingSFP.length) { + missingParts.push( + `
Missing SFP (${missingSFP.length}):
${missingSFP.map((p) => escapeHtml(physicalTest.normalizePortName(p.name))).join(', ')}
` + ) + } + if (isSkipped) { + missingParts.push( + `
User Skipped Physical Test:
${escapeHtml(skipReason)}
` + ) + } + const missingDetailsHtml = missingParts.join('') + + // Verdict checkmark / cross path + const verdictPathSvg = verdictPass + ? '' + : '' + + // Physical Check checklist + const checklistItems: Array<[string, string]> = [ + ['ok', 'Packaging intact — no damage to box or foam'], + ['ok', 'No physical damage — chassis, fans, PSU'], + ['ok', `S/N matches label — ${productSN} verified`], + ['ok', 'All 48 GigE + 4 SFP+ ports clean'], + ['ok', 'Accessories — power cable, rack ears, console cable'], + ['warn', 'Minor scratch on top chassis (2cm) — cosmetic only'], + ] + const checklistRowsHtml = checklistItems + .map(([k, t]) => + k === 'ok' + ? `
${t}
` + : `
!${t}
` + ) + .join('') + + // Physical Check photo placeholder cell (4 of these in the photo grid) + const photoCellHtml = (label: string) => + `
${label}
` + + // ---- Body: full template mirroring index.html, table-based + inline styles ---- + const body = ` + + + +Equipment Report — Mail Summary + + + + + + +
+ + +
+ + + + + +
+ + + + + +
+ + + PROLOGY IT + Equipment Receiving & Testing Report +
+
+ #${escapeHtml(reportId)} + ${escapeHtml(reportDate)} +
+
+ + +
+ ${verdictPathSvg} + ${verdictLabel} + ${escapeHtml(verdictMsg)} +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + +
+
Product Info
+ + + + + + + + +
Name${productName}
P/N${productPN}
S/N${productSN}
TypeSwitch — Layer 3
Cond.Refurb — Grade A
SupplierTechData AU — PO #TD-88432
Warranty12 Months (→ May 2027)
+
+
+ + +
+
Technical Specs
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SpecificationActualDefault
IOS-XE Version${iosVersion}17.06.01
System RAM${memDisplay}8 GB
Flash Storage${flashDisplay}16 GB
Uplink ModuleC9300-NM-4GN/A
PSU Model715W AC715W AC
PoE Budget437 Watts437 Watts
+
+
+
+ + +
+
Issues Found
+ ${aiIssueRowsHtml} + + + + + +
COSMETICMinor scratch on top chassis (2cm) — non-functionalAccepted
+ + + + + +
MINORFan #2 at 48dB under stress (spec 45dB) — within rack toleranceMonitor
+
0 Critical · 0 Major · 1 Minor · 1 Cosmetic
+
+
+ + +
+
Receiving & Inspection Notes
+
+
⚠ Warning from Warehouse
+

Box arrived with slight indentation on the left corner. Internal foam was still intact. Serial number on box was partially obscured by shipping label but verified upon unboxing.

+
+
+
Accessory Checklist
+ + + + + + + + +
RackmountPSU (Internal)Console CableDocumentsOriginal Box
+
+
+
+ + +
+ + + + + + +
+
+
Received
+
Trung Nguyen
+
06 May 10:30
+
+
+
Physical Check
+
Khanh Le
+
06 May 11:15
+
+
+
Software Test
+
Duy Pham (remote)
+
06 May 14:00
+
+
+
+ + + + + + +
 Detail 
+
+ + +
+ + + + + +
+ + Physical Check + Khanh Le · 06 May 11:15
+ + + + + +
+ + + + + + + + + +
${photoCellHtml('Front')}${photoCellHtml('Rear')}
${photoCellHtml('S/N Label')}${photoCellHtml('Package')}
+
+ ${checklistRowsHtml} +
+
+
+ + +
+ + + + + +
+ + Software Check + Duy Pham (remote) · 06 May 14:00–17:45
+ + + + + + +
+
Hardware Inventory
+ + + + + +
${productPN}${productSN}
PWR-C1-715WAC-PLIT241525W1
C9300-NM-4GFDO2420H0X1
FAN-T2-GEN2 (x3)OK
+
+
System & License
+ ${licenseBoxesHtml} +
+
Port Test Summary
+ + + + + + + + + +
${escapeHtml(poeText)}
${hasPortData ? 'PoE UP' : 'GigE UP'}
${escapeHtml(sfpText)}
SFP+ UP
${missingSFP.length > 0 || missingPoE.length > 0 ? 'FAIL' : 'PASS'}
PoE+ Test
${Math.round(((totalPoE + totalSFP - (missingPoE.length + missingSFP.length)) / (totalPoE + totalSFP)) * 100)}%
Throughput
+ ${missingDetailsHtml} +
+ + + + +
${snapshot?.outputTestLog || 'No test log available'}
+
CONSOLE RAW OUTPUT (Boot Log snippet)
+
+
Prology IT — Equipment QA System · Confidential — Internal Use Only
+ + +` + + // this.updateNote(config?.inventory?.sn, this.dataDPELP as DataDPELP) + await sendMessageToMail( + `[ATC] - [${config.stationName} - Line: ${config.lineNumber}] - [${this.config.inventory?.pid}] - [${this.config.inventory?.sn}] - Summary of Testing Results`, + body + ) + this.socketIO.emit('summary_tested', { + stationId: this.config.stationId, + lineId: this.config.id, + body: body, + title: `[${config.stationName} - Line: ${config.lineNumber}] - Summary of Testing Results`, + }) + } + /** * Reset config information of line */ @@ -1933,6 +2404,7 @@ Ports Missing/Down: ${missing.length}\n\n` snapConfig: this.config, snapPhysical: this.physicalTest, reason: '', + outputTestLog: this.outputTestLog, } this.debounceSendSummaryReport = setTimeout(() => { if (!this.config.listFeatureTested?.includes('PHYSICAL')) { @@ -1942,7 +2414,8 @@ Ports Missing/Down: ${missing.length}\n\n` } this.config.listFeatureTested = ['DPELP', 'PHYSICAL', 'SUMMARY'] this.sendFeatureTested() - this.sendReportSummary(snapshot) + this.sendReportSummaryV2(snapshot) + this.outputTestLog = '' }, timeout) } diff --git a/BACKEND/app/ultils/helper.ts b/BACKEND/app/ultils/helper.ts index 39bbfe3..685ed1d 100644 --- a/BACKEND/app/ultils/helper.ts +++ b/BACKEND/app/ultils/helper.ts @@ -11,13 +11,13 @@ import ConfigRam from '#models/config_ram' import Keyword from '#models/keywords' const mailTo = 'andrew.ng@apactech.io' -const mailCC = [ - 'ips@ipsupply.com.au', - 'kay@ipsupply.com.au', - 'joseph@apactech.io', - 'kiet.phan@apactech.io', -] -// const mailCC = '' +// const mailCC = [ +// 'ips@ipsupply.com.au', +// 'kay@ipsupply.com.au', +// 'joseph@apactech.io', +// 'kiet.phan@apactech.io', +// ] +const mailCC = '' type DetectAI = { status: string[] diff --git a/BACKEND/providers/socket_io_provider.ts b/BACKEND/providers/socket_io_provider.ts index ac70af9..89c936d 100644 --- a/BACKEND/providers/socket_io_provider.ts +++ b/BACKEND/providers/socket_io_provider.ts @@ -678,10 +678,10 @@ export class WebSocketIo { const linkWiki = process.env.LINK_WIKI || 'https://logs.danielvu.com/api/wiki/page/insert?title=Dev_test' try { - await axios.post(linkWiki, { - data: tableHTML, - titleAuto: `[${scenarioName || 'DPELP'}] - ${stationName} - ` + dataFormat, - }) + // await axios.post(linkWiki, { + // data: tableHTML, + // titleAuto: `[${scenarioName || 'DPELP'}] - ${stationName} - ` + dataFormat, + // }) } catch (error) { console.error('Error sending wiki message:', error) } @@ -697,13 +697,13 @@ export class WebSocketIo { const contentZulip = `\n\n---\n**[${scenarioName || 'DPELP'}] - ${stationName} - ${dataFormat}**\n\n` + zulipMess - await sendMessageToZulip( - 'stream', - streamZulip || 'ATC_Report', - topicZulip, - contentZulip - ) - await sendMessageToZulip('stream', 'ATC_Report', station.name, contentZulip) + // await sendMessageToZulip( + // 'stream', + // streamZulip || 'ATC_Report', + // topicZulip, + // contentZulip + // ) + // await sendMessageToZulip('stream', 'ATC_Report', station.name, contentZulip) } catch (error) { console.error('Error sending zulip message:', error) }