Update load License

This commit is contained in:
nguyentrungthat 2026-01-13 10:54:12 +07:00
parent a9dce43ab2
commit 38b6cb4981
9 changed files with 497 additions and 230 deletions

View File

@ -1,18 +1,51 @@
import fs from 'node:fs' import fs from 'node:fs'
import path from 'node:path'
interface FileInfo {
name: string
fileSize: number
dateModify: number
}
export default class IosLicenseController { export default class IosLicenseController {
/* ================= HELPER ================= */ /* ================= HELPER ================= */
private getBinFiles(dir: string): string[] { private getBinFiles(dir: string): FileInfo[] {
if (!fs.existsSync(dir)) return [] if (!fs.existsSync(dir)) return []
return fs.readdirSync(dir).filter((file) => file.toLowerCase().endsWith('.bin')) return fs
.readdirSync(dir)
.filter((file) => file.toLowerCase().endsWith('.bin'))
.map((file) => {
const fullPath = path.join(dir, file)
const stat = fs.statSync(fullPath)
return {
name: file,
fileSize: stat.size,
dateModify: stat.mtime.getTime(),
}
})
.sort((a, b) => b.dateModify - a.dateModify)
} }
private getLicFiles(dir: string): string[] { private getLicFiles(dir: string): FileInfo[] {
if (!fs.existsSync(dir)) return [] if (!fs.existsSync(dir)) return []
return fs.readdirSync(dir).filter((file) => file.toLowerCase().endsWith('.lic')) return fs
.readdirSync(dir)
.filter((file) => file.toLowerCase().endsWith('.lic'))
.map((file) => {
const fullPath = path.join(dir, file)
const stat = fs.statSync(fullPath)
return {
name: file,
fileSize: stat.size,
dateModify: stat.mtime.getTime(),
}
})
.sort((a, b) => b.dateModify - a.dateModify)
} }
/* ================= IOS ================= */ /* ================= IOS ================= */

View File

@ -127,6 +127,7 @@ export default class LineConnection {
private session: TestSession private session: TestSession
public physicalTest: PhysicalPortTest public physicalTest: PhysicalPortTest
private outputPhysicalTest: string private outputPhysicalTest: string
private outputLoadIosLicense: string | boolean
private listDeviceIos: string[] private listDeviceIos: string[]
constructor(config: LineConfig, socketIO: any, handleClearLine: () => void) { constructor(config: LineConfig, socketIO: any, handleClearLine: () => void) {
@ -155,6 +156,7 @@ export default class LineConnection {
this.handleClearLine = handleClearLine this.handleClearLine = handleClearLine
this.physicalTest = new PhysicalPortTest([]) this.physicalTest = new PhysicalPortTest([])
this.outputPhysicalTest = '' this.outputPhysicalTest = ''
this.outputLoadIosLicense = ''
this.listDeviceIos = [] this.listDeviceIos = []
} }
/** /**
@ -200,6 +202,10 @@ export default class LineConnection {
if (!this.config.inventory) if (!this.config.inventory)
this.outputInventory = this.outputInventory.slice(-3000) + message this.outputInventory = this.outputInventory.slice(-3000) + message
} }
if (this.outputLoadIosLicense) {
if (this.outputLoadIosLicense === true) this.outputLoadIosLicense = ''
this.outputLoadIosLicense += message
}
if (this.config.runningPhysical) { if (this.config.runningPhysical) {
this.outputPhysicalTest += message this.outputPhysicalTest += message
const ports = this.physicalTest.handleLog(message) const ports = this.physicalTest.handleLog(message)
@ -1130,6 +1136,7 @@ export default class LineConnection {
async loadIosRouter(nameIos: string, userName: string) { async loadIosRouter(nameIos: string, userName: string) {
const station = await Station.find(this.config.stationId) const station = await Station.find(this.config.stationId)
if (!station) return if (!station) return
this.outputLoadIosLicense = true
const network = station?.gateway || '172.25.1.1' const network = station?.gateway || '172.25.1.1'
const tftpIp = station?.tftp_ip || '172.16.7.69' const tftpIp = station?.tftp_ip || '172.16.7.69'
const [a, b] = network.split('.').map(Number) const [a, b] = network.split('.').map(Number)
@ -1165,6 +1172,7 @@ export default class LineConnection {
async loadIosSwitch(nameIos: string, userName: string) { async loadIosSwitch(nameIos: string, userName: string) {
const station = await Station.find(this.config.stationId) const station = await Station.find(this.config.stationId)
if (!station) return if (!station) return
this.outputLoadIosLicense = true
const network = station?.gateway || '172.25.1.1' const network = station?.gateway || '172.25.1.1'
const tftpIp = station?.tftp_ip || '172.16.7.69' const tftpIp = station?.tftp_ip || '172.16.7.69'
const [a, b] = network.split('.').map(Number) const [a, b] = network.split('.').map(Number)
@ -1214,8 +1222,14 @@ export default class LineConnection {
await sendMessageToMail( await sendMessageToMail(
`[ATC] - [${this.config.stationName} - Line: ${this.config.lineNumber}] - Load IOS Report`, `[ATC] - [${this.config.stationName} - Line: ${this.config.lineNumber}] - Load IOS Report`,
body body +
`${`
<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.outputLoadIosLicense}</span></div>`}`
) )
this.outputLoadIosLicense = ''
} }
/** /**
@ -1237,8 +1251,14 @@ export default class LineConnection {
await sendMessageToMail( await sendMessageToMail(
`[ATC] - [${this.config.stationName} - Line: ${this.config.lineNumber}] - Load License Report`, `[ATC] - [${this.config.stationName} - Line: ${this.config.lineNumber}] - Load License Report`,
body body +
`${`
<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.outputLoadIosLicense}</span></div>`}`
) )
this.outputLoadIosLicense = ''
} }
/** /**
@ -1352,7 +1372,7 @@ export default class LineConnection {
// console.log(`SKIP active IOS: ${ios}`) // console.log(`SKIP active IOS: ${ios}`)
// continue // continue
// } // }
if (listIos?.includes(ios)) { if (listIos?.map((value) => value.name)?.includes(ios)) {
console.log(`Already backed up: ${ios}`) console.log(`Already backed up: ${ios}`)
if (ios !== nameIos) await this.deleteFileOnFlash(ios) if (ios !== nameIos) await this.deleteFileOnFlash(ios)
} else { } else {
@ -1373,7 +1393,7 @@ export default class LineConnection {
async loadLicenseSwitch(licenseFileName: string, userName: string, portName: string) { async loadLicenseSwitch(licenseFileName: string, userName: string, portName: string) {
const station = await Station.find(this.config.stationId) const station = await Station.find(this.config.stationId)
if (!station) return if (!station) return
this.outputLoadIosLicense = true
// Setup network variables (giống hệt logic load IOS để đảm bảo thông mạng) // 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 network = station?.gateway || '172.25.1.1'
const tftpIp = station?.tftp_ip || '172.16.7.69' const tftpIp = station?.tftp_ip || '172.16.7.69'
@ -1413,7 +1433,7 @@ export default class LineConnection {
async loadLicenseRouter(licenseFileName: string, userName: string, portName: string) { async loadLicenseRouter(licenseFileName: string, userName: string, portName: string) {
const station = await Station.find(this.config.stationId) const station = await Station.find(this.config.stationId)
if (!station) return if (!station) return
this.outputLoadIosLicense = true
const network = station?.gateway || '172.25.1.1' const network = station?.gateway || '172.25.1.1'
const tftpIp = station?.tftp_ip || '172.16.7.69' const tftpIp = station?.tftp_ip || '172.16.7.69'
const [a, b] = network.split('.').map(Number) const [a, b] = network.split('.').map(Number)

View File

@ -768,13 +768,6 @@ export function buildBody(
repeat: '1', repeat: '1',
note: '', note: '',
}, },
{
expect: '#',
send: `show license`,
delay: '1',
repeat: '1',
note: 'Verify license status',
},
{ {
expect: '#', expect: '#',
send: `show inventory`, send: `show inventory`,
@ -784,9 +777,16 @@ export function buildBody(
}, },
{ {
expect: '#', expect: '#',
send: `show version`, send: `show license`,
delay: '1', delay: '1',
repeat: '1', repeat: '1',
note: 'Verify license status',
},
{
expect: '#',
send: ` show version`,
delay: '3',
repeat: '1',
note: '', note: '',
}, },
{ {
@ -998,7 +998,7 @@ export function buildBody(
{ {
expect: '#', expect: '#',
send: ` show version`, send: ` show version`,
delay: '1', delay: '3',
repeat: '1', repeat: '1',
note: 'Verify version info', note: 'Verify version info',
}, },
@ -1073,7 +1073,7 @@ export function buildBody(
{ {
expect: '', expect: '',
send: ``, send: ``,
delay: '1', delay: '2',
repeat: '1', repeat: '1',
note: '', note: '',
}, },
@ -1159,7 +1159,7 @@ export function buildBody(
{ {
expect: '#', expect: '#',
send: ` show version`, send: ` show version`,
delay: '1', delay: '3',
repeat: '1', repeat: '1',
note: 'Verify version info', note: 'Verify version info',
}, },
@ -1234,7 +1234,7 @@ export function buildBody(
{ {
expect: '', expect: '',
send: ``, send: ``,
delay: '1', delay: '2',
repeat: '1', repeat: '1',
note: '', note: '',
}, },
@ -1273,6 +1273,13 @@ export function buildBody(
repeat: '1', repeat: '1',
note: 'Reload router', note: 'Reload router',
}, },
{
expect: '',
send: `yes`,
delay: '1',
repeat: '1',
note: 'Confirm reload',
},
{ {
expect: '', expect: '',
send: ``, send: ``,
@ -1320,7 +1327,7 @@ export function buildBody(
{ {
expect: '#', expect: '#',
send: ` show version`, send: ` show version`,
delay: '1', delay: '3',
repeat: '1', repeat: '1',
note: 'Verify version info', note: 'Verify version info',
}, },

View File

@ -24,6 +24,7 @@ import {
LoadingOverlay, LoadingOverlay,
} from "@mantine/core"; } from "@mantine/core";
import type { import type {
FileInfo,
IScenario, IScenario,
ReceivedFile, ReceivedFile,
ResponseData, ResponseData,
@ -103,8 +104,8 @@ function App() {
const flushScheduledRef = useRef(false); const flushScheduledRef = useRef(false);
const [listBrands, setListBrands] = useState<TBrands[]>([]); const [listBrands, setListBrands] = useState<TBrands[]>([]);
const [listCategories, setListCategories] = useState<TCategories[]>([]); const [listCategories, setListCategories] = useState<TCategories[]>([]);
const [listIos, setListIos] = useState<string[]>([]); const [listIos, setListIos] = useState<FileInfo[]>([]);
const [listLicense, setListLicense] = useState<string[]>([]); const [listLicense, setListLicense] = useState<FileInfo[]>([]);
const connectApcSwitch = (station: TStation) => { const connectApcSwitch = (station: TStation) => {
if (station?.apc_1_ip && station?.apc_1_port) { if (station?.apc_1_ip && station?.apc_1_port) {
@ -886,6 +887,8 @@ function App() {
scenarios={scenarios} scenarios={scenarios}
listIos={listIos} listIos={listIos}
listLicense={listLicense} listLicense={listLicense}
getListIos={getListIos}
getListLicense={getListLicense}
/> />
{/* <ModalConfirmRunScenario {/* <ModalConfirmRunScenario

View File

@ -1,7 +1,9 @@
import { import {
Box,
Button, Button,
Checkbox, Checkbox,
Flex, Flex,
Loader,
Modal, Modal,
ScrollArea, ScrollArea,
Table, Table,
@ -10,9 +12,11 @@ import {
TextInput, TextInput,
} from "@mantine/core"; } from "@mantine/core";
import type { Socket } from "socket.io-client"; import type { Socket } from "socket.io-client";
import type { TLine, TStation } from "../../untils/types"; import type { FileInfo, TLine, TStation } from "../../untils/types";
import { IconPlayerPlay, IconX } from "@tabler/icons-react"; import { IconPlayerPlay, IconRepeat, IconX } from "@tabler/icons-react";
import { useState } from "react"; import { useState } from "react";
import moment from "moment";
import { bytesToMB } from "../../untils/helper";
const ModalSelectIOS = ({ const ModalSelectIOS = ({
socket, socket,
@ -21,16 +25,19 @@ const ModalSelectIOS = ({
opened, opened,
close, close,
line, line,
getListIos,
}: { }: {
socket: Socket | null; socket: Socket | null;
station: TStation | undefined; station: TStation | undefined;
listIos: string[]; listIos: FileInfo[];
opened: boolean; opened: boolean;
close: () => void; close: () => void;
line: TLine | undefined; line: TLine | undefined;
getListIos: () => void;
}) => { }) => {
const [isReboot, setIsReboot] = useState<boolean>(true); const [isReboot, setIsReboot] = useState<boolean>(true);
const [inputSearch, setInputSearch] = useState<string>(""); const [inputSearch, setInputSearch] = useState<string>("");
const [isDisable, setIsDisable] = useState<boolean>(false);
const filterIos = (type: string = "") => { const filterIos = (type: string = "") => {
// Switch: Ưu tiên các dòng 4 chữ số cụ thể // Switch: Ưu tiên các dòng 4 chữ số cụ thể
@ -43,16 +50,18 @@ const ModalSelectIOS = ({
/^(c8\d{2}|c18|c19|c28|c29(?!60)|c38(?!50)|c39|isr|asr)/i; /^(c8\d{2}|c18|c19|c28|c29(?!60)|c38(?!50)|c39|isr|asr)/i;
return listIos return listIos
.filter((name) => { .filter((ios) => {
if (type === "switch") { if (type === "switch") {
return switchRegex.test(name); return switchRegex.test(ios.name);
} }
if (type === "router") { if (type === "router") {
return routerRegex.test(name); return routerRegex.test(ios.name);
} }
return false; return false;
}) })
.filter((ios) => ios.toLowerCase().includes(inputSearch.toLowerCase())); .filter((ios) =>
ios.name.toLowerCase().includes(inputSearch.toLowerCase())
);
}; };
return ( return (
@ -65,11 +74,28 @@ const ModalSelectIOS = ({
setIsReboot(true); setIsReboot(true);
}} }}
title={ title={
<Flex gap={"xs"}>
<Text fz={"lg"} fw={"bolder"}> <Text fz={"lg"} fw={"bolder"}>
Select IOS Select IOS
</Text> </Text>
<Button
size="xs"
disabled={isDisable}
variant="filled"
color="yellow"
onClick={() => {
getListIos();
setIsDisable(true);
setTimeout(() => {
setIsDisable(false);
}, 2000);
}}
>
<IconRepeat size={14} />
</Button>
</Flex>
} }
size="xl" size="55%"
> >
<Tabs defaultValue="router" h={"100%"}> <Tabs defaultValue="router" h={"100%"}>
<Tabs.List> <Tabs.List>
@ -106,6 +132,19 @@ const ModalSelectIOS = ({
onChange={(event) => setIsReboot(event.currentTarget.checked)} onChange={(event) => setIsReboot(event.currentTarget.checked)}
/> />
</Flex> </Flex>
{isDisable ? (
<Box
style={{
height: "70vh",
width: "100%",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<Loader color="blue" />
</Box>
) : (
<ScrollArea h={"70vh"} style={{ marginTop: "15px" }}> <ScrollArea h={"70vh"} style={{ marginTop: "15px" }}>
<Table <Table
stickyHeader stickyHeader
@ -128,6 +167,24 @@ const ModalSelectIOS = ({
> >
Name Name
</Table.Th> </Table.Th>
<Table.Th
style={{
width: "170px",
textAlign: "center",
backgroundColor: "#94c6ff",
}}
>
Date
</Table.Th>
<Table.Th
style={{
width: "100px",
textAlign: "center",
backgroundColor: "#94c6ff",
}}
>
Size
</Table.Th>
<Table.Th <Table.Th
style={{ style={{
width: "200px", width: "200px",
@ -142,7 +199,13 @@ const ModalSelectIOS = ({
<Table.Tbody> <Table.Tbody>
{filterIos("router")?.map((ios, i) => ( {filterIos("router")?.map((ios, i) => (
<Table.Tr key={i}> <Table.Tr key={i}>
<Table.Td>{ios || ""}</Table.Td> <Table.Td>{ios?.name || ""}</Table.Td>
<Table.Td>
{moment(ios?.dateModify).format(
"YYYY/MM/DD HH:mm:ss"
) || ""}
</Table.Td>
<Table.Td>{bytesToMB(ios?.fileSize) || "0"} MB</Table.Td>
<Table.Td <Table.Td
style={{ style={{
textAlign: "center", textAlign: "center",
@ -176,6 +239,7 @@ const ModalSelectIOS = ({
</Table.Tbody> </Table.Tbody>
</Table> </Table>
</ScrollArea> </ScrollArea>
)}
</Tabs.Panel> </Tabs.Panel>
<Tabs.Panel value="switch" w={"100%"}> <Tabs.Panel value="switch" w={"100%"}>
<Flex justify={"space-between"} align={"center"} mt={"xs"}> <Flex justify={"space-between"} align={"center"} mt={"xs"}>
@ -197,6 +261,19 @@ const ModalSelectIOS = ({
size="xs" size="xs"
/> />
</Flex> </Flex>
{isDisable ? (
<Box
style={{
height: "70vh",
width: "100%",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<Loader color="blue" />
</Box>
) : (
<ScrollArea h={"70vh"} style={{ marginTop: "15px" }}> <ScrollArea h={"70vh"} style={{ marginTop: "15px" }}>
<Table <Table
stickyHeader stickyHeader
@ -219,6 +296,24 @@ const ModalSelectIOS = ({
> >
Name Name
</Table.Th> </Table.Th>
<Table.Th
style={{
width: "170px",
textAlign: "center",
backgroundColor: "#94c6ff",
}}
>
Date
</Table.Th>
<Table.Th
style={{
width: "100px",
textAlign: "center",
backgroundColor: "#94c6ff",
}}
>
Size
</Table.Th>
<Table.Th <Table.Th
style={{ style={{
width: "200px", width: "200px",
@ -233,7 +328,13 @@ const ModalSelectIOS = ({
<Table.Tbody> <Table.Tbody>
{filterIos("switch")?.map((ios, i) => ( {filterIos("switch")?.map((ios, i) => (
<Table.Tr key={i}> <Table.Tr key={i}>
<Table.Td>{ios || ""}</Table.Td> <Table.Td>{ios?.name || ""}</Table.Td>
<Table.Td>
{moment(ios?.dateModify).format(
"YYYY/MM/DD HH:mm:ss"
) || ""}
</Table.Td>
<Table.Td>{bytesToMB(ios?.fileSize) || "0"} MB</Table.Td>
<Table.Td <Table.Td
style={{ style={{
textAlign: "center", textAlign: "center",
@ -262,6 +363,7 @@ const ModalSelectIOS = ({
</Table.Tbody> </Table.Tbody>
</Table> </Table>
</ScrollArea> </ScrollArea>
)}
</Tabs.Panel> </Tabs.Panel>
</Tabs> </Tabs>
</Modal> </Modal>

View File

@ -1,6 +1,8 @@
import { import {
Box,
Button, Button,
Flex, Flex,
Loader,
Modal, Modal,
ScrollArea, ScrollArea,
Table, Table,
@ -9,9 +11,11 @@ import {
TextInput, TextInput,
} from "@mantine/core"; } from "@mantine/core";
import type { Socket } from "socket.io-client"; import type { Socket } from "socket.io-client";
import type { TLine, TStation } from "../../untils/types"; import type { FileInfo, TLine, TStation } from "../../untils/types";
import { IconPlayerPlay, IconX } from "@tabler/icons-react"; import { IconPlayerPlay, IconRepeat, IconX } from "@tabler/icons-react";
import { useState } from "react"; import { useEffect, useState } from "react";
import moment from "moment";
import { bytesToKB } from "../../untils/helper";
const ModalSelectLicense = ({ const ModalSelectLicense = ({
socket, socket,
@ -20,22 +24,33 @@ const ModalSelectLicense = ({
opened, opened,
close, close,
line, line,
getListLicense,
}: { }: {
socket: Socket | null; socket: Socket | null;
station: TStation | undefined; station: TStation | undefined;
listLicense: string[]; listLicense: FileInfo[];
opened: boolean; opened: boolean;
close: () => void; close: () => void;
line: TLine | undefined; line: TLine | undefined;
getListLicense: () => void;
}) => { }) => {
const [inputSearch, setInputSearch] = useState<string>(""); const [inputSearch, setInputSearch] = useState<string>("");
const [inputPort, setInputPort] = useState<string>("GigabitEthernet0/0"); const [inputPort, setInputPort] = useState<string>("GigabitEthernet0/0");
const [licenseName, setLicenseName] = useState<string>(""); const [licenseName, setLicenseName] = useState<string>("");
const [modalConfirm, setModalConfirm] = useState<boolean>(false); const [modalConfirm, setModalConfirm] = useState<boolean>(false);
const [isDisable, setIsDisable] = useState<boolean>(false);
useEffect(() => {
if (opened) {
if (line?.inventory?.sn) setInputSearch(line?.inventory?.sn);
} else {
setInputSearch("");
}
}, [opened]);
const filterLicense = () => { const filterLicense = () => {
return listLicense.filter((ios) => return listLicense.filter((lic) =>
ios.toLowerCase().includes(inputSearch.toLowerCase()) lic.name.toLowerCase().includes(inputSearch.toLowerCase())
); );
}; };
@ -48,11 +63,28 @@ const ModalSelectLicense = ({
setInputSearch(""); setInputSearch("");
}} }}
title={ title={
<Flex gap={"xs"}>
<Text fz={"lg"} fw={"bolder"}> <Text fz={"lg"} fw={"bolder"}>
Select License Select License
</Text> </Text>
<Button
size="xs"
disabled={isDisable}
variant="filled"
color="yellow"
onClick={() => {
getListLicense();
setIsDisable(true);
setTimeout(() => {
setIsDisable(false);
}, 2000);
}}
>
<IconRepeat size={14} />
</Button>
</Flex>
} }
size="xl" size="55%"
> >
<Flex justify={"space-between"} align={"center"} mt={"xs"}> <Flex justify={"space-between"} align={"center"} mt={"xs"}>
<TextInput <TextInput
@ -73,6 +105,19 @@ const ModalSelectLicense = ({
size="xs" size="xs"
/> />
</Flex> </Flex>
{isDisable ? (
<Box
style={{
height: "70vh",
width: "100%",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<Loader color="blue" />
</Box>
) : (
<ScrollArea h={"70vh"} style={{ marginTop: "15px" }}> <ScrollArea h={"70vh"} style={{ marginTop: "15px" }}>
<Table <Table
stickyHeader stickyHeader
@ -95,6 +140,24 @@ const ModalSelectLicense = ({
> >
Name Name
</Table.Th> </Table.Th>
<Table.Th
style={{
width: "170px",
textAlign: "center",
backgroundColor: "#94c6ff",
}}
>
Date
</Table.Th>
<Table.Th
style={{
width: "100px",
textAlign: "center",
backgroundColor: "#94c6ff",
}}
>
Size
</Table.Th>
<Table.Th <Table.Th
style={{ style={{
width: "200px", width: "200px",
@ -109,7 +172,18 @@ const ModalSelectLicense = ({
<Table.Tbody> <Table.Tbody>
{filterLicense()?.map((lic, i) => ( {filterLicense()?.map((lic, i) => (
<Table.Tr key={i}> <Table.Tr key={i}>
<Table.Td>{lic || ""}</Table.Td> <Table.Td>{lic?.name || ""}</Table.Td>
<Table.Td>
{moment(lic?.dateModify).format("YYYY/MM/DD HH:mm:ss") ||
""}
</Table.Td>
<Table.Td
style={{
textAlign: "center",
}}
>
{bytesToKB(lic?.fileSize) || "0"} KB
</Table.Td>
<Table.Td <Table.Td
style={{ style={{
textAlign: "center", textAlign: "center",
@ -128,7 +202,7 @@ const ModalSelectLicense = ({
// iosName: ios, // iosName: ios,
// }); // });
// close(); // close();
setLicenseName(lic); setLicenseName(lic?.name);
setModalConfirm(true); setModalConfirm(true);
}} }}
> >
@ -140,6 +214,7 @@ const ModalSelectLicense = ({
</Table.Tbody> </Table.Tbody>
</Table> </Table>
</ScrollArea> </ScrollArea>
)}
<Modal <Modal
style={{ position: "absolute", left: 0 }} style={{ position: "absolute", left: 0 }}
@ -190,6 +265,7 @@ const ModalSelectLicense = ({
</Tabs.List> </Tabs.List>
<Tabs.Panel value="router" w={"100%"}> <Tabs.Panel value="router" w={"100%"}>
<Flex align={"center"} justify={"center"} p={"md"}> <Flex align={"center"} justify={"center"} p={"md"}>
<Text me={"xs"}>Port Name:</Text>{" "}
<TextInput <TextInput
style={{ width: "350px" }} style={{ width: "350px" }}
placeholder="Port name" placeholder="Port name"
@ -234,6 +310,7 @@ const ModalSelectLicense = ({
</Tabs.Panel> </Tabs.Panel>
<Tabs.Panel value="switch" w={"100%"}> <Tabs.Panel value="switch" w={"100%"}>
<Flex align={"center"} justify={"center"} p={"md"}> <Flex align={"center"} justify={"center"} p={"md"}>
<Text me={"xs"}>Port Name:</Text>
<TextInput <TextInput
style={{ width: "350px" }} style={{ width: "350px" }}
placeholder="Port name" placeholder="Port name"

View File

@ -15,6 +15,7 @@ import {
Tooltip, Tooltip,
} from "@mantine/core"; } from "@mantine/core";
import type { import type {
FileInfo,
IScenario, IScenario,
SwitchPortsProps, SwitchPortsProps,
TDataTicket, TDataTicket,
@ -66,6 +67,8 @@ const ModalTerminal = ({
selectedLines, selectedLines,
listIos, listIos,
listLicense, listLicense,
getListLicense,
getListIos,
}: { }: {
opened: boolean; opened: boolean;
onClose: () => void; onClose: () => void;
@ -74,8 +77,10 @@ const ModalTerminal = ({
stationItem: TStation | undefined; stationItem: TStation | undefined;
scenarios: IScenario[]; scenarios: IScenario[];
selectedLines: TLine[]; selectedLines: TLine[];
listIos: string[]; listIos: FileInfo[];
listLicense: string[]; listLicense: FileInfo[];
getListLicense: () => void;
getListIos: () => void;
}) => { }) => {
const user = useMemo(() => { const user = useMemo(() => {
return localStorage.getItem("user") && return localStorage.getItem("user") &&
@ -1210,18 +1215,20 @@ const ModalTerminal = ({
size="xs" size="xs"
onClick={() => { onClick={() => {
setOpenSelectIos(true); setOpenSelectIos(true);
getListIos();
}} }}
> >
Select IOS Select IOS
</Button> </Button>
<Button <Button
disabled={true} disabled={isDisable}
fw={400} fw={400}
variant="outline" variant="outline"
color="yellow" color="yellow"
size="xs" size="xs"
onClick={() => { onClick={() => {
setOpenSelectLicense(true); setOpenSelectLicense(true);
getListLicense();
}} }}
> >
Select License Select License
@ -1503,6 +1510,7 @@ const ModalTerminal = ({
station={stationItem} station={stationItem}
listIos={listIos} listIos={listIos}
line={line} line={line}
getListIos={getListIos}
/> />
<ModalSelectLicense <ModalSelectLicense
@ -1514,6 +1522,7 @@ const ModalTerminal = ({
station={stationItem} station={stationItem}
listLicense={listLicense} listLicense={listLicense}
line={line} line={line}
getListLicense={getListLicense}
/> />
</Box> </Box>
); );

View File

@ -79,3 +79,13 @@ export const handleGetStorageCommand = (lineId: number): string[] => {
const history: Record<number, string[]> = storage ? JSON.parse(storage) : {}; const history: Record<number, string[]> = storage ? JSON.parse(storage) : {};
return history[lineId] || []; return history[lineId] || [];
}; };
//Function convert bytes to MB
export function bytesToMB(bytes: number, decimal = 2): number {
return Number((bytes / (1024 * 1024)).toFixed(decimal));
}
//Function convert bytes to KB
export function bytesToKB(bytes: number, decimal = 2): number {
return Number((bytes / 1024).toFixed(decimal));
}

View File

@ -263,3 +263,9 @@ export type TCategories = {
id: number; id: number;
name: string; name: string;
}; };
export type FileInfo = {
name: string;
fileSize: number;
dateModify: string;
};