import fs from 'node:fs' import path from 'node:path' import nodeMailer from 'nodemailer' import zulip from 'zulip-js' type DetectAI = { status: string[] issue: string[] } type InputData = { lineNumber: number inventory: any latestScenario?: { detectAI?: DetectAI } data?: any[] } // Types type SendMailResponse = string type SendMessageType = 'stream' | 'private' /** * Function to clean up unwanted characters from the output data. * @param {string} data - The raw data to be cleaned. * @returns {string} - The cleaned data. */ export const cleanData = (data: string) => { return ( data // 1️⃣ Xóa chuỗi "--More--" (Cisco/Unix pager) .replace(/--More--[\s\x08\x1b\[K]*/g, '') // 2️⃣ Xóa toàn bộ chuỗi ANSI escape sequences // Ví dụ: ESC[2J, ESC[K, ESC[?25h, ESC[0m, ... .replace(/\x1B\[[0-9;?]*[A-Za-z]/g, '') // 3️⃣ Xóa ký tự Backspace (BS) hoặc Delete (DEL) .replace(/[\x08\x7F]/g, '') // 4️⃣ Xóa ký tự NUL và các control char khác (trừ \r, \n, \t) .replace(/[^\x09\x0A\x0D\x20-\x7E]/g, '') ) // 5️⃣ Chuẩn hóa xuống dòng nếu cần // .replace(/\r\n/g, '\n') } export function sleep(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)) } // 20250527-AUTO-Session.Station_1-13-192.168.171.9-2.log // {DATE}-AUTO-Session.{Station name}-{Station ID}-{Station IP}-{Line number}.log export function appendLog( output: string, stationId: number, stationName: string, stationIP: string, lineNumber: 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}-AUTO-Session.${stationName}-${stationId}-${stationIP}-${lineNumber}.log`) .replaceAll(' ', '_') // Ensure folder exists if (!fs.existsSync(logDir)) { fs.mkdirSync(logDir, { recursive: true }) } fs.appendFile(logFile, output, (err) => { if (err) { console.error('❌ Failed to write log:', err.message) } }) } 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ệ } } export function mapToLineFormat(input: InputData) { const line = input.lineNumber const pid = input.inventory?.pid || '' const vid = input.inventory?.vid || '' const sn = input.inventory?.sn || '' if (!pid || !sn) { return { line, pid: '', vid: '', sn: '', ios: '', mac: '', license: [], issues: ['No data'], } } // MAC let mac = '' let ios = '' const showVersion = input.data?.find( (d) => d.command === 'show version' || d.command === 'sh version' || d.command === 'show ver' || d.command === 'sh ver' ) 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 ?.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' // Issues const issues = Array.isArray(input.latestScenario?.detectAI?.issue) ? input.latestScenario.detectAI.issue : input.latestScenario?.detectAI?.issue ? [input.latestScenario.detectAI.issue] : [] return { line, pid, vid, sn, ios, mac, license, issues, } } export function sendMessageToMail( email: string, subject: string, text: string, cc?: string[] ): Promise { 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) } }) }) } export function sendMessageToZulip( type: SendMessageType, to: string | number | string[], topic: string | undefined, content: string ): Promise | null { return new Promise((resolve, reject) => { const config = { realm: process.env.ZULIP_REALM as string, username: process.env.ZULIP_USERNAME as string, apiKey: process.env.ZULIP_API_KEY as string, } zulip(config).then((client: any) => { if (type === 'stream') { client.messages .send({ type, to, topic: topic || '', content, }) .then((response: any) => { console.log('Message sent: ' + JSON.stringify(response)) resolve(response) }) .catch((error: any) => { console.error(error) reject(error) }) } else if (type === 'private') { client.messages .send({ type, to, content, }) .then((response: any) => { console.log('Message sent: ' + JSON.stringify(response)) resolve(response) }) .catch((error: any) => { console.error(error) reject(error) }) } }) }) }