Add baud and interface to lines, enhance line controls

Added 'baud' and 'interface' fields to the Line model and database schema. Implemented backend and frontend support for setting baud rate per line, including socket event handling and UI controls. Enhanced CardLine and BottomToolBar components to provide scenario and baud controls, and improved UI/UX for line management. Minor fixes and refactoring for consistency and usability.
This commit is contained in:
nguyentrungthat 2025-11-13 16:58:02 +07:00
parent 31036ff7da
commit 8a06650eab
14 changed files with 540 additions and 123 deletions

View File

@ -26,6 +26,12 @@ export default class Line extends BaseModel {
@column() @column()
declare outlet: number declare outlet: number
@column()
declare interface: string
@column()
declare baud: number
@belongsTo(() => Station) @belongsTo(() => Station)
declare station: BelongsTo<typeof Station> declare station: BelongsTo<typeof Station>

View File

@ -100,7 +100,7 @@ class APCController {
this.buffer = '' this.buffer = ''
} }
} }
appendLog(data, 0, 0, this.apc_number || 0) // appendLog(data, 0, 0, this.apc_number || 0)
} }
private _handleClose(): void { private _handleClose(): void {

View File

@ -200,8 +200,10 @@ export default class LineConnection {
if (char === '\x7F') this.bufferCommand = this.bufferCommand.slice(0, -1) if (char === '\x7F') this.bufferCommand = this.bufferCommand.slice(0, -1)
else if (char === '\r' && cleanData(this.bufferCommand).length > 0) { else if (char === '\r' && cleanData(this.bufferCommand).length > 0) {
this.config.commands = [ this.config.commands = [
cleanData(this.bufferCommand), cleanData(this.bufferCommand.replace('\r', '')),
...this.config.commands.filter((el) => el !== cleanData(this.bufferCommand)), ...this.config.commands.filter(
(el) => el !== cleanData(this.bufferCommand.replace('\r', ''))
),
].slice(0, 10) ].slice(0, 10)
this.bufferCommand = '' this.bufferCommand = ''
} else this.bufferCommand += char } else this.bufferCommand += char
@ -516,6 +518,7 @@ export default class LineConnection {
// Gửi nhiều ký tự ESC để vào ROMMON // Gửi nhiều ký tự ESC để vào ROMMON
breakSpam() { breakSpam() {
console.log('SPAM Break to line:', this.config.lineNumber)
let count = 0 let count = 0
const escInterval = setInterval(() => { const escInterval = setInterval(() => {
if (count >= 100) { if (count >= 100) {
@ -526,4 +529,16 @@ export default class LineConnection {
count++ count++
}, 1) }, 1)
} }
async setBaud(baud: number) {
this.writeCommand('enable\r\n')
await sleep(500)
this.writeCommand('line console 0\r\n')
await sleep(500)
this.writeCommand(`speed ${baud}\r\n`)
await sleep(500)
this.writeCommand('end\r\n')
await sleep(500)
this.writeCommand('write memory\r\n')
}
} }

View File

@ -0,0 +1,19 @@
import { BaseSchema } from '@adonisjs/lucid/schema'
export default class extends BaseSchema {
protected tableName = 'lines'
async up() {
this.schema.alterTable(this.tableName, (table) => {
table.string('interface').defaultTo('').nullable()
table.integer('baud').nullable()
})
}
async down() {
this.schema.alterTable(this.tableName, (table) => {
table.dropColumn('interface')
table.dropColumn('baud')
})
}
}

View File

@ -18,6 +18,7 @@ interface HandleOptions {
actionApc?: string actionApc?: string
scenario?: any scenario?: any
timeout?: number timeout?: number
baud?: number
} }
type LineAction = (line: LineConnection, options?: HandleOptions) => Promise<void | unknown> | void type LineAction = (line: LineConnection, options?: HandleOptions) => Promise<void | unknown> | void
@ -166,6 +167,26 @@ export class WebSocketIo {
) )
}) })
socket.on('set_baud', async (data) => {
const lineId = data.lineId
const baud = data.baud
const line = await Line.find(lineId)
if (line) {
Object.assign(line, { baud })
line?.save()
}
await this.handleLineOperation(
io,
data.stationId,
[lineId],
async (value) => value.setBaud(baud),
{
baud,
timeout: 120000,
}
)
})
socket.on('open_cli', async (data) => { socket.on('open_cli', async (data) => {
const { lineId, userEmail, userName: name, stationId } = data const { lineId, userEmail, userName: name, stationId } = data
const line = this.lineMap.get(lineId) const line = this.lineMap.get(lineId)

View File

@ -112,10 +112,15 @@ function App() {
const response = await axios.get(apiUrl + "api/stations"); const response = await axios.get(apiUrl + "api/stations");
if (response.status) { if (response.status) {
if (Array.isArray(response.data)) { if (Array.isArray(response.data)) {
setStations(response.data); setStations(
response.data.forEach((station) => { response.data.map((station) => {
connectApcSwitch(station); connectApcSwitch(station);
}); const lines = (station?.lines || []).sort(
(a: TLine, b: TLine) => a?.lineNumber - b?.lineNumber
);
return { ...station, lines };
})
);
} }
} }
} catch (error) { } catch (error) {
@ -441,6 +446,7 @@ function App() {
loadingTerminal && loadingTerminal &&
Number(station.id) === Number(activeTab) Number(station.id) === Number(activeTab)
} }
scenarios={scenarios}
/> />
))} ))}
</Flex> </Flex>
@ -462,6 +468,7 @@ function App() {
loadingTerminal && loadingTerminal &&
Number(station.id) === Number(activeTab) Number(station.id) === Number(activeTab)
} }
scenarios={scenarios}
/> />
))} ))}
</Flex> </Flex>
@ -483,6 +490,7 @@ function App() {
loadingTerminal && loadingTerminal &&
Number(station.id) === Number(activeTab) Number(station.id) === Number(activeTab)
} }
scenarios={scenarios}
/> />
))} ))}
</Flex> </Flex>
@ -505,6 +513,7 @@ function App() {
isLogModalOpen={isLogModalOpen} isLogModalOpen={isLogModalOpen}
setIsLogModalOpen={setIsLogModalOpen} setIsLogModalOpen={setIsLogModalOpen}
setTestLogContent={setTestLogContent} setTestLogContent={setTestLogContent}
scenarios={scenarios}
/> />
</Flex> </Flex>
</Tabs.Panel> </Tabs.Panel>

View File

@ -4,29 +4,32 @@ import {
CloseButton, CloseButton,
Flex, Flex,
Input, Input,
Menu,
ScrollArea, ScrollArea,
Tabs, Tabs,
Text, Text,
} from "@mantine/core"; } from "@mantine/core";
import { useState } from "react"; import { useMemo, useState } from "react";
import classes from "./Component.module.css"; import classes from "./Component.module.css";
import type { TLine, TStation } from "../untils/types"; import type { IScenario, TLine, TStation } from "../untils/types";
import type { Socket } from "socket.io-client"; import type { Socket } from "socket.io-client";
import { ButtonDPELP, ButtonSelect } from "./ButtonAction"; import { ButtonDPELP, ButtonScenario, ButtonSelect } from "./ButtonAction";
import DrawerLogs from "./DrawerLogs"; import DrawerLogs from "./DrawerLogs";
import { DrawerAPCControl, DrawerSwitchControl } from "./DrawerControl"; import { DrawerAPCControl, DrawerSwitchControl } from "./DrawerControl";
import { isJsonString } from "../untils/helper";
interface TabsProps { interface TabsProps {
selectedLines: TLine[]; selectedLines: TLine[];
socket: Socket | null; socket: Socket | null;
setSelectedLines: (lines: React.SetStateAction<TLine[]>) => void; setSelectedLines: (value: React.SetStateAction<TLine[]>) => void;
isDisable: boolean; isDisable: boolean;
station: TStation; station: TStation;
setIsDisable: (lines: React.SetStateAction<boolean>) => void; setIsDisable: (value: React.SetStateAction<boolean>) => void;
testLogContent: string; testLogContent: string;
isLogModalOpen: boolean; isLogModalOpen: boolean;
setIsLogModalOpen: (lines: React.SetStateAction<boolean>) => void; setIsLogModalOpen: (value: React.SetStateAction<boolean>) => void;
setTestLogContent: (lines: React.SetStateAction<string>) => void; setTestLogContent: (value: React.SetStateAction<string>) => void;
scenarios: IScenario[];
} }
const BottomToolBar = ({ const BottomToolBar = ({
@ -40,7 +43,14 @@ const BottomToolBar = ({
isLogModalOpen, isLogModalOpen,
setIsLogModalOpen, setIsLogModalOpen,
setTestLogContent, setTestLogContent,
scenarios,
}: TabsProps) => { }: TabsProps) => {
const user = useMemo(() => {
return localStorage.getItem("user") &&
isJsonString(localStorage.getItem("user"))
? JSON.parse(localStorage.getItem("user") || "")
: null;
}, []);
const [valueInput, setValueInput] = useState<string>(""); const [valueInput, setValueInput] = useState<string>("");
const [activeTabBottom, setActiveBottom] = useState<string>("command"); const [activeTabBottom, setActiveBottom] = useState<string>("command");
@ -129,7 +139,7 @@ const BottomToolBar = ({
socket?.emit("write_command_line_from_web", { socket?.emit("write_command_line_from_web", {
lineIds: listLine.map((line) => line.id), lineIds: listLine.map((line) => line.id),
stationId: station.id, stationId: station.id,
command: " \n", command: "spam_break",
}); });
setIsDisable(true); setIsDisable(true);
setTimeout(() => { setTimeout(() => {
@ -207,24 +217,27 @@ const BottomToolBar = ({
}, 5000); }, 5000);
}} }}
/> />
<Menu shadow="md" position="top">
<Menu.Target>
<Button <Button
disabled={isDisable || selectedLines.length === 0} disabled={isDisable || selectedLines.length === 0}
variant="outline" variant="filled"
color="green" color="yellow"
style={{ height: "30px", width: "100px" }} style={{ height: "30px", width: "100px" }}
onClick={() => { onClick={() => {}}
if (selectedLines.length !== station.lines.length)
setSelectedLines(station.lines);
else setSelectedLines([]);
}}
> >
Scenario Scenario
</Button> </Button>
{/* <Flex </Menu.Target>
w={"100%"} <Menu.Dropdown>
direction={"column"} <Box
wrap={"wrap"} px="xs"
gap={"6px"} py="sm"
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr",
gap: "12px",
}}
> >
{scenarios.map((el, i) => ( {scenarios.map((el, i) => (
<ButtonScenario <ButtonScenario
@ -253,7 +266,9 @@ const BottomToolBar = ({
scenario={el} scenario={el}
/> />
))} ))}
</Flex> */} </Box>
</Menu.Dropdown>
</Menu>
<DrawerLogs <DrawerLogs
socket={socket} socket={socket}
isLogModalOpen={isLogModalOpen} isLogModalOpen={isLogModalOpen}

View File

@ -133,7 +133,7 @@ export const ButtonScenario = ({
}: { }: {
socket: Socket | null; socket: Socket | null;
isDisable: boolean; isDisable: boolean;
onClick: () => void; onClick: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
selectedLines: TLine[]; selectedLines: TLine[];
scenario: IScenario; scenario: IScenario;
}) => { }) => {
@ -146,8 +146,8 @@ export const ButtonScenario = ({
variant="outline" variant="outline"
color="#00a164" color="#00a164"
className={classes.buttonScenario} className={classes.buttonScenario}
onClick={async () => { onClick={async (e) => {
onClick(); onClick(e);
selectedLines?.forEach((el) => { selectedLines?.forEach((el) => {
socket?.emit( socket?.emit(
"run_scenario", "run_scenario",

View File

@ -1,11 +1,13 @@
import { Card, Text, Box, Flex } from "@mantine/core"; import { Card, Text, Box, Flex, Menu, Button, Input } from "@mantine/core";
import type { TLine, TStation } from "../untils/types"; import type { IScenario, TLine, TStation } from "../untils/types";
import classes from "./Component.module.css"; import classes from "./Component.module.css";
import TerminalCLI from "./TerminalXTerm"; import TerminalCLI from "./TerminalXTerm";
import type { Socket } from "socket.io-client"; import type { Socket } from "socket.io-client";
import { IconCircleCheckFilled } from "@tabler/icons-react"; import { memo, useMemo, useState } from "react";
import { memo, useMemo } from "react";
import { convertTimestampToDate } from "../untils/helper"; import { convertTimestampToDate } from "../untils/helper";
import { ButtonDPELP, ButtonScenario } from "./ButtonAction";
import { notifications } from "@mantine/notifications";
import { listBaudDefault } from "../untils/constanst";
const CardLine = ({ const CardLine = ({
line, line,
@ -15,6 +17,7 @@ const CardLine = ({
stationItem, stationItem,
openTerminal, openTerminal,
loadTerminal, loadTerminal,
scenarios,
}: { }: {
line: TLine; line: TLine;
selectedLines: TLine[]; selectedLines: TLine[];
@ -23,6 +26,7 @@ const CardLine = ({
stationItem: TStation; stationItem: TStation;
openTerminal: (value: TLine) => void; openTerminal: (value: TLine) => void;
loadTerminal: boolean; loadTerminal: boolean;
scenarios: IScenario[];
}) => { }) => {
const user = useMemo(() => { const user = useMemo(() => {
return localStorage.getItem("user") && return localStorage.getItem("user") &&
@ -30,7 +34,51 @@ const CardLine = ({
? JSON.parse(localStorage.getItem("user") || "") ? JSON.parse(localStorage.getItem("user") || "")
: null; : null;
}, []); }, []);
const [showMenu, setShowMenu] = useState<boolean>(false);
const [isDisabled, setIsDisabled] = useState<boolean>(false);
const [valueBaud, setValueBaud] = useState<string>("");
const controlApc = (action: string) => {
if (!line.outlet) {
notifications.show({
title: "Error",
message: "Hasn't config outlet number",
color: "red",
});
return;
}
const apcName = line.apcName || line.apc_name;
if (!apcName) {
notifications.show({
title: "Error",
message: "Hasn't config apc",
color: "red",
});
return;
}
if (
(apcName === "apc_1" && !stationItem.apc_1_ip) ||
(apcName === "apc_2" && !stationItem.apc_2_ip)
) {
notifications.show({
title: "Error",
message: "Hasn't config apc ip",
color: "red",
});
return;
}
socket?.emit("control_apc", {
outletNumbers: [line.outlet],
station: stationItem,
action: action,
apcName: line.apcName || line.apc_name,
});
setIsDisabled(true);
setTimeout(() => {
setIsDisabled(false);
}, 5000);
};
console.log("RERENDER", line.lineNumber);
return ( return (
<Card <Card
key={line.id} key={line.id}
@ -55,6 +103,7 @@ const CardLine = ({
setSelectedLines(selectedLines.filter((val) => val.id !== line.id)); setSelectedLines(selectedLines.filter((val) => val.id !== line.id));
else setSelectedLines((pre) => [...pre, line]); else setSelectedLines((pre) => [...pre, line]);
}} }}
onMouseLeave={() => setTimeout(() => setShowMenu(false), 150)}
> >
<Flex <Flex
justify={"space-between"} justify={"space-between"}
@ -62,54 +111,286 @@ const CardLine = ({
// gap={"md"} // gap={"md"}
// align={"center"} // align={"center"}
> >
<Flex justify={"space-between"}> <Menu
shadow="md"
position="right-start"
transitionProps={{ transition: "pop-top-right" }}
opened={showMenu}
onChange={setShowMenu}
withArrow
arrowSize={8}
>
<Menu.Target>
<Flex
justify={"space-between"}
direction={"column"}
// className={classes.topBarLine}
onMouseEnter={() => setShowMenu(true)}
>
<Flex gap={"8px"}>
<Flex direction={"column"} justify={"center"} align={"center"}>
<Box>
<Text <Text
fw={600} fw={600}
style={{ display: "flex", gap: "4px", fontSize: "15px" }}
>
Line: {line.lineNumber || line.line_number} - {line.port}{" "}
{line.status === "connected" && (
<IconCircleCheckFilled color="green" />
)}
</Text>
<div
style={{ style={{
alignItems: "center", fontSize: "24px",
marginLeft: "16px", lineHeight: "normal",
fontSize: "12px", // color: line.status === "connected" ? "green" : "dark",
color: "red", border: "1px solid #ccc",
display: "flex", borderRadius: "12px",
paddingLeft: "4px",
paddingRight: "4px",
backgroundColor:
line.status === "connected" ? "#4cc64c" : "",
}} }}
> >
{line?.userOpenCLI ? line?.userOpenCLI + " is using" : ""} {line.lineNumber || line.line_number}
</div> </Text>
</Box>
<Box>
<Text
fw={500}
style={{ fontSize: "12px", color: "#767676" }}
>
{line.port}
</Text>
</Box>
</Flex> </Flex>
<Flex justify={"space-between"}> <Box w={"100%"}>
<Flex justify={"space-between"} w={"100%"}>
<div className={classes.info_line}> <div className={classes.info_line}>
PID:{" "} PID:{" "}
<Text className={classes.info_line} fs={"italic"}> <Text className={classes.info_line} fs={"italic"}>
{line?.inventory?.pid || ""} {line?.inventory?.pid || ""}
</Text> </Text>
{line?.inventory?.vid ? (
<Text className={classes.info_line} fs={"italic"}>
{" | " + line?.inventory?.vid}
</Text>
) : (
""
)}
</div> </div>
<div className={classes.info_line}> <div
className={classes.info_line}
style={{ width: "120px" }}
>
SN:{" "} SN:{" "}
<Text className={classes.info_line} fs={"italic"}> <Text className={classes.info_line} fs={"italic"}>
{line?.inventory?.sn || ""} {line?.inventory?.sn || ""}
</Text> </Text>
</div> </div>
<div className={classes.info_line} style={{ minWidth: "50px" }}>
VID:{" "}
<Text className={classes.info_line} fs={"italic"}>
{line?.inventory?.vid || ""}
</Text>
</div>
</Flex> </Flex>
<div
style={{
fontSize: "12px",
color: "red",
}}
>
{line?.userOpenCLI ? line?.userOpenCLI + " is using" : ""}
</div>
</Box>
</Flex>
</Flex>
</Menu.Target>
<Menu.Dropdown style={{ width: "110px", backgroundColor: "#2d2d2d" }}>
<Flex
justify={"space-between"}
direction={"column"}
style={{
gap: "8px",
}}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
// onMouseEnter={() => setShowMenu(true)}
// onMouseLeave={() => setTimeout(() => setShowMenu(false), 300)}
>
<Button
disabled={isDisabled}
variant="filled"
color="orange"
size="xs"
onClick={() => {
socket?.emit("write_command_line_from_web", {
lineIds: [line.id],
stationId: Number(stationItem.id),
command: "spam_break",
});
setIsDisabled(true);
setTimeout(() => {
setIsDisabled(false);
}, 5000);
}}
>
Send Break
</Button>
<ButtonDPELP
socket={socket}
selectedLines={[line]}
isDisable={isDisabled}
onClick={() => {
setIsDisabled(true);
setTimeout(() => {
setIsDisabled(false);
}, 5000);
}}
/>
<Menu shadow="md" position="right">
<Menu.Target>
<Button
disabled={isDisabled}
variant="filled"
color="yellow"
style={{ height: "30px", width: "100px" }}
onClick={() => {}}
>
Scenario
</Button>
</Menu.Target>
<Menu.Dropdown style={{ width: "130px" }}>
<Flex
justify={"space-between"}
direction={"column"}
style={{
gap: "8px",
}}
>
{scenarios.map((el, i) => (
<ButtonScenario
key={i}
socket={socket}
selectedLines={[line]}
isDisable={isDisabled}
onClick={() => {
setShowMenu(true);
setIsDisabled(true);
setTimeout(() => {
setIsDisabled(false);
}, 5000);
}}
scenario={el}
/>
))}
</Flex>
</Menu.Dropdown>
</Menu>
<Menu shadow="md" position="right">
<Menu.Target>
<Button
disabled={isDisabled}
variant="filled"
size="xs"
onClick={() => {}}
>
BAUD
</Button>
</Menu.Target>
<Menu.Dropdown style={{ width: "130px" }}>
<Flex
justify={"space-between"}
direction={"column"}
style={{
gap: "8px",
}}
>
{listBaudDefault.map((el, i) => (
<Button
key={i}
disabled={isDisabled}
variant="outline"
size="xs"
onClick={() => {
setShowMenu(true);
socket?.emit("set_baud", {
lineId: line.id,
baud: el,
});
setIsDisabled(true);
setTimeout(() => {
setIsDisabled(false);
}, 5000);
}}
>
{el}
</Button>
))}
<Input
placeholder="Custom"
value={valueBaud}
onChange={(e) => setValueBaud(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
socket?.emit("set_baud", {
lineId: line.id,
baud: Number(valueBaud),
});
setValueBaud("");
setIsDisabled(true);
setTimeout(() => {
setIsDisabled(false);
}, 5000);
}
}}
/>
</Flex>
</Menu.Dropdown>
</Menu>
<Flex justify={"space-between"} direction={"column"}>
<hr style={{ width: "100%" }} />
<Box style={{ textAlign: "center" }}>
<Text p={0} style={{ fontSize: "13px" }} c={"white"}>
Power
</Text>
</Box>
<Button
mt={"8px"}
disabled={isDisabled}
variant="outline"
color="green"
size="xs"
onClick={() => {
controlApc("on");
}}
>
ON
</Button>
<Button
mt={"8px"}
disabled={isDisabled}
variant="outline"
color="orange"
size="xs"
onClick={() => {
controlApc("off");
}}
>
OFF
</Button>
<Button
mt={"8px"}
disabled={isDisabled}
variant="outline"
color="red"
size="xs"
onClick={() => {
controlApc("restart");
}}
>
Restart
</Button>
</Flex>
</Flex>
</Menu.Dropdown>
</Menu>
<Box <Box
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
}} }}
style={{ height: "175px", width: "332px" }} style={{ height: "160px", width: "332px" }}
> >
<TerminalCLI <TerminalCLI
cliOpened={loadTerminal} cliOpened={loadTerminal}
@ -130,8 +411,8 @@ const CardLine = ({
fontSize={11} fontSize={11}
miniSize={true} miniSize={true}
customStyle={{ customStyle={{
maxHeight: "175px", maxHeight: "160px",
height: "175px", height: "160px",
fontSize: "7px", fontSize: "7px",
padding: "0px", padding: "0px",
paddingBottom: "0px", paddingBottom: "0px",

View File

@ -10,6 +10,7 @@
color: dimgrey; color: dimgrey;
font-size: 11px; font-size: 11px;
display: flex; display: flex;
align-items: center;
gap: 4px; gap: 4px;
/* margin-top: 4px; */ /* margin-top: 4px; */
height: 20px; height: 20px;
@ -119,3 +120,7 @@
border-radius: 8px; border-radius: 8px;
background-color: #f3f3f38c; background-color: #f3f3f38c;
} }
.topBarLine:hover {
background-color: #f3f3f3;
}

View File

@ -229,8 +229,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
APC 1 APC 1
</Text> </Text>
{RenderAPCStatus(dataStation?.apc1)} {RenderAPCStatus(dataStation?.apc1)}
{dataStation?.apc1?.status === "DISCONNECTED" || {dataStation?.apc1?.status !== "CONNECTED" ? (
dataStation?.apc1?.status === "TIMEOUT" ? (
<Button <Button
style={{ height: "24px" }} style={{ height: "24px" }}
size="xs" size="xs"
@ -486,8 +485,7 @@ export const DrawerAPCControl: React.FC<DrawerProps> = ({
APC 2 APC 2
</Text> </Text>
{RenderAPCStatus(dataStation?.apc2)} {RenderAPCStatus(dataStation?.apc2)}
{dataStation?.apc2?.status === "DISCONNECTED" || {dataStation?.apc2?.status !== "CONNECTED" ? (
dataStation?.apc2?.status === "TIMEOUT" ? (
<Button <Button
style={{ height: "24px" }} style={{ height: "24px" }}
size="xs" size="xs"
@ -883,6 +881,30 @@ export const DrawerSwitchControl: React.FC<DrawerProps> = ({
<Box ps={"8px"} pt={"4px"}> <Box ps={"8px"} pt={"4px"}>
{dataStation?.switch ? RenderAPCStatus(dataStation?.switch) : ""} {dataStation?.switch ? RenderAPCStatus(dataStation?.switch) : ""}
</Box> </Box>
{dataStation?.switch?.status !== "CONNECTED" ? (
<Button
size="xs"
disabled={isSubmit}
variant="filled"
color="yellow"
onClick={() => {
socket?.emit("control_switch", {
ports: [],
command: "reconnect",
station: stationAPI,
ip: stationAPI?.switch_control_ip,
});
setIsSubmit(true);
setTimeout(() => {
setIsSubmit(false);
}, 10000);
}}
>
<IconRepeat size={14} />
</Button>
) : (
""
)}
<Button <Button
disabled={isSubmit} disabled={isSubmit}
title={ title={

View File

@ -4,6 +4,7 @@ import {
Button, Button,
Flex, Flex,
Group, Group,
Input,
Modal, Modal,
NumberInput, NumberInput,
PasswordInput, PasswordInput,
@ -97,6 +98,8 @@ const StationSetting = ({
apc_name: value.apcName || value.apc_name, apc_name: value.apcName || value.apc_name,
outlet: value.outlet, outlet: value.outlet,
station_id: value.stationId || value.station_id, station_id: value.stationId || value.station_id,
interface: value.interface,
baud: value.baud,
})); }));
setLines(dataLine); setLines(dataLine);
} }
@ -186,6 +189,20 @@ const StationSetting = ({
} }
/> />
</Table.Td> </Table.Td>
<Table.Td fz={"sm"} p="3px" ta={"center"} fw={700}>
<Input
value={row?.interface}
onChange={(e) =>
setLines((pre) =>
pre.map((value, i) =>
i === index
? { ...value, interface: e.target.value }
: value
)
)
}
/>
</Table.Td>
<Table.Td fz={"sm"} p="3px" ta={"center"} fw={700}> <Table.Td fz={"sm"} p="3px" ta={"center"} fw={700}>
{row?.lineNumber ? ( {row?.lineNumber ? (
<ActionIcon <ActionIcon
@ -339,7 +356,7 @@ const StationSetting = ({
)} )}
</div> </div>
} }
size={"80%"} size={"90%"}
style={{ position: "absolute", left: 0 }} style={{ position: "absolute", left: 0 }}
centered centered
opened={isOpen} opened={isOpen}
@ -648,21 +665,24 @@ const StationSetting = ({
> >
<Table.Thead> <Table.Thead>
<Table.Tr> <Table.Tr>
<Table.Th fz={"sm"} w={"15%"} ta={"center"}> <Table.Th fz={"sm"} w={"10%"} ta={"center"}>
Line number Number
</Table.Th> </Table.Th>
<Table.Th fz={"sm"} w={"15%"} ta={"center"}> <Table.Th fz={"sm"} w={"15%"} ta={"center"}>
Port Port
</Table.Th> </Table.Th>
<Table.Th fz={"sm"} w={"15%"} ta={"center"}> <Table.Th fz={"sm"} w={"10%"} ta={"center"}>
Clear line Clear line
</Table.Th> </Table.Th>
<Table.Th fz={"sm"} w={"15%"} ta={"center"}> <Table.Th fz={"sm"} w={"15%"} ta={"center"}>
APC APC
</Table.Th> </Table.Th>
<Table.Th fz={"sm"} w={"15%"} ta={"center"}> <Table.Th fz={"sm"} w={"10%"} ta={"center"}>
Outlet Outlet
</Table.Th> </Table.Th>
<Table.Th fz={"sm"} w={"15%"} ta={"center"}>
Interface
</Table.Th>
<Table.Th fz={"sm"} w={"10%"} ta={"center"}></Table.Th> <Table.Th fz={"sm"} w={"10%"} ta={"center"}></Table.Th>
</Table.Tr> </Table.Tr>
</Table.Thead> </Table.Thead>

View File

@ -244,3 +244,5 @@ export const dataPermission = [
requiredPermissions: [], requiredPermissions: [],
}, },
]; ];
export const listBaudDefault = [4800, 9600, 19200, 115200, 230400];

View File

@ -93,6 +93,8 @@ export type TLine = {
time: number; time: number;
}; };
commands?: string[]; commands?: string[];
interface?: string;
baud?: number;
}; };
export type TUser = { export type TUser = {