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.
This commit is contained in:
parent
f3dbd3d4cc
commit
c644e798c6
|
|
@ -138,6 +138,7 @@ export default class LineConnection {
|
||||||
private debounceTimer: NodeJS.Timeout | null = null
|
private debounceTimer: NodeJS.Timeout | null = null
|
||||||
private testingPortPoE: boolean
|
private testingPortPoE: boolean
|
||||||
private outputTestingPortPoE: string
|
private outputTestingPortPoE: string
|
||||||
|
private debounceSendSummaryReport: NodeJS.Timeout | null = null
|
||||||
|
|
||||||
constructor(config: LineConfig, socketIO: any, handleClearLine: () => void) {
|
constructor(config: LineConfig, socketIO: any, handleClearLine: () => void) {
|
||||||
this.config = config
|
this.config = config
|
||||||
|
|
@ -168,6 +169,7 @@ export default class LineConnection {
|
||||||
this.outputLoadIosLicense = ''
|
this.outputLoadIosLicense = ''
|
||||||
this.listDeviceIos = []
|
this.listDeviceIos = []
|
||||||
this.debounceTimer = null
|
this.debounceTimer = null
|
||||||
|
this.debounceSendSummaryReport = null
|
||||||
this.testingPortPoE = false
|
this.testingPortPoE = false
|
||||||
this.outputTestingPortPoE = ''
|
this.outputTestingPortPoE = ''
|
||||||
}
|
}
|
||||||
|
|
@ -284,6 +286,8 @@ export default class LineConnection {
|
||||||
console.log(`[${Date.now()}] 🔌 Line ${lineNumber} disconnected`)
|
console.log(`[${Date.now()}] 🔌 Line ${lineNumber} disconnected`)
|
||||||
this.config.status = 'disconnected'
|
this.config.status = 'disconnected'
|
||||||
this.config.output += this.config.output + '[CLEAR_TERMINAL_SCROLL_BACK]'
|
this.config.output += this.config.output + '[CLEAR_TERMINAL_SCROLL_BACK]'
|
||||||
|
this.config.listFeatureTested = []
|
||||||
|
this.physicalTest = new PhysicalPortTest([])
|
||||||
// this.config.inventory = undefined
|
// this.config.inventory = undefined
|
||||||
this.socketIO.emit('line_disconnected', {
|
this.socketIO.emit('line_disconnected', {
|
||||||
stationId,
|
stationId,
|
||||||
|
|
@ -588,6 +592,18 @@ export default class LineConnection {
|
||||||
...new Set([...this.config.listFeatureTested, 'DPELP']),
|
...new Set([...this.config.listFeatureTested, 'DPELP']),
|
||||||
]
|
]
|
||||||
this.sendFeatureTested()
|
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)
|
if (this.config.latestScenario)
|
||||||
this.config.latestScenario = { ...this.config.latestScenario, detectAI: detectLog }
|
this.config.latestScenario = { ...this.config.latestScenario, detectAI: detectLog }
|
||||||
|
|
@ -1218,15 +1234,16 @@ export default class LineConnection {
|
||||||
const lines = statusOutput.split('\n')
|
const lines = statusOutput.split('\n')
|
||||||
const ports = []
|
const ports = []
|
||||||
for (const line of lines) {
|
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)
|
const matchPoE = line.match(/^(\S+)\s+\S+\s+(on|off)/i)
|
||||||
if (matchPoE) {
|
if (matchPoE) {
|
||||||
const name = matchPoE[1]
|
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/15 notconnect 1 auto auto 1000BaseSX SFP"
|
||||||
// Match: "Gi0/16 notconnect 1 auto auto Not Present"
|
// 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) {
|
if (matchSFP) {
|
||||||
const name = matchSFP[1]
|
const name = matchSFP[1]
|
||||||
ports.push(normalizeInterface(name) + ' (SFP)')
|
ports.push(normalizeInterface(name) + ' (SFP)')
|
||||||
|
|
@ -1656,6 +1673,9 @@ ${log}
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check config RAM and Flash, if higher config will send report
|
||||||
|
*/
|
||||||
async checkConfigRam(mem: string, flash: string, pid: string, output: string) {
|
async checkConfigRam(mem: string, flash: string, pid: string, output: string) {
|
||||||
const configRam = await detectConfigRamByModel(pid)
|
const configRam = await detectConfigRamByModel(pid)
|
||||||
if (configRam) {
|
if (configRam) {
|
||||||
|
|
@ -1687,6 +1707,9 @@ ${log}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send list feature tested
|
||||||
|
*/
|
||||||
sendFeatureTested = async () => {
|
sendFeatureTested = async () => {
|
||||||
this.socketIO.emit('feature_tested', {
|
this.socketIO.emit('feature_tested', {
|
||||||
stationId: this.config.stationId,
|
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 missing = portPhysical.filter((p) => !p.tested)
|
||||||
const missingPoE = missing.filter((p) => !p.name.includes('SFP'))
|
const missingPoE = missing.filter((p) => !p.name.includes('SFP'))
|
||||||
const missingSFP = 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')
|
(d) => d.command?.trim()?.includes('show ver') || d.command?.trim()?.includes('sh ver')
|
||||||
)
|
)
|
||||||
const dataShowVersion =
|
const dataShowVersion =
|
||||||
showVersion?.textfsm && showVersion?.textfsm?.[0]
|
showVersion?.textfsm && showVersion?.textfsm?.[0]
|
||||||
? 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')
|
(d) => d.command?.trim()?.includes('show lic') || d.command?.trim()?.includes('sh lic')
|
||||||
)
|
)
|
||||||
const dataShowLic =
|
const dataShowLic =
|
||||||
showLicense?.textfsm && Array.isArray(showLicense?.textfsm) ? showLicense?.textfsm : null
|
showLicense?.textfsm && Array.isArray(showLicense?.textfsm) ? showLicense?.textfsm : null
|
||||||
const issue = this.config?.latestScenario?.detectAI?.issue || []
|
const issue = config?.latestScenario?.detectAI?.issue || []
|
||||||
const summary = this.config?.latestScenario?.detectAI?.summary || ''
|
const summary = config?.latestScenario?.detectAI?.summary || ''
|
||||||
const body = `<table cellpadding="6" cellspacing="0" border="1" style="margin-top: 10px; border-collapse: collapse; width: 100%">
|
const body = `<table cellpadding="6" cellspacing="0" border="1" style="margin-top: 10px; border-collapse: collapse; width: 100%">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="width: 50%; text-align: center;">DPELP</td>
|
<td style="width: 600px; text-align: center;">DPELP</td>
|
||||||
<td style="text-align: center;">Physical Testing</td>
|
<td style="text-align: center;">Physical Testing</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="width: 50%;">
|
<td>
|
||||||
Model: <b>${this.config?.inventory?.pid ?? ''}</b> <b>${this.config?.inventory?.vid ?? ''}</b><br/>
|
Model: <b>${config?.inventory?.pid ?? ''}</b> <b>${config?.inventory?.vid ?? ''}</b><br/>
|
||||||
Serial Number: <b>${this.config?.inventory?.sn ?? ''}</b><br/>
|
Serial Number: <b>${config?.inventory?.sn ?? ''}</b><br/>
|
||||||
MAC: <b>${dataShowVersion?.MAC_ADDRESS ?? ''}</b><br/>
|
MAC: <b>${dataShowVersion?.MAC_ADDRESS ?? ''}</b><br/>
|
||||||
IOS: <b>${dataShowVersion?.SOFTWARE_IMAGE ?? ''}</b> <b>${dataShowVersion?.VERSION ?? ''}</b><br/>
|
IOS: <b>${dataShowVersion?.SOFTWARE_IMAGE ?? ''}</b> <b>${dataShowVersion?.VERSION ?? ''}</b><br/>
|
||||||
MEM: <b>${dataShowVersion?.MEMORY ? convertFromKilobytesString(dataShowVersion?.MEMORY) : ''}</b><br/>
|
MEM: <b>${dataShowVersion?.MEMORY ? convertFromKilobytesString(dataShowVersion?.MEMORY) : ''}</b><br/>
|
||||||
|
|
@ -1741,14 +1776,14 @@ ${log}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
Total Ports: ${portPhysical?.length}<br/>
|
Total Ports: ${portPhysical?.length}<br/>
|
||||||
Ports Tested (Link UP): <b style="color: #008000;">${portPhysical.filter((p) => p.tested).length}</b><br/>
|
Ports Tested (Link UP): <b style="color: #008000;">${tested.length} (${testedPoE?.length} PoE, ${testedSFP?.length} SFP)</b><br/>
|
||||||
Ports Missing/Down: <b style="color: #ff0000;">${portPhysical.filter((p) => !p.tested).length}</b><br/>
|
Ports Missing/Down: <b style="color: #ff0000;">${missing.length}</b><br/>
|
||||||
${
|
${
|
||||||
missingPoE?.length
|
missingPoE?.length
|
||||||
? `
|
? `
|
||||||
<br/><b style="color: #ff0000;">Ports Missing PoE</b><br/>
|
<br/><b style="color: #ff0000;">Ports Missing PoE</b><br/>
|
||||||
────────────────────────────────<br/>
|
────────────────────────────────<br/>
|
||||||
<div style="column-count: 12;">${missingPoE.map((p) => this.physicalTest.normalizePortName(p.name)).join('<br/>')}</div>
|
<div style="column-count: 6;">${missingPoE.map((p) => physicalTest.normalizePortName(p.name)).join('<br/>')}</div>
|
||||||
`
|
`
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
|
|
@ -1757,7 +1792,7 @@ ${log}
|
||||||
? `
|
? `
|
||||||
<br/><b style="color: #ff0000;">Ports Missing SFP</b><br/>
|
<br/><b style="color: #ff0000;">Ports Missing SFP</b><br/>
|
||||||
────────────────────────────────<br/>
|
────────────────────────────────<br/>
|
||||||
<div style="column-count: 12;">${missingSFP.map((p) => this.physicalTest.normalizePortName(p.name)).join('<br/>')}</div>`
|
<div style="column-count: 6;">${missingSFP.map((p) => physicalTest.normalizePortName(p.name)).join('<br/>')}</div>`
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -1765,8 +1800,37 @@ ${log}
|
||||||
</table>`
|
</table>`
|
||||||
|
|
||||||
await sendMessageToMail(
|
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
|
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([])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ export class PhysicalPortTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
onDone() {
|
onDone() {
|
||||||
this.getFormReport()
|
// this.getFormReport()
|
||||||
// this.ports.clear()
|
// this.ports.clear()
|
||||||
console.log('✅ Physical Test DONE')
|
console.log('✅ Physical Test DONE')
|
||||||
}
|
}
|
||||||
|
|
@ -175,7 +175,7 @@ export class PhysicalPortTest {
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
Total Ports: ${report.ports.length}<br/>
|
Total Ports: ${report.ports.length}<br/>
|
||||||
Ports Tested (UP): <b style="color: #008000;">${tested.length}</b><br/>
|
Ports Tested (UP): <b style="color: #008000;">${tested.length} (${testedPoE?.length} PoE, ${testedSFP?.length} SFP)</b><br/>
|
||||||
Ports Missing: <b style="color: #ff0000;">${missing.length}</b><br/>
|
Ports Missing: <b style="color: #ff0000;">${missing.length}</b><br/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
||||||
|
|
@ -510,6 +510,13 @@ export const RULES: LogRule[] = [
|
||||||
level: 'WARN',
|
level: 'WARN',
|
||||||
message: 'Hardware environment warning',
|
message: 'Hardware environment warning',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'PS_INCOMPATIBLE',
|
||||||
|
category: 'HARDWARE',
|
||||||
|
match: /%PLATFORM_FEP-\d+-FRU_PS_INCOMPATIBLE/i,
|
||||||
|
level: 'FAIL',
|
||||||
|
message: 'Power supply incompatible',
|
||||||
|
},
|
||||||
// ERROR
|
// ERROR
|
||||||
{
|
{
|
||||||
id: 'MEMORY_ERROR',
|
id: 'MEMORY_ERROR',
|
||||||
|
|
|
||||||
|
|
@ -635,10 +635,10 @@ export class WebSocketIo {
|
||||||
console.error('Error sending wiki message:', error)
|
console.error('Error sending wiki message:', error)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await sendMessageToMail(
|
// await sendMessageToMail(
|
||||||
`[${scenarioName || 'DPELP'}] - ${stationName} - ${dataFormat}`,
|
// `[${scenarioName || 'DPELP'}] - ${stationName} - ${dataFormat}`,
|
||||||
tableHTML
|
// tableHTML
|
||||||
)
|
// )
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error sending mail:', error)
|
console.error('Error sending mail:', error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -601,7 +601,7 @@ function App() {
|
||||||
const openTerminal = (line: TLine) => {
|
const openTerminal = (line: TLine) => {
|
||||||
setOpenModalTerminal(true);
|
setOpenModalTerminal(true);
|
||||||
const data = { ...line };
|
const data = { ...line };
|
||||||
if (!line.userEmailOpenCLI) {
|
if (!line.userOpenCLI) {
|
||||||
data.cliOpened = true;
|
data.cliOpened = true;
|
||||||
data.userEmailOpenCLI = user?.email;
|
data.userEmailOpenCLI = user?.email;
|
||||||
data.userOpenCLI = user?.userName;
|
data.userOpenCLI = user?.userName;
|
||||||
|
|
|
||||||
|
|
@ -923,6 +923,16 @@ const ModalTerminal = ({
|
||||||
: ""}
|
: ""}
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Text size="md" mr={"sm"} fw={"bold"}>
|
||||||
|
MAC:
|
||||||
|
</Text>
|
||||||
|
<Text size="md">
|
||||||
|
{findDataShowVersion()
|
||||||
|
? findDataShowVersion()?.MAC_ADDRESS || ""
|
||||||
|
: ""}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
<Flex>
|
<Flex>
|
||||||
<Text size="md" mr={"sm"} fw={"bold"}>
|
<Text size="md" mr={"sm"} fw={"bold"}>
|
||||||
License:
|
License:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue