diff --git a/BACKEND/app/services/line_connection.ts b/BACKEND/app/services/line_connection.ts index 8c7e4e9..fcf202f 100644 --- a/BACKEND/app/services/line_connection.ts +++ b/BACKEND/app/services/line_connection.ts @@ -157,7 +157,9 @@ export default class LineConnection { this.outputPhysicalTest = '' this.listDeviceIos = [] } - + /** + * Connect to line with socket + */ connect(timeoutMs = 5000) { return new Promise((resolve, reject) => { const { ip, port, lineNumber, id, stationId } = this.config @@ -304,10 +306,16 @@ export default class LineConnection { }) } + /** + * Waiting with millisecond + */ private sleep(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)) } + /** + * Write a command with socket.write + */ async writeCommand(cmd: string | Buffer, userName = '') { if (this.client.destroyed) { console.log(`⚠️ Cannot send, line ${this.config.lineNumber} is closed`) @@ -326,6 +334,9 @@ export default class LineConnection { this.client.write(cmd) } + /** + * Disconnect socket with line + */ async disconnect() { try { console.log('[DISCONNECT] Line', this.config.lineNumber) @@ -342,6 +353,9 @@ export default class LineConnection { } } + /** + * Run a scenario as DPELP, Breaking password, load ios,... + */ async runScript(script: Scenario, userName: string) { if (!this.client || this.client.destroyed) { console.log('Not connected') @@ -575,7 +589,7 @@ export default class LineConnection { // } if (typeof step.send !== 'undefined') { - // console.log(Date.now() - now, (step?.send ?? '[ENTER]').toString()) + console.log(Date.now() - now, (step?.send ?? '[ENTER]').toString()) this.outputScenario += `\n---send-command---"${(step?.send ?? '[ENTER]').toString().replace(/\r/g, '\\r').replace(/\n/g, '\\n')}"---${now}---\n` appendLog( `\n---send-command---"${(step?.send ?? '[ENTER]').toString().replace(/\r/g, '\\r').replace(/\n/g, '\\n')}"---${now}---\n`, @@ -620,6 +634,9 @@ export default class LineConnection { }) } + /** + * Reconnect socket with line + */ public async reconnect(): Promise { try { this.disconnect() @@ -632,6 +649,9 @@ export default class LineConnection { } } + /** + * User open CLI from front-end + */ userOpenCLI(user: User) { this.config.openCLI = true this.config.userEmailOpenCLI = user.userEmail @@ -651,6 +671,9 @@ export default class LineConnection { ) } + /** + * User close CLI from front-end + */ userCloseCLI() { this.config.openCLI = false this.config.userEmailOpenCLI = '' @@ -662,6 +685,9 @@ export default class LineConnection { }) } + /** + * Clear output buffer + */ clearCLI() { this.config.output = '' this.socketIO.emit('user_clear_terminal', { @@ -671,6 +697,9 @@ export default class LineConnection { setTimeout(() => this.writeCommand('\r\n'), 100) } + /** + * Waiting for a expect with until catch it from output + */ waitForExpect = async (expect: string, timeout = 60000) => { const start = Date.now() // console.log('[EXPECT]', expect, timeout) @@ -684,6 +713,9 @@ export default class LineConnection { return false } + /** + * Detect inventory data from output + */ getInventory = () => { const data = textfsmResults(this.outputInventory, 'show inventory') try { @@ -714,7 +746,9 @@ export default class LineConnection { } } - // Gửi nhiều ký tự ESC để vào ROMMON + /** + * Gửi nhiều ký tự ESC để vào ROMMON + */ breakSpam() { console.log('SPAM Break to line:', this.config.lineNumber) let count = 0 @@ -728,6 +762,9 @@ export default class LineConnection { }, 1) } + /** + * Set Baud of line + */ async setBaud(baud: number) { this.writeCommand('enable\r\n') await sleep(500) @@ -743,6 +780,9 @@ export default class LineConnection { this.writeCommand('\r\n') } + /** + * Get content's log of line with date + */ async getLog(date: string) { const logDir = path.join('storage', 'system_logs') const logFile = path @@ -759,6 +799,9 @@ export default class LineConnection { return await fs.promises.readFile(logFile, 'utf8') } + /** + * Detect log by call api gpt, return summary and issues + */ async detectLogWithAI(log: string) { try { const payload = { @@ -812,6 +855,9 @@ export default class LineConnection { return '' } + /** + * Add cache to list history devices on this line + */ async addHistory(stationId: number, lineId: number, item: HistoryItem) { if (!item.pid || !item.sn) return const key = `station:${stationId}:line:${lineId}:history` @@ -850,12 +896,18 @@ export default class LineConnection { return true } + /** + * Get list history devices + */ async getHistory(stationId: number, lineId: number) { const key = `station:${stationId}:line:${lineId}:history` const items = await redis.zrange(key, 0, -1) return items.map((i) => JSON.parse(i)) } + /** + * Handle raw log to regex error + */ handleLogLine = (line: string) => { try { const parsed = classifyLog(line) @@ -865,6 +917,9 @@ export default class LineConnection { } } + /** + * Check raw log was regex each 5 minutes, if has error will send email report + */ checkLog() { const interval = setInterval(async () => { try { @@ -902,6 +957,9 @@ export default class LineConnection { }, 300000) } + /** + * Render table to view error + */ renderErrorTable(rows: ErrorRow[]): string { if (!rows.length) { return `

No errors detected

` @@ -942,6 +1000,9 @@ export default class LineConnection { ` } + /** + * Return a body email + */ buildEmailContent(result: TestResult): string { const rows = mapErrorsToRows(result.errors) const table = this.renderErrorTable(rows) @@ -956,6 +1017,9 @@ export default class LineConnection { ` } + /** + * Update note of SN to ERP after run DPELP + */ async updateNote(sn: string, data: DataDPELP) { const licenses = Array.isArray(data.license) ? [...new Set(data.license)] @@ -968,6 +1032,9 @@ export default class LineConnection { await updateNoteToERP(sn, note) } + /** + * Starting physical test (PoE ports testing) + */ async runPhysicalTest() { if (this.config.runningPhysical) { console.log('Running physical test') @@ -1002,6 +1069,9 @@ export default class LineConnection { // }, 10000) } + /** + * End all testing + */ endTesting() { this.physicalTest.done = true this.physicalTest.resetTestedPorts() @@ -1018,6 +1088,9 @@ export default class LineConnection { }) } + /** + * Get list PoE ports + */ async getPorts(): Promise { this.writeCommand(' show power inline\r\n') this.writeCommand(' \r\n') @@ -1040,6 +1113,9 @@ export default class LineConnection { return [...new Set(ports)] } + /** + * Send report after done physical test + */ async sendReportPhysicalTest() { const formReport = this.physicalTest.getFormReport() await sendMessageToMail( @@ -1048,6 +1124,9 @@ export default class LineConnection { ) } + /** + * Handle load ios for router + */ async loadIosRouter(nameIos: string, userName: string) { const station = await Station.find(this.config.stationId) if (!station) return @@ -1193,6 +1272,9 @@ export default class LineConnection { await this.sendEmailLoadIos(nameIos, startTime) } + /** + * Handle load ios for switch + */ async loadIosSwitch(nameIos: string, userName: string) { const station = await Station.find(this.config.stationId) if (!station) return @@ -1402,6 +1484,9 @@ export default class LineConnection { await this.sendEmailLoadIos(nameIos, startTime) } + /** + * Send mail report after load ios + */ async sendEmailLoadIos(nameIos: string, startTime: string) { const timeZone = process.env.TIME_ZONE || 'Australia/Sydney' const dataFormat = momentTZ().tz(timeZone).format('YYYY/MM/DD, HH:mm:ss') @@ -1422,20 +1507,38 @@ export default class LineConnection { ) } + /** + * Check list ios exist on flash + */ async checkDeviceFlash() { this.writeCommand(' enable\r\n') this.writeCommand('show flash:\r\n') await sleep(2000) - const ios = [] - const binRegex = /^\s*\d+\s+-rwx\s+\d+\s+.*?\s+([^\s]+\.bin)\s*$/gim + const output = this.outputBuffer + const ios: string[] = [] let match - while ((match = binRegex.exec(this.outputBuffer)) !== null) { - ios.push(match[1]) + + const SWITCH_BIN_REGEX = /^\s*\d+\s+-rwx\s+\d+\s+.*?\s+([^\s]+\.bin)\s*$/gim + const ROUTER_BIN_REGEX = /^\s*\d+\s+(\d+)\s+.*?\s+([^\s]+\.bin)\s*$/gim + + // 🔍 Detect device type + const isSwitch = output.includes('-rwx') + const regex = isSwitch ? SWITCH_BIN_REGEX : ROUTER_BIN_REGEX + + // reset regex state + regex.lastIndex = 0 + + while ((match = regex.exec(output)) !== null) { + ios.push(isSwitch ? match[1] : match[2]) } + return ios } + /** + * Delete File on Flash + */ async deleteFileOnFlash(fileName: string) { await this.writeCommand(`delete flash:${fileName}\r\n`) await this.writeCommand(`\r\n`) @@ -1443,6 +1546,9 @@ export default class LineConnection { await sleep(3000) } + /** + * Upload file from flash to TFTP server + */ async uploadFileToServerTFTP(fileName: string, server: string) { this.config.runningScenario = 'Upload file' await this.writeCommand(`copy flash: tftp:\r\n`) @@ -1460,7 +1566,9 @@ export default class LineConnection { } } - // function get list ios + /** + * Get list ios from TFTP server + */ async getListIos() { try { const controller = new IosLicenseController() @@ -1472,6 +1580,9 @@ export default class LineConnection { } } + /** + * Get current boot ios of device + */ async getCurrentBootIos() { this.writeCommand('show version | include System image\r\n') await sleep(2000) @@ -1482,6 +1593,9 @@ export default class LineConnection { return match ? match[1] : null } + /** + * Backup ios to TFTP, after that delete it on flash for free space + */ async backupIos(nameIos: string) { const station = await Station.find(this.config.stationId) if (!station) return