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:
parent
31036ff7da
commit
8a06650eab
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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={
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -244,3 +244,5 @@ export const dataPermission = [
|
||||||
requiredPermissions: [],
|
requiredPermissions: [],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const listBaudDefault = [4800, 9600, 19200, 115200, 230400];
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue