Update load license

This commit is contained in:
nguyentrungthat 2026-01-09 16:08:50 +07:00
parent 9266b63823
commit 5586f8f930
7 changed files with 902 additions and 315 deletions

View File

@ -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

View File

@ -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<br/>
<br/>
Station : <b>${this.config.stationName}</b><br/>
Line : <b>${this.config.lineNumber}</b><br/>
License : <b>${nameLicense}</b> <br/>
Started At : ${startTime}<br/>
Finished At : ${dataFormat}<br/>
<br/>
`.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)
}
}

View File

@ -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 []
}
}

View File

@ -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')

View File

@ -104,6 +104,7 @@ function App() {
const [listBrands, setListBrands] = useState<TBrands[]>([]);
const [listCategories, setListCategories] = useState<TCategories[]>([]);
const [listIos, setListIos] = useState<string[]>([]);
const [listLicense, setListLicense] = useState<string[]>([]);
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}
/>
{/* <ModalConfirmRunScenario

View File

@ -0,0 +1,141 @@
import {
Button,
Flex,
Modal,
ScrollArea,
Table,
Text,
TextInput,
} from "@mantine/core";
import type { Socket } from "socket.io-client";
import type { TLine, TStation } from "../../untils/types";
import { IconPlayerPlay, IconX } from "@tabler/icons-react";
import { useState } from "react";
const ModalSelectLicense = ({
socket,
station,
listLicense,
opened,
close,
line,
}: {
socket: Socket | null;
station: TStation | undefined;
listLicense: string[];
opened: boolean;
close: () => void;
line: TLine | undefined;
}) => {
const [inputSearch, setInputSearch] = useState<string>("");
const filterLicense = () => {
return listLicense.filter((ios) =>
ios.toLowerCase().includes(inputSearch.toLowerCase())
);
};
return (
<Modal
style={{ position: "absolute", left: 0 }}
opened={opened}
onClose={() => {
close();
setInputSearch("");
}}
title={
<Text fz={"lg"} fw={"bolder"}>
Select License
</Text>
}
size="xl"
>
<Flex justify={"space-between"} align={"center"} mt={"xs"}>
<TextInput
style={{ width: "350px" }}
placeholder="Search file name"
value={inputSearch}
onChange={(event) => setInputSearch(event.currentTarget.value)}
rightSection={
inputSearch ? (
<IconX
size={14}
style={{ cursor: "pointer" }}
onClick={() => setInputSearch("")}
/>
) : null
}
rightSectionPointerEvents="auto"
size="xs"
/>
</Flex>
<ScrollArea h={"70vh"} style={{ marginTop: "15px" }}>
<Table
stickyHeader
striped
highlightOnHover
withRowBorders={true}
withTableBorder={true}
withColumnBorders={true}
>
<Table.Thead
style={{
top: 1,
}}
>
<Table.Tr>
<Table.Th
style={{
backgroundColor: "#94c6ff",
}}
>
Name
</Table.Th>
<Table.Th
style={{
width: "200px",
textAlign: "center",
backgroundColor: "#94c6ff",
}}
>
Action
</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{filterLicense()?.map((ios, i) => (
<Table.Tr key={i}>
<Table.Td>{ios || ""}</Table.Td>
<Table.Td
style={{
textAlign: "center",
}}
>
<Button
style={{ width: "100px" }}
variant="light"
color="green"
size="sm"
leftSection={<IconPlayerPlay size={16} />}
onClick={() => {
socket?.emit("load_ios_switch", {
stationId: Number(station?.id),
lineId: Number(line?.id),
iosName: ios,
});
close();
}}
>
Run
</Button>
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
</ScrollArea>
</Modal>
);
};
export default ModalSelectLicense;

View File

@ -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<boolean>(false);
const [openSelectIos, setOpenSelectIos] = useState<boolean>(false);
const [openSelectLicense, setOpenSelectLicense] = useState<boolean>(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
</Button>
@ -1498,6 +1504,17 @@ const ModalTerminal = ({
listIos={listIos}
line={line}
/>
<ModalSelectLicense
close={() => {
setOpenSelectLicense(false);
}}
opened={openSelectLicense}
socket={socket}
station={stationItem}
listLicense={listLicense}
line={line}
/>
</Box>
);
};