Update
This commit is contained in:
parent
6c00e35072
commit
b8ab1f0583
|
|
@ -3,11 +3,15 @@ import { textfsmResults } from './../ultils/templates/index.js'
|
|||
import net from 'node:net'
|
||||
import {
|
||||
appendLog,
|
||||
applyRules,
|
||||
classifyLog,
|
||||
cleanData,
|
||||
detectScenarioByModel,
|
||||
isValidJson,
|
||||
LogStreamBuffer,
|
||||
mapToLineFormat,
|
||||
sleep,
|
||||
TestSession,
|
||||
} from '../ultils/helper.js'
|
||||
import Scenario from '#models/scenario'
|
||||
import path from 'node:path'
|
||||
|
|
@ -105,10 +109,11 @@ export default class LineConnection {
|
|||
private waitingScenario: boolean
|
||||
private outputInventory: string
|
||||
private outputScenario: string
|
||||
// private bufferCommand: string
|
||||
private bufferLog: LogStreamBuffer
|
||||
public dataDPELP: DataDPELP | string
|
||||
private listScenarios: number[]
|
||||
public handleClearLine: () => void
|
||||
private session: TestSession
|
||||
|
||||
constructor(config: LineConfig, socketIO: any, handleClearLine: () => void) {
|
||||
this.config = config
|
||||
|
|
@ -120,7 +125,7 @@ export default class LineConnection {
|
|||
this.waitingScenario = false
|
||||
this.outputInventory = ''
|
||||
this.outputScenario = ''
|
||||
// this.bufferCommand = ''
|
||||
this.bufferLog = new LogStreamBuffer()
|
||||
this.dataDPELP = {
|
||||
line: this.config.lineNumber,
|
||||
pid: '',
|
||||
|
|
@ -133,6 +138,7 @@ export default class LineConnection {
|
|||
summary: '',
|
||||
}
|
||||
this.listScenarios = []
|
||||
this.session = new TestSession()
|
||||
this.handleClearLine = handleClearLine
|
||||
}
|
||||
|
||||
|
|
@ -159,12 +165,15 @@ export default class LineConnection {
|
|||
lineNumber,
|
||||
status: 'connected',
|
||||
})
|
||||
// this.checkLog()
|
||||
resolve()
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
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.isRunningScript) {
|
||||
this.waitingScenario = true
|
||||
|
|
@ -449,9 +458,14 @@ export default class LineConnection {
|
|||
})
|
||||
const scenario = await detectScenarioByModel(pid, this.listScenarios)
|
||||
console.log(pid, scenario?.title, this.listScenarios)
|
||||
if (scenario && scenario.id !== script.id) {
|
||||
if (
|
||||
scenario &&
|
||||
scenario.id !== script.id &&
|
||||
scenario.title.includes('DPELP') &&
|
||||
script.title.includes('DPELP')
|
||||
) {
|
||||
this.listScenarios.push(scenario.id)
|
||||
// this.outputScenario = ''
|
||||
this.outputScenario = ''
|
||||
this.runScript(scenario, userName)
|
||||
// this.socketIO.emit('confirm_scenario', {
|
||||
// scenario: scenario,
|
||||
|
|
@ -770,4 +784,33 @@ export default class LineConnection {
|
|||
const items = await redis.zrange(key, 0, -1)
|
||||
return items.map((i) => JSON.parse(i))
|
||||
}
|
||||
|
||||
handleLogLine = (line: string) => {
|
||||
try {
|
||||
const parsed = classifyLog(line)
|
||||
const matchedRules = applyRules(parsed)
|
||||
|
||||
matchedRules.forEach((rule) => {
|
||||
// console.log(rule)
|
||||
this.session.applyRule(rule)
|
||||
})
|
||||
} catch (error) {
|
||||
console.log('handleLogLine', error)
|
||||
}
|
||||
}
|
||||
|
||||
checkLog() {
|
||||
const interval = setInterval(async () => {
|
||||
try {
|
||||
if (this.config.status !== 'connected') {
|
||||
clearInterval(interval)
|
||||
return
|
||||
}
|
||||
const result = this.session.finalize()
|
||||
console.log('FINAL RESULT:', this.config.apcName, this.config.lineNumber, result)
|
||||
} catch (err: any) {
|
||||
console.error('Error checking log:', err)
|
||||
}
|
||||
}, 30000)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import fs from 'node:fs'
|
|||
import path from 'node:path'
|
||||
import nodeMailer from 'nodemailer'
|
||||
import zulip from 'zulip-js'
|
||||
import { LogRule, ParsedLog } from './types.js'
|
||||
|
||||
type DetectAI = {
|
||||
status: string[]
|
||||
|
|
@ -309,6 +310,7 @@ export function sendMessageToZulip(
|
|||
// Catch scenario with key longer
|
||||
export const detectScenarioByModel = async (model: string, listScenarios: number[]) => {
|
||||
let scenarios = await Scenario.query().preload('brand').preload('category')
|
||||
let scenarioDefault = await Scenario.findBy('title', 'DPELP DEFAULT')
|
||||
const normalizedModel = model.trim().toUpperCase()
|
||||
let matched: { scenario: Scenario; score: number } | null = null
|
||||
|
||||
|
|
@ -331,5 +333,95 @@ export const detectScenarioByModel = async (model: string, listScenarios: number
|
|||
}
|
||||
}
|
||||
|
||||
return matched?.scenario || null
|
||||
return matched?.scenario ? matched?.scenario : listScenarios.length === 0 ? scenarioDefault : null
|
||||
}
|
||||
|
||||
export function classifyLog(line: string): ParsedLog {
|
||||
if (/System Bootstrap|IOS XE Software|Booting/.test(line)) return { raw: line, category: 'BOOT' }
|
||||
|
||||
if (/LICENSE|Smart Licensing|Evaluation/.test(line)) return { raw: line, category: 'LICENSE' }
|
||||
|
||||
if (/LINK-3-UPDOWN|line protocol/.test(line)) return { raw: line, category: 'INTERFACE' }
|
||||
|
||||
if (/FAN|TEMP|POWER|PSU/.test(line)) return { raw: line, category: 'HARDWARE' }
|
||||
|
||||
if (/ERROR|FAIL|CRITICAL|Traceback/.test(line)) return { raw: line, category: 'ERROR' }
|
||||
|
||||
return { raw: line, category: 'UNKNOWN' }
|
||||
}
|
||||
|
||||
export const RULES: LogRule[] = [
|
||||
{
|
||||
id: 'BOOT_OK',
|
||||
category: 'BOOT',
|
||||
match: /IOS XE Software|System Bootstrap/,
|
||||
result: 'PASS',
|
||||
message: 'Boot successful',
|
||||
},
|
||||
{
|
||||
id: 'BOOT_LOOP',
|
||||
category: 'BOOT',
|
||||
match: /boot loop|reloading|restart/i,
|
||||
result: 'FAIL',
|
||||
message: 'Boot loop detected',
|
||||
},
|
||||
{
|
||||
id: 'LICENSE_EXPIRED',
|
||||
category: 'LICENSE',
|
||||
match: /Evaluation.*expired|license expired/i,
|
||||
result: 'WARN',
|
||||
message: 'License expired',
|
||||
},
|
||||
{
|
||||
id: 'HW_FAIL',
|
||||
category: 'HARDWARE',
|
||||
match: /(FAN|PSU).*(FAIL|CRITICAL)/i,
|
||||
result: 'FAIL',
|
||||
message: 'Hardware failure',
|
||||
},
|
||||
]
|
||||
|
||||
export function applyRules(log: ParsedLog): LogRule[] {
|
||||
return RULES.filter((rule) => rule.category === log.category && rule.match.test(log.raw))
|
||||
}
|
||||
|
||||
export class TestSession {
|
||||
boot = false
|
||||
hasHwFail = false
|
||||
licenseWarn = false
|
||||
issues: string[] = []
|
||||
|
||||
public applyRule(rule: LogRule) {
|
||||
if (rule.id === 'BOOT_OK') this.boot = true
|
||||
if (rule.result === 'FAIL') this.hasHwFail = true
|
||||
if (rule.result === 'WARN') this.licenseWarn = true
|
||||
this.issues.push(rule.message)
|
||||
}
|
||||
|
||||
public finalize() {
|
||||
if (!this.boot) return 'FAIL'
|
||||
if (this.hasHwFail) return 'FAIL'
|
||||
if (this.licenseWarn) return 'PARTIAL'
|
||||
return 'PASS'
|
||||
}
|
||||
}
|
||||
|
||||
export class LogStreamBuffer {
|
||||
private buffer = ''
|
||||
|
||||
public push(chunk: Buffer): string[] {
|
||||
this.buffer += chunk.toString('utf8')
|
||||
|
||||
const lines = this.buffer.split(/\r?\n/)
|
||||
this.buffer = lines.pop() || ''
|
||||
|
||||
return lines.map((l) => l.trim()).filter(Boolean)
|
||||
}
|
||||
|
||||
public flush(): string | null {
|
||||
if (!this.buffer) return null
|
||||
const last = this.buffer
|
||||
this.buffer = ''
|
||||
return last
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,3 +7,21 @@ export interface CustomSocket extends Socket {
|
|||
export interface CustomServer extends Server {
|
||||
userKeys?: string[]
|
||||
}
|
||||
|
||||
type LogCategory = 'BOOT' | 'LICENSE' | 'INTERFACE' | 'HARDWARE' | 'ERROR' | 'UNKNOWN'
|
||||
|
||||
export interface ParsedLog {
|
||||
raw: string
|
||||
category: LogCategory
|
||||
timestamp?: Date
|
||||
}
|
||||
|
||||
type RuleResult = 'PASS' | 'FAIL' | 'WARN'
|
||||
|
||||
export interface LogRule {
|
||||
id: string
|
||||
category: LogCategory
|
||||
match: RegExp
|
||||
result: RuleResult
|
||||
message: string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1092,7 +1092,7 @@ export class WebSocketIo {
|
|||
<td style="width:270px">${item.ios || ''}</td>
|
||||
<td style="width:200px;">${licenseHTML}</td>
|
||||
<td style="width:200px; text-wrap: wrap;">${item.summary || ''}</td>
|
||||
<td>${item.issues?.length ? `- ` + item.issues.join(`<br>- `) : ''}</td>
|
||||
<td>${item.issues?.length ? `- ` + item.issues.join(`<br>- `) : '- No issues detected.'}</td>
|
||||
</tr>
|
||||
`
|
||||
}
|
||||
|
|
@ -1127,7 +1127,7 @@ export class WebSocketIo {
|
|||
// Format issues
|
||||
const issuesMd = item.issues?.length
|
||||
? item.issues.map((i: string) => `• ${i}`).join(' --')
|
||||
: ''
|
||||
: '- No issues detected.'
|
||||
|
||||
msg +=
|
||||
`| ${item.line || ''}` +
|
||||
|
|
|
|||
Loading…
Reference in New Issue