336 lines
9.3 KiB
TypeScript
336 lines
9.3 KiB
TypeScript
import { textfsmResults } from './../ultils/templates/index.js'
|
|
import fs from 'node:fs'
|
|
import net from 'node:net'
|
|
import {
|
|
appendLog,
|
|
cleanData,
|
|
getLogWithTimeScenario,
|
|
getPathLog,
|
|
isValidJson,
|
|
sleep,
|
|
} from '../ultils/helper.js'
|
|
import Scenario from '#models/scenario'
|
|
|
|
interface LineConfig {
|
|
id: number
|
|
port: number
|
|
lineNumber: number
|
|
ip: string
|
|
stationId: number
|
|
apcName?: string
|
|
output: string
|
|
status: string
|
|
openCLI: boolean
|
|
userEmailOpenCLI: string
|
|
userOpenCLI: string
|
|
inventory?: string
|
|
latestScenario?: {
|
|
name: string
|
|
time: number
|
|
}
|
|
data: {
|
|
command: string
|
|
output: string
|
|
textfsm: string
|
|
}[]
|
|
}
|
|
|
|
interface User {
|
|
userEmail: string
|
|
userName: string
|
|
}
|
|
|
|
export default class LineConnection {
|
|
public client: net.Socket
|
|
public readonly config: LineConfig
|
|
public readonly socketIO: any
|
|
private outputBuffer: string
|
|
private isRunningScript: boolean
|
|
private connecting: boolean
|
|
|
|
constructor(config: LineConfig, socketIO: any) {
|
|
this.config = config
|
|
this.socketIO = socketIO
|
|
this.client = new net.Socket()
|
|
this.outputBuffer = ''
|
|
this.isRunningScript = false
|
|
this.connecting = false
|
|
}
|
|
|
|
connect(timeoutMs = 5000) {
|
|
return new Promise<void>((resolve, reject) => {
|
|
const { ip, port, lineNumber, id, stationId } = this.config
|
|
let resolvedOrRejected = false
|
|
// Set timeout
|
|
this.client.setTimeout(timeoutMs)
|
|
|
|
this.client.connect(port, ip, () => {
|
|
if (resolvedOrRejected) return
|
|
resolvedOrRejected = true
|
|
|
|
console.log(`✅ Connected to line ${lineNumber} (${ip}:${port})`)
|
|
this.connecting = true
|
|
setTimeout(() => {
|
|
this.config.status = 'connected'
|
|
this.connecting = false
|
|
this.socketIO.emit('line_connected', {
|
|
stationId,
|
|
lineId: id,
|
|
lineNumber,
|
|
status: 'connected',
|
|
})
|
|
resolve()
|
|
}, 1000)
|
|
})
|
|
|
|
this.client.on('data', (data) => {
|
|
if (this.connecting) return
|
|
let message = data.toString()
|
|
if (this.isRunningScript) this.outputBuffer += message
|
|
// 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 {
|
|
this.config.output += cleanData(char)
|
|
}
|
|
}
|
|
this.config.output = this.config.output.slice(-15000)
|
|
this.socketIO.emit('line_output', {
|
|
stationId,
|
|
lineId: id,
|
|
data: message,
|
|
})
|
|
appendLog(
|
|
cleanData(message),
|
|
this.config.stationId,
|
|
this.config.lineNumber,
|
|
this.config.port
|
|
)
|
|
})
|
|
|
|
this.client.on('error', (err) => {
|
|
if (resolvedOrRejected) return
|
|
resolvedOrRejected = true
|
|
console.error(`❌ Error line ${lineNumber}:`, err.message)
|
|
this.config.output += err.message
|
|
this.socketIO.emit('line_error', {
|
|
stationId,
|
|
lineId: id,
|
|
error: err.message + '\r\n',
|
|
})
|
|
reject(err)
|
|
})
|
|
|
|
this.client.on('close', () => {
|
|
console.log(`🔌 Line ${lineNumber} disconnected`)
|
|
this.config.status = 'disconnected'
|
|
this.socketIO.emit('line_disconnected', {
|
|
stationId,
|
|
lineId: id,
|
|
lineNumber,
|
|
status: 'disconnected',
|
|
})
|
|
})
|
|
|
|
this.client.on('timeout', () => {
|
|
if (resolvedOrRejected) return
|
|
resolvedOrRejected = true
|
|
|
|
console.log(`⏳ Connection timeout line ${lineNumber}`)
|
|
this.client.destroy()
|
|
// reject(new Error('Connection timeout'))
|
|
})
|
|
})
|
|
}
|
|
|
|
writeCommand(cmd: string) {
|
|
if (this.client.destroyed) {
|
|
console.log(`⚠️ Cannot send, line ${this.config.lineNumber} is closed`)
|
|
return
|
|
}
|
|
this.client.write(`${cmd}`)
|
|
}
|
|
|
|
disconnect() {
|
|
try {
|
|
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)
|
|
}
|
|
}
|
|
|
|
async runScript(script: Scenario) {
|
|
if (!this.client || this.client.destroyed) {
|
|
console.log('Not connected')
|
|
this.isRunningScript = false
|
|
this.outputBuffer = ''
|
|
return
|
|
}
|
|
if (this.isRunningScript) {
|
|
console.log('Script already running')
|
|
return
|
|
}
|
|
|
|
this.isRunningScript = true
|
|
const now = Date.now()
|
|
appendLog(
|
|
`\n\n---start-scenarios---${now}---\n---scenario---${script?.title}---${now}---\n`,
|
|
this.config.stationId,
|
|
this.config.lineNumber,
|
|
this.config.port
|
|
)
|
|
this.config.latestScenario = {
|
|
name: script?.title,
|
|
time: now,
|
|
}
|
|
const steps = typeof script?.body === 'string' ? JSON.parse(script?.body) : []
|
|
let stepIndex = 0
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const timeoutTimer = setTimeout(() => {
|
|
this.isRunningScript = false
|
|
this.outputBuffer = ''
|
|
this.config.output += 'Timeout run scenario'
|
|
this.socketIO.emit('line_output', {
|
|
stationId: this.config.stationId,
|
|
lineId: this.config.id,
|
|
data: 'Timeout run scenario',
|
|
})
|
|
appendLog(
|
|
`\n---end-scenarios---${now}---\n`,
|
|
this.config.stationId,
|
|
this.config.lineNumber,
|
|
this.config.port
|
|
)
|
|
// reject(new Error('Script timeout'))
|
|
}, script.timeout || 300000)
|
|
|
|
const runStep = async (index: number) => {
|
|
if (index >= steps.length) {
|
|
clearTimeout(timeoutTimer)
|
|
this.isRunningScript = false
|
|
this.outputBuffer = ''
|
|
appendLog(
|
|
`\n---end-scenarios---${now}---\n`,
|
|
this.config.stationId,
|
|
this.config.lineNumber,
|
|
this.config.port
|
|
)
|
|
const pathLog = getPathLog(
|
|
this.config.stationId,
|
|
this.config.lineNumber,
|
|
this.config.port
|
|
)
|
|
|
|
if (pathLog)
|
|
fs.readFile(pathLog, 'utf8', async (err, content) => {
|
|
if (err) return
|
|
|
|
const logScenarios = getLogWithTimeScenario(content, now) || ''
|
|
const data = textfsmResults(logScenarios, '')
|
|
try {
|
|
data.forEach((item) => {
|
|
if (item?.textfsm && isValidJson(item?.textfsm)) {
|
|
if (
|
|
['show inventory', 'sh inventory', 'show inv', 'sh inv'].includes(
|
|
item.command
|
|
)
|
|
) {
|
|
this.config.inventory = JSON.parse(item.textfsm)[0]
|
|
}
|
|
item.textfsm = JSON.parse(item.textfsm)
|
|
}
|
|
})
|
|
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)
|
|
}
|
|
})
|
|
|
|
resolve(true)
|
|
return
|
|
}
|
|
|
|
const step = steps[index]
|
|
appendLog(
|
|
`\n---send-command---"${step?.send ?? ''}"---${now}---\n`,
|
|
this.config.stationId,
|
|
this.config.lineNumber,
|
|
this.config.port
|
|
)
|
|
let repeatCount = Number(step.repeat) || 1
|
|
const sendCommand = () => {
|
|
if (repeatCount <= 0) {
|
|
// Done → next step
|
|
stepIndex++
|
|
return runStep(stepIndex)
|
|
}
|
|
|
|
if (step.send) {
|
|
this.writeCommand(step?.send + '\r\n')
|
|
}
|
|
|
|
repeatCount--
|
|
setTimeout(() => sendCommand(), Number(step?.delay) || 500)
|
|
}
|
|
|
|
// Nếu expect rỗng → gửi ngay
|
|
if (!step?.expect || step?.expect.trim() === '') {
|
|
setTimeout(() => sendCommand(), Number(step?.delay) || 500)
|
|
return
|
|
}
|
|
|
|
while (this.outputBuffer) {
|
|
await sleep(200)
|
|
if (this.outputBuffer.includes(step.expect)) {
|
|
this.outputBuffer = ''
|
|
setTimeout(() => sendCommand(), Number(step?.delay) || 500)
|
|
}
|
|
}
|
|
}
|
|
|
|
runStep(stepIndex)
|
|
})
|
|
}
|
|
|
|
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,
|
|
})
|
|
}
|
|
|
|
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: '',
|
|
})
|
|
}
|
|
}
|