Update
This commit is contained in:
parent
00621e2cbe
commit
fb1554d857
|
|
@ -1,5 +1,14 @@
|
||||||
|
import { textfsmResults } from './../ultils/templates/index.js'
|
||||||
|
import fs from 'node:fs'
|
||||||
import net from 'node:net'
|
import net from 'node:net'
|
||||||
import { appendLog, cleanData, sleep } from '../ultils/helper.js'
|
import {
|
||||||
|
appendLog,
|
||||||
|
cleanData,
|
||||||
|
getLogWithTimeScenario,
|
||||||
|
getPathLog,
|
||||||
|
isValidJson,
|
||||||
|
sleep,
|
||||||
|
} from '../ultils/helper.js'
|
||||||
import Scenario from '#models/scenario'
|
import Scenario from '#models/scenario'
|
||||||
|
|
||||||
interface LineConfig {
|
interface LineConfig {
|
||||||
|
|
@ -14,6 +23,11 @@ interface LineConfig {
|
||||||
openCLI: boolean
|
openCLI: boolean
|
||||||
userEmailOpenCLI: string
|
userEmailOpenCLI: string
|
||||||
userOpenCLI: string
|
userOpenCLI: string
|
||||||
|
data: {
|
||||||
|
command: string
|
||||||
|
output: string
|
||||||
|
textfsm: string
|
||||||
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
|
|
@ -163,8 +177,9 @@ export default class LineConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isRunningScript = true
|
this.isRunningScript = true
|
||||||
|
const now = Date.now()
|
||||||
appendLog(
|
appendLog(
|
||||||
`\n\n---start-scenarios---${Date.now()}---\n---scenario---${script?.title}---${Date.now()}---\n`,
|
`\n\n---start-scenarios---${now}---\n---scenario---${script?.title}---${now}---\n`,
|
||||||
this.config.stationId,
|
this.config.stationId,
|
||||||
this.config.lineNumber,
|
this.config.lineNumber,
|
||||||
this.config.port
|
this.config.port
|
||||||
|
|
@ -183,7 +198,7 @@ export default class LineConnection {
|
||||||
data: 'Timeout run scenario',
|
data: 'Timeout run scenario',
|
||||||
})
|
})
|
||||||
appendLog(
|
appendLog(
|
||||||
`\n---end-scenarios---${Date.now()}---\n`,
|
`\n---end-scenarios---${now}---\n`,
|
||||||
this.config.stationId,
|
this.config.stationId,
|
||||||
this.config.lineNumber,
|
this.config.lineNumber,
|
||||||
this.config.port
|
this.config.port
|
||||||
|
|
@ -197,18 +212,47 @@ export default class LineConnection {
|
||||||
this.isRunningScript = false
|
this.isRunningScript = false
|
||||||
this.outputBuffer = ''
|
this.outputBuffer = ''
|
||||||
appendLog(
|
appendLog(
|
||||||
`\n---end-scenarios---${Date.now()}---\n`,
|
`\n---end-scenarios---${now}---\n`,
|
||||||
this.config.stationId,
|
this.config.stationId,
|
||||||
this.config.lineNumber,
|
this.config.lineNumber,
|
||||||
this.config.port
|
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 = await textfsmResults(logScenarios, '')
|
||||||
|
try {
|
||||||
|
data.forEach((item) => {
|
||||||
|
if (item?.textfsm && isValidJson(item?.textfsm)) {
|
||||||
|
item.textfsm = JSON.parse(item.textfsm)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.config.data = data
|
||||||
|
this.socketIO.emit('data_textfsm', {
|
||||||
|
stationId: this.config.stationId,
|
||||||
|
lineId: this.config.id,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
resolve(true)
|
resolve(true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const step = steps[index]
|
const step = steps[index]
|
||||||
appendLog(
|
appendLog(
|
||||||
`\n---send-command---"${step?.send ?? ''}"---${Date.now()}---\n`,
|
`\n---send-command---"${step?.send ?? ''}"---${now}---\n`,
|
||||||
this.config.stationId,
|
this.config.stationId,
|
||||||
this.config.lineNumber,
|
this.config.lineNumber,
|
||||||
this.config.port
|
this.config.port
|
||||||
|
|
|
||||||
|
|
@ -36,3 +36,68 @@ export function appendLog(output: string, stationId: number, lineNumber: number,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getPathLog = (stationId: number, lineNumber: number, port: number) => {
|
||||||
|
const date = new Date().toISOString().slice(0, 10).replace(/-/g, '') // YYYYMMDD
|
||||||
|
const logDir = path.join('storage', 'system_logs')
|
||||||
|
const logFile = path.join(logDir, `${date}-Station_${stationId}-Line_${lineNumber}_${port}.log`)
|
||||||
|
// Ensure folder exists
|
||||||
|
if (!fs.existsSync(logDir)) {
|
||||||
|
fs.mkdirSync(logDir, { recursive: true })
|
||||||
|
return null
|
||||||
|
} else return logFile
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function get scope log with timestamp.
|
||||||
|
* @param {string} text - content log.
|
||||||
|
* @param {number} time - Timestamp.
|
||||||
|
*/
|
||||||
|
export const getLogWithTimeScenario = (text: string, time: number) => {
|
||||||
|
try {
|
||||||
|
// Match all start and end blocks
|
||||||
|
const regex = /---(start|end)-scenarios---(\d+)---/g
|
||||||
|
|
||||||
|
let match
|
||||||
|
const blocks = []
|
||||||
|
|
||||||
|
while ((match = regex.exec(text)) !== null) {
|
||||||
|
blocks.push({
|
||||||
|
type: match[1],
|
||||||
|
timestamp: match[2],
|
||||||
|
index: match.index,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the matching block for the end timestamp
|
||||||
|
let result = null
|
||||||
|
for (let i = 0; i < blocks.length; i++) {
|
||||||
|
const block = blocks[i]
|
||||||
|
if (block.type === 'end' && block.timestamp === time.toString()) {
|
||||||
|
// Find nearest preceding "start"
|
||||||
|
for (let j = i - 1; j >= 0; j--) {
|
||||||
|
if (blocks[j].type === 'start') {
|
||||||
|
const startIndex = blocks[j].index
|
||||||
|
const endIndex = block.index + text.slice(block.index).indexOf('\n') // or manually offset length of the line
|
||||||
|
result = text.slice(startIndex, endIndex).trim()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error get log:', err)
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isValidJson(string: string) {
|
||||||
|
try {
|
||||||
|
JSON.parse(string)
|
||||||
|
return true // Chuỗi là định dạng JSON hợp lệ
|
||||||
|
} catch (e) {
|
||||||
|
return false // Chuỗi không phải là định dạng JSON hợp lệ
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
import showInventory from './show_inventory.js'
|
||||||
|
import showVersion from './show_version.js'
|
||||||
|
import showLicense from './show_license.js'
|
||||||
|
import showLogging from './show_logging.js'
|
||||||
|
// const showPower = require("./show_power.js");
|
||||||
|
|
||||||
|
// Function to parse logs
|
||||||
|
function getStructuredDataTextfsm(output: string, command: string) {
|
||||||
|
switch (command) {
|
||||||
|
case 'show inventory':
|
||||||
|
case 'show inv':
|
||||||
|
case 'sh inventory':
|
||||||
|
case 'sh inv':
|
||||||
|
return showInventory(output)
|
||||||
|
case 'show version':
|
||||||
|
case 'show ver':
|
||||||
|
case 'sh version':
|
||||||
|
case 'sh ver':
|
||||||
|
return showVersion(output)
|
||||||
|
case 'show license':
|
||||||
|
case 'sh license':
|
||||||
|
return showLicense(output)
|
||||||
|
case 'show logging':
|
||||||
|
case 'sh logging':
|
||||||
|
return showLogging(output)
|
||||||
|
default:
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the parseLog method with log data and patterns
|
||||||
|
}
|
||||||
|
|
||||||
|
export const textfsmResults = (logContent: string, cmd: string) => {
|
||||||
|
let results = []
|
||||||
|
if (cmd) {
|
||||||
|
let structuredOutput = getStructuredDataTextfsm(logContent, cmd)
|
||||||
|
|
||||||
|
if (typeof structuredOutput === 'string') {
|
||||||
|
structuredOutput = {} // Convert to an empty object if it's a string
|
||||||
|
}
|
||||||
|
results = [
|
||||||
|
{
|
||||||
|
command: cmd,
|
||||||
|
output: logContent,
|
||||||
|
textfsm: JSON.stringify(structuredOutput).replace(/[\x00-\x1f\x7f-\x9f]/g, ''),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
// Regular expression to parse commands and outputs inside the scoped content
|
||||||
|
const regexPattern =
|
||||||
|
/---send-command---"(?<command>.+?)"---\d+---(?<output>[\s\S]*?)(?=(---send-command---|---split-point---|---end-testing---|---end-scenarios---))/gms
|
||||||
|
|
||||||
|
// Parse commands and outputs
|
||||||
|
const matches = [...logContent.matchAll(regexPattern)]
|
||||||
|
|
||||||
|
// Process matches
|
||||||
|
results = matches
|
||||||
|
.map((match) => {
|
||||||
|
const command = match.groups?.command.trim() || ''
|
||||||
|
const output = match.groups?.output.trim() || ''
|
||||||
|
|
||||||
|
// Get structured output using the parser
|
||||||
|
let structuredOutput = getStructuredDataTextfsm(output, command)
|
||||||
|
|
||||||
|
if (typeof structuredOutput === 'string') {
|
||||||
|
structuredOutput = {} // Convert to an empty object if it's a string
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
command,
|
||||||
|
output,
|
||||||
|
textfsm: JSON.stringify(structuredOutput).replace(/[\x00-\x1f\x7f-\x9f]/g, ''), // Clean special characters
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter((el) => el.command)
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
import XRegExp from 'xregexp'
|
||||||
|
|
||||||
|
// Parse the log data
|
||||||
|
const parseLog = (data: string) => {
|
||||||
|
const patterns = [
|
||||||
|
XRegExp('^NAME:\\s+"(?<name>.*)",\\s+DESCR:\\s+"(?<descr>.*)"'),
|
||||||
|
XRegExp('^PID:\\s+(?<pid>[\\S+]+|.*),.*VID:\\s+(?<vid>.*),.*SN:\\s+(?<sn>[\\w+\\d+]*)'),
|
||||||
|
XRegExp('^PID:\\s+,.*VID:\\s+(?<vid>.*),.*SN:\\s+(?<sn>[\\w+\\d+]*)'),
|
||||||
|
XRegExp('^PID:\\s+(?<pid>[\\S+]+|.*),.*VID:\\s+(?<vid>.*),.*SN:'),
|
||||||
|
XRegExp('^PID:\\s+(?<pid>\\S+)(?:,|\\s+)VID:\\s+(?<vid>\\S+)(?:,|\\s+)SN:\\s+(?<sn>\\w+)'),
|
||||||
|
// License info
|
||||||
|
XRegExp('^License Level:\\s+(?<licenseLevel>.*)'),
|
||||||
|
XRegExp('^License Type:\\s+(?<licenseType>.*)'),
|
||||||
|
XRegExp('^Next reload license Level:\\s+(?<nextLicenseLevel>.*)'),
|
||||||
|
]
|
||||||
|
|
||||||
|
const lines = data.split('\n')
|
||||||
|
const licenseLog = data.split('show version | include License')
|
||||||
|
let records: any = []
|
||||||
|
let currentRecord: any = {
|
||||||
|
name: '',
|
||||||
|
descr: '',
|
||||||
|
pid: '',
|
||||||
|
vid: '',
|
||||||
|
sn: '',
|
||||||
|
licenseLevel: '',
|
||||||
|
licenseType: '',
|
||||||
|
nextLicenseLevel: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
for (const pattern of patterns) {
|
||||||
|
const match = XRegExp.exec(line, pattern)
|
||||||
|
if (match) {
|
||||||
|
const item = match?.groups
|
||||||
|
// Update current record with matched fields
|
||||||
|
Object.keys(currentRecord).forEach((key) => {
|
||||||
|
if (item && item[key] !== undefined) {
|
||||||
|
currentRecord[key] = item[key].trim()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// If "pid", "vid", or "sn" are matched, push a completed record
|
||||||
|
if (currentRecord.pid || currentRecord.vid || currentRecord.sn) {
|
||||||
|
records.push({ ...currentRecord })
|
||||||
|
currentRecord = {
|
||||||
|
name: '',
|
||||||
|
descr: '',
|
||||||
|
pid: '',
|
||||||
|
vid: '',
|
||||||
|
sn: '',
|
||||||
|
licenseLevel: '',
|
||||||
|
licenseType: '',
|
||||||
|
nextLicenseLevel: '',
|
||||||
|
} // Reset for the next record
|
||||||
|
}
|
||||||
|
if ((currentRecord.licenseLevel || currentRecord.licenseType) && records.length > 0) {
|
||||||
|
const value = records[0]
|
||||||
|
value.licenseLevel = currentRecord.licenseLevel
|
||||||
|
? currentRecord.licenseLevel
|
||||||
|
: value.licenseLevel
|
||||||
|
value.licenseType = currentRecord.licenseType
|
||||||
|
? currentRecord.licenseType
|
||||||
|
: value.licenseType
|
||||||
|
value.nextLicenseLevel = currentRecord.nextLicenseLevel
|
||||||
|
? currentRecord.nextLicenseLevel
|
||||||
|
: value.nextLicenseLevel
|
||||||
|
records = [value]
|
||||||
|
currentRecord = {
|
||||||
|
name: '',
|
||||||
|
descr: '',
|
||||||
|
pid: '',
|
||||||
|
vid: '',
|
||||||
|
sn: '',
|
||||||
|
licenseLevel: '',
|
||||||
|
licenseType: '',
|
||||||
|
nextLicenseLevel: '',
|
||||||
|
} // Reset for the next record
|
||||||
|
}
|
||||||
|
break // Stop checking other patterns for this line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (records.length > 0) {
|
||||||
|
let extend = null
|
||||||
|
const firstRecord = records[0]
|
||||||
|
// check license and last 2 chars of pid
|
||||||
|
if (
|
||||||
|
firstRecord.licenseLevel &&
|
||||||
|
typeof firstRecord.licenseLevel === 'string' &&
|
||||||
|
firstRecord.pid.length >= 2
|
||||||
|
) {
|
||||||
|
switch (firstRecord.licenseLevel.toLowerCase()) {
|
||||||
|
case 'network essentials':
|
||||||
|
case 'network-essentials':
|
||||||
|
case 'dna essentials':
|
||||||
|
case 'dna-essentials':
|
||||||
|
extend = '-E'
|
||||||
|
break
|
||||||
|
case 'network advantage':
|
||||||
|
case 'network-advantage':
|
||||||
|
case 'dna advantage':
|
||||||
|
case 'dna-advantage':
|
||||||
|
extend = '-A'
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (licenseLog[1] && !firstRecord.licenseLevel) {
|
||||||
|
if (licenseLog[1]?.includes('essentials')) {
|
||||||
|
extend = '-E'
|
||||||
|
firstRecord.licenseLevel = 'network-essentials'
|
||||||
|
firstRecord.licenseType = 'Smart License'
|
||||||
|
} else if (licenseLog[1]?.includes('advantage')) {
|
||||||
|
extend = '-A'
|
||||||
|
firstRecord.licenseLevel = 'network-advantage'
|
||||||
|
firstRecord.licenseType = 'Smart License'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (extend) {
|
||||||
|
const key = firstRecord.pid.slice(-2)
|
||||||
|
if (key !== extend) firstRecord.pid = firstRecord.pid + extend
|
||||||
|
records = [firstRecord]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return records
|
||||||
|
}
|
||||||
|
|
||||||
|
export default parseLog
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
import XRegExp from 'xregexp'
|
||||||
|
|
||||||
|
// Patterns for each field
|
||||||
|
|
||||||
|
// Parser function
|
||||||
|
const parseLog = (data: string) => {
|
||||||
|
const patterns = [
|
||||||
|
XRegExp('^Index\\s+\\d+\\s+Feature:\\s+(?<FEATURE>\\S+)'),
|
||||||
|
XRegExp('^Period\\s+left:\\s+(?<PERIOD_LEFT>.+)'),
|
||||||
|
XRegExp('^Period\\s+Used:\\s+(?<PERIOD_USED>.+)'),
|
||||||
|
XRegExp('^License\\s+Type:\\s+(?<LICENSE_TYPE>.+)'),
|
||||||
|
XRegExp('^License\\s+State:\\s+(?<LICENSE_STATE>.+)'),
|
||||||
|
XRegExp('^License\\s+Count:\\s+(?<LICENSE_COUNT>.+)'),
|
||||||
|
XRegExp('^License\\s+Priority:\\s+(?<LICENSE_PRIORITY>.+)'),
|
||||||
|
]
|
||||||
|
|
||||||
|
const lines = data.split('\n')
|
||||||
|
const records = []
|
||||||
|
let currentRecord: any = null
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (XRegExp.test(line, XRegExp('^Index\\s+\\d+\\s+Feature:'))) {
|
||||||
|
// Start a new record
|
||||||
|
if (currentRecord) {
|
||||||
|
records.push(currentRecord)
|
||||||
|
}
|
||||||
|
currentRecord = {
|
||||||
|
FEATURE: '',
|
||||||
|
PERIOD_LEFT: '',
|
||||||
|
PERIOD_USED: '',
|
||||||
|
LICENSE_TYPE: '',
|
||||||
|
LICENSE_STATE: '',
|
||||||
|
LICENSE_COUNT: '',
|
||||||
|
LICENSE_PRIORITY: '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentRecord) {
|
||||||
|
for (const pattern of patterns) {
|
||||||
|
const match = XRegExp.exec(line, pattern)
|
||||||
|
if (match) {
|
||||||
|
const item = match?.groups || {}
|
||||||
|
Object.keys(item).forEach((key) => {
|
||||||
|
if (item && item[key] !== undefined) {
|
||||||
|
currentRecord[key] = item[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
break // Stop processing this line once a pattern matches
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push the last record if it exists
|
||||||
|
if (currentRecord) {
|
||||||
|
records.push(currentRecord)
|
||||||
|
}
|
||||||
|
|
||||||
|
return records
|
||||||
|
}
|
||||||
|
|
||||||
|
export default parseLog
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
// Import XRegExp
|
||||||
|
import XRegExp from 'xregexp'
|
||||||
|
|
||||||
|
// Example matching function
|
||||||
|
const parseLog = (data: string) => {
|
||||||
|
// Define the regex components
|
||||||
|
const logPattern = XRegExp(
|
||||||
|
`
|
||||||
|
^\\*(?<month>\\w{3})\\s+
|
||||||
|
(?<day>\\d{1,2})\\s+
|
||||||
|
(?<time>\\d{2}:\\d{2}:\\d{2}\\.\\d{3}):\\s+
|
||||||
|
%(?<facility>\\w+)-(?<severity>\\d)-(?<mnemonic>\\w+):\\s+
|
||||||
|
(?<message>.+)
|
||||||
|
`,
|
||||||
|
'x'
|
||||||
|
)
|
||||||
|
|
||||||
|
const logPattern2 = XRegExp(
|
||||||
|
`
|
||||||
|
^(?<day>\\d{2})-(?<month>\\w{3})-(?<year>\\d{4})\\s+
|
||||||
|
(?<time>\\d{2}:\\d{2}:\\d{2})\\s+
|
||||||
|
:%(?<facility>\\w+)-(?<severity>[A-Z0-9])-(?<mnemonic>\\w+):\\s+
|
||||||
|
(?<message>.+)
|
||||||
|
`,
|
||||||
|
'x'
|
||||||
|
)
|
||||||
|
|
||||||
|
// Split log content into individual lines
|
||||||
|
// const logLines = data.split("\n").filter((line) => line.startsWith("*"));
|
||||||
|
const logLines = data.split('\n')
|
||||||
|
|
||||||
|
// Parse each log line
|
||||||
|
const logs = logLines
|
||||||
|
.map((line) => {
|
||||||
|
const match = XRegExp.exec(line, logPattern)
|
||||||
|
const match2 = XRegExp.exec(line, logPattern2)
|
||||||
|
if (match && match.groups) {
|
||||||
|
return {
|
||||||
|
month: match.groups.month,
|
||||||
|
day: match.groups.day,
|
||||||
|
time: match.groups.time,
|
||||||
|
facility: match.groups.facility,
|
||||||
|
severity: match.groups.severity,
|
||||||
|
mnemonic: match.groups.mnemonic,
|
||||||
|
message: match.groups.message?.trim() ?? '',
|
||||||
|
}
|
||||||
|
} else if (match2 && match2.groups) {
|
||||||
|
return {
|
||||||
|
month: match2.groups.month,
|
||||||
|
day: match2.groups.day,
|
||||||
|
time: match2.groups.time,
|
||||||
|
facility: match2.groups.facility,
|
||||||
|
severity: match2.groups.severity,
|
||||||
|
mnemonic: match2.groups.mnemonic,
|
||||||
|
message: match2.groups.message?.trim() ?? '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null // Ignore lines that do not match the pattern
|
||||||
|
})
|
||||||
|
.filter(Boolean) // Remove null entries
|
||||||
|
|
||||||
|
return logs
|
||||||
|
}
|
||||||
|
|
||||||
|
export default parseLog
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
import XRegExp from 'xregexp'
|
||||||
|
|
||||||
|
// Parse the log data
|
||||||
|
const parseLog = (data: string) => {
|
||||||
|
const patterns = [
|
||||||
|
XRegExp('^NAME:\\s+"(?<name>.*)",\\s+DESCR:\\s+"(?<descr>.*)"'),
|
||||||
|
XRegExp('^PID:\\s+(?<pid>[\\S+]+|.*),.*VID:\\s+(?<vid>.*),.*SN:\\s+(?<sn>[\\w+\\d+]*)'),
|
||||||
|
XRegExp('^PID:\\s+,.*VID:\\s+(?<vid>.*),.*SN:\\s+(?<sn>[\\w+\\d+]*)'),
|
||||||
|
XRegExp('^PID:\\s+(?<pid>[\\S+]+|.*),.*VID:\\s+(?<vid>.*),.*SN:'),
|
||||||
|
]
|
||||||
|
const lines = data.split('\n')
|
||||||
|
const records = []
|
||||||
|
let currentRecord: any = { name: '', descr: '', pid: '', vid: '', sn: '' }
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
for (const pattern of patterns) {
|
||||||
|
const match = XRegExp.exec(line, pattern)
|
||||||
|
if (match) {
|
||||||
|
const item = match?.groups
|
||||||
|
// Update current record with matched fields
|
||||||
|
Object.keys(currentRecord).forEach((key) => {
|
||||||
|
if (item && item[key] !== undefined) {
|
||||||
|
currentRecord[key] = item[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// If "pid", "vid", or "sn" are matched, push a completed record
|
||||||
|
if (currentRecord.pid || currentRecord.vid || currentRecord.sn) {
|
||||||
|
records.push({ ...currentRecord })
|
||||||
|
currentRecord = { name: '', descr: '', pid: '', vid: '', sn: '' } // Reset for the next record
|
||||||
|
}
|
||||||
|
break // Stop checking other patterns for this line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return records
|
||||||
|
}
|
||||||
|
|
||||||
|
export default parseLog
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
import XRegExp from 'xregexp'
|
||||||
|
|
||||||
|
// Parser function
|
||||||
|
const parseLog = (data: string) => {
|
||||||
|
const patterns = [
|
||||||
|
XRegExp(
|
||||||
|
'^.*Software.*\\((?<SOFTWARE_IMAGE>\\S+)\\),\\s+Version\\s+(?<VERSION>.+?),\\s+RELEASE.*\\((?<RELEASE>\\S+)\\)'
|
||||||
|
),
|
||||||
|
XRegExp('Active-image:\\s+(?<SOFTWARE_IMAGE>\\S+)'),
|
||||||
|
XRegExp('Version:\\s+(?<VERSION>\\S+)'),
|
||||||
|
XRegExp('^ROM:\\s+(?<ROMMON>\\S+)'),
|
||||||
|
XRegExp('^(?<HOSTNAME>\\S+)\\s+uptime\\s+is\\s+(?<UPTIME>.+)'),
|
||||||
|
XRegExp('Date:\\s+(?<UPTIME>\\S+)'),
|
||||||
|
XRegExp('uptime\\s+is.*\\s+(?<UPTIME_YEARS>\\d+)\\syear'),
|
||||||
|
XRegExp('uptime\\s+is.*\\s+(?<UPTIME_WEEKS>\\d+)\\sweek'),
|
||||||
|
XRegExp('uptime\\s+is.*\\s+(?<UPTIME_DAYS>\\d+)\\sday'),
|
||||||
|
XRegExp('uptime\\s+is.*\\s+(?<UPTIME_HOURS>\\d+)\\shour'),
|
||||||
|
XRegExp('uptime\\s+is.*\\s+(?<UPTIME_MINUTES>\\d+)\\sminute'),
|
||||||
|
XRegExp('System\\s+image\\s+file\\s+is\\s+"(?:.*?):(?<RUNNING_IMAGE>\\S+)"'),
|
||||||
|
XRegExp('(?:Last reload reason:|System returned to ROM by)\\s+(?<RELOAD_REASON>.+)'),
|
||||||
|
XRegExp('[Pp]rocessor\\s+board\\s+ID\\s+(?<SERIAL>\\w+)'),
|
||||||
|
XRegExp(
|
||||||
|
'[Cc]isco\\s+(?<HARDWARE>\\S+|\\S+\\d\\S+)\\s+\\(.+\\)\\s+with\\s+(?<MEMORY>.+)\\s+bytes'
|
||||||
|
),
|
||||||
|
// XRegExp("^(?<USB_FLASH>.+)\\s+bytes\\s+of\\s+[Uu][Ss][Bb]+\\s+[Ff]lash"),
|
||||||
|
XRegExp(
|
||||||
|
'^(?<USB_FLASH>.+?)\\s+bytes\\s+of\\s+(?:' +
|
||||||
|
'[Uu][Ss][Bb]+\\s+[Ff]lash' + // USB Flash
|
||||||
|
'|ATA\\s+System\\s+CompactFlash.*' + // ATA System CompactFlash
|
||||||
|
'|flash\\s+memory\\s+at\\s+bootflash:' + // flash memory at bootflash
|
||||||
|
')'
|
||||||
|
),
|
||||||
|
XRegExp(
|
||||||
|
'Base\\s+[Ee]thernet\\s+MAC\\s+[Aa]ddress\\s+:\\s+(?<MAC_ADDRESS>[0-9a-fA-F]{2}(?::[0-9a-fA-F]{2}){5})'
|
||||||
|
),
|
||||||
|
XRegExp('Configuration\\s+register\\s+is\\s+(?<CONFIG_REGISTER>\\S+)'),
|
||||||
|
]
|
||||||
|
|
||||||
|
const lines = data.split('\n')
|
||||||
|
const records: any = {
|
||||||
|
SOFTWARE_IMAGE: '',
|
||||||
|
VERSION: '',
|
||||||
|
RELEASE: '',
|
||||||
|
ROMMON: '',
|
||||||
|
HOSTNAME: '',
|
||||||
|
UPTIME: '',
|
||||||
|
UPTIME_YEARS: '',
|
||||||
|
UPTIME_WEEKS: '',
|
||||||
|
UPTIME_DAYS: '',
|
||||||
|
UPTIME_HOURS: '',
|
||||||
|
UPTIME_MINUTES: '',
|
||||||
|
RELOAD_REASON: '',
|
||||||
|
RUNNING_IMAGE: '',
|
||||||
|
HARDWARE: '',
|
||||||
|
SERIAL: '',
|
||||||
|
CONFIG_REGISTER: '',
|
||||||
|
MAC_ADDRESS: '',
|
||||||
|
MEMORY: '',
|
||||||
|
USB_FLASH: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
for (const pattern of patterns) {
|
||||||
|
const match = XRegExp.exec(line, pattern)
|
||||||
|
if (match) {
|
||||||
|
const item = match?.groups || {}
|
||||||
|
Object.keys(item).forEach((key) => {
|
||||||
|
if (item[key] !== undefined) {
|
||||||
|
records[key] = item[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [records]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default parseLog
|
||||||
|
|
@ -18,7 +18,8 @@
|
||||||
"mysql2": "^3.15.3",
|
"mysql2": "^3.15.3",
|
||||||
"net": "^1.0.2",
|
"net": "^1.0.2",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"socket.io": "^4.8.1"
|
"socket.io": "^4.8.1",
|
||||||
|
"xregexp": "^5.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@adonisjs/assembler": "^7.8.2",
|
"@adonisjs/assembler": "^7.8.2",
|
||||||
|
|
@ -648,6 +649,18 @@
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/runtime-corejs3": {
|
||||||
|
"version": "7.28.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.28.4.tgz",
|
||||||
|
"integrity": "sha512-h7iEYiW4HebClDEhtvFObtPmIvrd1SSfpI9EhOeKk4CtIK/ngBWFpuhCzhdmRKtg71ylcue+9I6dv54XYO1epQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"core-js-pure": "^3.43.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@borewit/text-codec": {
|
"node_modules/@borewit/text-codec": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz",
|
||||||
|
|
@ -3107,6 +3120,17 @@
|
||||||
"url": "https://opencollective.com/core-js"
|
"url": "https://opencollective.com/core-js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/core-js-pure": {
|
||||||
|
"version": "3.46.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.46.0.tgz",
|
||||||
|
"integrity": "sha512-NMCW30bHNofuhwLhYPt66OLOKTMbOhgTTatKVbaQC3KRHpTCiRIBYvtshr+NBYSnBxwAFhjW/RfJ0XbIjS16rw==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/core-js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cors": {
|
"node_modules/cors": {
|
||||||
"version": "2.8.5",
|
"version": "2.8.5",
|
||||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||||
|
|
@ -7535,6 +7559,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/xregexp": {
|
||||||
|
"version": "5.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/xregexp/-/xregexp-5.1.2.tgz",
|
||||||
|
"integrity": "sha512-6hGgEMCGhqCTFEJbqmWrNIPqfpdirdGWkqshu7fFZddmTSfgv5Sn9D2SaKloR79s5VUiUlpwzg3CM3G6D3VIlw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime-corejs3": "^7.26.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yargs-parser": {
|
"node_modules/yargs-parser": {
|
||||||
"version": "21.1.1",
|
"version": "21.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,8 @@
|
||||||
"mysql2": "^3.15.3",
|
"mysql2": "^3.15.3",
|
||||||
"net": "^1.0.2",
|
"net": "^1.0.2",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"socket.io": "^4.8.1"
|
"socket.io": "^4.8.1",
|
||||||
|
"xregexp": "^5.1.2"
|
||||||
},
|
},
|
||||||
"hotHook": {
|
"hotHook": {
|
||||||
"boundaries": [
|
"boundaries": [
|
||||||
|
|
|
||||||
|
|
@ -283,6 +283,7 @@ export class WebSocketIo {
|
||||||
openCLI: false,
|
openCLI: false,
|
||||||
userEmailOpenCLI: '',
|
userEmailOpenCLI: '',
|
||||||
userOpenCLI: '',
|
userOpenCLI: '',
|
||||||
|
data: [],
|
||||||
},
|
},
|
||||||
socket
|
socket
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import type {
|
||||||
LineConfig,
|
LineConfig,
|
||||||
ReceivedFile,
|
ReceivedFile,
|
||||||
ResponseData,
|
ResponseData,
|
||||||
|
TextFSM,
|
||||||
TLine,
|
TLine,
|
||||||
TStation,
|
TStation,
|
||||||
TUser,
|
TUser,
|
||||||
|
|
@ -158,8 +159,8 @@ function App() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
updateValueLineStation(data.lineId, {
|
updateValueLineStation(data.lineId, {
|
||||||
cliOpened: false,
|
cliOpened: false,
|
||||||
userEmailOpenCLI: "",
|
userEmailOpenCLI: undefined,
|
||||||
userOpenCLI: "",
|
userOpenCLI: undefined,
|
||||||
});
|
});
|
||||||
}, 100);
|
}, 100);
|
||||||
});
|
});
|
||||||
|
|
@ -225,6 +226,14 @@ function App() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket?.on("data_textfsm", (data) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
updateValueLineStation(data.lineId, {
|
||||||
|
data: data.data,
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
// ✅ cleanup on unmount or when socket changes
|
// ✅ cleanup on unmount or when socket changes
|
||||||
return () => {
|
return () => {
|
||||||
socket.off("init");
|
socket.off("init");
|
||||||
|
|
@ -432,6 +441,59 @@ function App() {
|
||||||
>
|
>
|
||||||
Connect
|
Connect
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
disabled={selectedLines.length === 0}
|
||||||
|
variant="outline"
|
||||||
|
style={{ height: "30px", width: "100px" }}
|
||||||
|
onClick={() => {
|
||||||
|
if (selectedLines?.length > 0) {
|
||||||
|
const value = selectedLines
|
||||||
|
?.map((el) => {
|
||||||
|
// Get data platform
|
||||||
|
const dataPlatform = el.data?.find(
|
||||||
|
(comm: TextFSM) =>
|
||||||
|
comm.command?.trim() === "show platform"
|
||||||
|
);
|
||||||
|
const DPELP =
|
||||||
|
dataPlatform &&
|
||||||
|
!dataPlatform?.output?.includes("Incomplete")
|
||||||
|
? true
|
||||||
|
: false;
|
||||||
|
|
||||||
|
// Get data license
|
||||||
|
const dataLicense = el.data?.find(
|
||||||
|
(comm: TextFSM) =>
|
||||||
|
comm.command?.trim() === "show license" ||
|
||||||
|
comm.command?.trim() === "sh license"
|
||||||
|
);
|
||||||
|
const listLicense =
|
||||||
|
dataLicense?.textfsm &&
|
||||||
|
Array.isArray(dataLicense?.textfsm)
|
||||||
|
? dataLicense?.textfsm
|
||||||
|
?.map(
|
||||||
|
(val: { FEATURE: string }) =>
|
||||||
|
val.FEATURE
|
||||||
|
)
|
||||||
|
.join(", ")
|
||||||
|
: "";
|
||||||
|
|
||||||
|
return `Line ${el.line_number ?? ""}: PID: ${
|
||||||
|
el.inventory?.pid ?? ""
|
||||||
|
}, SN: ${el.inventory?.sn ?? ""}, VID: ${
|
||||||
|
el.inventory?.vid ?? ""
|
||||||
|
}, Tested mode: ${
|
||||||
|
DPELP ? "DPELP" : "DPEL"
|
||||||
|
}, License: ${listLicense}`;
|
||||||
|
})
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(value);
|
||||||
|
setSelectedLines([]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Copy
|
||||||
|
</Button>
|
||||||
<hr style={{ width: "100%" }} />
|
<hr style={{ width: "100%" }} />
|
||||||
<DrawerScenario
|
<DrawerScenario
|
||||||
scenarios={scenarios}
|
scenarios={scenarios}
|
||||||
|
|
@ -484,6 +546,7 @@ function App() {
|
||||||
))}
|
))}
|
||||||
onChange={(id) => {
|
onChange={(id) => {
|
||||||
setActiveTab(id?.toString() || "0");
|
setActiveTab(id?.toString() || "0");
|
||||||
|
setSelectedLines([]);
|
||||||
setLoadingTerminal(false);
|
setLoadingTerminal(false);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setLoadingTerminal(true);
|
setLoadingTerminal(true);
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,7 @@ const CardLine = ({
|
||||||
station_id={Number(stationItem.id)}
|
station_id={Number(stationItem.id)}
|
||||||
isDisabled={
|
isDisabled={
|
||||||
typeof line?.userEmailOpenCLI !== "undefined" &&
|
typeof line?.userEmailOpenCLI !== "undefined" &&
|
||||||
|
typeof line?.userEmailOpenCLI !== "string" &&
|
||||||
line?.userEmailOpenCLI !== user?.email
|
line?.userEmailOpenCLI !== user?.email
|
||||||
}
|
}
|
||||||
line_status={line?.status || ""}
|
line_status={line?.status || ""}
|
||||||
|
|
|
||||||
|
|
@ -19,3 +19,10 @@ export function isJsonString(str: string | null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mergeArray(array: any[], key: string) {
|
||||||
|
return array
|
||||||
|
.map((el) => el[key])
|
||||||
|
.flat()
|
||||||
|
.filter((el) => Object.keys(el).length > 0);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -181,3 +181,9 @@ export type ReceivedFile = {
|
||||||
receivedChunks: number;
|
receivedChunks: number;
|
||||||
totalChunks: number;
|
totalChunks: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TextFSM = {
|
||||||
|
command: string;
|
||||||
|
output: string;
|
||||||
|
textfsm: any;
|
||||||
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue