Updaet wiki

This commit is contained in:
nguyentrungthat 2025-12-03 11:37:31 +07:00
parent ae01929bf2
commit 8cf801d964
6 changed files with 1601 additions and 84 deletions

View File

@ -89,7 +89,7 @@ interface DataDPELP {
pid: any
vid: any
sn: any
mode: string
ios: string
mac: string
license: any
issues: string[]
@ -121,7 +121,16 @@ export default class LineConnection {
this.outputInventory = ''
this.outputScenario = ''
// this.bufferCommand = ''
this.dataDPELP = 'No data'
this.dataDPELP = {
line: this.config.lineNumber,
pid: '',
vid: '',
sn: '',
ios: '',
mac: '',
license: [],
issues: ['No data'],
}
this.retryConnect = 0
this.handleClearLine = handleClearLine
}
@ -212,8 +221,6 @@ export default class LineConnection {
})
this.client.on('close', async () => {
if (resolvedOrRejected) return
resolvedOrRejected = true
console.log(`[${Date.now()}] 🔌 Line ${lineNumber} disconnected`)
this.config.status = 'disconnected'
// this.config.inventory = undefined
@ -223,14 +230,14 @@ export default class LineConnection {
lineNumber,
status: 'disconnected',
})
if (this.retryConnect <= 5) {
await this.sleep(5000)
console.log(`Retry connect line [${this.config.lineNumber}] times`, this.retryConnect)
this.retryConnect += 1
await this.reconnect()
} else {
this.retryConnect = 0
}
// if (this.retryConnect <= 5) {
// await this.sleep(5000)
// console.log(`Retry connect line [${this.config.lineNumber}] times`, this.retryConnect)
// this.retryConnect += 1
// await this.reconnect()
// } else {
// this.retryConnect = 0
// }
})
this.client.on('timeout', () => {
@ -265,9 +272,9 @@ export default class LineConnection {
async writeCommand(cmd: string | Buffer<ArrayBuffer>, userName = '') {
if (this.client.destroyed) {
console.log(`⚠️ Cannot send, line ${this.config.lineNumber} is closed`)
this.disconnect()
await sleep(2000)
await this.connect()
// this.disconnect()
// await sleep(2000)
// await this.connect()
return
}
@ -301,7 +308,8 @@ export default class LineConnection {
async disconnect() {
try {
this.handleClearLine()
console.log('[DISCONNECT] Line', this.config.lineNumber)
// this.handleClearLine()
this.client.destroy()
this.config.status = 'disconnected'
this.socketIO.emit('line_disconnected', {
@ -731,24 +739,22 @@ export default class LineConnection {
role: 'user',
content: `Bạn là chuyên gia phân tích log thiết bị mạng.
Hãy phân tích đoạn log sau xuất kết quả theo đúng format:
${log}
Yêu cầu đu ra đúng cấu trúc:
status:
(Tóm tắt trạng thái tổng thể của hệ thống trong 24 ý)
issue:
(Tóm tắt cực ngắn gọn các lỗi/dấu hiệu bất thường, mỗi vấn đ 1 dòng, không bắt các vấn đ không quan trọng như về port changed state to up/down hay Invalid input, Incomplete command)
(Tóm tắt ngắn gọn các lỗi/dấu hiệu bất thường, mỗi vấn đ 1 dòng, bỏ qua các vấn đ không quan trọng như về port up/down hay Invalid input, Incomplete command)
Quy tắc:
Không giải thích dài dòng.
Chỉ tập trung vào lỗi, cảnh báo, thay đi trạng thái up/down bất thường.
tập trung vào lỗi phần cứng, cảnh báo bất thường
Nếu log không lỗi ghi "No issues detected.".
Ngắn gọn, dễ đc, đúng format
Return only json format with English`,
Return only json format with English.
Here is the log:
${log}
`,
},
],
}

View File

@ -1,5 +1,6 @@
import fs from 'node:fs'
import path from 'node:path'
import nodeMailer from 'nodemailer'
type DetectAI = {
status: string[]
@ -15,6 +16,9 @@ type InputData = {
data?: any[]
}
// Types
type SendMailResponse = string
/**
* Function to clean up unwanted characters from the output data.
* @param {string} data - The raw data to be cleaned.
@ -150,7 +154,7 @@ export function mapToLineFormat(input: InputData) {
pid: '',
vid: '',
sn: '',
mode: '',
ios: '',
mac: '',
license: [],
issues: ['No data'],
@ -159,21 +163,27 @@ export function mapToLineFormat(input: InputData) {
// MAC
let mac = ''
let ios = ''
const showVersion = input.data?.find((d) => d.command === 'show version')
if (showVersion?.textfsm?.[0]?.MAC_ADDRESS) {
mac = showVersion.textfsm[0].MAC_ADDRESS
}
if (showVersion?.textfsm?.[0]?.SOFTWARE_IMAGE) {
ios = showVersion.textfsm[0].SOFTWARE_IMAGE + ' ' + (showVersion?.textfsm?.[0]?.VERSION || '')
}
// License
const dataLicense = input.data?.find((comm) => comm.command?.trim() === 'show license')
const license =
dataLicense?.textfsm && Array.isArray(dataLicense.textfsm)
? dataLicense.textfsm.map((v: any) => v.FEATURE)
? dataLicense.textfsm
?.filter((el: any) => el.LICENSE_TYPE === 'Permanent')
.map((v: any) => v.FEATURE)
: ''
// Mode (DPEL / DPELP)
const dataPlatform = input.data?.find((el) => el.command?.trim() === 'show platform')
const mode = dataPlatform && !dataPlatform.output?.includes('Incomplete') ? 'DPELP' : 'DPEL'
// // Mode (DPEL / DPELP)
// const dataPlatform = input.data?.find((el) => el.command?.trim() === 'show platform')
// const mode = dataPlatform && !dataPlatform.output?.includes('Incomplete') ? 'DPELP' : 'DPEL'
// Issues
const issues = Array.isArray(input.latestScenario?.detectAI?.issue)
@ -187,9 +197,47 @@ export function mapToLineFormat(input: InputData) {
pid,
vid,
sn,
mode,
ios,
mac,
license,
issues,
}
}
export function sendMessageToMail(
email: string,
subject: string,
text: string,
cc?: string[]
): Promise<SendMailResponse> {
return new Promise((resolve, reject) => {
const transporter = nodeMailer.createTransport({
pool: true,
host: process.env.SMTP_HOST,
port: Number(process.env.SMTP_PORT),
secure: true,
auth: {
user: process.env.SMTP_USERNAME,
pass: process.env.SMTP_PASSWORD,
},
})
const mailOptions = {
from: process.env.SMTP_USERNAME,
to: email,
subject,
html: text,
cc: cc,
}
transporter.sendMail(mailOptions, (error: any, info: any) => {
if (error) {
console.error(error)
reject(error)
} else {
console.log('Email sent: ' + info.response)
resolve(info.response)
}
})
})
}

View File

@ -1,31 +1,33 @@
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>.+)'),
XRegExp('^Index\\s+(?<INDEX>\\d+)\\s+Feature:\\s*(?<FEATURE>.*)$'),
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 = []
const records: any[] = []
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)
}
for (let line of lines) {
line = line.trim() // ⭐ RẤT QUAN TRỌNG
if (!line) continue
const indexMatch = XRegExp.exec(line, patterns[0])
if (indexMatch) {
if (currentRecord) records.push(currentRecord)
currentRecord = {
FEATURE: '',
INDEX: indexMatch.groups?.INDEX || '',
FEATURE: indexMatch.groups?.FEATURE?.trim() || '',
PERIOD_LEFT: '',
PERIOD_USED: '',
LICENSE_TYPE: '',
@ -33,28 +35,23 @@ const parseLog = (data: string) => {
LICENSE_COUNT: '',
LICENSE_PRIORITY: '',
}
continue
}
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]
}
if (!currentRecord) continue
for (const p of patterns) {
const m = XRegExp.exec(line, p)
if (m?.groups) {
Object.entries(m.groups).forEach(([k, v]) => {
currentRecord[k] = (v || '').trim()
})
break // Stop processing this line once a pattern matches
}
break
}
}
}
// Push the last record if it exists
if (currentRecord) {
records.push(currentRecord)
}
if (currentRecord) records.push(currentRecord)
return records
}

1456
BACKEND/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -43,6 +43,7 @@
"@swc/core": "1.11.24",
"@types/luxon": "^3.7.1",
"@types/node": "^22.15.18",
"@types/nodemailer": "^7.0.4",
"eslint": "^9.26.0",
"hot-hook": "^0.4.0",
"pino-pretty": "^13.0.0",
@ -59,8 +60,11 @@
"@vinejs/vine": "^3.0.1",
"axios": "^1.13.2",
"luxon": "^3.7.2",
"moment": "^2.30.1",
"moment-timezone": "^0.6.0",
"mysql2": "^3.15.3",
"net": "^1.0.2",
"nodemailer": "^7.0.9",
"reflect-metadata": "^0.2.2",
"socket.io": "^4.8.1",
"xregexp": "^5.1.2"

View File

@ -1,3 +1,5 @@
import moment from 'moment'
import momentTZ from 'moment-timezone'
import net from 'node:net'
import fs from 'node:fs'
import { Server as SocketIOServer } from 'socket.io'
@ -9,7 +11,7 @@ import { CustomServer, CustomSocket } from '../app/ultils/types.js'
import Line from '#models/line'
import Station from '#models/station'
import APCController from '#services/apc_connection'
import { appendLog, cleanData, sleep } from '../app/ultils/helper.js'
import { appendLog, cleanData, sendMessageToMail, sleep } from '../app/ultils/helper.js'
import SwitchController from '#services/switch_connection'
import redis from '@adonisjs/redis/services/main'
import axios from 'axios'
@ -541,17 +543,20 @@ export class WebSocketIo {
const results = await this.waitUntilAllReady(lineIds)
const tableHTML = this.generateTable(results)
const d = new Date(Date.now())
const timeZone = process.env.TIME_ZONE || 'Australia/Sydney'
const dataFormat = momentTZ().tz(timeZone).format('YYYY/MM/DD, HH:mm:ss')
const dataFormat =
`${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}, ` +
`${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}`
const linkWiki =
process.env.LINK_WIKI || 'https://logs.danielvu.com/api/wiki/page/insert?title=Dev_test'
await axios.post(linkWiki, {
data: tableHTML,
titleAuto: 'AUTO - ' + dataFormat,
titleAuto: '[DPELP] Report AUTO - ' + dataFormat,
})
await sendMessageToMail(
'andrew.ng@apactech.io',
`[DPELP] Report AUTO - ${dataFormat}`,
tableHTML
)
} catch (error) {
console.log(error)
}
@ -1059,19 +1064,20 @@ export class WebSocketIo {
generateTable(results: any) {
let html = `<table border="1" cellpadding="6" style="border-collapse: collapse; font-size: 14px;">
<tr>
<th style="text-align:center; width:65px;">Line</th>
<th style="width:180px;">PID</th>
<th style="text-align:center; width:40px;">Line</th>
<th style="width:190px;">PID</th>
<th style="text-align:center; width:140px;">SN</th>
<th>MAC</th>
<th>Mode</th>
<th style="width:600px; word-wrap: break-word; white-space: normal;">License</th>
<th style="width:270px; text-align:center;">IOS</th>
<th style="width:200px; word-wrap: break-word; white-space: normal;">License</th>
<th>Issues</th>
</tr>
`
for (const item of results) {
if (!item) continue
const licenses = Array.isArray(item.license)
? item.license
? [...new Set(item.license)]
: item.license
? [item.license]
: []
@ -1080,7 +1086,7 @@ export class WebSocketIo {
const licenseHTML = `
<div style="
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-columns: repeat(2, 1fr);
gap: 4px;
width: 100%;
word-break: break-word;
@ -1091,12 +1097,12 @@ export class WebSocketIo {
html += `
<tr>
<td style="text-align:center;">${item.line}</td>
<td><div style="display:flex;"><div style="width:135px; margin-right:4px;">${item.pid}</div><div>| ${item.vid}</div></div></td>
<td style="text-align:center;">${item.sn}</td>
<td>${item.mac}</td>
<td>${item.mode}</td>
<td style="width:600px;">${licenseHTML}</td>
<td style="text-align:center;">${item.line || ''}</td>
<td><div style="display:flex;"><div style="width:150px; margin-right:4px;">${item.pid || ''}</div><div> ${item.vid || ''}</div></div></td>
<td style="text-align:center;">${item.sn || ''}</td>
<td>${item.mac || ''}</td>
<td style="width:270px">${item.ios || ''}</td>
<td style="width:200px;">${licenseHTML}</td>
<td>${item.issues?.length ? `- ` + item.issues.join(`<br>- `) : ''}</td>
</tr>
`