Update physical test
This commit is contained in:
parent
2f484e19b6
commit
b1b4f1b907
|
|
@ -25,3 +25,5 @@ yarn-error.log
|
|||
.DS_Store
|
||||
|
||||
storage/system_logs
|
||||
storage/ios
|
||||
storage/license
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
import type { HttpContext } from '@adonisjs/core/http'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
export default class IosLicenseController {
|
||||
/* ================= LIST ================= */
|
||||
|
||||
async getIos() {
|
||||
return fs.readdirSync('storage/ios')
|
||||
}
|
||||
|
||||
async getLicense() {
|
||||
return fs.readdirSync('storage/license')
|
||||
}
|
||||
|
||||
/* ================= UPLOAD ================= */
|
||||
|
||||
async uploadIos({ request, response }: HttpContext) {
|
||||
const file = request.file('file', {
|
||||
size: '4gb',
|
||||
extnames: ['bin', 'img', 'tar'],
|
||||
})
|
||||
|
||||
if (!file) {
|
||||
return response.badRequest('File is required')
|
||||
}
|
||||
|
||||
await file.move('storage/ios', {
|
||||
name: file.clientName,
|
||||
overwrite: true,
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
filename: file.clientName,
|
||||
}
|
||||
}
|
||||
|
||||
async uploadLicense({ request, response }: HttpContext) {
|
||||
const file = request.file('file', {
|
||||
size: '100mb',
|
||||
extnames: ['lic', 'txt'],
|
||||
})
|
||||
|
||||
if (!file) {
|
||||
return response.badRequest('File is required')
|
||||
}
|
||||
|
||||
await file.move('storage/license', {
|
||||
name: file.clientName,
|
||||
overwrite: true,
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
filename: file.clientName,
|
||||
}
|
||||
}
|
||||
|
||||
/* ================= DOWNLOAD ================= */
|
||||
|
||||
async downloadIos({ params, response }: HttpContext) {
|
||||
const filePath = path.join('"storage/ios"', params.filename)
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return response.notFound('File not found')
|
||||
}
|
||||
|
||||
return response.download(filePath)
|
||||
}
|
||||
|
||||
async downloadLicense({ params, response }: HttpContext) {
|
||||
const filePath = path.join('"storage/license"', params.filename)
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return response.notFound('File not found')
|
||||
}
|
||||
|
||||
return response.download(filePath)
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,6 @@ import axios from 'axios'
|
|||
import redis from '@adonisjs/redis/services/main'
|
||||
import Line from '#models/line'
|
||||
import { ErrorRow, TestResult } from '../ultils/types.js'
|
||||
import moment from 'moment'
|
||||
import momentTZ from 'moment-timezone'
|
||||
import { PhysicalPortTest } from './physical_test_service.js'
|
||||
|
||||
|
|
@ -359,7 +358,7 @@ export default class LineConnection {
|
|||
console.log(
|
||||
`Run scenario "${script?.title}" to line ${this.config.lineNumber} of ${this.config.stationName}`
|
||||
)
|
||||
this.config.runningScenario = ''
|
||||
this.config.runningScenario = script?.title
|
||||
this.socketIO.emit('running_scenario', {
|
||||
stationId: this.config.stationId,
|
||||
lineId: this.config.id,
|
||||
|
|
@ -473,6 +472,12 @@ export default class LineConnection {
|
|||
timestamp: Date.now(),
|
||||
})
|
||||
}
|
||||
if (['show version', 'sh version', 'show ver', 'sh ver'].includes(item.command)) {
|
||||
const dataVer = JSON.parse(item.textfsm)[0]
|
||||
this.config.inventory = this.config.inventory
|
||||
? { ...this.config.inventory, ...dataVer }
|
||||
: dataVer
|
||||
}
|
||||
item.textfsm = JSON.parse(item.textfsm)
|
||||
}
|
||||
})
|
||||
|
|
@ -646,6 +651,8 @@ export default class LineConnection {
|
|||
const start = Date.now()
|
||||
// console.log('[EXPECT]', expect, timeout)
|
||||
while (Date.now() - start < timeout) {
|
||||
console.log(expect)
|
||||
console.log(this.outputBuffer)
|
||||
if (this.outputBuffer.includes(expect)) {
|
||||
this.outputBuffer = ''
|
||||
return true
|
||||
|
|
@ -662,7 +669,15 @@ export default class LineConnection {
|
|||
if (item?.textfsm && isValidJson(item?.textfsm)) {
|
||||
if (['show inventory', 'sh inventory', 'show inv', 'sh inv'].includes(item.command)) {
|
||||
const dataInventory = JSON.parse(item.textfsm)[0]
|
||||
this.config.inventory = dataInventory
|
||||
this.config.inventory = this.config.inventory
|
||||
? { ...this.config.inventory, ...dataInventory }
|
||||
: dataInventory
|
||||
}
|
||||
if (['show version', 'sh version', 'show ver', 'sh ver'].includes(item.command)) {
|
||||
const dataVer = JSON.parse(item.textfsm)[0]
|
||||
this.config.inventory = this.config.inventory
|
||||
? { ...this.config.inventory, ...dataVer }
|
||||
: dataVer
|
||||
}
|
||||
item.textfsm = JSON.parse(item.textfsm)
|
||||
}
|
||||
|
|
@ -855,15 +870,13 @@ export default class LineConnection {
|
|||
// console.log(detectLog)
|
||||
const tableHTML = this.buildEmailContent(result)
|
||||
await sendMessageToMail(
|
||||
'andrew.ng@apactech.io',
|
||||
`[ATC] - [${this.config.stationName} - Line: ${this.config.lineNumber}] - Raw log issue`,
|
||||
tableHTML +
|
||||
`${`
|
||||
<hr />
|
||||
<p>Logs:</p>
|
||||
<div style="white-space: break-spaces; background-color: #f5f5f5; color: black; padding: 8px; max-height: 500px; overflow-y: scroll; border: 1px #ccc solid;"><span style="color: black;">
|
||||
${this.bufferLog.allBuffer}</span></div>`}`,
|
||||
['ips@ipsupply.com.au', 'kay@ipsupply.com.au', 'joseph@apactech.io']
|
||||
${this.bufferLog.allBuffer}</span></div>`}`
|
||||
)
|
||||
this.session.clear()
|
||||
this.bufferLog.clear()
|
||||
|
|
@ -962,12 +975,6 @@ export default class LineConnection {
|
|||
}
|
||||
this.config.runningPhysical = true
|
||||
this.config.runningScenario = 'Physical Test'
|
||||
this.socketIO.emit('running_scenario', {
|
||||
stationId: this.config.stationId,
|
||||
lineId: this.config.id,
|
||||
title: 'Physical Test',
|
||||
physical: true,
|
||||
})
|
||||
const listPorts = await this.getPorts()
|
||||
this.socketIO.emit('running_scenario', {
|
||||
stationId: this.config.stationId,
|
||||
|
|
@ -982,16 +989,17 @@ export default class LineConnection {
|
|||
return
|
||||
}
|
||||
|
||||
this.physicalTest.start(listPorts)
|
||||
const interval = setInterval(async () => {
|
||||
if (!this.physicalTest.done) {
|
||||
const result = this.physicalTest.getResult()
|
||||
// console.warn('⚠️ Missing ports:', result.missingPorts)
|
||||
} else {
|
||||
clearInterval(interval)
|
||||
this.endTesting()
|
||||
}
|
||||
}, 10000)
|
||||
this.physicalTest.start(listPorts, this.config.inventory)
|
||||
// const interval = setInterval(async () => {
|
||||
// if (!this.physicalTest.done) {
|
||||
// // const result = this.physicalTest.getResult()
|
||||
// // console.warn('⚠️ Missing ports:', result.missingPorts)
|
||||
// } else {
|
||||
// clearInterval(interval)
|
||||
// await this.sendReportPhysicalTest()
|
||||
// this.endTesting()
|
||||
// }
|
||||
// }, 10000)
|
||||
}
|
||||
|
||||
endTesting() {
|
||||
|
|
@ -1030,4 +1038,12 @@ export default class LineConnection {
|
|||
this.config.ports = [...new Set(ports)]
|
||||
return [...new Set(ports)]
|
||||
}
|
||||
|
||||
async sendReportPhysicalTest() {
|
||||
const formReport = this.physicalTest.getFormReport()
|
||||
await sendMessageToMail(
|
||||
`[ATC] - [${this.config.stationName} - Line: ${this.config.lineNumber}] - Physical Port Test`,
|
||||
formReport
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import moment from 'moment'
|
||||
import { normalizeInterface } from '../ultils/helper.js'
|
||||
import { PhysicalTestResult, PortState } from '../ultils/types.js'
|
||||
import { PhysicalTestReport, PhysicalTestResult, PortState } from '../ultils/types.js'
|
||||
const LINK_UPDOWN_REGEX =
|
||||
/Interface\s+((?:FastEthernet|GigabitEthernet|TenGigabitEthernet|TwentyFiveGigE|FortyGigabitEthernet|HundredGigE|Ethernet|Port-channel|Fa|Gi|Te|Hu|Eth)[\w\/.-]+),\s+changed state to\s+(up|down)/i
|
||||
|
||||
|
|
@ -7,9 +8,13 @@ export class PhysicalPortTest {
|
|||
public ports = new Map<string, PortState>()
|
||||
private expectedPorts: string[]
|
||||
public done = false
|
||||
private startTime: Date
|
||||
public inventory: any
|
||||
|
||||
constructor(expectedPorts: string[]) {
|
||||
this.expectedPorts = expectedPorts
|
||||
this.startTime = new Date()
|
||||
this.inventory = ''
|
||||
|
||||
expectedPorts.forEach((p) => {
|
||||
this.ports.set(normalizeInterface(p), {
|
||||
|
|
@ -19,9 +24,11 @@ export class PhysicalPortTest {
|
|||
})
|
||||
}
|
||||
|
||||
start(expectedPorts: string[]) {
|
||||
start(expectedPorts: string[], inventory: any) {
|
||||
this.ports.clear()
|
||||
this.startTime = new Date()
|
||||
this.expectedPorts = expectedPorts
|
||||
this.inventory = inventory
|
||||
this.done = false
|
||||
expectedPorts.forEach((p) => {
|
||||
this.ports.set(normalizeInterface(p), {
|
||||
|
|
@ -79,10 +86,26 @@ export class PhysicalPortTest {
|
|||
}
|
||||
|
||||
onDone() {
|
||||
this.ports.clear()
|
||||
this.getFormReport()
|
||||
// this.ports.clear()
|
||||
console.log('✅ Physical Test DONE')
|
||||
}
|
||||
|
||||
getFormReport() {
|
||||
const report: PhysicalTestReport = {
|
||||
device: {
|
||||
model: this?.inventory?.pid || '',
|
||||
serial: this?.inventory?.sn || '',
|
||||
},
|
||||
startTime: this.startTime,
|
||||
endTime: new Date(),
|
||||
durationMs: Date.now() - this.startTime.getTime(),
|
||||
ports: Array.from(this.ports.values()),
|
||||
}
|
||||
return this.generateEmailReport(report)
|
||||
// console.log('✅ Physical Test DONE')
|
||||
}
|
||||
|
||||
getResult(): PhysicalTestResult {
|
||||
const tested = [...this.ports.values()].filter((p) => p.tested)
|
||||
const missing = [...this.ports.values()].filter((p) => !p.tested).map((p) => p.name)
|
||||
|
|
@ -94,4 +117,46 @@ export class PhysicalPortTest {
|
|||
status: this.done ? 'DONE' : 'RUNNING',
|
||||
}
|
||||
}
|
||||
|
||||
generateEmailReport(report: PhysicalTestReport): string {
|
||||
const tested = report.ports.filter((p) => p.tested)
|
||||
const missing = report.ports.filter((p) => !p.tested)
|
||||
|
||||
const status = missing.length === 0 ? 'PASS' : 'WARNING'
|
||||
|
||||
return `
|
||||
Physical Port Test Report<br/>
|
||||
────────────────────────────────<br/>
|
||||
Model : <b>${report.device.model ?? 'N/A'}</b><br/>
|
||||
Serial Number : <b>${report.device.serial ?? 'N/A'}</b><br/>
|
||||
Started At : ${moment(report.startTime).format('YYYY/MM/DD, HH:mm:ss')}<br/>
|
||||
Finished At : ${moment(report.endTime).format('YYYY/MM/DD, HH:mm:ss')}<br/>
|
||||
Duration : ${Math.floor(report.durationMs / 1000)} sec<br/>
|
||||
Status : ${status === 'PASS' ? '✅ PASS' : '⚠️ WARNING'}<br/>
|
||||
<br/>
|
||||
────────────────────────────────<br/>
|
||||
<b>Test Summary</b><br/>
|
||||
────────────<br/>
|
||||
Total Ports : ${report.ports.length}<br/>
|
||||
Ports Tested (UP) : ${tested.length}<br/>
|
||||
Ports Missing : ${missing.length}<br/>
|
||||
<br/>
|
||||
────────────────────────────────<br/>
|
||||
<b>Passed Ports</b><br/>
|
||||
────────────<br/>
|
||||
${tested.map((p) => p.name).join('<br/>')}<br/>
|
||||
<br/>
|
||||
${
|
||||
missing.length
|
||||
? `
|
||||
────────────────────────────────<br/>
|
||||
<b>Missing Ports</b><br/>
|
||||
─────────────<br/>
|
||||
${missing.map((p) => p.name).join('<br/>')}
|
||||
`
|
||||
: ''
|
||||
}<br/>
|
||||
<br/>
|
||||
`.trim()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ import { ErrorRow, LogRule, ParsedLog, TestError, TestResult } from './types.js'
|
|||
import axios from 'axios'
|
||||
import moment from 'moment'
|
||||
|
||||
const mailTo = 'andrew.ng@apactech.io'
|
||||
// const mailCC = ['ips@ipsupply.com.au', 'kay@ipsupply.com.au', 'joseph@apactech.io']
|
||||
const mailCC = ''
|
||||
|
||||
type DetectAI = {
|
||||
status: string[]
|
||||
issue: string[]
|
||||
|
|
@ -221,12 +225,7 @@ export function mapToLineFormat(input: InputData) {
|
|||
}
|
||||
}
|
||||
|
||||
export function sendMessageToMail(
|
||||
email: string,
|
||||
subject: string,
|
||||
text: string,
|
||||
cc?: string[]
|
||||
): Promise<SendMailResponse> {
|
||||
export function sendMessageToMail(subject: string, text: string): Promise<SendMailResponse> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const transporter = nodeMailer.createTransport({
|
||||
pool: true,
|
||||
|
|
@ -241,10 +240,10 @@ export function sendMessageToMail(
|
|||
|
||||
const mailOptions = {
|
||||
from: process.env.SMTP_USERNAME,
|
||||
to: email,
|
||||
to: mailTo,
|
||||
subject,
|
||||
html: text,
|
||||
cc: cc,
|
||||
cc: mailCC,
|
||||
}
|
||||
|
||||
transporter.sendMail(mailOptions, (error: any, info: any) => {
|
||||
|
|
|
|||
|
|
@ -69,3 +69,14 @@ export interface PhysicalTestResult {
|
|||
missingPorts: string[]
|
||||
status: 'RUNNING' | 'DONE' | 'WARNING'
|
||||
}
|
||||
|
||||
export interface PhysicalTestReport {
|
||||
device: {
|
||||
model?: string
|
||||
serial?: string
|
||||
}
|
||||
startTime: Date
|
||||
endTime: Date
|
||||
durationMs: number
|
||||
ports: PortState[]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -586,10 +586,8 @@ export class WebSocketIo {
|
|||
titleAuto: `[${scenarioName || 'DPELP'}] - ${stationName} - ` + dataFormat,
|
||||
})
|
||||
await sendMessageToMail(
|
||||
'andrew.ng@apactech.io',
|
||||
`[${scenarioName || 'DPELP'}] - ${stationName} - ${dataFormat}`,
|
||||
tableHTML,
|
||||
['ips@ipsupply.com.au', 'kay@ipsupply.com.au', 'joseph@apactech.io']
|
||||
tableHTML
|
||||
)
|
||||
await sendMessageToZulip(
|
||||
'stream',
|
||||
|
|
@ -631,7 +629,10 @@ export class WebSocketIo {
|
|||
io,
|
||||
stationId,
|
||||
[lineId],
|
||||
async (lineCon) => lineCon.endTesting(),
|
||||
async (lineCon) => {
|
||||
lineCon.endTesting()
|
||||
await lineCon.sendReportPhysicalTest()
|
||||
},
|
||||
{}
|
||||
)
|
||||
})
|
||||
|
|
@ -895,7 +896,13 @@ export class WebSocketIo {
|
|||
this.lineMap.forEach((line, id) => {
|
||||
if (line && line.config) {
|
||||
newMap.set(id, {
|
||||
config: { ...line.config, status: 'disconnected' },
|
||||
config: {
|
||||
...line.config,
|
||||
status: 'disconnected',
|
||||
userEmailOpenCLI: '',
|
||||
userOpenCLI: '',
|
||||
openCLI: false,
|
||||
},
|
||||
} as LineConnection)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -105,3 +105,15 @@ router
|
|||
router.get('/', '#controllers/healcheck_controller.check')
|
||||
})
|
||||
.prefix('atc/health-check')
|
||||
|
||||
router
|
||||
.group(() => {
|
||||
router.get('/ios', '#controllers/ios_license_controller.getIos')
|
||||
router.post('/ios/upload', '#controllers/ios_license_controller.uploadIos')
|
||||
router.get('/ios/download/:filename', '#controllers/ios_license_controller.downloadIos')
|
||||
|
||||
router.get('/license', '#controllers/ios_license_controller.getLicense')
|
||||
router.post('/license/upload', '#controllers/ios_license_controller.uploadLicense')
|
||||
router.get('/license/download/:filename', '#controllers/ios_license_controller.downloadLicense')
|
||||
})
|
||||
.prefix('/api')
|
||||
|
|
|
|||
|
|
@ -428,17 +428,15 @@ function App() {
|
|||
lines: station.lines.map((line) => {
|
||||
const buffered = lineBuffersRef.current.get(line.id || 0);
|
||||
if (!buffered) return line; // không có update
|
||||
updateValueSelectedLine(line?.id || 0, {
|
||||
netOutput: buffered,
|
||||
loadingClearTerminal: false,
|
||||
});
|
||||
return {
|
||||
const data = {
|
||||
...line,
|
||||
netOutput: (line.netOutput || "") + buffered,
|
||||
output: buffered,
|
||||
loadingOutput: line.loadingOutput ? false : true,
|
||||
loadingClearTerminal: false,
|
||||
};
|
||||
updateValueSelectedLine(line?.id || 0, data);
|
||||
return data;
|
||||
}),
|
||||
}))
|
||||
);
|
||||
|
|
@ -507,28 +505,16 @@ function App() {
|
|||
[]
|
||||
);
|
||||
|
||||
const updateValueSelectedLine = useCallback(
|
||||
(lineId: number, updates: Partial<TLine>) => {
|
||||
// Update selectedLine nếu nó đang được chọn
|
||||
setSelectedLine((prevSelected) => {
|
||||
if (!prevSelected || prevSelected.id !== lineId) return prevSelected;
|
||||
|
||||
const isNetOutput = typeof updates?.netOutput !== "undefined";
|
||||
|
||||
return {
|
||||
...prevSelected,
|
||||
...updates,
|
||||
...(isNetOutput && {
|
||||
netOutput:
|
||||
(prevSelected.netOutput || "") + (updates.netOutput || ""),
|
||||
output: updates.netOutput,
|
||||
loadingOutput: prevSelected.loadingOutput ? false : true,
|
||||
}),
|
||||
};
|
||||
});
|
||||
},
|
||||
[]
|
||||
);
|
||||
const updateValueSelectedLine = (lineId: number, updates: Partial<TLine>) => {
|
||||
// Update selectedLine nếu nó đang được chọn
|
||||
setSelectedLine((prevSelected) => {
|
||||
if (!prevSelected || prevSelected.id !== lineId) return prevSelected;
|
||||
return {
|
||||
...prevSelected,
|
||||
...updates,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// const getLine = (lineId: number, stationId: number) => {
|
||||
// const station = stations?.find((sta) => sta.id === stationId);
|
||||
|
|
|
|||
|
|
@ -432,7 +432,7 @@ const ModalTerminal = ({
|
|||
);
|
||||
return showVersion?.textfsm && showVersion?.textfsm?.[0]
|
||||
? showVersion?.textfsm?.[0]
|
||||
: null;
|
||||
: line?.inventory;
|
||||
};
|
||||
|
||||
const findDataShowLicense = () => {
|
||||
|
|
@ -746,9 +746,9 @@ const ModalTerminal = ({
|
|||
</Text>
|
||||
<Text size="md">
|
||||
{findDataShowVersion()
|
||||
? findDataShowVersion()?.MEMORY +
|
||||
? (findDataShowVersion()?.MEMORY || "") +
|
||||
(findDataShowVersion()?.USB_FLASH
|
||||
? " - " + findDataShowVersion()?.USB_FLASH
|
||||
? " - " + (findDataShowVersion()?.USB_FLASH || "")
|
||||
: "")
|
||||
: ""}
|
||||
</Text>
|
||||
|
|
|
|||
Loading…
Reference in New Issue