562 lines
18 KiB
TypeScript
562 lines
18 KiB
TypeScript
import { Card, Text, Box, Flex, Menu, Button, Input } from "@mantine/core";
|
|
import type { IScenario, TLine, TStation } from "../untils/types";
|
|
import classes from "./Component.module.css";
|
|
import TerminalCLI from "./TerminalXTerm";
|
|
import type { Socket } from "socket.io-client";
|
|
import { memo, useEffect, useMemo, useState } from "react";
|
|
import { convertTimestampToDate } from "../untils/helper";
|
|
import { ButtonDPELP, ButtonScenario } from "./ButtonAction";
|
|
import { notifications } from "@mantine/notifications";
|
|
import { listBaudDefault } from "../untils/constanst";
|
|
import { IconCaretRight } from "@tabler/icons-react";
|
|
|
|
const CardLine = ({
|
|
line,
|
|
selectedLines,
|
|
setSelectedLines,
|
|
socket,
|
|
stationItem,
|
|
openTerminal,
|
|
loadTerminal,
|
|
scenarios,
|
|
}: {
|
|
line: TLine;
|
|
selectedLines: TLine[];
|
|
setSelectedLines: (lines: React.SetStateAction<TLine[]>) => void;
|
|
socket: Socket | null;
|
|
stationItem: TStation;
|
|
openTerminal: (value: TLine) => void;
|
|
loadTerminal: boolean;
|
|
scenarios: IScenario[];
|
|
}) => {
|
|
const user = useMemo(() => {
|
|
return localStorage.getItem("user") &&
|
|
typeof localStorage.getItem("user") === "string"
|
|
? JSON.parse(localStorage.getItem("user") || "")
|
|
: null;
|
|
}, []);
|
|
const [isDisabled, setIsDisabled] = useState<boolean>(false);
|
|
const [valueBaud, setValueBaud] = useState<string>("");
|
|
|
|
useEffect(() => {
|
|
if (
|
|
typeof line?.userOpenCLI !== "undefined" &&
|
|
typeof line?.userOpenCLI === "string" &&
|
|
line?.userOpenCLI.length > 0 &&
|
|
line?.userOpenCLI !== user?.userName
|
|
)
|
|
setIsDisabled(true);
|
|
else setIsDisabled(false);
|
|
}, [line?.userOpenCLI]);
|
|
|
|
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);
|
|
};
|
|
|
|
const handleClick = (isSelect = false) => {
|
|
if (
|
|
typeof line?.userOpenCLI !== "undefined" &&
|
|
typeof line?.userOpenCLI === "string" &&
|
|
line?.userOpenCLI.length > 0 &&
|
|
line?.userOpenCLI !== user?.userName
|
|
)
|
|
return;
|
|
|
|
if (selectedLines.find((val) => val.id === line.id)) {
|
|
if (isSelect) {
|
|
setSelectedLines(selectedLines.filter((val) => val.id !== line.id));
|
|
socket?.emit("close_cli", {
|
|
lineId: line?.id,
|
|
stationId: line.stationId || line.station_id,
|
|
});
|
|
}
|
|
} else {
|
|
setSelectedLines((pre) => [...pre, line]);
|
|
socket?.emit("open_cli", {
|
|
lineId: line.id,
|
|
stationId: line.stationId || line.station_id,
|
|
userEmail: user?.email,
|
|
userName: user?.userName,
|
|
});
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Card
|
|
key={line.id}
|
|
shadow="sm"
|
|
radius="md"
|
|
withBorder
|
|
className={classes.card_line}
|
|
style={
|
|
selectedLines.find((val) => val.id === line.id)
|
|
? { backgroundColor: "#8bf55940" }
|
|
: {}
|
|
}
|
|
onDoubleClick={(e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
openTerminal(line);
|
|
}}
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
handleClick(true);
|
|
}}
|
|
// onMouseLeave={() => setTimeout(() => setShowMenu(false), 150)}
|
|
>
|
|
<Flex
|
|
justify={"space-between"}
|
|
direction={"column"}
|
|
// gap={"md"}
|
|
// align={"center"}
|
|
>
|
|
<Menu
|
|
shadow="md"
|
|
position="right-start"
|
|
transitionProps={{ transition: "pop-top-right" }}
|
|
// opened={showMenu}
|
|
// onChange={setShowMenu}
|
|
trigger="hover"
|
|
withArrow
|
|
arrowSize={8}
|
|
closeOnItemClick={false}
|
|
closeOnClickOutside={false}
|
|
>
|
|
<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
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
navigator.clipboard.writeText(
|
|
`PID: ${line?.inventory?.pid || ""} | SN: ${
|
|
line?.inventory?.sn || ""
|
|
}`
|
|
);
|
|
}}
|
|
className={classes.buttonCopy}
|
|
fw={600}
|
|
style={{
|
|
fontSize: "24px",
|
|
lineHeight: "normal",
|
|
// color: line.status === "connected" ? "green" : "dark",
|
|
border: "1px solid #ccc",
|
|
borderRadius: "12px",
|
|
paddingLeft: "4px",
|
|
paddingRight: "4px",
|
|
backgroundColor:
|
|
line.status === "connected" ? "#4cc64c" : "",
|
|
}}
|
|
>
|
|
{line.lineNumber || line.line_number}
|
|
</Text>
|
|
</Box>
|
|
<Box>
|
|
<Text
|
|
fw={500}
|
|
style={{ fontSize: "12px", color: "#767676" }}
|
|
>
|
|
{line.port}
|
|
</Text>
|
|
</Box>
|
|
</Flex>
|
|
<Box w={"100%"}>
|
|
<Flex justify={"space-between"} w={"100%"}>
|
|
<div className={classes.info_line}>
|
|
PID:{" "}
|
|
<Text
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
navigator.clipboard.writeText(
|
|
`PID: ${line?.inventory?.pid || ""} | SN: ${
|
|
line?.inventory?.sn || ""
|
|
}`
|
|
);
|
|
}}
|
|
className={`${classes.info_line} ${classes.buttonCopy}`}
|
|
fs={"italic"}
|
|
>
|
|
{line?.inventory?.pid || ""}
|
|
</Text>
|
|
{line?.inventory?.vid ? (
|
|
<Text className={classes.info_line} fs={"italic"}>
|
|
{" | " + line?.inventory?.vid}
|
|
</Text>
|
|
) : (
|
|
""
|
|
)}
|
|
</div>
|
|
<div
|
|
className={classes.info_line}
|
|
style={{ width: "120px" }}
|
|
>
|
|
SN:{" "}
|
|
<Text
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
navigator.clipboard.writeText(
|
|
`PID: ${line?.inventory?.pid || ""} | SN: ${
|
|
line?.inventory?.sn || ""
|
|
}`
|
|
);
|
|
}}
|
|
className={`${classes.info_line} ${classes.buttonCopy}`}
|
|
fs={"italic"}
|
|
>
|
|
{line?.inventory?.sn || ""}
|
|
</Text>
|
|
</div>
|
|
</Flex>
|
|
<Flex justify={"space-between"} w={"100%"}>
|
|
<Box></Box>
|
|
<div
|
|
style={{
|
|
fontSize: "11px",
|
|
color: "red",
|
|
}}
|
|
>
|
|
{line?.userOpenCLI
|
|
? (line?.userOpenCLI === user?.userName
|
|
? "You are"
|
|
: line?.userOpenCLI + " is") + " using"
|
|
: ""}
|
|
</div>
|
|
</Flex>
|
|
</Box>
|
|
</Flex>
|
|
</Flex>
|
|
</Menu.Target>
|
|
<Menu.Dropdown style={{ width: "80px", backgroundColor: "#2d2d2d" }}>
|
|
<Flex
|
|
justify={"space-between"}
|
|
direction={"column"}
|
|
style={{
|
|
gap: "8px",
|
|
}}
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
}}
|
|
onDoubleClick={(e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
}}
|
|
// onMouseEnter={() => setShowMenu(true)}
|
|
// onMouseLeave={() => setTimeout(() => setShowMenu(false), 300)}
|
|
>
|
|
<Button
|
|
disabled={isDisabled}
|
|
variant="filled"
|
|
color="orange"
|
|
// size="xs"
|
|
className={classes.buttonMenuTool}
|
|
onClick={() => {
|
|
socket?.emit("write_command_line_from_web", {
|
|
lineIds: [line.id],
|
|
stationId: Number(stationItem.id),
|
|
command: "spam_break",
|
|
});
|
|
setIsDisabled(true);
|
|
setTimeout(() => {
|
|
setIsDisabled(false);
|
|
}, 2000);
|
|
}}
|
|
>
|
|
Send Break
|
|
</Button>
|
|
<ButtonDPELP
|
|
className={classes.buttonMenuTool}
|
|
socket={socket}
|
|
selectedLines={[line]}
|
|
isDisable={isDisabled}
|
|
onClick={() => {
|
|
setIsDisabled(true);
|
|
setTimeout(() => {
|
|
setIsDisabled(false);
|
|
}, 5000);
|
|
}}
|
|
/>
|
|
<Menu
|
|
closeOnItemClick={false}
|
|
closeOnClickOutside={false}
|
|
trigger="hover"
|
|
shadow="md"
|
|
position="right"
|
|
>
|
|
<Menu.Target>
|
|
<Button
|
|
disabled={isDisabled}
|
|
variant="filled"
|
|
color="yellow"
|
|
className={classes.buttonMenuTool}
|
|
onClick={() => {}}
|
|
>
|
|
Scenario <IconCaretRight size={"14px"} />
|
|
</Button>
|
|
</Menu.Target>
|
|
<Menu.Dropdown style={{ width: "120px" }}>
|
|
<Flex
|
|
justify={"space-between"}
|
|
direction={"column"}
|
|
style={{
|
|
gap: "8px",
|
|
}}
|
|
>
|
|
{scenarios.map((el, i) => (
|
|
<ButtonScenario
|
|
key={i}
|
|
socket={socket}
|
|
selectedLines={[line]}
|
|
isDisable={isDisabled}
|
|
onClick={() => {
|
|
setIsDisabled(true);
|
|
setTimeout(() => {
|
|
setIsDisabled(false);
|
|
}, 5000);
|
|
}}
|
|
scenario={el}
|
|
fontSize="9px"
|
|
/>
|
|
))}
|
|
</Flex>
|
|
</Menu.Dropdown>
|
|
</Menu>
|
|
<Menu
|
|
closeOnItemClick={false}
|
|
closeOnClickOutside={false}
|
|
trigger="hover"
|
|
shadow="md"
|
|
position="right"
|
|
>
|
|
<Menu.Target>
|
|
<Button
|
|
className={classes.buttonMenuTool}
|
|
disabled={isDisabled}
|
|
variant="filled"
|
|
size="xs"
|
|
onClick={() => {}}
|
|
>
|
|
BAUD <IconCaretRight size={"14px"} />
|
|
</Button>
|
|
</Menu.Target>
|
|
<Menu.Dropdown style={{ width: "90px" }}>
|
|
<Flex
|
|
justify={"space-between"}
|
|
direction={"column"}
|
|
style={{
|
|
gap: "8px",
|
|
}}
|
|
>
|
|
{listBaudDefault.map((el, i) => (
|
|
<Button
|
|
key={i}
|
|
disabled={isDisabled}
|
|
className={classes.buttonMenuTool}
|
|
variant="outline"
|
|
size="xs"
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
socket?.emit("set_baud", {
|
|
lineId: line.id,
|
|
baud: el,
|
|
stationId: Number(stationItem.id),
|
|
});
|
|
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),
|
|
stationId: Number(stationItem.id),
|
|
});
|
|
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
|
|
className={classes.buttonMenuTool}
|
|
mt={"8px"}
|
|
disabled={isDisabled}
|
|
variant="outline"
|
|
color="green"
|
|
size="xs"
|
|
onClick={() => {
|
|
controlApc("on");
|
|
}}
|
|
>
|
|
ON
|
|
</Button>
|
|
<Button
|
|
className={classes.buttonMenuTool}
|
|
mt={"8px"}
|
|
disabled={isDisabled}
|
|
variant="outline"
|
|
color="red"
|
|
size="xs"
|
|
onClick={() => {
|
|
controlApc("off");
|
|
}}
|
|
>
|
|
OFF
|
|
</Button>
|
|
<Button
|
|
className={classes.buttonMenuTool}
|
|
mt={"8px"}
|
|
disabled={isDisabled}
|
|
variant="outline"
|
|
color="orange"
|
|
size="xs"
|
|
onClick={() => {
|
|
controlApc("restart");
|
|
}}
|
|
>
|
|
Restart
|
|
</Button>
|
|
</Flex>
|
|
</Flex>
|
|
</Menu.Dropdown>
|
|
</Menu>
|
|
|
|
<Box
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
}}
|
|
style={{ height: "160px", width: "332px" }}
|
|
>
|
|
<TerminalCLI
|
|
cliOpened={loadTerminal}
|
|
socket={socket}
|
|
content={line?.output ?? ""}
|
|
initContent={line?.netOutput ?? ""}
|
|
loadingContent={line?.loadingOutput}
|
|
line_id={Number(line?.id)}
|
|
line={line}
|
|
station_id={Number(stationItem.id)}
|
|
// isDisabled={
|
|
// typeof line?.userOpenCLI !== "undefined" &&
|
|
// typeof line?.userOpenCLI === "string" &&
|
|
// line?.userOpenCLI.length > 0 &&
|
|
// line?.userOpenCLI !== user?.userName
|
|
// }
|
|
isDisabled={false}
|
|
line_status={line?.status || ""}
|
|
fontSize={11}
|
|
miniSize={true}
|
|
customStyle={{
|
|
maxHeight: "160px",
|
|
height: "160px",
|
|
fontSize: "7px",
|
|
padding: "0px",
|
|
paddingBottom: "0px",
|
|
}}
|
|
onDoubleClick={() => {
|
|
openTerminal(line);
|
|
}}
|
|
onFocus={() => {
|
|
handleClick();
|
|
}}
|
|
onBlur={() => {
|
|
if (
|
|
!selectedLines.find((value) => value.id === line?.id) &&
|
|
line?.userOpenCLI === user?.userName
|
|
)
|
|
socket?.emit("close_cli", {
|
|
lineId: line?.id,
|
|
stationId: line.stationId || line.station_id,
|
|
});
|
|
}}
|
|
/>
|
|
</Box>
|
|
<Box>
|
|
<div className={classes.info_line}>
|
|
Latest: {line?.latestScenario?.name || ""}
|
|
<Text style={{ fontStyle: "italic", fontSize: "11px" }}>
|
|
{line?.latestScenario?.time
|
|
? "(" + convertTimestampToDate(line?.latestScenario?.time) + ")"
|
|
: ""}
|
|
</Text>
|
|
</div>
|
|
</Box>
|
|
</Flex>
|
|
</Card>
|
|
);
|
|
};
|
|
|
|
export default memo(CardLine);
|