Implement DPELP batch run and result aggregation
Added backend and frontend support for running DPELP scenarios on all lines of a station and aggregating results. Introduced a new socket event 'run_all_dpelp', a helper for formatting line results, and logic to post results to a wiki endpoint. Also updated scenario command delays and improved overlay positioning logic in the UI.
This commit is contained in:
parent
3e1ad11e72
commit
77027d4f8a
|
|
@ -6,6 +6,7 @@ import {
|
|||
cleanData,
|
||||
getLogWithTimeScenario,
|
||||
isValidJson,
|
||||
mapToLineFormat,
|
||||
sleep,
|
||||
} from '../ultils/helper.js'
|
||||
import Scenario from '#models/scenario'
|
||||
|
|
@ -15,6 +16,15 @@ import path from 'node:path'
|
|||
import axios from 'axios'
|
||||
import redis from '@adonisjs/redis/services/main'
|
||||
|
||||
type Inventory = {
|
||||
pid: string
|
||||
vid: string
|
||||
sn: string
|
||||
licenseLevel: string
|
||||
licenseType: string
|
||||
nextLicenseLevel: string
|
||||
}
|
||||
|
||||
interface LineConfig {
|
||||
id: number
|
||||
port: number
|
||||
|
|
@ -84,7 +94,7 @@ export default class LineConnection {
|
|||
private outputInventory: string
|
||||
private outputScenario: string
|
||||
private bufferCommand: string
|
||||
private retryConnect: number
|
||||
public dataDPELP: string
|
||||
|
||||
constructor(config: LineConfig, socketIO: any) {
|
||||
this.config = config
|
||||
|
|
@ -97,7 +107,7 @@ export default class LineConnection {
|
|||
this.outputInventory = ''
|
||||
this.outputScenario = ''
|
||||
this.bufferCommand = ''
|
||||
this.retryConnect = 0
|
||||
this.dataDPELP = 'No data'
|
||||
}
|
||||
|
||||
connect(timeoutMs = 5000) {
|
||||
|
|
@ -160,7 +170,7 @@ export default class LineConnection {
|
|||
if (!this.config.inventory) {
|
||||
setTimeout(() => {
|
||||
this.getInventory()
|
||||
}, 3000)
|
||||
}, 5000)
|
||||
}
|
||||
appendLog(
|
||||
cleanData(message),
|
||||
|
|
@ -227,13 +237,7 @@ export default class LineConnection {
|
|||
this.disconnect()
|
||||
await sleep(2000)
|
||||
await this.connect()
|
||||
// await this.writeCommand(cmd)
|
||||
// if (this.retryConnect <= 3) {
|
||||
// console.log('Retry connect times', this.retryConnect)
|
||||
// this.retryConnect += 1
|
||||
// await this.connect()
|
||||
// await this.writeCommand(cmd)
|
||||
// }
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -293,7 +297,7 @@ export default class LineConnection {
|
|||
console.log(
|
||||
`Run scenario "${script?.title}" to line ${this.config.lineNumber} of ${this.config.stationName}`
|
||||
)
|
||||
|
||||
if (script?.title === 'DPELP') this.dataDPELP = ''
|
||||
this.isRunningScript = true
|
||||
const now = Date.now()
|
||||
this.outputScenario += `\n\n---start-scenarios---${now}---${userName}---${script?.title}---\n---scenario---${script?.title}---${now}---\n`
|
||||
|
|
@ -378,6 +382,15 @@ export default class LineConnection {
|
|||
}
|
||||
})
|
||||
const detectLog = await this.detectLogWithAI(logScenarios)
|
||||
const result = mapToLineFormat({
|
||||
lineNumber: this.config.lineNumber,
|
||||
inventory: this.config.inventory,
|
||||
latestScenario: {
|
||||
detectAI: detectLog,
|
||||
},
|
||||
data,
|
||||
})
|
||||
if (script?.title === 'DPELP') this.dataDPELP = result
|
||||
if (this.config.latestScenario)
|
||||
this.config.latestScenario = { ...this.config.latestScenario, detectAI: detectLog }
|
||||
this.config.data = data
|
||||
|
|
@ -398,14 +411,6 @@ export default class LineConnection {
|
|||
}
|
||||
|
||||
const step = steps[index]
|
||||
this.outputScenario += `\n---send-command---"${step?.send ?? ''}"---${now}---\n`
|
||||
appendLog(
|
||||
`\n---send-command---"${step?.send ?? ''}"---${now}---\n`,
|
||||
this.config.stationId,
|
||||
this.config.stationName,
|
||||
this.config.stationIp,
|
||||
this.config.lineNumber
|
||||
)
|
||||
let repeatCount = Number(step.repeat) || 1
|
||||
const sendCommand = async () => {
|
||||
if (repeatCount <= 0) {
|
||||
|
|
@ -415,6 +420,14 @@ export default class LineConnection {
|
|||
}
|
||||
|
||||
if (step.send) {
|
||||
this.outputScenario += `\n---send-command---"${step?.send ?? ''}"---${now}---\n`
|
||||
appendLog(
|
||||
`\n---send-command---"${step?.send ?? ''}"---${now}---\n`,
|
||||
this.config.stationId,
|
||||
this.config.stationName,
|
||||
this.config.stationIp,
|
||||
this.config.lineNumber
|
||||
)
|
||||
this.writeCommand(step?.send + '\r\n')
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,20 @@
|
|||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
type DetectAI = {
|
||||
status: string[]
|
||||
issue: string[]
|
||||
}
|
||||
|
||||
type InputData = {
|
||||
lineNumber: number
|
||||
inventory: any
|
||||
latestScenario?: {
|
||||
detectAI?: DetectAI
|
||||
}
|
||||
data?: any[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to clean up unwanted characters from the output data.
|
||||
* @param {string} data - The raw data to be cleaned.
|
||||
|
|
@ -122,3 +136,54 @@ export function isValidJson(string: string) {
|
|||
return false // Chuỗi không phải là định dạng JSON hợp lệ
|
||||
}
|
||||
}
|
||||
|
||||
export function mapToLineFormat(input: InputData): string {
|
||||
const line = input.lineNumber
|
||||
|
||||
// Inventory info
|
||||
const pid = input.inventory?.pid || ''
|
||||
const vid = input.inventory?.vid || ''
|
||||
const sn = input.inventory?.sn || ''
|
||||
|
||||
if (!pid || !sn) return `Line ${line}: No data`
|
||||
|
||||
// Find MAC address from "show version" or other sources
|
||||
let mac = ''
|
||||
const showVersion = input.data?.find((d) => d.command === 'show version')
|
||||
if (showVersion?.textfsm?.[0]?.MAC_ADDRESS) {
|
||||
mac = showVersion.textfsm[0].MAC_ADDRESS
|
||||
}
|
||||
|
||||
// Get data license
|
||||
const dataLicense = input.data?.find((comm: any) => comm.command?.trim() === 'show license')
|
||||
const listLicense =
|
||||
dataLicense?.textfsm && Array.isArray(dataLicense?.textfsm)
|
||||
? dataLicense?.textfsm?.map((val: any) => val.FEATURE).join(', ')
|
||||
: ''
|
||||
const dataPlatform = input.data?.find((el) => el.command?.trim() === 'show platform')
|
||||
const DPELP = dataPlatform && !dataPlatform?.output?.includes('Incomplete') ? true : false
|
||||
|
||||
// Detect AI issues
|
||||
const issues = input.latestScenario?.detectAI?.issue || []
|
||||
|
||||
// Build output
|
||||
let output = `Line ${line}: `
|
||||
output += `PID: ${pid}, `
|
||||
output += `VID: ${vid}, `
|
||||
output += `SN: ${sn}, `
|
||||
output += `Tested mode: ${DPELP ? 'DPELP' : 'DPEL'}, `
|
||||
output += `${mac ? 'MAC Address:' + mac + `, ` : ''} `
|
||||
|
||||
output += `${listLicense ? 'License: ' + listLicense : ''}`
|
||||
|
||||
if (Array.isArray(issues) && issues.length > 0) {
|
||||
output += `\n Issues:\n`
|
||||
for (const issue of issues) {
|
||||
output += ` - ${issue}\n`
|
||||
}
|
||||
} else if (typeof issues === 'string') {
|
||||
output += `\n Issues: ${issues}`
|
||||
}
|
||||
|
||||
return output.trim()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,8 +53,26 @@ export const textfsmResults = (logContent: string, cmd: string) => {
|
|||
// Parse commands and outputs
|
||||
const matches = [...logContent.matchAll(regexPattern)]
|
||||
|
||||
const mergedMatches = []
|
||||
|
||||
for (let match of matches) {
|
||||
const m = match
|
||||
const command = m.groups?.command?.trim() || ''
|
||||
|
||||
if (command?.toLowerCase() === 'show version | include license') {
|
||||
// Gộp vào phần tử trước
|
||||
if (mergedMatches.length > 1) {
|
||||
const lastMatch = mergedMatches[mergedMatches.length - 1]
|
||||
if (lastMatch?.groups?.output) lastMatch.groups.output += '\n' + m?.groups?.output
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
mergedMatches.push(m)
|
||||
}
|
||||
|
||||
// Process matches
|
||||
results = matches
|
||||
results = mergedMatches
|
||||
.map((match) => {
|
||||
const command = match.groups?.command.trim() || ''
|
||||
const output = match.groups?.output.trim() || ''
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import APCController from '#services/apc_connection'
|
|||
import { appendLog, cleanData, sleep } from '../app/ultils/helper.js'
|
||||
import SwitchController from '#services/switch_connection'
|
||||
import redis from '@adonisjs/redis/services/main'
|
||||
import axios from 'axios'
|
||||
|
||||
interface HandleOptions {
|
||||
command?: string
|
||||
|
|
@ -531,6 +532,26 @@ export class WebSocketIo {
|
|||
|
||||
io.to(socket.id).emit('list_histories', result)
|
||||
})
|
||||
|
||||
socket.on('run_all_dpelp', async (data) => {
|
||||
const lineIds = data.lineIds
|
||||
console.log('[DPELP] Received run all dpelp')
|
||||
|
||||
const results = await this.waitUntilAllReady(lineIds)
|
||||
|
||||
const d = new Date(Date.now())
|
||||
const pad = (n: number) => String(n).padStart(2, '0')
|
||||
|
||||
const dataFormat =
|
||||
`${d.getFullYear()}/${pad(d.getMonth() + 1)}/${pad(d.getDate())}, ` +
|
||||
`${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
|
||||
const linkWiki =
|
||||
process.env.LINK_WIKI || 'https://logs.danielvu.com/api/wiki/page/insert?title=Dev_test'
|
||||
await axios.post(linkWiki, {
|
||||
data: `<pre>${results.join('\n\n')}\n</pre>`,
|
||||
titleAuto: 'AUTO - ' + dataFormat,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
socketServer.listen(SOCKET_IO_PORT, () => {
|
||||
|
|
@ -1003,4 +1024,27 @@ export class WebSocketIo {
|
|||
const items = await redis.zrange(key, 0, -1)
|
||||
return items.map((i) => JSON.parse(i))
|
||||
}
|
||||
|
||||
async waitUntilAllReady(lineIds: number[]) {
|
||||
return new Promise<string[]>((resolve) => {
|
||||
const interval = setInterval(() => {
|
||||
let allReady = true
|
||||
const results: string[] = []
|
||||
for (const lineId of lineIds) {
|
||||
const line = this.lineMap.get(lineId)
|
||||
if (!line || !line.dataDPELP) {
|
||||
allReady = false
|
||||
break
|
||||
}
|
||||
results.push(line.dataDPELP)
|
||||
}
|
||||
|
||||
if (allReady) {
|
||||
clearInterval(interval)
|
||||
console.log('[DPELP] All lines ready')
|
||||
resolve(results)
|
||||
}
|
||||
}, 5000) // check mỗi 5 giây
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -761,6 +761,14 @@ const BottomToolBar = ({
|
|||
isDisable={isDisable || selectedLines.length === 0}
|
||||
onClick={() => {
|
||||
// setSelectedLines([]);
|
||||
if (
|
||||
selectedLines.length > 0 &&
|
||||
selectedLines.length === station?.lines?.length
|
||||
) {
|
||||
socket?.emit("run_all_dpelp", {
|
||||
lineIds: selectedLines.map((line) => line.id),
|
||||
});
|
||||
}
|
||||
setIsDisable(true);
|
||||
setTimeout(() => {
|
||||
setIsDisable(false);
|
||||
|
|
|
|||
|
|
@ -65,45 +65,52 @@ export const ButtonDPELP = ({
|
|||
repeat: "1",
|
||||
note: "",
|
||||
},
|
||||
{
|
||||
expect: "",
|
||||
send: "show version",
|
||||
delay: "1000",
|
||||
repeat: "1",
|
||||
note: "",
|
||||
},
|
||||
{
|
||||
expect: "",
|
||||
send: "show diag",
|
||||
delay: "1500",
|
||||
delay: "3000",
|
||||
repeat: "1",
|
||||
note: "",
|
||||
},
|
||||
{
|
||||
expect: "",
|
||||
send: "show post",
|
||||
delay: "1500",
|
||||
delay: "3000",
|
||||
repeat: "1",
|
||||
note: "",
|
||||
},
|
||||
{
|
||||
expect: "",
|
||||
send: "show env all",
|
||||
delay: "1500",
|
||||
delay: "3000",
|
||||
repeat: "1",
|
||||
note: "",
|
||||
},
|
||||
{
|
||||
expect: "",
|
||||
send: "show license",
|
||||
delay: "1500",
|
||||
delay: "3000",
|
||||
repeat: "1",
|
||||
note: "",
|
||||
},
|
||||
{
|
||||
expect: "",
|
||||
send: "show log",
|
||||
delay: "1500",
|
||||
delay: "3000",
|
||||
repeat: "1",
|
||||
note: "",
|
||||
},
|
||||
{
|
||||
expect: "",
|
||||
send: "show platform",
|
||||
delay: "1500",
|
||||
delay: "3000",
|
||||
repeat: "1",
|
||||
note: "",
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue