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 || ""} + + + + + ))} + +
+
+
+ ); +}; + +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} + /> ); };