Updaet wiki
This commit is contained in:
parent
ae01929bf2
commit
8cf801d964
|
|
@ -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 và 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 2–4 ý)
|
||||
|
||||
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 có lỗi → ghi rõ "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}
|
||||
`,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
})
|
||||
break // Stop processing this line once a pattern matches
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Push the last record if it exists
|
||||
if (currentRecord) {
|
||||
records.push(currentRecord)
|
||||
}
|
||||
if (currentRecord) records.push(currentRecord)
|
||||
|
||||
return records
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
`
|
||||
|
|
|
|||
Loading…
Reference in New Issue