Update line_connection.ts

This commit is contained in:
nguyentrungthat 2026-01-07 16:47:58 +07:00
parent 8580f3c88a
commit 429c570688
1 changed files with 122 additions and 8 deletions

View File

@ -157,7 +157,9 @@ export default class LineConnection {
this.outputPhysicalTest = '' this.outputPhysicalTest = ''
this.listDeviceIos = [] this.listDeviceIos = []
} }
/**
* Connect to line with socket
*/
connect(timeoutMs = 5000) { connect(timeoutMs = 5000) {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
const { ip, port, lineNumber, id, stationId } = this.config const { ip, port, lineNumber, id, stationId } = this.config
@ -304,10 +306,16 @@ export default class LineConnection {
}) })
} }
/**
* Waiting with millisecond
*/
private sleep(ms: number) { private sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms)) return new Promise((resolve) => setTimeout(resolve, ms))
} }
/**
* Write a command with socket.write
*/
async writeCommand(cmd: string | Buffer<ArrayBuffer>, userName = '') { async writeCommand(cmd: string | Buffer<ArrayBuffer>, userName = '') {
if (this.client.destroyed) { if (this.client.destroyed) {
console.log(`⚠️ Cannot send, line ${this.config.lineNumber} is closed`) console.log(`⚠️ Cannot send, line ${this.config.lineNumber} is closed`)
@ -326,6 +334,9 @@ export default class LineConnection {
this.client.write(cmd) this.client.write(cmd)
} }
/**
* Disconnect socket with line
*/
async disconnect() { async disconnect() {
try { try {
console.log('[DISCONNECT] Line', this.config.lineNumber) 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) { async runScript(script: Scenario, userName: string) {
if (!this.client || this.client.destroyed) { if (!this.client || this.client.destroyed) {
console.log('Not connected') console.log('Not connected')
@ -575,7 +589,7 @@ export default class LineConnection {
// } // }
if (typeof step.send !== 'undefined') { 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` this.outputScenario += `\n---send-command---"${(step?.send ?? '[ENTER]').toString().replace(/\r/g, '\\r').replace(/\n/g, '\\n')}"---${now}---\n`
appendLog( appendLog(
`\n---send-command---"${(step?.send ?? '[ENTER]').toString().replace(/\r/g, '\\r').replace(/\n/g, '\\n')}"---${now}---\n`, `\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<boolean> { public async reconnect(): Promise<boolean> {
try { try {
this.disconnect() this.disconnect()
@ -632,6 +649,9 @@ export default class LineConnection {
} }
} }
/**
* User open CLI from front-end
*/
userOpenCLI(user: User) { userOpenCLI(user: User) {
this.config.openCLI = true this.config.openCLI = true
this.config.userEmailOpenCLI = user.userEmail this.config.userEmailOpenCLI = user.userEmail
@ -651,6 +671,9 @@ export default class LineConnection {
) )
} }
/**
* User close CLI from front-end
*/
userCloseCLI() { userCloseCLI() {
this.config.openCLI = false this.config.openCLI = false
this.config.userEmailOpenCLI = '' this.config.userEmailOpenCLI = ''
@ -662,6 +685,9 @@ export default class LineConnection {
}) })
} }
/**
* Clear output buffer
*/
clearCLI() { clearCLI() {
this.config.output = '' this.config.output = ''
this.socketIO.emit('user_clear_terminal', { this.socketIO.emit('user_clear_terminal', {
@ -671,6 +697,9 @@ export default class LineConnection {
setTimeout(() => this.writeCommand('\r\n'), 100) setTimeout(() => this.writeCommand('\r\n'), 100)
} }
/**
* Waiting for a expect with until catch it from output
*/
waitForExpect = async (expect: string, timeout = 60000) => { waitForExpect = async (expect: string, timeout = 60000) => {
const start = Date.now() const start = Date.now()
// console.log('[EXPECT]', expect, timeout) // console.log('[EXPECT]', expect, timeout)
@ -684,6 +713,9 @@ export default class LineConnection {
return false return false
} }
/**
* Detect inventory data from output
*/
getInventory = () => { getInventory = () => {
const data = textfsmResults(this.outputInventory, 'show inventory') const data = textfsmResults(this.outputInventory, 'show inventory')
try { try {
@ -714,7 +746,9 @@ export default class LineConnection {
} }
} }
// Gửi nhiều ký tự ESC để vào ROMMON /**
* Gửi nhiều tự ESC đ vào ROMMON
*/
breakSpam() { breakSpam() {
console.log('SPAM Break to line:', this.config.lineNumber) console.log('SPAM Break to line:', this.config.lineNumber)
let count = 0 let count = 0
@ -728,6 +762,9 @@ export default class LineConnection {
}, 1) }, 1)
} }
/**
* Set Baud of line
*/
async setBaud(baud: number) { async setBaud(baud: number) {
this.writeCommand('enable\r\n') this.writeCommand('enable\r\n')
await sleep(500) await sleep(500)
@ -743,6 +780,9 @@ export default class LineConnection {
this.writeCommand('\r\n') this.writeCommand('\r\n')
} }
/**
* Get content's log of line with date
*/
async getLog(date: string) { async getLog(date: string) {
const logDir = path.join('storage', 'system_logs') const logDir = path.join('storage', 'system_logs')
const logFile = path const logFile = path
@ -759,6 +799,9 @@ export default class LineConnection {
return await fs.promises.readFile(logFile, 'utf8') return await fs.promises.readFile(logFile, 'utf8')
} }
/**
* Detect log by call api gpt, return summary and issues
*/
async detectLogWithAI(log: string) { async detectLogWithAI(log: string) {
try { try {
const payload = { const payload = {
@ -812,6 +855,9 @@ export default class LineConnection {
return '' return ''
} }
/**
* Add cache to list history devices on this line
*/
async addHistory(stationId: number, lineId: number, item: HistoryItem) { async addHistory(stationId: number, lineId: number, item: HistoryItem) {
if (!item.pid || !item.sn) return if (!item.pid || !item.sn) return
const key = `station:${stationId}:line:${lineId}:history` const key = `station:${stationId}:line:${lineId}:history`
@ -850,12 +896,18 @@ export default class LineConnection {
return true return true
} }
/**
* Get list history devices
*/
async getHistory(stationId: number, lineId: number) { async getHistory(stationId: number, lineId: number) {
const key = `station:${stationId}:line:${lineId}:history` const key = `station:${stationId}:line:${lineId}:history`
const items = await redis.zrange(key, 0, -1) const items = await redis.zrange(key, 0, -1)
return items.map((i) => JSON.parse(i)) return items.map((i) => JSON.parse(i))
} }
/**
* Handle raw log to regex error
*/
handleLogLine = (line: string) => { handleLogLine = (line: string) => {
try { try {
const parsed = classifyLog(line) 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() { checkLog() {
const interval = setInterval(async () => { const interval = setInterval(async () => {
try { try {
@ -902,6 +957,9 @@ export default class LineConnection {
}, 300000) }, 300000)
} }
/**
* Render table to view error
*/
renderErrorTable(rows: ErrorRow[]): string { renderErrorTable(rows: ErrorRow[]): string {
if (!rows.length) { if (!rows.length) {
return `<p style="color: green;">No errors detected</p>` return `<p style="color: green;">No errors detected</p>`
@ -942,6 +1000,9 @@ export default class LineConnection {
` `
} }
/**
* Return a body email
*/
buildEmailContent(result: TestResult): string { buildEmailContent(result: TestResult): string {
const rows = mapErrorsToRows(result.errors) const rows = mapErrorsToRows(result.errors)
const table = this.renderErrorTable(rows) 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) { async updateNote(sn: string, data: DataDPELP) {
const licenses = Array.isArray(data.license) const licenses = Array.isArray(data.license)
? [...new Set(data.license)] ? [...new Set(data.license)]
@ -968,6 +1032,9 @@ export default class LineConnection {
await updateNoteToERP(sn, note) await updateNoteToERP(sn, note)
} }
/**
* Starting physical test (PoE ports testing)
*/
async runPhysicalTest() { async runPhysicalTest() {
if (this.config.runningPhysical) { if (this.config.runningPhysical) {
console.log('Running physical test') console.log('Running physical test')
@ -1002,6 +1069,9 @@ export default class LineConnection {
// }, 10000) // }, 10000)
} }
/**
* End all testing
*/
endTesting() { endTesting() {
this.physicalTest.done = true this.physicalTest.done = true
this.physicalTest.resetTestedPorts() this.physicalTest.resetTestedPorts()
@ -1018,6 +1088,9 @@ export default class LineConnection {
}) })
} }
/**
* Get list PoE ports
*/
async getPorts(): Promise<string[]> { async getPorts(): Promise<string[]> {
this.writeCommand(' show power inline\r\n') this.writeCommand(' show power inline\r\n')
this.writeCommand(' \r\n') this.writeCommand(' \r\n')
@ -1040,6 +1113,9 @@ export default class LineConnection {
return [...new Set(ports)] return [...new Set(ports)]
} }
/**
* Send report after done physical test
*/
async sendReportPhysicalTest() { async sendReportPhysicalTest() {
const formReport = this.physicalTest.getFormReport() const formReport = this.physicalTest.getFormReport()
await sendMessageToMail( await sendMessageToMail(
@ -1048,6 +1124,9 @@ export default class LineConnection {
) )
} }
/**
* Handle load ios for router
*/
async loadIosRouter(nameIos: string, userName: string) { async loadIosRouter(nameIos: string, userName: string) {
const station = await Station.find(this.config.stationId) const station = await Station.find(this.config.stationId)
if (!station) return if (!station) return
@ -1193,6 +1272,9 @@ export default class LineConnection {
await this.sendEmailLoadIos(nameIos, startTime) await this.sendEmailLoadIos(nameIos, startTime)
} }
/**
* Handle load ios for switch
*/
async loadIosSwitch(nameIos: string, userName: string) { async loadIosSwitch(nameIos: string, userName: string) {
const station = await Station.find(this.config.stationId) const station = await Station.find(this.config.stationId)
if (!station) return if (!station) return
@ -1402,6 +1484,9 @@ export default class LineConnection {
await this.sendEmailLoadIos(nameIos, startTime) await this.sendEmailLoadIos(nameIos, startTime)
} }
/**
* Send mail report after load ios
*/
async sendEmailLoadIos(nameIos: string, startTime: string) { async sendEmailLoadIos(nameIos: string, startTime: string) {
const timeZone = process.env.TIME_ZONE || 'Australia/Sydney' const timeZone = process.env.TIME_ZONE || 'Australia/Sydney'
const dataFormat = momentTZ().tz(timeZone).format('YYYY/MM/DD, HH:mm:ss') 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() { async checkDeviceFlash() {
this.writeCommand(' enable\r\n') this.writeCommand(' enable\r\n')
this.writeCommand('show flash:\r\n') this.writeCommand('show flash:\r\n')
await sleep(2000) 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 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 return ios
} }
/**
* Delete File on Flash
*/
async deleteFileOnFlash(fileName: string) { async deleteFileOnFlash(fileName: string) {
await this.writeCommand(`delete flash:${fileName}\r\n`) await this.writeCommand(`delete flash:${fileName}\r\n`)
await this.writeCommand(`\r\n`) await this.writeCommand(`\r\n`)
@ -1443,6 +1546,9 @@ export default class LineConnection {
await sleep(3000) await sleep(3000)
} }
/**
* Upload file from flash to TFTP server
*/
async uploadFileToServerTFTP(fileName: string, server: string) { async uploadFileToServerTFTP(fileName: string, server: string) {
this.config.runningScenario = 'Upload file' this.config.runningScenario = 'Upload file'
await this.writeCommand(`copy flash: tftp:\r\n`) 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() { async getListIos() {
try { try {
const controller = new IosLicenseController() const controller = new IosLicenseController()
@ -1472,6 +1580,9 @@ export default class LineConnection {
} }
} }
/**
* Get current boot ios of device
*/
async getCurrentBootIos() { async getCurrentBootIos() {
this.writeCommand('show version | include System image\r\n') this.writeCommand('show version | include System image\r\n')
await sleep(2000) await sleep(2000)
@ -1482,6 +1593,9 @@ export default class LineConnection {
return match ? match[1] : null return match ? match[1] : null
} }
/**
* Backup ios to TFTP, after that delete it on flash for free space
*/
async backupIos(nameIos: string) { async backupIos(nameIos: string) {
const station = await Station.find(this.config.stationId) const station = await Station.find(this.config.stationId)
if (!station) return if (!station) return