ATC_SIMPLE/BACKEND/app/services/physical_test_service.ts

204 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import moment from 'moment'
import { normalizeInterface } from '../ultils/helper.js'
import { PhysicalTestReport, PhysicalTestResult, PortState } from '../ultils/types.js'
const LINK_REGEX =
/Interface\s+((?:FastEthernet|GigabitEthernet|TenGigabitEthernet|TwentyFiveGigE|FortyGigabitEthernet|HundredGigE|Ethernet|Port-channel|Fa|Gi|Te|Hu|Eth)[\w\/.-]+),\s+changed state to\s+(up|down)/i
const POE_GRANTED_REGEX = /%ILPOWER-\d+-POWER_GRANTED:\s+Interface\s+([\w\/.-]+):\s+Power granted/i
const POE_DISCONNECT_REGEX =
/%ILPOWER-\d+-IEEE_DISCONNECT:\s+Interface\s+([\w\/.-]+):\s+PD removed/i
export class PhysicalPortTest {
public ports = new Map<string, PortState>()
private expectedPorts: string[]
public done = false
private startTime: Date
public inventory: any
constructor(expectedPorts: string[]) {
this.expectedPorts = expectedPorts
this.startTime = new Date()
this.inventory = ''
expectedPorts.forEach((p) => {
this.ports.set(normalizeInterface(p), {
name: normalizeInterface(p),
tested: false,
})
})
}
start(expectedPorts: string[], inventory: any) {
this.ports.clear()
this.startTime = new Date()
this.expectedPorts = expectedPorts
this.inventory = inventory
this.done = false
expectedPorts.forEach((p) => {
this.ports.set(normalizeInterface(p), {
name: normalizeInterface(p),
tested: false,
})
})
// this.connection.writeCommand('terminal length 0')
// this.connection.writeCommand('terminal monitor')
// this.connection.onLog((line) => {
// this.handleLog(line);
// });
}
handleLog(line: string) {
let iface: string | null = null
let markTested = false
let state: 'up' | 'down' | undefined
// 1⃣ LINK / LINEPROTO
let match = line.match(LINK_REGEX)
if (match) {
iface = normalizeInterface(match[1])
state = match[2] as 'up' | 'down'
if (state === 'up') markTested = true
}
// 2⃣ POE POWER GRANTED
match = line.match(POE_GRANTED_REGEX)
if (match) {
iface = normalizeInterface(match[1])
markTested = true
}
// 3⃣ POE DISCONNECT
match = line.match(POE_DISCONNECT_REGEX)
if (match) {
iface = normalizeInterface(match[1])
markTested = true
}
if (!iface) return
const port = this.ports.get(iface)
if (!port) return
port.lastSeen = new Date()
if (state && port.lastState === state) return
if (state) port.lastState = state
// ⭐ PASS nếu có ít nhất 1 event hợp lệ
if (markTested && !port.tested) {
port.tested = true
this.checkDone()
}
return this.getTestedPorts()
}
getTestedPorts(): string[] {
return Array.from(this.ports.values())
.filter((p) => p.tested)
.map((p) => p.name)
.sort()
}
resetTestedPorts() {
// this.ports.clear()
this.expectedPorts.forEach((p) => {
this.ports.set(normalizeInterface(p), {
name: normalizeInterface(p),
tested: false,
})
})
}
private checkDone() {
const testedCount = [...this.ports.values()].filter((p) => p.tested).length
if (testedCount === this.expectedPorts.length) {
this.done = true
this.onDone()
}
}
onDone() {
this.getFormReport()
// this.ports.clear()
console.log('✅ Physical Test DONE')
}
getFormReport() {
const report: PhysicalTestReport = {
device: {
model: this?.inventory?.pid || '',
serial: this?.inventory?.sn || '',
},
startTime: this.startTime,
endTime: new Date(),
durationMs: Date.now() - this.startTime.getTime(),
ports: Array.from(this.ports.values()),
}
return this.generateEmailReport(report)
// console.log('✅ Physical Test DONE')
}
getResult(): PhysicalTestResult {
const tested = [...this.ports.values()].filter((p) => p.tested)
const missing = [...this.ports.values()].filter((p) => !p.tested).map((p) => p.name)
return {
expected: this.expectedPorts.length,
tested: tested.length,
missingPorts: missing,
status: this.done ? 'DONE' : 'RUNNING',
}
}
generateEmailReport(report: PhysicalTestReport): string {
const tested = report.ports.filter((p) => p.tested)
const missing = report.ports.filter((p) => !p.tested)
const status = missing.length === 0 ? 'PASS' : 'WARNING'
return `
Physical Port Test Report<br/>
────────────────────────────────<br/>
Model : <b>${report.device.model ?? 'N/A'}</b><br/>
Serial Number : <b>${report.device.serial ?? 'N/A'}</b><br/>
Started At : ${moment(report.startTime).format('YYYY/MM/DD, HH:mm:ss')}<br/>
Finished At : ${moment(report.endTime).format('YYYY/MM/DD, HH:mm:ss')}<br/>
Duration : ${this.formatDuration(report.durationMs)}<br/>
Status : ${status === 'PASS' ? '✅ PASS' : '⚠️ WARNING'}<br/>
<br/>
────────────────────────────────<br/>
<b>Test Summary</b><br/>
────────────<br/>
Total Ports : ${report.ports.length}<br/>
Ports Tested (UP) : ${tested.length}<br/>
Ports Missing : ${missing.length}<br/>
<br/>
────────────────────────────────<br/>
<b>Passed Ports</b><br/>
────────────<br/>
${tested.map((p) => p.name).join('<br/>')}<br/>
<br/>
${
missing.length
? `
────────────────────────────────<br/>
<b>Missing Ports</b><br/>
─────────────<br/>
${missing.map((p) => p.name).join('<br/>')}
`
: ''
}<br/>
<br/>
`.trim()
}
formatDuration(ms: number): string {
const totalSeconds = Math.floor(ms / 1000)
const minutes = Math.floor(totalSeconds / 60)
const seconds = totalSeconds % 60
return `${minutes}m ${seconds}s`
}
}