import fs from 'node:fs' import { textfsmResults } from './../ultils/templates/index.js' import net from 'node:net' import { appendLog, buildBody, canInputCommand, classifyLog, cleanData, convertFromKilobytesString, detectConfigRamByModel, detectScenarioByModel, escapeHtml, isRamSufficient, isValidJson, LogStreamBuffer, mapErrorsToRows, mapToLineFormat, normalizeInterface, parseLicenseReport, sendMessageToMail, sleep, TestSession, updateNoteToERP, } from '../ultils/helper.js' import Scenario from '#models/scenario' import path from 'node:path' import axios from 'axios' import redis from '@adonisjs/redis/services/main' import Line from '#models/line' import PromptAi from '#models/prompt_ai' import { CustomSocket, ErrorRow, TestResult } from '../ultils/types.js' import momentTZ from 'moment-timezone' import { PhysicalPortTest } from './physical_test_service.js' import Station from '#models/station' import IosLicenseController from '#controllers/ios_license_controller' type Inventory = { pid: string vid: string sn: string licenseLevel: string licenseType: string nextLicenseLevel: string } interface LineConfig { id: number port: number lineNumber: number ip: string stationId: number stationName: string stationIp: string apcName?: string outlet: number output: string status: string baud: number openCLI: boolean userEmailOpenCLI: string userOpenCLI: string inventory: any latestScenario?: { name: string time: number detectAI?: { status: string[] issue: string[] summary: string } } data: { command: string output: string textfsm: string }[] ports: string[] runningScenario: string runningPhysical: boolean listFeatureTested: string[] isReady: boolean isSkipPhysical?: boolean reasonSkipPhysical?: string // history: string } /** HISTORY * PID * SN * VID * Timestamp * Scenario */ interface HistoryItem { pid: string vid: string sn: string scenario: string id: number number: number stationId: number timestamp?: number } interface User { userEmail: string userName: string } interface DataDPELP { line: number pid: any vid: any sn: any ios: string mac: string license: any issues: string[] summary: string } export default class LineConnection { public client: net.Socket public config: LineConfig public readonly socketIO: any private outputBuffer: string private connecting: boolean private waitingScenario: boolean private outputInventory: string private outputScenario: string private bufferLog: LogStreamBuffer public dataDPELP: DataDPELP | string private listScenarios: number[] public handleClearLine: () => void private session: TestSession public physicalTest: PhysicalPortTest private outputPhysicalTest: string private outputLoadIosLicense: string | boolean private listDeviceIos: string[] private debounceTimer: NodeJS.Timeout | null = null private testingPortPoE: boolean private outputTestingPortPoE: string private debounceSendSummaryReport: NodeJS.Timeout | null = null private isPingToServer: boolean private outputPingToServer: string constructor(config: LineConfig, socketIO: any, handleClearLine: () => void) { this.config = config this.socketIO = socketIO this.client = new net.Socket() this.outputBuffer = '' this.connecting = false this.waitingScenario = false this.outputInventory = '' this.outputScenario = '' this.bufferLog = new LogStreamBuffer() this.dataDPELP = { line: this.config.lineNumber, pid: '', vid: '', sn: '', ios: '', mac: '', license: [], issues: ['No data'], summary: '', } this.listScenarios = [] this.session = new TestSession() this.handleClearLine = handleClearLine this.physicalTest = new PhysicalPortTest([]) this.outputPhysicalTest = '' this.outputLoadIosLicense = '' this.listDeviceIos = [] this.debounceTimer = null this.debounceSendSummaryReport = null this.testingPortPoE = false this.outputTestingPortPoE = '' this.isPingToServer = false this.outputPingToServer = '' } /** * Connect to line with socket */ connect(timeoutMs = 5000) { return new Promise((resolve, reject) => { const { ip, port, lineNumber, id, stationId } = this.config let resolvedOrRejected = false // Set timeout this.client.setTimeout(timeoutMs) console.log(`πŸ”Œ Connecting to line ${lineNumber} (${ip}:${port})...`) this.client.connect(port, ip, () => { if (resolvedOrRejected) return resolvedOrRejected = true console.log(`[${Date.now()}] βœ… Connected to line ${lineNumber} (${ip}:${port})`) this.connecting = true setTimeout(() => { this.config.status = 'connected' // this.retryConnect = 0 this.connecting = false this.socketIO.emit('line_connected', { stationId, lineId: id, lineNumber, status: 'connected', }) this.config.listFeatureTested = [] this.config.isSkipPhysical = false this.config.reasonSkipPhysical = '' this.sendFeatureTested() this.checkLog() resolve() }, 2000) }) this.client.on('data', (data) => { let message = this.connecting ? cleanData(data.toString()) : data.toString() const lines = this.bufferLog.push(data) lines.forEach(this.handleLogLine) let rawData = '' if (this.config.runningScenario) { this.waitingScenario = true this.outputBuffer += message this.outputScenario += message if (!this.config.inventory) this.outputInventory = this.outputInventory.slice(-3000) + message } if (this.outputLoadIosLicense) { if (this.outputLoadIosLicense === true) this.outputLoadIosLicense = '' this.outputLoadIosLicense += cleanData(data.toString()) } if (this.config.runningPhysical) { this.outputPhysicalTest += message this.outputTestingPortPoE += message if (this.debounceTimer) clearTimeout(this.debounceTimer) if (this.testingPortPoE) this.debounceTimer = setTimeout(() => { this.flushLogBuffer() }, 1000) // 1s debounce } if (this.isPingToServer) this.outputPingToServer += cleanData(data.toString()) if (data.toString().includes('More') || data.toString().includes('MORE')) this.writeCommand(' ') // let output = cleanData(message) // console.log(`πŸ“¨ [${this.config.port}] ${message}`) // Handle netOutput with backspace support for (const char of message) { if (char === '\x7F' || char === '\x08') { this.config.output = this.config.output.slice(0, -1) // message = message.slice(0, -1) } else { rawData += char } } this.config.output += cleanData(rawData) this.config.output = this.config.output.slice(-15000) if (!this.config.isReady && canInputCommand(message)) { this.config.isReady = true this.socketIO.emit('update_status_ready', { stationId, lineId: id, isReady: true, }) } this.socketIO.emit('line_output', { stationId, lineId: id, data: message, ports: this.config.ports, }) setTimeout(() => { if (!this.config.inventory) { this.getInventory() } }, 5000) appendLog( cleanData(message), this.config.stationId, this.config.stationName, this.config.stationIp, this.config.lineNumber ) }) this.client.on('error', (err) => { if (resolvedOrRejected) return resolvedOrRejected = true console.error(`❌ Error line ${lineNumber}:`, err.message) this.config.output += '\r\n' + err.message + '\r\n' this.socketIO.emit('line_error', { stationId, lineId: id, error: '\r\n' + err.message + '\r\n', }) this.endTesting() resolve() }) this.client.on('close', async () => { console.log(`[${Date.now()}] πŸ”Œ Line ${lineNumber} disconnected`) this.config.status = 'disconnected' this.config.output += this.config.output + '[CLEAR_TERMINAL_SCROLL_BACK]' this.config.listFeatureTested = [] this.config.isSkipPhysical = false this.config.reasonSkipPhysical = '' this.config.latestScenario = undefined this.physicalTest = new PhysicalPortTest([]) this.config.isReady = false // this.config.inventory = undefined this.socketIO.emit('line_disconnected', { stationId, lineId: id, lineNumber, status: 'disconnected', }) // if (this.retryConnect <= 5) { // await this.sleep(5000) // console.log(`Retry connect line [${this.config.lineNumber}] times`, this.retryConnect) // this.retryConnect += 1 // await this.reconnect() // } else { // this.retryConnect = 0 // } this.endTesting() }) this.client.on('timeout', () => { if (resolvedOrRejected) return resolvedOrRejected = true const message = '\r\nConnection timeout!!\r\n' this.config.output += message this.socketIO.emit('line_output', { stationId, lineId: id, data: message, }) appendLog( cleanData(message), this.config.stationId, this.config.stationName, this.config.stationIp, this.config.lineNumber ) console.log(`⏳ Connection timeout line ${lineNumber}`) this.client.destroy() resolve() // reject(new Error('Connection timeout')) }) }) } /** * 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) { if (this.client.destroyed) { console.log(`⚠️ Cannot send, line ${this.config.lineNumber} is closed`) this.disconnect() // this.disconnect() // await sleep(2000) // await this.connect() return } // console.log( // `Write command "${cmd.toString().replace(/\r/g, '\\r').replace(/\n/g, '\\n')}" to line ${this.config.lineNumber} of ${this.config.stationName}` // ) this.client.write(cmd) } /** * Disconnect socket with line */ async disconnect() { try { console.log('[DISCONNECT] Line', this.config.lineNumber) // this.handleClearLine() this.client.destroy() this.config.status = 'disconnected' this.socketIO.emit('line_disconnected', { ...this.config, status: 'disconnected', }) console.log(`πŸ”» Closed connection to line ${this.config.lineNumber}`) } catch (e) { console.error('Error closing line:', e) } } /** * 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') this.config.runningScenario = '' this.socketIO.emit('running_scenario', { stationId: this.config.stationId, lineId: this.config.id, title: '', }) this.outputBuffer = '' return } if (!this.config.isReady) { console.log('Device is not ready') return } if (this.config.runningScenario || this.config.runningPhysical) { console.log('Script already running') return } console.log( `Run scenario "${script?.title}" to line ${this.config.lineNumber} of ${this.config.stationName}` ) this.config.runningScenario = script?.title this.config.data = [] this.outputScenario = '' this.socketIO.emit('running_scenario', { stationId: this.config.stationId, lineId: this.config.id, title: script?.title, }) if (script?.send_result || script?.sendResult) { this.dataDPELP = '' // this.config.inventory = '' } if (script?.isReboot) { await sleep(10000) for (let index = 0; index < 30; index++) { await sleep(1000) this.breakSpam() } } const now = Date.now() this.outputScenario += `\n\n---start-scenarios---${now}---${userName}---${script?.title}---\n---scenario---${script?.title}---${now}---\n` appendLog( `\n\n---start-scenarios---${now}---${userName}---${script?.title}---\n---scenario---${script?.title}---${now}---\n`, this.config.stationId, this.config.stationName, this.config.stationIp, this.config.lineNumber ) this.config.latestScenario = { name: script?.title, time: now, } const steps = typeof script?.body === 'string' ? JSON.parse(script?.body) : [] let stepIndex = 0 // Create a timeout let timeoutTimer: NodeJS.Timeout | null = null const timeoutNumber = script.timeout ? Number(script.timeout) * 1000 : 300000 const onTimeout = () => { this.config.runningScenario = '' this.socketIO.emit('running_scenario', { stationId: this.config.stationId, lineId: this.config.id, title: '', }) this.outputBuffer = '' this.outputScenario = '' this.config.output += '\nTimeout run scenario\n' this.dataDPELP = { line: this.config.lineNumber, pid: '', vid: '', sn: '', ios: '', mac: '', license: [], issues: ['No data'], summary: '', } this.socketIO.emit('line_output', { stationId: this.config.stationId, lineId: this.config.id, data: '\nTimeout run scenario\n', }) this.outputScenario += `\n---end-scenarios---${now}---${userName}---\n` appendLog( `\n---end-scenarios---${now}---${userName}---\n`, this.config.stationId, this.config.stationName, this.config.stationIp, this.config.lineNumber ) // reject(new Error('Script timeout')) } const resetTimeout = () => { // console.log('resetTimeout', timeoutNumber) // this.outputBuffer = '' if (timeoutTimer) clearTimeout(timeoutTimer) timeoutTimer = setTimeout(onTimeout, timeoutNumber) } return new Promise((resolve, reject) => { timeoutTimer = setTimeout(onTimeout, timeoutNumber) const runStep = async (index: number) => { if (index >= steps.length) { if (this.waitingScenario) { this.waitingScenario = false setTimeout(() => { runStep(index) }, 5000) return } else if (timeoutTimer) clearTimeout(timeoutTimer) this.outputScenario += `\n---end-scenarios---${now}---${userName}---\n` this.outputBuffer = '' this.config.runningScenario = '' this.socketIO.emit('running_scenario', { stationId: this.config.stationId, lineId: this.config.id, title: '', }) const logScenarios = this.outputScenario const data = textfsmResults(logScenarios, '') let pid = this.config.inventory?.pid || '' try { for (const item of data) { if (item?.textfsm && isValidJson(item?.textfsm)) { if ( ['show inventory', 'sh inventory', 'show inv', 'sh inv'].includes(item.command) ) { const dataInventory = JSON.parse(item.textfsm)[0] this.config.inventory = this.config.inventory ? { ...this.config.inventory, ...dataInventory } : dataInventory pid = dataInventory?.pid || '' this.addHistory(this.config.stationId, this.config.id, { id: this.config.id, number: this.config.lineNumber, stationId: this.config.stationId, pid: dataInventory?.pid, sn: dataInventory?.sn, vid: dataInventory?.vid, scenario: script?.title, timestamp: Date.now(), }) } if (['show version', 'sh version', 'show ver', 'sh ver'].includes(item.command)) { const dataVer = JSON.parse(item.textfsm)[0] this.config.inventory = this.config.inventory ? { ...this.config.inventory, ...dataVer } : dataVer if (pid && (dataVer?.MEMORY || dataVer?.USB_FLASH)) { await this.checkConfigRam( dataVer?.MEMORY || '', dataVer?.USB_FLASH || '', pid, cleanData(item.output) ) } } if ( item.command?.trim()?.includes('show env') || item.command?.trim()?.includes('sh env') ) { const dataEnv = await this.detectShowEnvWithAI(item.output) item.dataAI = dataEnv } item.textfsm = JSON.parse(item.textfsm) } } const scenario = await detectScenarioByModel(pid, this.listScenarios) console.log(pid, scenario?.title, this.listScenarios) if ( scenario && scenario.id !== script.id && scenario.title.includes('DPELP') && script.title.includes('DPELP') ) { this.listScenarios.push(scenario.id) // this.outputScenario = '' this.runScript(scenario, userName) // this.socketIO.emit('confirm_scenario', { // scenario: scenario, // id: this.config.id, // }) resolve(true) return } if (script?.send_result || script?.sendResult) { const detectLog = await this.detectLogWithAI(logScenarios) const result = mapToLineFormat({ lineNumber: this.config.lineNumber, inventory: this.config.inventory, latestScenario: { detectAI: { issue: detectLog, summary: '', status: [] }, }, data, }) // if (script?.send_result || script?.sendResult) { this.dataDPELP = result console.log( `DPELP DATA line ${this.config.lineNumber} of ${this.config.stationName}:`, this.dataDPELP ) this.config.listFeatureTested = [ ...new Set([...this.config.listFeatureTested, 'DPELP']), ] // if (!this.config.listFeatureTested.includes('PHYSICAL')) this.runPhysicalTest() this.sendFeatureTested() // Set timeout send report // this.setTimeoutSendSummaryReport( // !this.config.listFeatureTested.includes('PHYSICAL') ? 600000 : 30000 // ) // } if (this.config.latestScenario) this.config.latestScenario = { ...this.config.latestScenario, detectAI: { issue: detectLog, summary: '', status: [] }, } // if (result.sn) { // this.updateNote(result.sn, result) // } } this.config.data = data this.socketIO.emit('data_textfsm', { stationId: this.config.stationId, lineId: this.config.id, data, inventory: this.config.inventory || null, latestScenario: this.config.latestScenario || null, }) } catch (error) { console.log(error) } appendLog( `\n---end-scenarios---${now}---${userName}---\n`, this.config.stationId, this.config.stationName, this.config.stationIp, this.config.lineNumber ) this.listScenarios = [] resolve(true) return } else resetTimeout() const step = steps[index] let repeatCount = Number(step.repeat) || 1 const delay = step?.delay ? Number(step?.delay) * 1000 : 1000 const sendCommand = async () => { // if (repeatCount <= 0) { // // Done β†’ next step // stepIndex++ // return runStep(stepIndex) // } if (typeof step.send !== 'undefined') { 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`, this.config.stationId, this.config.stationName, this.config.stationIp, this.config.lineNumber ) this.writeCommand((step?.send || '') + '\r\n') } repeatCount-- if (repeatCount <= 0) { // Done β†’ next step stepIndex++ return runStep(stepIndex) } else setTimeout(() => sendCommand(), delay) } // NαΊΏu expect rα»—ng β†’ gα»­i ngay if (!step?.expect || step?.expect.trim() === '') { setTimeout(() => sendCommand(), delay) return } // while (this.outputBuffer) { // await sleep(200) // if (this.outputBuffer.includes(step.expect)) { // this.outputBuffer = '' // setTimeout(() => sendCommand(), delay) // } // } const matched = await this.waitForExpect( step.expect.trim(), script?.timeout ? Number(script?.timeout) * 1000 : 60000 ) if (matched) setTimeout(() => sendCommand(), delay) } runStep(stepIndex) }) } /** * Reconnect socket with line */ public async reconnect(): Promise { try { this.disconnect() this.client = new net.Socket() await this.sleep(1000) await this.connect() return true } catch (err: any) { return false } } /** * User open CLI from front-end */ userOpenCLI(user: User) { this.config.openCLI = true this.config.userEmailOpenCLI = user.userEmail this.config.userOpenCLI = user.userName this.socketIO.emit('user_open_cli', { stationId: this.config.stationId, lineId: this.config.id, userEmailOpenCLI: user.userEmail, userOpenCLI: user.userName, }) appendLog( `\n-------${user.userName}-------\n`, this.config.stationId, this.config.stationName, this.config.stationIp, this.config.lineNumber ) } /** * User close CLI from front-end */ userCloseCLI() { this.config.openCLI = false this.config.userEmailOpenCLI = '' this.config.userOpenCLI = '' this.socketIO.emit('user_close_cli', { stationId: this.config.stationId, lineId: this.config.id, userEmailOpenCLI: '', }) } /** * Clear output buffer */ clearCLI() { this.config.output = '' this.socketIO.emit('user_clear_terminal', { stationId: this.config.stationId, lineId: this.config.id, }) 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) while (Date.now() - start < timeout) { if (this.outputBuffer.includes(expect)) { this.outputBuffer = '' return true } await sleep(200) } return false } /** * Detect inventory data from output */ getInventory = () => { const data = textfsmResults(this.outputInventory, 'show inventory') try { data.forEach((item) => { if (item?.textfsm && isValidJson(item?.textfsm)) { if (['show inventory', 'sh inventory', 'show inv', 'sh inv'].includes(item.command)) { const dataInventory = JSON.parse(item.textfsm)[0] this.config.inventory = this.config.inventory ? { ...this.config.inventory, ...dataInventory } : dataInventory } item.textfsm = JSON.parse(item.textfsm) } }) if (this.config.inventory) { this.config.data = data this.socketIO.emit('data_textfsm', { stationId: this.config.stationId, lineId: this.config.id, data, inventory: this.config.inventory || null, latestScenario: this.config.latestScenario || null, }) this.outputInventory = '' } } catch (error) { console.log(error) } } /** * Gα»­i nhiều kΓ½ tα»± ESC để vΓ o ROMMON */ breakSpam() { console.log('SPAM Break to line:', this.config.lineNumber) let count = 0 const escInterval = setInterval(() => { if (count >= 10) { clearInterval(escInterval) return } this.client.write(Buffer.from([0xff, 0xf3])) // Ctrl + Break count++ }, 1) } /** * Set Baud of line */ async setBaud(baud: number) { this.writeCommand('enable\r\n') await sleep(500) this.writeCommand('configure terminal\r\n') await sleep(500) this.writeCommand('line console 0\r\n') await sleep(500) this.writeCommand(`speed ${baud.toString()}\r\n`) await sleep(500) this.writeCommand('end\r\n') await sleep(500) this.writeCommand('write memory\r\n') 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 .join( logDir, `${date}-AUTO-Session.${this.config.stationName}-${this.config.stationId}-${this.config.stationIp}-${this.config.lineNumber}.log` ) .replaceAll(' ', '_') if (!fs.existsSync(logDir) || !fs.existsSync(logFile)) { return '' } return await fs.promises.readFile(logFile, 'utf8') } /** * Detect log by call api gpt, return summary and issues */ async detectLogWithAI(log: string) { try { // Get prompt from database const promptRecord = await PromptAi.findBy('type', 'dpelp') if (!promptRecord) { console.log('[ERROR] Prompt DPELP not found in database') return '' } const payload = { model: 'gpt-4o-mini', max_tokens: 1000, messages: [ { role: 'user', content: `${promptRecord.content} Return ONLY a valid JSON array of strings. Here is the log: ${log}`, }, ], } const remoteUrl = process.env.ERP_URL || 'https://stage.nswteam.net' const remoteResp = await axios.post( remoteUrl + '/api/transferPostData', { urlAPI: '/api/open-ai-sfp/model-image-info', data: payload, }, { headers: { Authorization: 'Bearer ' + process.env.ERP_TOKEN, }, } ) return remoteResp.data?.Status === 'OK' ? remoteResp.data?.data : '' } catch (error: any) { console.log('[ERROR] Detect log from AI', error) } 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` const now = Date.now() const newItem = JSON.stringify({ ...item, timestamp: now, }) // LαΊ₯y phαΊ§n tα»­ cuα»‘i const lastItems = await redis.zrevrange(key, 0, 0) if (lastItems.length > 0) { const last = JSON.parse(lastItems[0]) if (last.pid === item.pid && last.sn === item.sn) { return false // khΓ΄ng thay Δ‘α»•i } } const line = await Line.find(lineId) if (line) { const listHistory = line.history ? JSON.parse(line.history) : [] listHistory.unshift(newItem) line.history = JSON.stringify(listHistory) await line.save() } // Add vΓ o ZSET await redis.zadd(key, now, newItem) // Tα»± Δ‘α»™ng xΓ³a item > 96h const expireTime = now - 96 * 60 * 60 * 1000 await redis.zremrangebyscore(key, 0, expireTime) 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) this.session.applyParsedLog(parsed) } catch (error) { console.log('handleLogLine', error) } } /** * Check raw log was regex each 5 minutes, if has error will send email report */ checkLog() { const interval = setInterval(async () => { try { if (this.config.status !== 'connected') { clearInterval(interval) this.session.clear() this.bufferLog.clear() return } const result = this.session.finalize() if (result.errors.length === 0) { this.session.clear() this.bufferLog.clear() return } // const detectLog = await this.detectLogWithAI(this.bufferLog.allBuffer) // console.log(detectLog) const tableHTML = this.buildEmailContent(result) await sendMessageToMail( `[ATC] - [${this.config.stationName} - Line: ${this.config.lineNumber}] - Raw log issue ${result?.errors?.some((e) => e.category === 'SPECIAL_KEYWORD') ? '+ Special keywords' : ''}`, tableHTML + `${`

Logs:

${this.bufferLog.allBuffer}
`}` ) this.session.clear() this.bufferLog.clear() } catch (err: any) { console.error('Error checking log:', err) } }, 300000) } /** * Render table to view error */ renderErrorTable(rows: ErrorRow[]): string { if (!rows.length) { return `

No errors detected

` } const header = ` Level Rule Message Log Evidence ` const body = rows .map( (r) => ` ${r.level} ${r.rule} ${r.message} ${escapeHtml(r.log.trim()) .split('*') .filter((el) => el) .join('
*')} ` ) .join('') return ` ${header} ${body}
` } /** * Return a body email */ buildEmailContent(result: TestResult): string { const rows = mapErrorsToRows(result.errors) const table = this.renderErrorTable(rows) return `

Cisco Device Log Result

Line: ${this.config.lineNumber} - Station: ${this.config.stationName}

Summary: ${result.summary}


${table}
` } /** * Update note of SN to ERP after run DPELP */ async updateNote(sn: string, data: DataDPELP) { const portPhysical = Array.from(this.physicalTest.ports.values()) const missing = portPhysical.filter((p) => !p.tested) const missingPoE = missing.filter((p) => !p.name.includes('SFP')) const missingSFP = missing.filter((p) => p.name.includes('SFP')) 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 licenses = Array.isArray(data.license) ? [...new Set(data.license)] : data.license ? [data.license] : [] const timeZone = process.env.TIME_ZONE || 'Australia/Sydney' const dataFormat = momentTZ().tz(timeZone).format('YYYY/MM/DD, HH:mm') const note = `-------[ATC]-[${dataFormat}]------- *****[DPELP]***** License: ${licenses.join(', ')} Detected by AI: ${data.issues?.length ? `- ` + data.issues.join(`\n- `) : ''} *****[Physical]***** Total Ports: ${portPhysical?.length} Ports Tested (Link UP): ${tested.length} (${testedPoE?.length} PoE, ${testedSFP?.length} SFP) Ports Missing/Down: ${missing.length} ${this.config.reasonSkipPhysical ? `***User skip test ports:\n- ${this.config.reasonSkipPhysical}` : ''} \n` await updateNoteToERP(sn, note) } /** * Update note of SN to ERP from user input */ async updateNoteFromUser(sn: string, note: string, licenses: string[]) { const portPhysical = Array.from(this.physicalTest.ports.values()) const missing = portPhysical.filter((p) => !p.tested) const missingPoE = missing.filter((p) => !p.name.includes('SFP')) const missingSFP = missing.filter((p) => p.name.includes('SFP')) 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 timeZone = process.env.TIME_ZONE || 'Australia/Sydney' const dataFormat = momentTZ().tz(timeZone).format('YYYY/MM/DD, HH:mm') const data = `-------[ATC]-[${dataFormat}]------- *****[DPELP]***** License: ${licenses.join(', ')} Issues: ${note} *****[Physical]***** Total Ports: ${portPhysical?.length} Ports Tested (Link UP): ${tested.length} (${testedPoE?.length} PoE, ${testedSFP?.length} SFP) Ports Missing/Down: ${missing.length}\n\n` const issueList = note .split('\n') .map((line) => (line[0] === '-' ? line.substring(1).trim() : line.trim())) const detectAI = this.config?.latestScenario?.detectAI ? { ...this.config.latestScenario.detectAI, issue: issueList } : { issue: issueList, summary: '', status: [] } if (this.config.latestScenario) { this.config.latestScenario = { ...this.config.latestScenario, detectAI } } await updateNoteToERP(sn, data) } /** * Starting physical test (PoE ports testing) */ async runPhysicalTest() { if (this.config.runningPhysical) { console.log('Running physical test') return } this.setTimeoutSendSummaryReport(600000) this.config.runningPhysical = true this.config.runningScenario = 'Physical Test' this.config.isSkipPhysical = false this.config.reasonSkipPhysical = '' this.testingPortPoE = true this.outputTestingPortPoE = '' const listPorts = await this.getPorts() this.socketIO.emit('running_scenario', { stationId: this.config.stationId, lineId: this.config.id, title: 'Physical Test', physical: true, ports: listPorts, }) if (listPorts.length === 0) { this.config.listFeatureTested = [...new Set([...this.config.listFeatureTested, 'PHYSICAL'])] this.config.isSkipPhysical = true this.config.reasonSkipPhysical = '' this.sendFeatureTested() console.log('End physical test') this.endTesting() return } this.physicalTest.start( listPorts.map((el) => el), this.config.inventory ) const interval = setInterval(async () => { if (!this.config.runningPhysical || this.config.status !== 'connected') { clearInterval(interval) } else if (this.physicalTest.done) { clearInterval(interval) this.sendReportPhysicalTest() this.endTesting() } else { this.checkingPhysicalPort() } }, 15000) } async checkingPhysicalPort() { try { this.writeCommand('show power inline | include on\r\n') this.writeCommand('\r\n') await this.sleep(1000) this.writeCommand('show interfaces status | include SFP\r\n') this.writeCommand('\r\n') await this.sleep(2000) const output = this.outputPhysicalTest this.outputPhysicalTest = '' if (output) { const ports = this.physicalTest.detectPorts(output) this.socketIO.emit('test_port_physical', { stationId: this.config.stationId, lineId: this.config.id, data: ports, }) if (ports.length === this.config.ports.length) { this.sendReportPhysicalTest() this.endTesting() } } } catch (error) { console.log('checkingPhysicalPort', error) } } async flushLogBuffer() { try { const lines = this.outputTestingPortPoE.split(/\r?\n/) // giα»― lαΊ‘i dΓ²ng cuα»‘i nαΊΏu chΖ°a kαΊΏt thΓΊc hoΓ n chỉnh this.outputTestingPortPoE = lines.pop() || '' const completeLines = lines.join('\n') if (completeLines.trim()) { const ports = this.physicalTest.handleLog(completeLines) if (ports?.length) this.socketIO.emit('test_port_physical', { stationId: this.config.stationId, lineId: this.config.id, data: ports, }) } } catch (error) { console.log('flushLogBuffer', error) } } /** * End all testing */ endTesting() { this.physicalTest.done = true // this.physicalTest.resetTestedPorts() this.config.runningPhysical = false this.config.runningScenario = '' this.testingPortPoE = false this.outputTestingPortPoE = '' this.outputBuffer = '' this.outputScenario = '' this.outputPhysicalTest = '' this.config.ports = [] this.socketIO.emit('running_scenario', { stationId: this.config.stationId, lineId: this.config.id, title: '', }) } /** * Get list PoE ports */ async getPorts(): Promise { this.writeCommand(' terminal length 0\r\n') this.writeCommand(' show power inline\r\n') this.writeCommand(' \r\n') await this.sleep(3000) this.writeCommand(' show interfaces status\r\n') this.writeCommand(' \r\n') await this.sleep(6000) const statusOutput = this.outputPhysicalTest this.outputPhysicalTest = '' const lines = statusOutput.split('\n') const ports = [] for (const line of lines) { // 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) if (matchPoE) { const name = matchPoE[1] if (name.includes('/')) ports.push(normalizeInterface(name)) } // Match: "Gi0/15 notconnect 1 auto auto 1000BaseSX SFP" // Match: "Gi0/16 notconnect 1 auto auto Not Present" // 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) { const name = matchSFP[1] ports.push(normalizeInterface(name) + ' (SFP)') } } this.config.ports = [...new Set(ports)] return [...new Set(ports)] } /** * Send report after done physical test */ async sendReportPhysicalTest(reason?: string) { this.config.listFeatureTested = [...new Set([...this.config.listFeatureTested, 'PHYSICAL'])] if (typeof reason === 'string' && reason.trim().length > 0) { this.config.isSkipPhysical = true this.config.reasonSkipPhysical = reason } this.sendFeatureTested() // Set timeout send report if ( this.config.listFeatureTested?.includes('PHYSICAL') && this.config.listFeatureTested?.includes('DPELP') ) this.setTimeoutSendSummaryReport(5000) const formReport = this.physicalTest.getFormReport(this.config.inventory) const reasonSkipPhysical = typeof reason === 'string' && reason.trim().length > 0 ? `User Skip Test Port
────────────────────────────────
${reason}` : '' await sendMessageToMail( `[ATC] - [${this.config.stationName} - Line: ${this.config.lineNumber}] - [${this.config.inventory?.pid}] - [${this.config.inventory?.sn}] - Physical Ports Test`, formReport + reasonSkipPhysical ) } /** * Handle load ios for router */ async loadIosRouter(nameIos: string, userName: string) { const station = await Station.find(this.config.stationId) if (!station) return this.outputLoadIosLicense = true const network = station?.gateway || '172.25.1.1' const tftpIp = station?.tftp_ip || '172.16.7.69' const [a, b] = network.split('.').map(Number) const timeZone = process.env.TIME_ZONE || 'Australia/Sydney' const startTime = momentTZ().tz(timeZone).format('YYYY/MM/DD, HH:mm:ss') const body = buildBody( 'ROUTER_IOS', tftpIp, nameIos, `${a}.${b}.100.${this.config.id < 254 ? this.config.id : 254 - this.config.id}`, `${station?.gateway ? station?.gateway : '0.0.0.0'}`, this.listDeviceIos ) const script = { id: 0, isReboot: true, sendResult: false, send_result: false, title: 'Load IOS Router', timeout: 1000, body: JSON.stringify(body), } await sleep(5000) await this.runScript(script as any, userName) 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 this.outputLoadIosLicense = true const network = station?.gateway || '172.25.1.1' const tftpIp = station?.tftp_ip || '172.16.7.69' const [a, b] = network.split('.').map(Number) const timeZone = process.env.TIME_ZONE || 'Australia/Sydney' const startTime = momentTZ().tz(timeZone).format('YYYY/MM/DD, HH:mm:ss') const address = `${a}.${b}.100.${this.config.id < 254 ? this.config.id : 254 - this.config.id}` const gateway = `${station?.gateway ? station?.gateway : '0.0.0.0'}` await this.configAddressGateway(address, gateway, 'vlan 1') const pingSuccess = await this.pingToServer(tftpIp) if (!pingSuccess) return await this.backupIos(nameIos) const body = buildBody('SWITCH_IOS', tftpIp, nameIos, address, gateway, this.listDeviceIos) const script = { id: 0, isReboot: false, sendResult: false, send_result: false, title: 'Load IOS Switch', timeout: 1000, body: JSON.stringify(body), } await this.runScript(script as any, userName) 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') const body = ` Load IOS Report
────────────────────────────────
Station : ${this.config.stationName}
Line : ${this.config.lineNumber}
IOS : ${nameIos}
Started At : ${startTime}
Finished At : ${dataFormat}

`.trim() await sendMessageToMail( `[ATC] - [${this.config.stationName} - Line: ${this.config.lineNumber}] - Load IOS Report`, body + `${`

Logs:

${this.outputLoadIosLicense}
`}` ) this.outputLoadIosLicense = '' } /** * Send mail report after load license */ async sendEmailLoadLicense(nameLicense: string, startTime: string) { const timeZone = process.env.TIME_ZONE || 'Australia/Sydney' const dataFormat = momentTZ().tz(timeZone).format('YYYY/MM/DD, HH:mm:ss') const report = parseLicenseReport( typeof this.outputLoadIosLicense === 'string' ? this.outputLoadIosLicense : '' ) const body = ` Load License Report
────────────────────────────────
Station : ${this.config.stationName}
Line : ${this.config.lineNumber}
License : ${nameLicense}
Started At : ${startTime}
Finished At : ${dataFormat}
────────────────────────────────
Summary licenses: ${report.summary.join(', ')}
Successful: ${report.imported.join(', ')}
Exist: ${report.exist.join(', ')}
Failed: ${report.failed.join(', ')}

`.trim() await sendMessageToMail( `[ATC] - [${this.config.stationName} - Line: ${this.config.lineNumber}] - Load License Report`, body + `${`

Logs:

${this.outputLoadIosLicense}
`}` ) this.outputLoadIosLicense = '' } /** * Check list ios exist on flash */ async checkDeviceFlash() { this.writeCommand(' enable\r\n') this.writeCommand('show flash:\r\n') await sleep(2000) const output = this.outputBuffer const ios: string[] = [] let match 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`) await this.writeCommand(`\r\n`) 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`) await this.writeCommand(`${fileName}\r\n`) await this.writeCommand(`${server}\r\n`) await this.writeCommand(`i/${fileName}\r\n`) this.outputBuffer = '' await sleep(5000) while (true) { if (this.outputBuffer.includes('#')) { this.outputBuffer = '' this.config.runningScenario = '' return true } await sleep(5000) } } /** * Get list ios from TFTP server */ async getListIos() { try { const controller = new IosLicenseController() const listIos = await controller.getIos() return listIos } catch (error) { console.log('Error get ios', error) return [] } } /** * Get current boot ios of device */ async getCurrentBootIos() { this.writeCommand('show version | include System image\r\n') await sleep(2000) const match = this.outputBuffer.match(/"flash:(.+?)"/i) this.outputBuffer = '' 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 const server = station?.tftp_ip || '172.16.7.69' // const currentBootIos = await this.getCurrentBootIos() this.config.runningScenario = 'Backup IOS' this.socketIO.emit('running_scenario', { stationId: this.config.stationId, lineId: this.config.id, title: 'Backup IOS', }) await sleep(1000) const listIos = await this.getListIos() const dataDevice = await this.checkDeviceFlash() this.listDeviceIos = [...dataDevice] console.log('Data Device Flash', dataDevice) if (dataDevice && Array.isArray(dataDevice)) { for (const ios of dataDevice) { // if (ios === nameIos) { // console.log(`SKIP active IOS: ${ios}`) // continue // } if (listIos?.map((value) => value.name)?.includes(ios)) { console.log(`Already backed up: ${ios}`) if (ios !== nameIos) await this.deleteFileOnFlash(ios) } else { const ok = await this.uploadFileToServerTFTP(ios, server) if (ok && ios !== nameIos) await this.deleteFileOnFlash(ios) } } } this.outputBuffer = '' this.config.runningScenario = '' await sleep(1000) } /** * Handle load License for switch * Assumes traditional licensing (PAK/file-based) via TFTP */ async loadLicenseSwitch(licenseFileName: string, userName: string, portName: string) { const station = await Station.find(this.config.stationId) if (!station) return this.outputLoadIosLicense = true // Setup network variables (giα»‘ng hệt logic load IOS để Δ‘αΊ£m bαΊ£o thΓ΄ng mαΊ‘ng) const network = station?.gateway || '172.25.1.1' const tftpIp = station?.tftp_ip || '172.16.7.69' const [a, b] = network.split('.').map(Number) // Setup time/logging const timeZone = process.env.TIME_ZONE || 'Australia/Sydney' const startTime = momentTZ().tz(timeZone).format('YYYY/MM/DD, HH:mm:ss') const address = `${a}.${b}.100.${this.config.id < 254 ? this.config.id : 254 - this.config.id}` const gateway = `${station?.gateway ? station?.gateway : '0.0.0.0'}` await this.configAddressGateway(address, gateway, portName) const pingSuccess = await this.pingToServer(tftpIp) if (!pingSuccess) return const body = buildBody( 'SWITCH_LICENSE', tftpIp, licenseFileName, address, gateway, this.listDeviceIos, portName ) const script = { id: 0, isReboot: false, sendResult: false, send_result: false, title: 'Load License Switch', timeout: 1000, body: JSON.stringify(body), } await this.runScript(script as any, userName) await this.sendEmailLoadLicense(licenseFileName, startTime) // NαΊΏu bαΊ‘n cΓ³ hΓ m gα»­i mail bΓ‘o cΓ‘o } /** * Handle load License for Router */ async loadLicenseRouter(licenseFileName: string, userName: string, portName: string) { const station = await Station.find(this.config.stationId) if (!station) return this.outputLoadIosLicense = true const network = station?.gateway || '172.25.1.1' const tftpIp = station?.tftp_ip || '172.16.7.69' const [a, b] = network.split('.').map(Number) const timeZone = process.env.TIME_ZONE || 'Australia/Sydney' const startTime = momentTZ().tz(timeZone).format('YYYY/MM/DD, HH:mm:ss') const address = `${a}.${b}.100.${this.config.id < 254 ? this.config.id : 254 - this.config.id}` const gateway = `${station?.gateway ? station?.gateway : '0.0.0.0'}` await this.configAddressGateway(address, gateway, portName, true) const pingSuccess = await this.pingToServer(tftpIp) if (!pingSuccess) return const body = buildBody( 'ROUTER_LICENSE', tftpIp, licenseFileName, address, gateway, this.listDeviceIos, portName ) const script = { id: 0, isReboot: false, sendResult: false, send_result: false, title: 'Load License Router', timeout: 1000, body: JSON.stringify(body), } await this.runScript(script as any, userName) await this.sendEmailLoadLicense(licenseFileName, startTime) } /** * Detect log by call api gpt, return string[] */ async detectShowEnvWithAI(log: string) { try { // Get prompt from database const promptRecord = await PromptAi.findBy('type', 'env') if (!promptRecord) { console.log('[ERROR] Prompt ENV not found in database') return '' } const payload = { model: 'gpt-4o-mini', max_tokens: 1000, messages: [ { role: 'user', content: `${promptRecord.content} Return ONLY a valid JSON array of strings. Here is the log: ${log}`, }, ], } const remoteUrl = process.env.ERP_URL || 'https://stage.nswteam.net' const remoteResp = await axios.post( remoteUrl + '/api/transferPostData', { urlAPI: '/api/open-ai-sfp/model-image-info', data: payload, }, { headers: { Authorization: 'Bearer ' + process.env.ERP_TOKEN, }, } ) return remoteResp.data?.Status === 'OK' ? remoteResp.data?.data : '' } catch (error: any) { console.log('[ERROR] Detect log show env from AI', error) } return '' } /** * Check config RAM and Flash, if higher config will send report */ async checkConfigRam(mem: string, flash: string, pid: string, output: string) { const configRam = await detectConfigRamByModel(pid) if (configRam) { const isWarningRAM = isRamSufficient(mem, configRam.ram) const isWarningFlash = isRamSufficient(flash, configRam.flash) if (isWarningRAM || isWarningFlash) { const subject = `[ATC] - [${this.config.stationName} - Line: ${this.config.lineNumber}] - Warning RAM, Flash Configuration` const body = `

Station: ${this.config.stationName}

Line: ${this.config.lineNumber}

Model: ${pid}

RAM: ${mem ? `${convertFromKilobytesString(mem)} (default: ${configRam.ram})` : ''}

FLASH: ${flash ? `${convertFromKilobytesString(flash)} (default: ${configRam.flash})` : ''}


${escapeHtml(output) .replace('show ver', '') .replace('sh ver', '') .replace('show version', '') .replace('sh version', '') .replace(mem, `${mem}`) .replace( flash, `${flash}` )}
` await sendMessageToMail(subject, body) } } } /** * Send list feature tested */ sendFeatureTested = async () => { this.socketIO.emit('feature_tested', { stationId: this.config.stationId, lineId: this.config.id, listFeatureTested: this.config.listFeatureTested, isSkipPhysical: this.config.isSkipPhysical, reasonSkipPhysical: this.config.reasonSkipPhysical, pid: this.config.inventory?.pid, sn: this.config.inventory?.sn, vid: this.config.inventory?.vid, }) } /** * Send summary of all report (DPELP, Physical Testing) */ sendReportSummary = async (snapshot?: { snapConfig: LineConfig snapPhysical: PhysicalPortTest reason: string }) => { 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 missingPoE = missing.filter((p) => !p.name.includes('SFP')) const missingSFP = missing.filter((p) => p.name.includes('SFP')) 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') ) const dataShowVersion = showVersion?.textfsm && showVersion?.textfsm?.[0] ? showVersion?.textfsm?.[0] : config?.inventory const showLicense = config?.data?.find( (d) => d.command?.trim()?.includes('show lic') || d.command?.trim()?.includes('sh lic') ) const dataShowLic = showLicense?.textfsm && Array.isArray(showLicense?.textfsm) ? showLicense?.textfsm : null const issue = config?.latestScenario?.detectAI?.issue || [] const summary = config?.latestScenario?.detectAI?.summary || '' const reason = this.config.reasonSkipPhysical || snapshot?.reason const reasonSkipPhysical = typeof reason === 'string' && reason.trim().length > 0 ? `
User Skip Test Port
────────────────────────────────
${reason}` : '' const body = `
DPELP Physical Testing
Model: ${config?.inventory?.pid ?? ''} ${config?.inventory?.vid ?? ''}
Serial Number: ${config?.inventory?.sn ?? ''}
MAC: ${dataShowVersion?.MAC_ADDRESS ?? ''}
IOS: ${dataShowVersion?.SOFTWARE_IMAGE ?? ''} ${dataShowVersion?.VERSION ?? ''}
MEM: ${dataShowVersion?.MEMORY ? convertFromKilobytesString(dataShowVersion?.MEMORY) : ''}
FLASH: ${dataShowVersion?.USB_FLASH ? convertFromKilobytesString(dataShowVersion?.USB_FLASH) : ''}
Licenses: ${ dataShowLic ? dataShowLic ?.filter((el) => el.LICENSE_TYPE?.toLowerCase()?.includes('permanent')) ?.map((v) => v.FEATURE) ?.join(', ') : '' }
Detect from AI: ${issue?.length ? `
- ` + issue.join(`
`) : 'No issues detected.'}

Total Ports: ${portPhysical?.length}
Ports Tested (Link UP): ${tested.length} (${testedPoE?.length} PoE, ${testedSFP?.length} SFP)
Ports Missing/Down: ${missing.length}
${ missingPoE?.length ? `
Ports Missing PoE
────────────────────────────────
${missingPoE.map((p) => physicalTest.normalizePortName(p.name)).join('
')}
` : '' } ${ missingSFP?.length ? `
Ports Missing SFP
────────────────────────────────
${missingSFP.map((p) => physicalTest.normalizePortName(p.name)).join('
')}
` : '' } ${reasonSkipPhysical}
` this.updateNote(config?.inventory?.sn, this.dataDPELP as DataDPELP) await sendMessageToMail( `[ATC] - [${config.stationName} - Line: ${config.lineNumber}] - [${this.config.inventory?.pid}] - [${this.config.inventory?.sn}] - Summary of Testing Results`, body ) this.socketIO.emit('summary_tested', { stationId: this.config.stationId, lineId: this.config.id, body: body, title: `[${config.stationName} - Line: ${config.lineNumber}] - Summary of Testing Results`, }) } /** * 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: [], isReady: false, } this.physicalTest = new PhysicalPortTest([]) } setTimeoutSendSummaryReport(timeout: number) { // 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, reason: '', } this.debounceSendSummaryReport = setTimeout(() => { if (!this.config.listFeatureTested?.includes('PHYSICAL')) { this.config.isSkipPhysical = true this.config.reasonSkipPhysical = 'Timeout, The user has not completed the physical test' snapshot.reason = 'Timeout, The user has not completed the physical test' } this.config.listFeatureTested = ['DPELP', 'PHYSICAL', 'SUMMARY'] this.sendFeatureTested() this.sendReportSummary(snapshot) }, timeout) } resetDPELP() { this.config.listFeatureTested = [] this.config.isSkipPhysical = false this.config.reasonSkipPhysical = '' this.dataDPELP = '' this.sendFeatureTested() console.log('Reset DPELP data and features', this.config.id, this.config.listFeatureTested) } async pingToServer(serverIP: string) { this.isPingToServer = true this.writeCommand('\r\n') this.writeCommand('enable\r\n') await sleep(500) this.writeCommand(`ping ${serverIP}\r\n`) await sleep(500) const start = Date.now() // console.log('[EXPECT]', expect, timeout) while (Date.now() - start < 60000) { if (this.outputPingToServer.includes('Success rate')) { const match = this.outputPingToServer.match(/Success rate is (\d+) percent/) if (match) { const rate = Number(match[1]) if (rate > 0) { this.outputPingToServer = '' this.isPingToServer = false return true } else { this.isPingToServer = false this.outputPingToServer = '' this.config.output += '\n[CONNECT_TO_SERVER_TFTP_FAIL]\n' this.socketIO.emit('line_output', { stationId: this.config.stationId, lineId: this.config.id, data: '\n[CONNECT_TO_SERVER_TFTP_FAIL]\n', }) return false } } } await sleep(500) } this.isPingToServer = false this.outputPingToServer = '' this.config.output += '\n[CONNECT_TO_SERVER_TFTP_FAIL]\n' this.socketIO.emit('line_output', { stationId: this.config.stationId, lineId: this.config.id, data: '\n[CONNECT_TO_SERVER_TFTP_FAIL]\n', }) return false } /** * Config ip address and default gateway for line */ async configAddressGateway( address: string, gateway: string, portName: string, isRouter?: boolean ) { this.config.runningScenario = 'Config Network' this.socketIO.emit('running_scenario', { stationId: this.config.stationId, lineId: this.config.id, title: 'Config Network', }) await this.writeCommand(`enable\r\n`) await sleep(500) await this.writeCommand(`configure terminal\r\n`) await sleep(500) await this.writeCommand(`interface ${portName}\r\n`) await sleep(500) await this.writeCommand(`ip address ${address} 255.255.0.0\r\n`) await sleep(500) await this.writeCommand(`no shutdown\r\n`) await sleep(500) await this.writeCommand(`exit\r\n`) await sleep(500) await this.writeCommand( !isRouter ? `ip default-gateway ${gateway}\r\n` : `ip route 0.0.0.0 0.0.0.0 ${gateway}` ) await sleep(500) await this.writeCommand(`end\r\n`) await sleep(500) await this.writeCommand(`\r\n`) await sleep(1000) this.config.runningScenario = '' this.socketIO.emit('running_scenario', { stationId: this.config.stationId, lineId: this.config.id, title: '', }) } }