From 36a67c5b2d0c361ce5d4138e71f014a3f56000a7 Mon Sep 17 00:00:00 2001 From: nguyentrungthat <80239428+nguentrungthat@users.noreply.github.com> Date: Mon, 11 May 2026 16:25:15 +0700 Subject: [PATCH] Update form summary --- BACKEND/app/services/line_connection.ts | 123 ++++++++++-------- BACKEND/app/ultils/helper.ts | 14 +- BACKEND/app/ultils/templates/show_version.ts | 6 +- BACKEND/providers/socket_io_provider.ts | 29 +++-- FRONTEND/src/components/BottomToolBar.tsx | 23 ++-- FRONTEND/src/components/ButtonAction.tsx | 20 ++- FRONTEND/src/components/CardLine.tsx | 10 ++ .../src/components/Modal/ModalTerminal.tsx | 36 ++--- 8 files changed, 153 insertions(+), 108 deletions(-) diff --git a/BACKEND/app/services/line_connection.ts b/BACKEND/app/services/line_connection.ts index ed5011a..a442953 100644 --- a/BACKEND/app/services/line_connection.ts +++ b/BACKEND/app/services/line_connection.ts @@ -146,6 +146,10 @@ export default class LineConnection { private isPingToServer: boolean private outputPingToServer: string private outputTestLog: string + private userTest: { + dpelp: { name: string; time: number } + physical: { name: string; time: number } + } constructor(config: LineConfig, socketIO: any, handleClearLine: () => void) { this.config = config @@ -182,6 +186,7 @@ export default class LineConnection { this.isPingToServer = false this.outputPingToServer = '' this.outputTestLog = '' + this.userTest = { dpelp: { name: '', time: 0 }, physical: { name: '', time: 0 } } } /** * Connect to line with socket @@ -227,7 +232,7 @@ export default class LineConnection { this.waitingScenario = true this.outputBuffer += message this.outputScenario += message - this.outputTestLog += message + this.outputTestLog += cleanData(data.toString()) if (!this.config.inventory) this.outputInventory = this.outputInventory.slice(-3000) + message } @@ -442,6 +447,10 @@ export default class LineConnection { }) if (script?.send_result || script?.sendResult) { this.dataDPELP = '' + this.userTest = { + ...this.userTest, + dpelp: { name: userName || '', time: Date.now() }, + } // this.config.inventory = '' } @@ -1178,6 +1187,7 @@ Ports Missing/Down: ${missing.length}\n\n` this.config.reasonSkipPhysical = '' this.testingPortPoE = true this.outputTestingPortPoE = '' + this.userTest = { ...this.userTest, physical: { name: userName || '', time: Date.now() } } const listPorts = await this.getPorts() this.socketIO.emit('running_scenario', { stationId: this.config.stationId, @@ -1909,9 +1919,13 @@ Ports Missing/Down: ${missing.length}\n\n` snapPhysical: PhysicalPortTest reason: string outputTestLog: string + userTest: { + dpelp: { name: string; time: number } + physical: { name: string; time: number } + } }) => { if (this.debounceSendSummaryReport) clearTimeout(this.debounceSendSummaryReport) - + const timeZone = process.env.TIME_ZONE || 'Australia/Sydney' const physicalTest = snapshot?.snapPhysical ? snapshot?.snapPhysical : this.physicalTest const config = snapshot?.snapConfig ? snapshot?.snapConfig : this.config const portPhysical = Array.from(physicalTest.ports.values()) @@ -1944,7 +1958,7 @@ Ports Missing/Down: ${missing.length}\n\n` 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 verdictPass = missing.length === 0 && !isSkipped const verdictLabel = verdictPass ? 'PASSED' : 'NEEDS REVIEW' const verdictMsg = verdictPass ? 'All tests passed — Ready for deployment' @@ -1953,10 +1967,8 @@ Ports Missing/Down: ${missing.length}\n\n` 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 reportId = `RPT-${momentTZ().tz(timeZone).format('YYYY-MMDD')}` + const reportDate = momentTZ().tz(timeZone).format('DD MMM YYYY') const memText = dataShowVersion?.MEMORY ? convertFromKilobytesString(dataShowVersion.MEMORY) @@ -1971,8 +1983,10 @@ Ports Missing/Down: ${missing.length}\n\n` const productSN = escapeHtml(String(config?.inventory?.sn || '')) const productVid = escapeHtml(String(config?.inventory?.vid || '')) const iosVersion = escapeHtml(String(dataShowVersion?.VERSION || '')) - const memDisplay = escapeHtml(memText !== '—' ? memText : '') - const flashDisplay = escapeHtml(flashText !== '—' ? flashText : '') + const macAddress = escapeHtml(String(dataShowVersion?.MAC_ADDRESS || '')) + const memDisplay = escapeHtml(memText !== '—' ? memText : '-') + const flashDisplay = escapeHtml(flashText !== '—' ? flashText : '-') + const configRam = await detectConfigRamByModel(config?.inventory?.pid) // AI issue rows (one per real AI issue, fall back to file's hardcoded row when none) const aiIssueRowsHtml = @@ -1996,7 +2010,7 @@ Ports Missing/Down: ${missing.length}\n\n` `
${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 @@ -2111,13 +2125,14 @@ Ports Missing/Down: ${missing.length}\n\n`
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)
Name${productName}
P/N${productPN}
S/N${productSN}
MAC${macAddress}
Type--
Cond.--
Supplier-
Warranty-
@@ -2135,32 +2150,32 @@ Ports Missing/Down: ${missing.length}\n\n` IOS-XE Version ${iosVersion} - 17.06.01 + - System RAM ${memDisplay} - 8 GB + ${configRam?.ram || '-'} Flash Storage ${flashDisplay} - 16 GB + ${configRam?.flash || '-'} Uplink Module - C9300-NM-4G - N/A + - + - PSU Model - 715W AC - 715W AC + - + - PoE Budget - 437 Watts - 437 Watts + - + - @@ -2227,7 +2242,7 @@ Ports Missing/Down: ${missing.length}\n\n` -
+
 
@@ -2243,13 +2258,28 @@ Ports Missing/Down: ${missing.length}\n\n` Received
- Trung Nguyen + Unknown
-
- 06 May 10:30 +
+ ${momentTZ().tz(timeZone).format('DD MMM')}
+ +
+ ✓ +
+
+ Software Test +
+
+ ${snapshot?.userTest?.dpelp?.name || ''} +
+
+ ${momentTZ(snapshot?.userTest?.dpelp?.time).tz(timeZone).format('DD MMM, HH:mm')} +
+ +
✓ @@ -2259,25 +2289,10 @@ Ports Missing/Down: ${missing.length}\n\n` Physical Check
- Khanh Le + ${snapshot?.userTest?.physical?.name || ''}
-
- 06 May 11:15 -
- - - -
- ✓ -
-
- Software Test -
-
- Duy Pham (remote) -
-
- 06 May 14:00 +
+ ${momentTZ(snapshot?.userTest?.physical?.time).tz(timeZone).format('DD MMM, HH:mm')}
@@ -2307,7 +2322,7 @@ Ports Missing/Down: ${missing.length}\n\n` Physical Check - Khanh Le · 06 May 11:15 + ${snapshot?.userTest?.physical?.name || ''} · ${momentTZ(snapshot?.userTest?.physical?.time).tz(timeZone).format('DD MMM, HH:mm')} @@ -2343,7 +2358,7 @@ Ports Missing/Down: ${missing.length}\n\n` Software Check - +
Duy Pham (remote) · 06 May 14:00–17:45${snapshot?.userTest?.dpelp?.name || ''} · ${momentTZ(snapshot?.userTest?.dpelp?.time).tz(timeZone).format('DD MMM, HH:mm')}
@@ -2373,8 +2388,8 @@ Ports Missing/Down: ${missing.length}\n\n` - - + +
${escapeHtml(sfpText)}
SFP+ UP
${missingSFP.length > 0 || missingPoE.length > 0 ? 'FAIL' : 'PASS'}
PoE+ Test
${totalPoE + totalSFP === 0 ? 100 : Math.round(((totalPoE + totalSFP - (missingPoE.length + missingSFP.length)) / (totalPoE + totalSFP)) * 100)}%
Throughput
${missingSFP.length > 0 || missingPoE.length > 0 ? 'WARN' : 'PASS'}
PoE+ Test
${totalPoE + totalSFP === 0 ? 100 : Math.round(((totalPoE + totalSFP - (missingPoE.length + missingSFP.length)) / (totalPoE + totalSFP)) * 100)}%
Throughput
${missingDetailsHtml} @@ -2398,7 +2413,7 @@ Ports Missing/Down: ${missing.length}\n\n` ` - // this.updateNote(config?.inventory?.sn, this.dataDPELP as DataDPELP) + 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 @@ -2450,6 +2465,7 @@ Ports Missing/Down: ${missing.length}\n\n` snapPhysical: this.physicalTest, reason: '', outputTestLog: this.outputTestLog, + userTest: this.userTest, } this.debounceSendSummaryReport = setTimeout(() => { if (!this.config.listFeatureTested?.includes('PHYSICAL')) { @@ -2461,6 +2477,7 @@ Ports Missing/Down: ${missing.length}\n\n` this.sendFeatureTested() this.sendReportSummaryV2(snapshot) this.outputTestLog = '' + this.userTest = { dpelp: { name: '', time: 0 }, physical: { name: '', time: 0 } } }, timeout) } diff --git a/BACKEND/app/ultils/helper.ts b/BACKEND/app/ultils/helper.ts index 685ed1d..39bbfe3 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/app/ultils/templates/show_version.ts b/BACKEND/app/ultils/templates/show_version.ts index a72d19e..556e5c9 100644 --- a/BACKEND/app/ultils/templates/show_version.ts +++ b/BACKEND/app/ultils/templates/show_version.ts @@ -3,9 +3,11 @@ import XRegExp from 'xregexp' // Parser function const parseLog = (data: string) => { const patterns = [ - XRegExp('^.*Software.*\\((?\\S+)\\),\\s+Version\\s+(?[\\w\\.-]+)'), XRegExp( - '^\\*?\\s*\\d+\\s+\\d+\\s+[\\w-]+\\s+(?\\d[\\w\\.-]+)\\s+(?[\\w-]+)\\s+(?:BUNDLE|INSTALL)' + '^.*Software.*\\((?\\S+)\\),\\s+Version\\s+(?[\\w\\.\\(\\)\\-]+)' + ), + XRegExp( + '^\\*?\\s*\\d+\\s+\\d+\\s+[\\w-]+\\s+(?\\d[\\w\\.\\(\\)\\-]+)\\s+(?[\\w-]+)\\s+(?:BUNDLE|INSTALL)' ), XRegExp('System\\s+image\\s+file\\s+is\\s+"(?:[^:]*:)?(?[^"]+)"'), XRegExp('Active-image:\\s+(?\\S+)'), diff --git a/BACKEND/providers/socket_io_provider.ts b/BACKEND/providers/socket_io_provider.ts index b540061..f8a3df5 100644 --- a/BACKEND/providers/socket_io_provider.ts +++ b/BACKEND/providers/socket_io_provider.ts @@ -203,6 +203,7 @@ export class WebSocketIo { socket.on('run_scenario', async (data) => { const lineId = data.id const scenario = data.scenario + const name = data.userName || userName // Check station is active const activeStation = await checkStationActive(data.stationId) if (!activeStation) return @@ -211,7 +212,7 @@ export class WebSocketIo { io, data.stationId, [lineId], - async (line) => line.runScript(scenario, userName), + async (line) => line.runScript(scenario, name), { scenario, } @@ -678,10 +679,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 +698,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) } @@ -737,7 +738,7 @@ export class WebSocketIo { io, stationId, [lineId], - async (lineCon) => lineCon.runPhysicalTest(name), + async (lineCon) => lineCon.runPhysicalTest(name || userName), {} ) }) @@ -907,7 +908,7 @@ export class WebSocketIo { stationId, [lineId], async (lineCon) => { - lineCon.sendReportSummary() + lineCon.sendReportSummaryV2() }, {} ) diff --git a/FRONTEND/src/components/BottomToolBar.tsx b/FRONTEND/src/components/BottomToolBar.tsx index 61032c3..c5da472 100644 --- a/FRONTEND/src/components/BottomToolBar.tsx +++ b/FRONTEND/src/components/BottomToolBar.tsx @@ -223,8 +223,8 @@ const BottomToolBar = ({ onClick={() => { setSelectedLines( selectedLines.filter( - (line) => line.id !== el.id - ) + (line) => line.id !== el.id, + ), ); socket?.emit("close_cli", { lineId: el?.id, @@ -288,7 +288,7 @@ const BottomToolBar = ({ const lines = station.lines.filter( (line) => !line?.userOpenCLI || - line?.userOpenCLI === user?.userName + line?.userOpenCLI === user?.userName, ); if (selectedLines.length !== lines.length) { setSelectedLines(lines); @@ -365,7 +365,7 @@ const BottomToolBar = ({ selectedLines={selectedLines} isDisable={isDisable || selectedLines.length === 0} dataDPELP={scenarios?.find( - (el) => el.title.toUpperCase() === "DPELP" + (el) => el.title.toUpperCase() === "DPELP", )} onClick={() => { if (selectedLines.length > 0) { @@ -380,6 +380,11 @@ const BottomToolBar = ({ setIsDisable(false); }, 5000); }} + userName={ + user?.firstName + ? `${user.firstName} ${user.lastName || ""}` + : user?.userName || "Unknown User" + } />