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 { appendLog, cleanData, sleep } from '../ultils/helper.js'
|
||||
import {
|
||||
appendLog,
|
||||
cleanData,
|
||||
getLogWithTimeScenario,
|
||||
getPathLog,
|
||||
isValidJson,
|
||||
sleep,
|
||||
} from '../ultils/helper.js'
|
||||
import Scenario from '#models/scenario'
|
||||
|
||||
interface LineConfig {
|
||||
|
|
@ -14,6 +23,11 @@ interface LineConfig {
|
|||
openCLI: boolean
|
||||
userEmailOpenCLI: string
|
||||
userOpenCLI: string
|
||||
data: {
|
||||
command: string
|
||||
output: string
|
||||
textfsm: string
|
||||
}[]
|
||||
}
|
||||
|
||||
interface User {
|
||||
|
|
@ -163,8 +177,9 @@ export default class LineConnection {
|
|||
}
|
||||
|
||||
this.isRunningScript = true
|
||||
const now = Date.now()
|
||||
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.lineNumber,
|
||||
this.config.port
|
||||
|
|
@ -183,7 +198,7 @@ export default class LineConnection {
|
|||
data: 'Timeout run scenario',
|
||||
})
|
||||
appendLog(
|
||||
`\n---end-scenarios---${Date.now()}---\n`,
|
||||
`\n---end-scenarios---${now}---\n`,
|
||||
this.config.stationId,
|
||||
this.config.lineNumber,
|
||||
this.config.port
|
||||
|
|
@ -197,18 +212,47 @@ export default class LineConnection {
|
|||
this.isRunningScript = false
|
||||
this.outputBuffer = ''
|
||||
appendLog(
|
||||
`\n---end-scenarios---${Date.now()}---\n`,
|
||||
`\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 = 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)
|
||||
return
|
||||
}
|
||||
|
||||
const step = steps[index]
|
||||
appendLog(
|
||||
`\n---send-command---"${step?.send ?? ''}"---${Date.now()}---\n`,
|
||||
`\n---send-command---"${step?.send ?? ''}"---${now}---\n`,
|
||||
this.config.stationId,
|
||||
this.config.lineNumber,
|
||||
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",
|
||||
"net": "^1.0.2",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"socket.io": "^4.8.1"
|
||||
"socket.io": "^4.8.1",
|
||||
"xregexp": "^5.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@adonisjs/assembler": "^7.8.2",
|
||||
|
|
@ -648,6 +649,18 @@
|
|||
"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": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz",
|
||||
|
|
@ -3107,6 +3120,17 @@
|
|||
"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": {
|
||||
"version": "2.8.5",
|
||||
"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": {
|
||||
"version": "21.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||
|
|
|
|||
|
|
@ -60,7 +60,8 @@
|
|||
"mysql2": "^3.15.3",
|
||||
"net": "^1.0.2",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"socket.io": "^4.8.1"
|
||||
"socket.io": "^4.8.1",
|
||||
"xregexp": "^5.1.2"
|
||||
},
|
||||
"hotHook": {
|
||||
"boundaries": [
|
||||
|
|
|
|||
|
|
@ -283,6 +283,7 @@ export class WebSocketIo {
|
|||
openCLI: false,
|
||||
userEmailOpenCLI: '',
|
||||
userOpenCLI: '',
|
||||
data: [],
|
||||
},
|
||||
socket
|
||||
)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import type {
|
|||
LineConfig,
|
||||
ReceivedFile,
|
||||
ResponseData,
|
||||
TextFSM,
|
||||
TLine,
|
||||
TStation,
|
||||
TUser,
|
||||
|
|
@ -158,8 +159,8 @@ function App() {
|
|||
setTimeout(() => {
|
||||
updateValueLineStation(data.lineId, {
|
||||
cliOpened: false,
|
||||
userEmailOpenCLI: "",
|
||||
userOpenCLI: "",
|
||||
userEmailOpenCLI: undefined,
|
||||
userOpenCLI: undefined,
|
||||
});
|
||||
}, 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
|
||||
return () => {
|
||||
socket.off("init");
|
||||
|
|
@ -432,6 +441,59 @@ function App() {
|
|||
>
|
||||
Connect
|
||||
</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%" }} />
|
||||
<DrawerScenario
|
||||
scenarios={scenarios}
|
||||
|
|
@ -484,6 +546,7 @@ function App() {
|
|||
))}
|
||||
onChange={(id) => {
|
||||
setActiveTab(id?.toString() || "0");
|
||||
setSelectedLines([]);
|
||||
setLoadingTerminal(false);
|
||||
setTimeout(() => {
|
||||
setLoadingTerminal(true);
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ const CardLine = ({
|
|||
station_id={Number(stationItem.id)}
|
||||
isDisabled={
|
||||
typeof line?.userEmailOpenCLI !== "undefined" &&
|
||||
typeof line?.userEmailOpenCLI !== "string" &&
|
||||
line?.userEmailOpenCLI !== user?.email
|
||||
}
|
||||
line_status={line?.status || ""}
|
||||
|
|
|
|||
|
|
@ -19,3 +19,10 @@ export function isJsonString(str: string | null) {
|
|||
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;
|
||||
totalChunks: number;
|
||||
};
|
||||
|
||||
export type TextFSM = {
|
||||
command: string;
|
||||
output: string;
|
||||
textfsm: any;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue