205 lines
6.1 KiB
TypeScript
205 lines
6.1 KiB
TypeScript
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(lines: string) {
|
||
for (const line of lines.split('\n')) {
|
||
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) continue
|
||
|
||
const port = this.ports.get(iface)
|
||
if (!port) continue
|
||
|
||
port.lastSeen = new Date()
|
||
|
||
if (state && port.lastState === state) continue
|
||
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`
|
||
}
|
||
}
|