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() 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]) state = 'up' 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 Ports Test Report
Model : ${report.device.model ?? 'N/A'}
Serial Number : ${report.device.serial ?? 'N/A'}
Status : ${status === 'PASS' ? '✅ PASS' : '⚠️ WARNING'}
Total Ports : ${report.ports.length}
Ports Tested (UP) : ${tested.length}
Ports Missing : ${missing.length}

────────────────────────────────
Passed Ports
${ tested.length ? `
${tested.map((p) => this.normalizePortName(p.name)).join('
')}

` : '' } ${ missing.length ? ` ────────────────────────────────
Missing Ports
${missing.map((p) => this.normalizePortName(p.name)).join('
')}

` : '' }

`.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` } normalizePortName(port: string): string { if (!port) return '' // Example inputs: "Fa0/1", "Gi0/0/1", "Fa0/0/2" const isSFP = port.includes('SFP') const match = port .replace('(SFP)', '') .trim() .match(/^([A-Za-z]+)([\d/]+)$/) if (!match) return port const type = match[1] // Fa, Gi, Te, etc. const numbers = match[2] // "0/1" / "0/0/1" / "0/0/2" // Get the last part after slash const parts = numbers.split('/') const last = parts[parts.length - 1] const preLast = parts?.length > 1 ? parts[parts.length - 2] : '' return `${type?.slice(0, 2)}${preLast ? preLast + '/' : ''}${last}${isSFP ? ' (SFP)' : ''}` } /** * Function 1: Lấy danh sách các cổng có module SFP từ lệnh 'show interfaces status' * Logic: Tìm dòng bắt đầu bằng Tên Port và có chứa từ khóa "SFP" ở cuối. * Function 2: Lấy danh sách các cổng đang cấp nguồn (PoE on) từ lệnh 'show power inline' * Logic: Tìm dòng bắt đầu bằng Tên Port, cột tiếp theo là Admin status, cột tiếp theo là 'on'. */ detectPorts(output: string): string[] { for (const line of output.split('\n')) { if (line?.includes('include')) continue const ports: string[] = [] const regexPoE = /^([A-Za-z0-9\/]+).*\on\b/i const regexSFP = /^([A-Za-z0-9\/]+).*\bSFP\b/i let matchPoE = line.match(regexPoE) if (matchPoE) { ports.push(matchPoE[1]) } let matchSFP = line.match(regexSFP) if (matchSFP) { ports.push(matchSFP[1] + ' (SFP)') } if (ports.length > 0) { ports .filter((el) => el) .forEach((el) => { const iface = normalizeInterface(el) const port = this.ports.get(iface) if (port) { port.lastState = 'up' port.tested = true } }) } } return this.getTestedPorts() } }