Update physical test, load ios
This commit is contained in:
parent
07cfde8c15
commit
28059f85ed
|
|
@ -54,7 +54,9 @@ export default class HealCheckController {
|
|||
serialNumberA: dataSN?.serialNumberA,
|
||||
productModelId: dataSN?.productModelId,
|
||||
orgId: dataSN?.orgId,
|
||||
condition: dataSN?.condition,
|
||||
testNotes: dataSN?.testNotes,
|
||||
healthCheck: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -71,10 +73,11 @@ export default class HealCheckController {
|
|||
},
|
||||
{
|
||||
...dataCheckNote,
|
||||
status: resSN.data?.error ? false : true,
|
||||
message: resSN.data?.error
|
||||
? `Checking api update note SN false: '${resSN.data?.error?.message}'`
|
||||
: 'Checking api update note SN success',
|
||||
status: resSN?.data?.Status === 'ERROR' ? false : true,
|
||||
message:
|
||||
resSN?.data?.Status === 'ERROR'
|
||||
? `Checking api update note SN false: '${resSN.data?.Msg}'`
|
||||
: 'Checking api update note SN success',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import {
|
|||
updateNoteToERP,
|
||||
} from '../ultils/helper.js'
|
||||
import Scenario from '#models/scenario'
|
||||
import path from 'node:path'
|
||||
import path, { join } from 'node:path'
|
||||
import axios from 'axios'
|
||||
import redis from '@adonisjs/redis/services/main'
|
||||
import Line from '#models/line'
|
||||
|
|
@ -27,6 +27,7 @@ import { ErrorRow, TestResult } from '../ultils/types.js'
|
|||
import momentTZ from 'moment-timezone'
|
||||
import { PhysicalPortTest } from './physical_test_service.js'
|
||||
import Station from '#models/station'
|
||||
import IosLicenseController from '#controllers/ios_license_controller'
|
||||
|
||||
type Inventory = {
|
||||
pid: string
|
||||
|
|
@ -126,6 +127,7 @@ export default class LineConnection {
|
|||
private session: TestSession
|
||||
public physicalTest: PhysicalPortTest
|
||||
private outputPhysicalTest: string
|
||||
private listDeviceIos: string[]
|
||||
|
||||
constructor(config: LineConfig, socketIO: any, handleClearLine: () => void) {
|
||||
this.config = config
|
||||
|
|
@ -153,6 +155,7 @@ export default class LineConnection {
|
|||
this.handleClearLine = handleClearLine
|
||||
this.physicalTest = new PhysicalPortTest([])
|
||||
this.outputPhysicalTest = ''
|
||||
this.listDeviceIos = []
|
||||
}
|
||||
|
||||
connect(timeoutMs = 5000) {
|
||||
|
|
@ -922,10 +925,10 @@ export default class LineConnection {
|
|||
</td>
|
||||
<td style="padding:6px; text-align:center;">${r.rule}</td>
|
||||
<td style="padding:6px; text-align:center;">${r.message}</td>
|
||||
<td style="padding:6px; font-family:monospace;">
|
||||
<div style="white-space: break-spaces;"><span style="color: black;">
|
||||
${escapeHtml(r.log.trim())}</span></div>
|
||||
</td>
|
||||
<td style="padding:6px; font-family:monospace;">*${escapeHtml(r.log.trim())
|
||||
.split('*')
|
||||
.filter((el) => el)
|
||||
.join('<br/>*')}</td>
|
||||
</tr>
|
||||
`
|
||||
)
|
||||
|
|
@ -939,26 +942,10 @@ export default class LineConnection {
|
|||
`
|
||||
}
|
||||
|
||||
renderAIDetectTable(row: any): string {
|
||||
return `
|
||||
<table border="1" cellpadding="6" style="border-collapse: collapse; width:100%; margin-bottom: 15px;">
|
||||
<tr>
|
||||
<th style="padding:6px;">Summary</th>
|
||||
<th style="padding:6px;">Issues</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-wrap: wrap;">${row.summary || ''}</td>
|
||||
<td style="width:1000px; text-wrap: wrap;">${row.issues?.length ? `- ` + row.issues.join(`<br>- `) : '- No issues detected.'}</td>
|
||||
</tr>
|
||||
</table>
|
||||
`
|
||||
}
|
||||
|
||||
buildEmailContent(result: TestResult): string {
|
||||
const rows = mapErrorsToRows(result.errors)
|
||||
const table = this.renderErrorTable(rows)
|
||||
// const tableAI = this.renderAIDetectTable(value)
|
||||
|
||||
console.log(table)
|
||||
return `
|
||||
<h3>Cisco Device Log Result</h3>
|
||||
<p>Line: <b>${this.config.lineNumber}</b> - Station: <b>${this.config.stationName}</b></p>
|
||||
|
|
@ -1034,7 +1021,7 @@ export default class LineConnection {
|
|||
async getPorts(): Promise<string[]> {
|
||||
this.writeCommand(' show power inline\r\n')
|
||||
this.writeCommand(' \r\n')
|
||||
await this.sleep(3000)
|
||||
await this.sleep(5000)
|
||||
const statusOutput = this.outputPhysicalTest
|
||||
this.outputPhysicalTest = ''
|
||||
|
||||
|
|
@ -1107,14 +1094,14 @@ export default class LineConnection {
|
|||
},
|
||||
{
|
||||
expect: 'rommon',
|
||||
send: `tftpdnld`,
|
||||
send: this.listDeviceIos?.includes(nameIos) ? '' : `tftpdnld`,
|
||||
delay: '1',
|
||||
repeat: '1',
|
||||
note: '',
|
||||
},
|
||||
{
|
||||
expect: 'y/n',
|
||||
send: `y`,
|
||||
expect: this.listDeviceIos?.includes(nameIos) ? '' : 'y/n',
|
||||
send: this.listDeviceIos?.includes(nameIos) ? '' : `y`,
|
||||
delay: '2',
|
||||
repeat: '1',
|
||||
note: '',
|
||||
|
|
@ -1200,9 +1187,10 @@ export default class LineConnection {
|
|||
timeout: 1000,
|
||||
body: JSON.stringify(body),
|
||||
}
|
||||
|
||||
await sleep(5000)
|
||||
await this.runScript(script as any, userName)
|
||||
await this.endEmailLoadIos(nameIos, startTime)
|
||||
await this.sendEmailLoadIos(nameIos, startTime)
|
||||
}
|
||||
|
||||
async loadIosSwitch(nameIos: string, userName: string) {
|
||||
|
|
@ -1213,6 +1201,8 @@ export default class LineConnection {
|
|||
const [a, b] = network.split('.').map(Number)
|
||||
const timeZone = process.env.TIME_ZONE || 'Australia/Sydney'
|
||||
const startTime = momentTZ().tz(timeZone).format('YYYY/MM/DD, HH:mm:ss')
|
||||
await this.backupIos(nameIos)
|
||||
|
||||
const body = [
|
||||
{
|
||||
expect: '',
|
||||
|
|
@ -1286,21 +1276,21 @@ export default class LineConnection {
|
|||
},
|
||||
{
|
||||
expect: '#',
|
||||
send: `copy tftp: flash:`,
|
||||
send: this.listDeviceIos?.includes(nameIos) ? '' : `copy tftp: flash:`,
|
||||
delay: '1',
|
||||
repeat: '1',
|
||||
note: '',
|
||||
},
|
||||
{
|
||||
expect: '',
|
||||
send: `${tftpIp}`,
|
||||
send: this.listDeviceIos?.includes(nameIos) ? '' : `${tftpIp}`,
|
||||
delay: '1',
|
||||
repeat: '1',
|
||||
note: '',
|
||||
},
|
||||
{
|
||||
expect: '',
|
||||
send: `ios/${nameIos}`,
|
||||
send: this.listDeviceIos?.includes(nameIos) ? '' : `ios/${nameIos}`,
|
||||
delay: '1',
|
||||
repeat: '1',
|
||||
note: '',
|
||||
|
|
@ -1409,10 +1399,10 @@ export default class LineConnection {
|
|||
}
|
||||
|
||||
await this.runScript(script as any, userName)
|
||||
await this.endEmailLoadIos(nameIos, startTime)
|
||||
await this.sendEmailLoadIos(nameIos, startTime)
|
||||
}
|
||||
|
||||
async endEmailLoadIos(nameIos: string, startTime: string) {
|
||||
async sendEmailLoadIos(nameIos: string, startTime: string) {
|
||||
const timeZone = process.env.TIME_ZONE || 'Australia/Sydney'
|
||||
const dataFormat = momentTZ().tz(timeZone).format('YYYY/MM/DD, HH:mm:ss')
|
||||
const body = `
|
||||
|
|
@ -1431,4 +1421,100 @@ export default class LineConnection {
|
|||
body
|
||||
)
|
||||
}
|
||||
|
||||
async checkDeviceFlash() {
|
||||
this.writeCommand(' enable\r\n')
|
||||
this.writeCommand('show flash:\r\n')
|
||||
await sleep(2000)
|
||||
const ios = []
|
||||
const binRegex = /^\s*\d+\s+-rwx\s+\d+\s+.*?\s+([^\s]+\.bin)\s*$/gim
|
||||
|
||||
let match
|
||||
while ((match = binRegex.exec(this.outputBuffer)) !== null) {
|
||||
ios.push(match[1])
|
||||
}
|
||||
return ios
|
||||
}
|
||||
|
||||
async deleteFileOnFlash(fileName: string) {
|
||||
await this.writeCommand(`delete flash:${fileName}\r\n`)
|
||||
await this.writeCommand(`\r\n`)
|
||||
await this.writeCommand(`\r\n`)
|
||||
await sleep(3000)
|
||||
}
|
||||
|
||||
async uploadFileToServerTFTP(fileName: string, server: string) {
|
||||
this.config.runningScenario = 'Upload file'
|
||||
await this.writeCommand(`copy flash: tftp:\r\n`)
|
||||
await this.writeCommand(`${fileName}\r\n`)
|
||||
await this.writeCommand(`${server}\r\n`)
|
||||
await this.writeCommand(`ios/${fileName}\r\n`)
|
||||
await sleep(5000)
|
||||
while (true) {
|
||||
if (this.outputBuffer.includes('#')) {
|
||||
this.outputBuffer = ''
|
||||
this.config.runningScenario = ''
|
||||
return true
|
||||
}
|
||||
await sleep(5000)
|
||||
}
|
||||
}
|
||||
|
||||
// function get list ios
|
||||
async getListIos() {
|
||||
try {
|
||||
const controller = new IosLicenseController()
|
||||
const listIos = await controller.getIos()
|
||||
return listIos
|
||||
} catch (error) {
|
||||
console.log('Error get ios', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
async getCurrentBootIos() {
|
||||
this.writeCommand('show version | include System image\r\n')
|
||||
await sleep(2000)
|
||||
|
||||
const match = this.outputBuffer.match(/"flash:(.+?)"/i)
|
||||
this.outputBuffer = ''
|
||||
|
||||
return match ? match[1] : null
|
||||
}
|
||||
|
||||
async backupIos(nameIos: string) {
|
||||
const station = await Station.find(this.config.stationId)
|
||||
if (!station) return
|
||||
const server = station?.tftp_ip || '172.16.7.69'
|
||||
// const currentBootIos = await this.getCurrentBootIos()
|
||||
this.config.runningScenario = 'Backup IOS'
|
||||
this.socketIO.emit('running_scenario', {
|
||||
stationId: this.config.stationId,
|
||||
lineId: this.config.id,
|
||||
title: 'Backup IOS',
|
||||
})
|
||||
await sleep(1000)
|
||||
const listIos = await this.getListIos()
|
||||
const dataDevice = await this.checkDeviceFlash()
|
||||
this.listDeviceIos = [...dataDevice]
|
||||
console.log('Data Device Flash', dataDevice)
|
||||
if (dataDevice && Array.isArray(dataDevice)) {
|
||||
for (const ios of dataDevice) {
|
||||
// if (ios === nameIos) {
|
||||
// console.log(`SKIP active IOS: ${ios}`)
|
||||
// continue
|
||||
// }
|
||||
if (listIos?.includes(ios)) {
|
||||
console.log(`Already backed up: ${ios}`)
|
||||
if (ios !== nameIos) await this.deleteFileOnFlash(ios)
|
||||
} else {
|
||||
const ok = await this.uploadFileToServerTFTP(ios, server)
|
||||
if (ok && ios !== nameIos) await this.deleteFileOnFlash(ios)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.outputBuffer = ''
|
||||
this.config.runningScenario = ''
|
||||
await sleep(1000)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
import moment from 'moment'
|
||||
import { normalizeInterface } from '../ultils/helper.js'
|
||||
import { PhysicalTestReport, PhysicalTestResult, PortState } from '../ultils/types.js'
|
||||
const LINK_UPDOWN_REGEX =
|
||||
const LINK_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
|
||||
const POE_GRANTED_REGEX = /%ILPOWER-\d+-POWER_GRANTED:\s+Interface\s+([\w\/.-]+):\s+Power granted/i
|
||||
const POE_DISCONNECT_REGEX =
|
||||
/%ILPOWER-\d+-IEEE_DISCONNECT:\s+Interface\s+([\w\/.-]+):\s+PD removed/i
|
||||
|
||||
export class PhysicalPortTest {
|
||||
public ports = new Map<string, PortState>()
|
||||
|
|
@ -44,24 +47,44 @@ export class PhysicalPortTest {
|
|||
}
|
||||
|
||||
handleLog(line: string) {
|
||||
const match = line.match(LINK_UPDOWN_REGEX)
|
||||
if (!match) return
|
||||
let iface: string | null = null
|
||||
let markTested = false
|
||||
let state: 'up' | 'down' | undefined
|
||||
|
||||
const rawIface = match[1]
|
||||
const state = match[2] as 'up' | 'down'
|
||||
const iface = normalizeInterface(rawIface)
|
||||
// 1️⃣ LINK / LINEPROTO
|
||||
let match = line.match(LINK_REGEX)
|
||||
if (match) {
|
||||
iface = normalizeInterface(match[1])
|
||||
state = match[2] as 'up' | 'down'
|
||||
if (state === 'up') markTested = true
|
||||
}
|
||||
|
||||
// 2️⃣ POE POWER GRANTED
|
||||
match = line.match(POE_GRANTED_REGEX)
|
||||
if (match) {
|
||||
iface = normalizeInterface(match[1])
|
||||
markTested = true
|
||||
}
|
||||
|
||||
// 3️⃣ POE DISCONNECT
|
||||
match = line.match(POE_DISCONNECT_REGEX)
|
||||
if (match) {
|
||||
iface = normalizeInterface(match[1])
|
||||
markTested = true
|
||||
}
|
||||
|
||||
if (!iface) return
|
||||
|
||||
const port = this.ports.get(iface)
|
||||
if (!port) return
|
||||
|
||||
// tránh update trùng state liên tiếp
|
||||
if (port.lastState === state) return
|
||||
|
||||
port.lastState = state
|
||||
port.lastSeen = new Date()
|
||||
|
||||
// chỉ cần UP 1 lần là pass
|
||||
if (state === 'up' && !port.tested) {
|
||||
if (state && port.lastState === state) return
|
||||
if (state) port.lastState = state
|
||||
|
||||
// ⭐ PASS nếu có ít nhất 1 event hợp lệ
|
||||
if (markTested && !port.tested) {
|
||||
port.tested = true
|
||||
this.checkDone()
|
||||
}
|
||||
|
|
@ -141,7 +164,7 @@ export class PhysicalPortTest {
|
|||
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/>
|
||||
Duration : ${this.formatDuration(report.durationMs)}<br/>
|
||||
Status : ${status === 'PASS' ? '✅ PASS' : '⚠️ WARNING'}<br/>
|
||||
<br/>
|
||||
────────────────────────────────<br/>
|
||||
|
|
@ -169,4 +192,12 @@ export class PhysicalPortTest {
|
|||
<br/>
|
||||
`.trim()
|
||||
}
|
||||
|
||||
formatDuration(ms: number): string {
|
||||
const totalSeconds = Math.floor(ms / 1000)
|
||||
const minutes = Math.floor(totalSeconds / 60)
|
||||
const seconds = totalSeconds % 60
|
||||
|
||||
return `${minutes}m ${seconds}s`
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ 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 = ''
|
||||
// const mailCC = ['ips@ipsupply.com.au', 'kay@ipsupply.com.au', 'joseph@apactech.io']
|
||||
const mailCC = ''
|
||||
|
||||
type DetectAI = {
|
||||
status: string[]
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ export interface PortState {
|
|||
tested: boolean
|
||||
lastState?: 'up' | 'down'
|
||||
lastSeen?: Date
|
||||
poeGranted?: boolean
|
||||
poeDisconnected?: boolean
|
||||
}
|
||||
|
||||
export interface PhysicalTestResult {
|
||||
|
|
|
|||
|
|
@ -663,13 +663,31 @@ export class WebSocketIo {
|
|||
})
|
||||
|
||||
socket.on('load_ios_router', async (data) => {
|
||||
const { stationId, lineId, iosName } = data
|
||||
const { stationId, lineId, iosName, outletNumber, station, apcName, isReboot } = data
|
||||
await this.handleLineOperation(
|
||||
io,
|
||||
stationId,
|
||||
[lineId],
|
||||
async (lineCon) => {
|
||||
lineCon.loadIosRouter(iosName, userName)
|
||||
await lineCon.backupIos(iosName)
|
||||
if (isReboot) {
|
||||
if (!outletNumber || outletNumber < 0) return
|
||||
if (!station) return
|
||||
const apcIp = (station as any)[`${apcName}_ip`] as string
|
||||
if (!this.apcsControl.get(apcIp)) await this.connectApc(io, apcName, station)
|
||||
const apc = this.apcsControl.get(apcIp)
|
||||
if (apc && apc.status !== 'CONNECTED') {
|
||||
await apc.reconnect()
|
||||
this.keepConnectAPC(apcIp, io)
|
||||
}
|
||||
if (apc) {
|
||||
await apc?.restartOutlet(outletNumber)
|
||||
setTimeout(() => {
|
||||
apc?.navigateToOutlets()
|
||||
}, 10000)
|
||||
}
|
||||
}
|
||||
await lineCon.loadIosRouter(iosName, userName)
|
||||
},
|
||||
{}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -153,18 +153,16 @@ const ModalSelectIOS = ({
|
|||
size="sm"
|
||||
leftSection={<IconPlayerPlay size={16} />}
|
||||
onClick={() => {
|
||||
if (isReboot)
|
||||
socket?.emit("control_apc", {
|
||||
outletNumbers: [line?.outlet],
|
||||
station: { ...station, lines: [] },
|
||||
action: "restart",
|
||||
apcName: line?.apc_name || line?.apcName,
|
||||
});
|
||||
socket?.emit("load_ios_router", {
|
||||
const payload = {
|
||||
stationId: Number(station?.id),
|
||||
lineId: Number(line?.id),
|
||||
iosName: ios,
|
||||
});
|
||||
station: station,
|
||||
outletNumber: line?.outlet || -1,
|
||||
apcName: line?.apcName || line?.apc_name,
|
||||
isReboot: isReboot,
|
||||
};
|
||||
socket?.emit("load_ios_router", payload);
|
||||
close();
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1032,7 +1032,7 @@ const ModalTerminal = ({
|
|||
setIsDisable(true);
|
||||
setTimeout(() => {
|
||||
setIsDisable(false);
|
||||
}, 4000);
|
||||
}, 10000);
|
||||
}}
|
||||
>
|
||||
Start Physical Test
|
||||
|
|
|
|||
Loading…
Reference in New Issue