From 5586f8f930dcadbdd030c8f79d9a6d5ca9a0abea Mon Sep 17 00:00:00 2001
From: nguyentrungthat <80239428+nguentrungthat@users.noreply.github.com>
Date: Fri, 9 Jan 2026 16:08:50 +0700
Subject: [PATCH] Update load license
---
.../app/controllers/ios_license_controller.ts | 4 +-
BACKEND/app/services/line_connection.ts | 426 ++++--------
BACKEND/app/ultils/helper.ts | 605 ++++++++++++++++++
BACKEND/start/routes.ts | 5 -
FRONTEND/src/App.tsx | 15 +
.../components/Modal/ModalSelectLicense.tsx | 141 ++++
.../src/components/Modal/ModalTerminal.tsx | 21 +-
7 files changed, 902 insertions(+), 315 deletions(-)
create mode 100644 FRONTEND/src/components/Modal/ModalSelectLicense.tsx
diff --git a/BACKEND/app/controllers/ios_license_controller.ts b/BACKEND/app/controllers/ios_license_controller.ts
index 1df8375..bb64a8b 100644
--- a/BACKEND/app/controllers/ios_license_controller.ts
+++ b/BACKEND/app/controllers/ios_license_controller.ts
@@ -19,7 +19,7 @@ export default class IosLicenseController {
async getIos() {
const smbPath = '/ipsteamSMB/IOS/i'
- const localPath = 'storage/ios'
+ const localPath = 'storage/i'
const targetPath = fs.existsSync(smbPath) ? smbPath : localPath
@@ -30,7 +30,7 @@ export default class IosLicenseController {
async getLicense() {
const smbPath = '/ipsteamSMB/IOS/License'
- const localPath = 'storage/license'
+ const localPath = 'storage/License'
const targetPath = fs.existsSync(smbPath) ? smbPath : localPath
diff --git a/BACKEND/app/services/line_connection.ts b/BACKEND/app/services/line_connection.ts
index c524dd1..8b98131 100644
--- a/BACKEND/app/services/line_connection.ts
+++ b/BACKEND/app/services/line_connection.ts
@@ -3,7 +3,7 @@ import { textfsmResults } from './../ultils/templates/index.js'
import net from 'node:net'
import {
appendLog,
- applyRules,
+ buildBody,
classifyLog,
cleanData,
detectScenarioByModel,
@@ -1135,127 +1135,14 @@ 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')
- const body = [
- {
- expect: '',
- send: `IP_ADDRESS=${a}.${b}.100.${this.config.id < 254 ? this.config.id : 254 - this.config.id}`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: 'rommon',
- send: `IP_SUBNET_MASK=255.255.0.0`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: 'rommon',
- send: `DEFAULT_GATEWAY=${station?.gateway ? station?.gateway : '0.0.0.0'}`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: 'rommon',
- send: `TFTP_SERVER=${tftpIp}`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: 'rommon',
- send: `TFTP_FILE=i/${nameIos}`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: 'rommon',
- send: this.listDeviceIos?.includes(nameIos) ? '' : `tftpdnld`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: this.listDeviceIos?.includes(nameIos) ? '' : 'y/n',
- send: this.listDeviceIos?.includes(nameIos) ? '' : `y`,
- delay: '2',
- repeat: '1',
- note: '',
- },
- {
- expect: 'rommon',
- send: `boot usbflash0:${nameIos}`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: 'Press RETURN to get started',
- send: ``,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '',
- send: ``,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '',
- send: `enable`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '#',
- send: `show version`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '#',
- send: `configure terminal`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '',
- send: `boot system usbflash0:${nameIos}`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '',
- send: `end`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '',
- send: `write memory`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '',
- send: ``,
- delay: '1',
- repeat: '1',
- note: '',
- },
- ]
+ const body = buildBody(
+ 'ROUTER_IOS',
+ tftpIp,
+ nameIos,
+ `${a}.${b}.100.${this.config.id < 254 ? this.config.id : 254 - this.config.id}`,
+ `${station?.gateway ? station?.gateway : '0.0.0.0'}`,
+ this.listDeviceIos
+ )
const script = {
id: 0,
@@ -1285,190 +1172,14 @@ export default class LineConnection {
const startTime = momentTZ().tz(timeZone).format('YYYY/MM/DD, HH:mm:ss')
await this.backupIos(nameIos)
- const body = [
- {
- expect: '',
- send: ``,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '',
- send: `enable`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '#',
- send: `configure terminal`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '#',
- send: `interface vlan 1`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '#',
- send: `ip address ${a}.${b}.100.${this.config.id < 254 ? this.config.id : 254 - this.config.id} 255.255.0.0`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '#',
- send: `no shutdown`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '#',
- send: `exit`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '#',
- send: `ip default-gateway ${station?.gateway ? station?.gateway : '0.0.0.0'}`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '#',
- send: `end`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '#',
- send: ``,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '#',
- send: this.listDeviceIos?.includes(nameIos) ? '' : `copy tftp: flash:`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '',
- send: this.listDeviceIos?.includes(nameIos) ? '' : `${tftpIp}`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '',
- send: this.listDeviceIos?.includes(nameIos) ? '' : `i/${nameIos}`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '',
- send: ``,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '',
- send: ``,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '#',
- send: ``,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '#',
- send: `configure terminal`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '#',
- send: `boot system flash:${nameIos}`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '',
- send: `end`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '',
- send: `write memory`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '',
- send: `reload`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '',
- send: ``,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: 'Press RETURN to get started!',
- send: ``,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '',
- send: ``,
- delay: '1',
- repeat: '1',
- note: '',
- },
- {
- expect: '',
- send: `enable`,
- delay: '3',
- repeat: '1',
- note: '',
- },
- {
- expect: '#',
- send: `show version`,
- delay: '1',
- repeat: '1',
- note: '',
- },
- ]
+ const body = buildBody(
+ 'SWITCH_IOS',
+ tftpIp,
+ nameIos,
+ `${a}.${b}.100.${this.config.id < 254 ? this.config.id : 254 - this.config.id}`,
+ `${station?.gateway ? station?.gateway : '0.0.0.0'}`,
+ this.listDeviceIos
+ )
const script = {
id: 0,
@@ -1507,6 +1218,29 @@ export default class LineConnection {
)
}
+ /**
+ * Send mail report after load license
+ */
+ async sendEmailLoadLicense(nameLicense: 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 = `
+ Load IOS Report
+ ────────────────────────────────
+ Station : ${this.config.stationName}
+ Line : ${this.config.lineNumber}
+ License : ${nameLicense}
+ Started At : ${startTime}
+ Finished At : ${dataFormat}
+ ────────────────────────────────
+ `.trim()
+
+ await sendMessageToMail(
+ `[ATC] - [${this.config.stationName} - Line: ${this.config.lineNumber}] - Load IOS Report`,
+ body
+ )
+ }
+
/**
* Check list ios exist on flash
*/
@@ -1631,4 +1365,84 @@ export default class LineConnection {
this.config.runningScenario = ''
await sleep(1000)
}
+
+ /**
+ * Handle load License for switch
+ * Assumes traditional licensing (PAK/file-based) via TFTP
+ */
+ async loadLicenseSwitch(licenseFileName: string, userName: string) {
+ const station = await Station.find(this.config.stationId)
+ if (!station) return
+
+ // Setup network variables (giống hệt logic load IOS để đảm bảo thông mạng)
+ const network = station?.gateway || '172.25.1.1'
+ const tftpIp = station?.tftp_ip || '172.16.7.69'
+ const [a, b] = network.split('.').map(Number)
+
+ // Setup time/logging
+ const timeZone = process.env.TIME_ZONE || 'Australia/Sydney'
+ const startTime = momentTZ().tz(timeZone).format('YYYY/MM/DD, HH:mm:ss')
+
+ const body = buildBody(
+ 'ROUTER_IOS',
+ tftpIp,
+ licenseFileName,
+ `${a}.${b}.100.${this.config.id < 254 ? this.config.id : 254 - this.config.id}`,
+ `${station?.gateway ? station?.gateway : '0.0.0.0'}`,
+ this.listDeviceIos
+ )
+
+ const script = {
+ id: 0, // Hoặc ID khác tuỳ logic DB
+ isReboot: true, // License thường cần reboot
+ sendResult: false,
+ send_result: false,
+ title: 'Load License Switch',
+ timeout: 1000, // Tăng timeout nếu cần vì lệnh install có thể lâu
+ body: JSON.stringify(body),
+ }
+
+ await this.runScript(script as any, userName)
+ await this.sendEmailLoadLicense(licenseFileName, startTime) // Nếu bạn có hàm gửi mail báo cáo
+ }
+
+ /**
+ * Handle load License for Router
+ */
+ async loadLicenseRouter(licenseFileName: string, userName: string) {
+ const station = await Station.find(this.config.stationId)
+ if (!station) return
+
+ const network = station?.gateway || '172.25.1.1'
+ const tftpIp = station?.tftp_ip || '172.16.7.69'
+ const [a, b] = network.split('.').map(Number)
+
+ // Tên Interface dùng để load (Cần sửa nếu router dùng 0/0/0 hoặc tên khác)
+ const mgmtInterface = 'GigabitEthernet0/0'
+
+ const timeZone = process.env.TIME_ZONE || 'Australia/Sydney'
+ const startTime = momentTZ().tz(timeZone).format('YYYY/MM/DD, HH:mm:ss')
+
+ const body = buildBody(
+ 'ROUTER_IOS',
+ tftpIp,
+ licenseFileName,
+ `${a}.${b}.100.${this.config.id < 254 ? this.config.id : 254 - this.config.id}`,
+ `${station?.gateway ? station?.gateway : '0.0.0.0'}`,
+ this.listDeviceIos
+ )
+
+ const script = {
+ id: 0,
+ isReboot: true,
+ sendResult: false,
+ send_result: false,
+ title: 'Load License Router',
+ timeout: 1000,
+ body: JSON.stringify(body),
+ }
+
+ await this.runScript(script as any, userName)
+ await this.sendEmailLoadLicense(licenseFileName, startTime)
+ }
}
diff --git a/BACKEND/app/ultils/helper.ts b/BACKEND/app/ultils/helper.ts
index 7c8ce81..9aee0c6 100644
--- a/BACKEND/app/ultils/helper.ts
+++ b/BACKEND/app/ultils/helper.ts
@@ -675,3 +675,608 @@ export function normalizeInterface(name: string): string {
.replace(/^Hu(?=\d)/, 'HundredGigE')
.replace(/^Eth(?=\d)/, 'Ethernet')
}
+
+type BodyType = 'ROUTER_IOS' | 'SWITCH_IOS' | 'SWITCH_LICENSE' | 'ROUTER_LICENSE'
+
+export function buildBody(
+ type: BodyType,
+ tftpIp: string,
+ fileName: string,
+ address: string,
+ gateway: string,
+ listDeviceIos: string[]
+) {
+ switch (type) {
+ /* ================= ROUTER LOAD IOS ================= */
+ case 'ROUTER_IOS':
+ return [
+ {
+ expect: '',
+ send: `IP_ADDRESS=${address}`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: 'rommon',
+ send: `IP_SUBNET_MASK=255.255.0.0`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: 'rommon',
+ send: `DEFAULT_GATEWAY=${gateway}`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: 'rommon',
+ send: `TFTP_SERVER=${tftpIp}`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: 'rommon',
+ send: `TFTP_FILE=i/${fileName}`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: 'rommon',
+ send: listDeviceIos?.includes(fileName) ? '' : `tftpdnld`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: listDeviceIos?.includes(fileName) ? '' : 'y/n',
+ send: listDeviceIos?.includes(fileName) ? '' : `y`,
+ delay: '2',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: 'rommon',
+ send: `boot usbflash0:${fileName}`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: 'Press RETURN to get started',
+ send: ``,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: '',
+ send: ``,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: '',
+ send: `enable`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: '#',
+ send: `show inventory`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: '#',
+ send: `show version`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: '#',
+ send: `configure terminal`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: '',
+ send: `boot system usbflash0:${fileName}`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: '',
+ send: `end`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: '',
+ send: `write memory`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: '',
+ send: ``,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ ]
+
+ /* ================= SWITCH LOAD IOS ================= */
+ case 'SWITCH_IOS':
+ return [
+ {
+ expect: '',
+ send: ``,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: '',
+ send: `enable`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: '#',
+ send: `configure terminal`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: '#',
+ send: `interface vlan 1`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: '#',
+ send: `ip address ${address} 255.255.0.0`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: '#',
+ send: `no shutdown`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: '#',
+ send: `exit`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: '#',
+ send: `ip default-gateway ${gateway}`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: '#',
+ send: `end`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ { expect: '', send: ``, delay: '1', repeat: '1', note: '' },
+ {
+ expect: '#',
+ send: listDeviceIos?.includes(fileName) ? '' : `copy tftp: flash:`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: '',
+ send: listDeviceIos?.includes(fileName) ? '' : `${tftpIp}`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: '',
+ send: listDeviceIos?.includes(fileName) ? '' : `i/${fileName}`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ { expect: '', send: ``, delay: '1', repeat: '1', note: '' },
+ { expect: '', send: ``, delay: '1', repeat: '1', note: '' },
+ { expect: '#', send: ``, delay: '1', repeat: '1', note: '' },
+ {
+ expect: '#',
+ send: `configure terminal`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: '#',
+ send: `boot system flash:${fileName}`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: '',
+ send: `end`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: '',
+ send: `write memory`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: '',
+ send: `reload`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: '', // Router thường hỏi câu này
+ send: ``, // Enter confirm
+ delay: '1',
+ repeat: '1',
+ note: 'Confirm reload',
+ },
+ {
+ expect: '',
+ send: ``,
+ delay: '1',
+ repeat: '1',
+ note: 'Waiting for reboot...',
+ },
+ // --- PHẦN 4: VERIFY ---
+ {
+ expect: 'Press RETURN to get started!',
+ send: ``,
+ delay: '1',
+ repeat: '1',
+ note: 'Router is back online',
+ },
+ {
+ expect: '',
+ send: `enable`,
+ delay: '3',
+ repeat: '1',
+ note: 'Enable again',
+ },
+ {
+ expect: '#',
+ send: `show inventory`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: '#',
+ send: `show license`,
+ delay: '1',
+ repeat: '1',
+ note: 'Verify license status',
+ },
+ {
+ expect: '#',
+ send: `show version`,
+ delay: '1',
+ repeat: '1',
+ note: 'Verify version info',
+ },
+ ]
+
+ /* ================= SWITCH LICENSE ================= */
+ case 'SWITCH_LICENSE':
+ return [
+ {
+ expect: '',
+ send: ``,
+ delay: '1',
+ repeat: '1',
+ note: 'Start session',
+ },
+ {
+ expect: '',
+ send: `enable`,
+ delay: '1',
+ repeat: '1',
+ note: 'Enter Enable mode',
+ },
+ {
+ expect: '#',
+ send: `configure terminal`,
+ delay: '1',
+ repeat: '1',
+ note: 'Enter Config mode',
+ },
+ {
+ expect: '#',
+ send: `interface vlan 1`,
+ delay: '1',
+ repeat: '1',
+ note: 'Select Interface Vlan 1',
+ },
+ {
+ expect: '#',
+ send: `ip address ${address} 255.255.0.0`,
+ delay: '1',
+ repeat: '1',
+ note: 'Set IP Address',
+ },
+ {
+ expect: '#',
+ send: `no shutdown`,
+ delay: '1',
+ repeat: '1',
+ note: 'Up interface',
+ },
+ {
+ expect: '#',
+ send: `exit`,
+ delay: '1',
+ repeat: '1',
+ note: 'Exit interface',
+ },
+ {
+ expect: '#',
+ send: `ip default-gateway ${gateway}`,
+ delay: '1',
+ repeat: '1',
+ note: 'Set Gateway',
+ },
+ {
+ expect: '#',
+ send: `end`,
+ delay: '1',
+ repeat: '1',
+ note: 'End config',
+ },
+ {
+ expect: '#',
+ send: `license install tftp://${tftpIp}/License/${fileName}`,
+ delay: '1',
+ repeat: '1',
+ note: 'Install license',
+ },
+ {
+ expect: '#',
+ send: `write memory`,
+ delay: '1',
+ repeat: '1',
+ note: 'Save config',
+ },
+ {
+ expect: '#',
+ send: `reload`,
+ delay: '1',
+ repeat: '1',
+ note: 'Reload switch',
+ },
+ {
+ expect: '', // Router thường hỏi câu này
+ send: ``, // Enter confirm
+ delay: '1',
+ repeat: '1',
+ note: 'Confirm reload',
+ },
+ {
+ expect: '',
+ send: ``,
+ delay: '1',
+ repeat: '1',
+ note: 'Waiting for reboot...',
+ },
+
+ // --- PHẦN 4: VERIFY ---
+ {
+ expect: 'Press RETURN to get started!',
+ send: ``,
+ delay: '1',
+ repeat: '1',
+ note: 'Router is back online',
+ },
+ {
+ expect: '',
+ send: `enable`,
+ delay: '3',
+ repeat: '1',
+ note: 'Enable again',
+ },
+ {
+ expect: '#',
+ send: `show inventory`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: '#',
+ send: `show license`,
+ delay: '1',
+ repeat: '1',
+ note: 'Verify license status',
+ },
+ {
+ expect: '#',
+ send: `show version`,
+ delay: '1',
+ repeat: '1',
+ note: 'Verify version info',
+ },
+ ]
+
+ /* ================= ROUTER LICENSE ================= */
+ case 'ROUTER_LICENSE':
+ return [
+ {
+ expect: '',
+ send: ``,
+ delay: '1',
+ repeat: '1',
+ note: 'Start session',
+ },
+ {
+ expect: '',
+ send: `enable`,
+ delay: '1',
+ repeat: '1',
+ note: 'Enter Enable mode',
+ },
+ {
+ expect: '#',
+ send: `configure terminal`,
+ delay: '1',
+ repeat: '1',
+ note: 'Enter Config mode',
+ },
+ {
+ expect: '#',
+ send: `interface GigabitEthernet0/0`,
+ delay: '1',
+ repeat: '1',
+ note: 'Select management interface',
+ },
+ {
+ expect: '#',
+ send: `ip address ${address} 255.255.0.0`,
+ delay: '1',
+ repeat: '1',
+ note: 'Set IP Address',
+ },
+ {
+ expect: '#',
+ send: `no shutdown`,
+ delay: '1',
+ repeat: '1',
+ note: 'Up interface',
+ },
+ {
+ expect: '#',
+ send: `exit`,
+ delay: '1',
+ repeat: '1',
+ note: 'Exit interface',
+ },
+ {
+ expect: '#',
+ send: `ip route 0.0.0.0 0.0.0.0 ${gateway}`,
+ delay: '1',
+ repeat: '1',
+ note: 'Set default route',
+ },
+ {
+ expect: '#',
+ send: `end`,
+ delay: '1',
+ repeat: '1',
+ note: 'End config',
+ },
+ {
+ expect: '#',
+ send: `license install tftp://${tftpIp}/License/${fileName}`,
+ delay: '1',
+ repeat: '1',
+ note: 'Install license',
+ },
+ {
+ expect: '#',
+ send: `write memory`,
+ delay: '1',
+ repeat: '1',
+ note: 'Save config',
+ },
+ {
+ expect: '#',
+ send: `reload`,
+ delay: '1',
+ repeat: '1',
+ note: 'Reload router',
+ },
+ {
+ expect: '', // Router thường hỏi câu này
+ send: ``, // Enter confirm
+ delay: '1',
+ repeat: '1',
+ note: 'Confirm reload',
+ },
+ {
+ expect: '',
+ send: ``,
+ delay: '1',
+ repeat: '1',
+ note: 'Waiting for reboot...',
+ },
+
+ // --- PHẦN 4: VERIFY ---
+ {
+ expect: 'Press RETURN to get started!',
+ send: ``,
+ delay: '1',
+ repeat: '1',
+ note: 'Router is back online',
+ },
+ {
+ expect: '',
+ send: `enable`,
+ delay: '3',
+ repeat: '1',
+ note: 'Enable again',
+ },
+ {
+ expect: '#',
+ send: `show inventory`,
+ delay: '1',
+ repeat: '1',
+ note: '',
+ },
+ {
+ expect: '#',
+ send: `show license`,
+ delay: '1',
+ repeat: '1',
+ note: 'Verify license status',
+ },
+ {
+ expect: '#',
+ send: `show version`,
+ delay: '1',
+ repeat: '1',
+ note: 'Verify version info',
+ },
+ ]
+
+ default:
+ return []
+ }
+}
diff --git a/BACKEND/start/routes.ts b/BACKEND/start/routes.ts
index b67df6d..4db01ba 100644
--- a/BACKEND/start/routes.ts
+++ b/BACKEND/start/routes.ts
@@ -109,11 +109,6 @@ router
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')
diff --git a/FRONTEND/src/App.tsx b/FRONTEND/src/App.tsx
index 2361f53..d105a61 100644
--- a/FRONTEND/src/App.tsx
+++ b/FRONTEND/src/App.tsx
@@ -104,6 +104,7 @@ function App() {
const [listBrands, setListBrands] = useState([]);
const [listCategories, setListCategories] = useState([]);
const [listIos, setListIos] = useState([]);
+ const [listLicense, setListLicense] = useState([]);
const connectApcSwitch = (station: TStation) => {
if (station?.apc_1_ip && station?.apc_1_port) {
@@ -202,6 +203,18 @@ function App() {
}
};
+ // function get list license
+ const getListLicense = async () => {
+ try {
+ const response = await axios.get(apiUrl + "api/license");
+ if (response.data && Array.isArray(response.data)) {
+ setListLicense(response.data);
+ }
+ } catch (error) {
+ console.log("Error get ios", error);
+ }
+ };
+
useEffect(() => {
if (!socket) return;
getStation();
@@ -209,6 +222,7 @@ function App() {
getBrands();
getCategories();
getListIos();
+ getListLicense();
}, [socket]);
useEffect(() => {
@@ -871,6 +885,7 @@ function App() {
stationItem={stations.find((el) => el.id === Number(activeTab))}
scenarios={scenarios}
listIos={listIos}
+ listLicense={listLicense}
/>
{/* void;
+ line: TLine | undefined;
+}) => {
+ const [inputSearch, setInputSearch] = useState("");
+
+ const filterLicense = () => {
+ return listLicense.filter((ios) =>
+ ios.toLowerCase().includes(inputSearch.toLowerCase())
+ );
+ };
+
+ return (
+ {
+ close();
+ setInputSearch("");
+ }}
+ title={
+
+ Select License
+
+ }
+ size="xl"
+ >
+
+ setInputSearch(event.currentTarget.value)}
+ rightSection={
+ inputSearch ? (
+ setInputSearch("")}
+ />
+ ) : null
+ }
+ rightSectionPointerEvents="auto"
+ size="xs"
+ />
+
+
+
+
+
+
+ Name
+
+
+ Action
+
+
+
+
+ {filterLicense()?.map((ios, i) => (
+
+ {ios || ""}
+
+ }
+ onClick={() => {
+ socket?.emit("load_ios_switch", {
+ stationId: Number(station?.id),
+ lineId: Number(line?.id),
+ iosName: ios,
+ });
+ close();
+ }}
+ >
+ Run
+
+
+
+ ))}
+
+
+
+
+ );
+};
+
+export default ModalSelectLicense;
diff --git a/FRONTEND/src/components/Modal/ModalTerminal.tsx b/FRONTEND/src/components/Modal/ModalTerminal.tsx
index 037bd55..676b271 100644
--- a/FRONTEND/src/components/Modal/ModalTerminal.tsx
+++ b/FRONTEND/src/components/Modal/ModalTerminal.tsx
@@ -44,6 +44,7 @@ import classes from "../Component.module.css";
import { listBaudDefault } from "../../untils/constanst";
import { motion } from "motion/react";
import ModalSelectIOS from "./ModalSelectIOS";
+import ModalSelectLicense from "./ModalSelectLicense";
const apiUrl = import.meta.env.VITE_BACKEND_URL;
const INIT_TICKET = {
@@ -64,6 +65,7 @@ const ModalTerminal = ({
scenarios,
selectedLines,
listIos,
+ listLicense,
}: {
opened: boolean;
onClose: () => void;
@@ -73,6 +75,7 @@ const ModalTerminal = ({
scenarios: IScenario[];
selectedLines: TLine[];
listIos: string[];
+ listLicense: string[];
}) => {
const user = useMemo(() => {
return localStorage.getItem("user") &&
@@ -92,6 +95,7 @@ const ModalTerminal = ({
const [isClearKeepScrollBack, setIsClearKeepScrollBack] =
useState(false);
const [openSelectIos, setOpenSelectIos] = useState(false);
+ const [openSelectLicense, setOpenSelectLicense] = useState(false);
useEffect(() => {
if (opened && line?.tickets && line?.tickets?.length > 0) {
@@ -1214,9 +1218,11 @@ const ModalTerminal = ({
disabled={true}
fw={400}
variant="outline"
- color="green"
+ color="yellow"
size="xs"
- onClick={() => {}}
+ onClick={() => {
+ setOpenSelectLicense(true);
+ }}
>
Select License
@@ -1498,6 +1504,17 @@ const ModalTerminal = ({
listIos={listIos}
line={line}
/>
+
+ {
+ setOpenSelectLicense(false);
+ }}
+ opened={openSelectLicense}
+ socket={socket}
+ station={stationItem}
+ listLicense={listLicense}
+ line={line}
+ />
);
};