1423 lines
49 KiB
TypeScript
1423 lines
49 KiB
TypeScript
import {
|
|
Box,
|
|
Button,
|
|
Card,
|
|
Flex,
|
|
Grid,
|
|
Group,
|
|
Loader,
|
|
Radio,
|
|
ScrollArea,
|
|
Text,
|
|
} from "@mantine/core";
|
|
import { IconRepeat } from "@tabler/icons-react";
|
|
import { useEffect, useState } from "react";
|
|
import classes from "../Component.module.css";
|
|
import type { APCProps, SwitchPortsProps, TStation } from "../../untils/types";
|
|
import type { Socket } from "socket.io-client";
|
|
|
|
interface DrawerProps {
|
|
stationAPI: TStation;
|
|
socket: Socket | null;
|
|
stationId: number;
|
|
}
|
|
|
|
type TSelectedOutlet = {
|
|
number?: number;
|
|
name: string;
|
|
status: string;
|
|
apc: number;
|
|
};
|
|
|
|
export const DrawerAPCControl: React.FC<DrawerProps> = ({
|
|
stationAPI,
|
|
socket,
|
|
stationId,
|
|
}) => {
|
|
const findLineByOutlet = (outlet: TSelectedOutlet) => {
|
|
return stationAPI.lines.find(
|
|
(line) =>
|
|
line.outlet === outlet.number && line.apcName === `apc_${outlet.apc}`
|
|
);
|
|
};
|
|
|
|
const [listOutlet, setListOutlet] = useState<TSelectedOutlet[]>(
|
|
Array.from({ length: 16 })
|
|
.map((_, index) => ({
|
|
number: index / 8 < 1 ? index + 1 : index - 7, // Outlet numbers 1-8 for APC 1, 1-8 for APC 2
|
|
name: `Outlet ${(index % 8) + 1}`,
|
|
status: "OFF",
|
|
apc: index / 8 < 1 ? 1 : 2, // Assuming two APCs, alternating outlets
|
|
}))
|
|
.map((el) => {
|
|
const line = findLineByOutlet(el);
|
|
if (!line) return el;
|
|
return {
|
|
...el,
|
|
name: "Line " + line.lineNumber || el.name,
|
|
};
|
|
})
|
|
);
|
|
const [dataStation, setDataStation] = useState<TStation>(stationAPI);
|
|
const [listOutletSelected, setListOutletSelected] = useState<
|
|
TSelectedOutlet[]
|
|
>([]);
|
|
const [isSubmit, setIsSubmit] = useState(true);
|
|
|
|
const detectOutlet = (
|
|
apc: APCProps,
|
|
lines: string[],
|
|
result: TSelectedOutlet[],
|
|
i: number
|
|
) => {
|
|
let insideDeviceManager = false;
|
|
let currentBlock: string[] = [];
|
|
let lastDeviceManagerBlock: string[] = [];
|
|
|
|
for (const line of lines) {
|
|
// Detect start of any section
|
|
const isSectionHeader = line.match(/^-{5,}.*-{5,}$/);
|
|
|
|
// If it's the Device Manager section, start capturing
|
|
if (line.includes("------- Device Manager")) {
|
|
insideDeviceManager = true;
|
|
currentBlock = [];
|
|
continue;
|
|
}
|
|
|
|
// If a new section starts and we were inside Device Manager, save it
|
|
if (
|
|
insideDeviceManager &&
|
|
isSectionHeader &&
|
|
!line.includes("Device Manager")
|
|
) {
|
|
insideDeviceManager = false;
|
|
if (currentBlock.length > 0) {
|
|
lastDeviceManagerBlock = [...currentBlock];
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// If inside, accumulate
|
|
if (insideDeviceManager) {
|
|
currentBlock.push(line);
|
|
}
|
|
}
|
|
|
|
// Edge case: block was still open till end of file
|
|
if (insideDeviceManager && currentBlock.length > 0) {
|
|
lastDeviceManagerBlock = [...currentBlock];
|
|
}
|
|
|
|
// Parse outlets from the lastDeviceManagerBlock
|
|
let indexOutlet = 1;
|
|
lastDeviceManagerBlock.forEach((line) => {
|
|
const match = line.match(
|
|
/^\s*\d+-\s+((?:Outlet|Port)\s+\d+)\s+(ON|OFF)/i
|
|
);
|
|
if (match) {
|
|
const [, , status] = match;
|
|
result.push({
|
|
number: indexOutlet,
|
|
name: "Outlet " + indexOutlet,
|
|
status: status.toUpperCase(),
|
|
apc: i + 1,
|
|
});
|
|
indexOutlet++;
|
|
}
|
|
});
|
|
|
|
if (result.length > 0 && apc?.status === "CONNECTED") apc.outlets = result;
|
|
else {
|
|
Array.from({ length: 8 }).forEach((_, index) => {
|
|
result.push({
|
|
number: index + 1,
|
|
name: `Outlet ${index + 1}`,
|
|
status: "OFF",
|
|
apc: i + 1,
|
|
});
|
|
});
|
|
apc.outlets = result;
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
socket?.on("apc_output", (data) => {
|
|
if (data.stationId !== stationId) return;
|
|
const outlets: TSelectedOutlet[] = [];
|
|
const apc1 =
|
|
data.apcNumber === 1
|
|
? { output: data.data, status: data.status }
|
|
: dataStation?.apc1;
|
|
const apc2 =
|
|
data.apcNumber === 2
|
|
? { output: data.data, status: data.status }
|
|
: dataStation?.apc2;
|
|
setDataStation((prev) => ({ ...prev, apc1: apc1, apc2: apc2 }));
|
|
[apc1, apc2].forEach((apc, i) => {
|
|
if (!apc) return;
|
|
const result: TSelectedOutlet[] = [];
|
|
const lines = apc?.output?.split("\n") || [];
|
|
detectOutlet(apc, lines, result, i);
|
|
outlets.push(...result);
|
|
});
|
|
setTimeout(() => {
|
|
if (apc1 || apc2) setIsSubmit(false);
|
|
|
|
if (outlets.length > 0)
|
|
setListOutlet(
|
|
listOutlet.map((el) =>
|
|
outlets.find((o) => o.number === el.number && o.apc === el.apc)
|
|
? {
|
|
...el,
|
|
...outlets.find(
|
|
(o) => o.number === el.number && o.apc === el.apc
|
|
),
|
|
}
|
|
: el
|
|
)
|
|
);
|
|
}, 1000);
|
|
});
|
|
return () => {
|
|
socket?.off("apc_output");
|
|
};
|
|
}, [socket, dataStation, stationId]);
|
|
|
|
const toggleSelect = (outlet: TSelectedOutlet, number: number) => {
|
|
setListOutletSelected((prev) =>
|
|
prev.find((pid) => pid.name === outlet.name && pid.apc === outlet.apc)
|
|
? prev.filter(
|
|
(pid) => pid.name !== outlet.name || pid.apc !== outlet.apc
|
|
)
|
|
: [...prev, { ...outlet, number }]
|
|
);
|
|
};
|
|
|
|
const RenderAPCStatus = (apc: APCProps) => {
|
|
switch (apc?.status) {
|
|
case "CONNECTED":
|
|
return (
|
|
<>
|
|
<Text fw={800} c="green" fz={"12px"}>
|
|
{apc?.status}
|
|
</Text>
|
|
</>
|
|
);
|
|
|
|
case "DISCONNECTED":
|
|
return (
|
|
<>
|
|
<Text fw={800} c="red" fz={"12px"}>
|
|
{apc?.status}
|
|
</Text>
|
|
</>
|
|
);
|
|
case "TIMEOUT":
|
|
return (
|
|
<>
|
|
<Text fw={800} c="yellow" fz={"12px"}>
|
|
{apc?.status}
|
|
</Text>
|
|
</>
|
|
);
|
|
default:
|
|
return (
|
|
<>
|
|
<Text fw={800} c="red" fz={"12px"}>
|
|
DISCONNECTED
|
|
</Text>
|
|
</>
|
|
);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
setTimeout(() => {
|
|
setIsSubmit(false);
|
|
}, 15000);
|
|
}, []);
|
|
|
|
return (
|
|
<Grid>
|
|
<Grid.Col span={6}>
|
|
<fieldset style={{ padding: "4px 6px" }}>
|
|
<legend>
|
|
<div className={classes.titleAPC}>
|
|
<Text fw={700} c={"#514d4d"} fz={"xs"}>
|
|
APC 1
|
|
</Text>
|
|
{dataStation?.apc_1_ip ? (
|
|
RenderAPCStatus(dataStation?.apc1)
|
|
) : (
|
|
<Text fw={800} c="red" fz={"12px"}>
|
|
APC not available
|
|
</Text>
|
|
)}
|
|
{dataStation?.apc1?.status !== "CONNECTED" ? (
|
|
<Button
|
|
style={{ height: "24px" }}
|
|
size="xs"
|
|
disabled={isSubmit || !dataStation?.apc_1_ip}
|
|
variant="filled"
|
|
color="yellow"
|
|
onClick={() => {
|
|
socket?.emit("control_apc", {
|
|
outletNumbers: [],
|
|
station: { ...stationAPI, lines: [] },
|
|
action: "reconnect",
|
|
apcName: "apc_1",
|
|
});
|
|
setListOutletSelected([]); // Clear selected outlets
|
|
setIsSubmit(true);
|
|
setTimeout(() => {
|
|
setIsSubmit(false);
|
|
}, 5000);
|
|
}}
|
|
>
|
|
<IconRepeat size={14} />
|
|
</Button>
|
|
) : (
|
|
<div style={{ height: "24px" }}></div>
|
|
)}
|
|
<Button
|
|
disabled={
|
|
isSubmit ||
|
|
!dataStation?.apc_1_ip ||
|
|
dataStation?.apc1?.status !== "CONNECTED"
|
|
}
|
|
title={
|
|
listOutletSelected.filter((el) => el.apc === 1).length ===
|
|
listOutlet.length
|
|
? "Deselect All"
|
|
: "Select All"
|
|
}
|
|
// mt={'xs'}
|
|
className={classes.buttonMenuTool}
|
|
variant="filled"
|
|
onClick={() => {
|
|
if (
|
|
listOutletSelected.filter((el) => el.apc === 1).length ===
|
|
listOutlet.filter((el) => el.apc === 1).length
|
|
) {
|
|
setListOutletSelected([]);
|
|
} else {
|
|
setListOutletSelected(
|
|
listOutlet
|
|
.filter((el) => el.apc === 1)
|
|
.map((el) => ({ ...el, apc: 1 }))
|
|
);
|
|
}
|
|
}}
|
|
>
|
|
{listOutletSelected.filter((el) => el.apc === 1).length ===
|
|
listOutlet.filter((el) => el.apc === 1).length
|
|
? "Deselect"
|
|
: "Select All"}
|
|
</Button>
|
|
<Button
|
|
className={classes.buttonMenuTool}
|
|
disabled={
|
|
isSubmit ||
|
|
listOutlet.filter((el) => el.apc === 1).length === 0 ||
|
|
listOutletSelected.filter((el) => el.apc === 1).length ===
|
|
0 ||
|
|
dataStation?.apc1?.status === "DISCONNECTED" ||
|
|
dataStation?.apc1?.status === "TIMEOUT"
|
|
}
|
|
title={"Restart"}
|
|
variant="filled"
|
|
color="yellow"
|
|
onClick={() => {
|
|
socket?.emit("control_apc", {
|
|
outletNumbers: listOutletSelected
|
|
.filter((el) => el.apc === 1)
|
|
.map((el) => el.number),
|
|
station: { ...stationAPI, lines: [] },
|
|
action: "restart",
|
|
apcName: "apc_1",
|
|
});
|
|
setListOutletSelected([]);
|
|
setIsSubmit(true);
|
|
setTimeout(() => {
|
|
setIsSubmit(false);
|
|
}, 5000);
|
|
}}
|
|
>
|
|
Restart
|
|
</Button>
|
|
<Button
|
|
disabled={
|
|
isSubmit ||
|
|
listOutlet.filter((el) => el.apc === 1).length === 0 ||
|
|
listOutletSelected.filter((el) => el.apc === 1).length ===
|
|
0 ||
|
|
dataStation?.apc1?.status === "DISCONNECTED" ||
|
|
dataStation?.apc1?.status === "TIMEOUT"
|
|
}
|
|
title={"Turn On"}
|
|
// mt={'xs'}
|
|
className={classes.buttonMenuTool}
|
|
variant="filled"
|
|
color="green"
|
|
onClick={() => {
|
|
socket?.emit("control_apc", {
|
|
outletNumbers: listOutletSelected
|
|
.filter((el) => el.apc === 1)
|
|
.map((el) => el.number),
|
|
station: { ...stationAPI, lines: [] },
|
|
action: "on",
|
|
apcName: "apc_1",
|
|
});
|
|
setListOutletSelected([]);
|
|
setIsSubmit(true);
|
|
setTimeout(() => {
|
|
setIsSubmit(false);
|
|
}, 5000);
|
|
}}
|
|
>
|
|
Turn On
|
|
</Button>
|
|
<Button
|
|
disabled={
|
|
isSubmit ||
|
|
listOutlet.filter((el) => el.apc === 1).length === 0 ||
|
|
listOutletSelected.filter((el) => el.apc === 1).length ===
|
|
0 ||
|
|
dataStation?.apc1?.status === "DISCONNECTED" ||
|
|
dataStation?.apc1?.status === "TIMEOUT"
|
|
}
|
|
title={"Turn Off"}
|
|
// mt={'xs'}
|
|
className={classes.buttonMenuTool}
|
|
variant="filled"
|
|
color="red"
|
|
onClick={() => {
|
|
socket?.emit("control_apc", {
|
|
outletNumbers: listOutletSelected
|
|
.filter((el) => el.apc === 1)
|
|
.map((el) => el.number),
|
|
station: { ...stationAPI, lines: [] },
|
|
action: "off",
|
|
apcName: "apc_1",
|
|
});
|
|
setListOutletSelected([]);
|
|
setIsSubmit(true);
|
|
setTimeout(() => {
|
|
setIsSubmit(false);
|
|
}, 5000);
|
|
}}
|
|
>
|
|
Turn Off
|
|
</Button>
|
|
</div>
|
|
</legend>
|
|
<Box>
|
|
<Box
|
|
style={{
|
|
display: "flex",
|
|
flexWrap: "wrap",
|
|
gap: "10px",
|
|
paddingTop: "8px",
|
|
paddingBottom: "8px",
|
|
}}
|
|
>
|
|
{listOutlet
|
|
.filter((el) => el.apc === 1)
|
|
.map((outlet, i) => (
|
|
<Card
|
|
key={i}
|
|
shadow="sm"
|
|
padding="xs"
|
|
radius="md"
|
|
withBorder
|
|
className={`${
|
|
isSubmit || !dataStation?.apc_1_ip
|
|
? classes.isDisabled
|
|
: ""
|
|
}`}
|
|
style={{
|
|
paddingLeft: 0,
|
|
paddingRight: 0,
|
|
width: "55px",
|
|
position: "relative",
|
|
cursor: "pointer",
|
|
textAlign: "center",
|
|
border: listOutletSelected.find(
|
|
(el) => el.name === outlet.name && el.apc === outlet.apc
|
|
)?.name
|
|
? "1px solid #0018ff"
|
|
: "",
|
|
}}
|
|
onClick={() => {
|
|
toggleSelect(outlet, i + 1);
|
|
}}
|
|
>
|
|
<Text
|
|
fw={500}
|
|
fz={"12px"}
|
|
style={{
|
|
color: outlet.status === "ON" ? "#40c057" : "#f03e3e",
|
|
}}
|
|
>
|
|
{findLineByOutlet(outlet)
|
|
? "Line " + findLineByOutlet(outlet)?.lineNumber
|
|
: outlet.name}
|
|
</Text>
|
|
</Card>
|
|
))}
|
|
</Box>
|
|
</Box>
|
|
</fieldset>
|
|
</Grid.Col>
|
|
<Grid.Col span={6}>
|
|
<fieldset style={{ padding: "4px 6px" }}>
|
|
<legend>
|
|
<div className={classes.titleAPC}>
|
|
<Text fw={700} c={"#514d4d"} fz={"xs"}>
|
|
APC 2
|
|
</Text>
|
|
{dataStation?.apc_2_ip ? (
|
|
RenderAPCStatus(dataStation?.apc2)
|
|
) : (
|
|
<Text fw={800} c="red" fz={"12px"}>
|
|
APC not available
|
|
</Text>
|
|
)}
|
|
{dataStation?.apc2?.status !== "CONNECTED" ? (
|
|
<Button
|
|
style={{ height: "24px" }}
|
|
size="xs"
|
|
disabled={isSubmit || !dataStation?.apc_2_ip}
|
|
variant="filled"
|
|
color="yellow"
|
|
onClick={() => {
|
|
socket?.emit("control_apc", {
|
|
outletNumbers: [],
|
|
station: { ...stationAPI, lines: [] },
|
|
action: "reconnect",
|
|
apcName: "apc_2",
|
|
});
|
|
setListOutletSelected([]);
|
|
setIsSubmit(true);
|
|
setTimeout(() => {
|
|
setIsSubmit(false);
|
|
}, 5000);
|
|
}}
|
|
>
|
|
<IconRepeat size={14} />
|
|
</Button>
|
|
) : (
|
|
<div style={{ height: "24px" }}></div>
|
|
)}
|
|
<Button
|
|
disabled={
|
|
isSubmit ||
|
|
!dataStation?.apc_2_ip ||
|
|
dataStation?.apc2?.status !== "CONNECTED"
|
|
}
|
|
title={
|
|
listOutletSelected.filter((el) => el.apc === 2).length ===
|
|
listOutlet.length
|
|
? "Deselect All"
|
|
: "Select All"
|
|
}
|
|
className={classes.buttonMenuTool}
|
|
variant="filled"
|
|
onClick={() => {
|
|
if (
|
|
listOutletSelected.filter((el) => el.apc === 2).length ===
|
|
listOutlet.filter((el) => el.apc === 2).length
|
|
) {
|
|
setListOutletSelected([]);
|
|
} else {
|
|
setListOutletSelected(
|
|
listOutlet
|
|
.filter((el) => el.apc === 2)
|
|
.map((el) => ({ ...el }))
|
|
);
|
|
}
|
|
}}
|
|
>
|
|
{listOutletSelected.filter((el) => el.apc === 2).length ===
|
|
listOutlet.filter((el) => el.apc === 2).length
|
|
? "Deselect"
|
|
: "Select All"}
|
|
</Button>
|
|
<Button
|
|
disabled={
|
|
isSubmit ||
|
|
listOutlet.filter((el) => el.apc === 2).length === 0 ||
|
|
listOutletSelected.filter((el) => el.apc === 2).length ===
|
|
0 ||
|
|
dataStation?.apc2?.status === "DISCONNECTED" ||
|
|
dataStation?.apc2?.status === "TIMEOUT"
|
|
}
|
|
title={"Restart"}
|
|
className={classes.buttonMenuTool}
|
|
variant="filled"
|
|
color="yellow"
|
|
onClick={() => {
|
|
socket?.emit("control_apc", {
|
|
outletNumbers: listOutletSelected
|
|
.filter((el) => el.apc === 2)
|
|
.map((el) => el.number),
|
|
station: { ...stationAPI, lines: [] },
|
|
action: "restart",
|
|
apcName: "apc_2",
|
|
});
|
|
setListOutletSelected([]);
|
|
setIsSubmit(true);
|
|
setTimeout(() => {
|
|
setIsSubmit(false);
|
|
}, 5000);
|
|
}}
|
|
>
|
|
Restart
|
|
</Button>
|
|
<Button
|
|
disabled={
|
|
isSubmit ||
|
|
listOutlet.filter((el) => el.apc === 2).length === 0 ||
|
|
listOutletSelected.filter((el) => el.apc === 2).length ===
|
|
0 ||
|
|
dataStation?.apc2?.status === "DISCONNECTED" ||
|
|
dataStation?.apc2?.status === "TIMEOUT"
|
|
}
|
|
title={"Turn On All"}
|
|
className={classes.buttonMenuTool}
|
|
variant="filled"
|
|
color="green"
|
|
onClick={() => {
|
|
socket?.emit("control_apc", {
|
|
outletNumbers: listOutletSelected
|
|
.filter((el) => el.apc === 2)
|
|
.map((el) => el.number),
|
|
station: { ...stationAPI, lines: [] },
|
|
action: "on",
|
|
apcName: "apc_2",
|
|
});
|
|
setListOutletSelected([]);
|
|
setIsSubmit(true);
|
|
setTimeout(() => {
|
|
setIsSubmit(false);
|
|
}, 5000);
|
|
}}
|
|
>
|
|
Turn On
|
|
</Button>
|
|
<Button
|
|
disabled={
|
|
isSubmit ||
|
|
listOutlet.filter((el) => el.apc === 2).length === 0 ||
|
|
listOutletSelected.filter((el) => el.apc === 2).length ===
|
|
0 ||
|
|
dataStation?.apc2?.status === "DISCONNECTED" ||
|
|
dataStation?.apc2?.status === "TIMEOUT"
|
|
}
|
|
className={classes.buttonMenuTool}
|
|
title={"Turn Off"}
|
|
// mt={'xs'}
|
|
miw={"80px"}
|
|
size="xs"
|
|
fz={"xs"}
|
|
variant="filled"
|
|
color="red"
|
|
onClick={() => {
|
|
socket?.emit("control_apc", {
|
|
outletNumbers: listOutletSelected
|
|
.filter((el) => el.apc === 2)
|
|
.map((el) => el.number),
|
|
station: { ...stationAPI, lines: [] },
|
|
action: "off",
|
|
apcName: "apc_2",
|
|
});
|
|
setListOutletSelected([]);
|
|
setIsSubmit(true);
|
|
setTimeout(() => {
|
|
setIsSubmit(false);
|
|
}, 5000);
|
|
}}
|
|
>
|
|
Turn Off
|
|
</Button>
|
|
</div>
|
|
</legend>
|
|
<Box>
|
|
<Box
|
|
style={{
|
|
display: "flex",
|
|
flexWrap: "wrap",
|
|
gap: "10px",
|
|
paddingTop: "8px",
|
|
paddingBottom: "8px",
|
|
}}
|
|
>
|
|
{listOutlet
|
|
.filter((el) => el.apc === 2)
|
|
.map((outlet, i) => (
|
|
<Card
|
|
key={i}
|
|
shadow="sm"
|
|
padding="xs"
|
|
radius="md"
|
|
withBorder
|
|
className={`${
|
|
isSubmit || !dataStation?.apc_2_ip
|
|
? classes.isDisabled
|
|
: ""
|
|
}`}
|
|
style={{
|
|
paddingLeft: 0,
|
|
paddingRight: 0,
|
|
width: "55px",
|
|
position: "relative",
|
|
cursor: "pointer",
|
|
textAlign: "center",
|
|
border: listOutletSelected.find(
|
|
(el) => el.name === outlet.name && el.apc === outlet.apc
|
|
)?.name
|
|
? "1px solid #0018ff"
|
|
: "",
|
|
}}
|
|
onClick={() => {
|
|
toggleSelect(outlet, i + 1);
|
|
}}
|
|
>
|
|
<Text
|
|
fw={500}
|
|
fz={"12px"}
|
|
style={{
|
|
color: outlet.status === "ON" ? "#40c057" : "#f03e3e",
|
|
}}
|
|
>
|
|
{findLineByOutlet(outlet)
|
|
? "Line " + findLineByOutlet(outlet)?.lineNumber
|
|
: outlet.name}
|
|
</Text>
|
|
</Card>
|
|
))}
|
|
</Box>
|
|
</Box>
|
|
</fieldset>
|
|
</Grid.Col>
|
|
</Grid>
|
|
);
|
|
};
|
|
|
|
export const DrawerSwitchControl: React.FC<DrawerProps> = ({
|
|
stationAPI,
|
|
socket,
|
|
stationId,
|
|
}) => {
|
|
const [listPorts, setListPorts] = useState<SwitchPortsProps[][]>([]);
|
|
const [dataStation, setDataStation] = useState<TStation>(stationAPI);
|
|
const [listPortsSelected, setListPortsSelected] = useState<
|
|
SwitchPortsProps[]
|
|
>([]);
|
|
const [isSubmit, setIsSubmit] = useState(false);
|
|
const [loading, setLoading] = useState(true);
|
|
const [checkedActive, setCheckedActive] = useState("all");
|
|
|
|
useEffect(() => {
|
|
setListPortsSelected([]);
|
|
setLoading(true);
|
|
}, [stationId]);
|
|
|
|
useEffect(() => {
|
|
const value = localStorage.getItem("show-switch-port");
|
|
if (value) {
|
|
setCheckedActive(value);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (loading)
|
|
setTimeout(() => {
|
|
setLoading(false);
|
|
}, 15000);
|
|
}, [loading]);
|
|
|
|
useEffect(() => {
|
|
socket?.on("switch_output", (data) => {
|
|
if (data.stationId !== stationId) return;
|
|
if (data?.portGroups && data?.portGroups.length > 0)
|
|
setListPorts(data?.portGroups || []);
|
|
setLoading(false);
|
|
setDataStation({ ...dataStation, switch: { status: data.status } });
|
|
});
|
|
return () => {
|
|
socket?.off("switch_output");
|
|
};
|
|
}, [socket, dataStation, stationId]);
|
|
|
|
const toggleSelect = (port: SwitchPortsProps) => {
|
|
setListPortsSelected((prev) =>
|
|
prev.find((pid) => pid.name === port.name)
|
|
? prev.filter((pid) => pid.name !== port.name)
|
|
: [...prev, { ...port }]
|
|
);
|
|
};
|
|
|
|
const RenderAPCStatus = (apc: APCProps) => {
|
|
switch (apc.status) {
|
|
case "CONNECTED":
|
|
return (
|
|
<>
|
|
<Text size="sm" fw={800} c="green">
|
|
{apc.status}
|
|
</Text>
|
|
</>
|
|
);
|
|
|
|
case "DISCONNECTED":
|
|
return (
|
|
<>
|
|
<Text size="sm" fw={800} c="red">
|
|
{apc.status}
|
|
</Text>
|
|
</>
|
|
);
|
|
case "TIMEOUT":
|
|
return (
|
|
<>
|
|
<Text size="sm" fw={800} c="yellow">
|
|
{apc.status}
|
|
</Text>
|
|
</>
|
|
);
|
|
default:
|
|
return (
|
|
<>
|
|
{/* <Text size="sm" fw={800} c="blue">
|
|
{line.status}
|
|
</Text> */}
|
|
</>
|
|
);
|
|
}
|
|
};
|
|
|
|
const sortedPorts = (ports: SwitchPortsProps[]) => {
|
|
return ports.sort((a: SwitchPortsProps, b: SwitchPortsProps) => {
|
|
const splitNameA = a.name.split("/");
|
|
const splitNameB = b.name.split("/");
|
|
const numA = parseInt(splitNameA[splitNameA.length - 1], 10);
|
|
const numB = parseInt(splitNameB[splitNameB.length - 1], 10);
|
|
|
|
const isOddA = numA % 2 !== 0;
|
|
const isOddB = numB % 2 !== 0;
|
|
|
|
// if (numA / 48 > 1) return 0;
|
|
|
|
// Ưu tiên số lẻ lên trước
|
|
if (isOddA && !isOddB) return -1;
|
|
if (!isOddA && isOddB) return 1;
|
|
|
|
// Cùng là chẵn hoặc cùng là lẻ -> sắp theo thứ tự tăng dần
|
|
return numA - numB;
|
|
});
|
|
};
|
|
|
|
const normalizePortName = (port: string): string => {
|
|
if (!port) return "";
|
|
|
|
// Example inputs: "Fa0/1", "Gi0/0/1", "Fa0/0/2"
|
|
const match = port.match(/^([A-Za-z]+)([\d/]+)$/);
|
|
|
|
if (!match) return port;
|
|
|
|
// const type = match[1]; // Fa, Gi, Te, etc.
|
|
const numbers = match[2]; // "0/1" / "0/0/1" / "0/0/2"
|
|
|
|
// Get the last part after slash
|
|
const parts = numbers.split("/");
|
|
const last = parts[parts.length - 1];
|
|
|
|
return `${last}`;
|
|
};
|
|
|
|
const convertPortName = (port: string): string => {
|
|
if (!port) return "";
|
|
|
|
// Example inputs: "Fa0/1", "Gi0/0/1", "Fa0/0/2"
|
|
const match = port.match(/^([A-Za-z]+)([\d/]+)$/);
|
|
|
|
if (!match) return port;
|
|
|
|
const type = match[1]; // Fa, Gi, Te, etc.
|
|
const numbers = match[2]; // "0/1" / "0/0/1" / "0/0/2"
|
|
|
|
// Get the last part after slash
|
|
const parts = numbers.split("/");
|
|
const last = parts[parts.length - 1];
|
|
|
|
return `${type?.slice(0, 2)}${last}`;
|
|
};
|
|
|
|
const changeShowPort = (status: string) => {
|
|
localStorage.setItem("show-switch-port", status);
|
|
setCheckedActive(status);
|
|
};
|
|
|
|
const checkFilterPort = (status: string, portName: string) => {
|
|
if (checkedActive === "all") return true;
|
|
if (checkedActive === "on") return status === "ON";
|
|
if (checkedActive === "off") return status === "OFF";
|
|
if (checkedActive === "config") {
|
|
const listInterface = stationAPI?.lines
|
|
?.filter((el) => el.interface)
|
|
.map((el) => convertPortName(el.interface || ""));
|
|
return listInterface?.includes(convertPortName(portName));
|
|
}
|
|
};
|
|
|
|
return loading ? (
|
|
<Box
|
|
style={{
|
|
height: "10vh",
|
|
width: "100%",
|
|
display: "flex",
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
}}
|
|
>
|
|
<Loader color="blue" />
|
|
</Box>
|
|
) : (
|
|
<Grid>
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
justifyContent: "space-between",
|
|
marginTop: "6px",
|
|
minWidth: "98%",
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
justifyContent: "left",
|
|
marginTop: "6px",
|
|
marginBottom: "10px",
|
|
gap: "20px",
|
|
}}
|
|
>
|
|
<Box ps={"8px"} pt={"4px"}>
|
|
{dataStation?.switch_control_ip ? (
|
|
dataStation?.switch ? (
|
|
RenderAPCStatus(dataStation?.switch)
|
|
) : (
|
|
<Text fw={800} c="red" fz={"12px"}>
|
|
DISCONNECTED
|
|
</Text>
|
|
)
|
|
) : (
|
|
<Text fw={800} c="red" fz={"12px"}>
|
|
Switch not available
|
|
</Text>
|
|
)}
|
|
</Box>
|
|
{dataStation?.switch?.status !== "CONNECTED" ? (
|
|
<Button
|
|
size="xs"
|
|
disabled={isSubmit || !dataStation?.switch_control_ip}
|
|
variant="filled"
|
|
color="yellow"
|
|
onClick={() => {
|
|
socket?.emit("control_switch", {
|
|
ports: [],
|
|
command: "reconnect",
|
|
station: { ...stationAPI, lines: [] },
|
|
ip: stationAPI?.switch_control_ip,
|
|
});
|
|
setIsSubmit(true);
|
|
setTimeout(() => {
|
|
setIsSubmit(false);
|
|
}, 10000);
|
|
}}
|
|
>
|
|
<IconRepeat size={14} />
|
|
</Button>
|
|
) : (
|
|
""
|
|
)}
|
|
<Button
|
|
className={classes.buttonMenuTool}
|
|
disabled={isSubmit || !dataStation?.switch_control_ip}
|
|
title={
|
|
listPortsSelected.length === listPorts.flat().length &&
|
|
listPorts.flat().length > 0
|
|
? "Deselect All"
|
|
: "Select All"
|
|
}
|
|
miw={"80px"}
|
|
size="xs"
|
|
fz={"xs"}
|
|
variant="filled"
|
|
onClick={() => {
|
|
if (listPortsSelected.length === listPorts.flat().length) {
|
|
setListPortsSelected([]);
|
|
} else {
|
|
setListPortsSelected(listPorts.flat());
|
|
}
|
|
}}
|
|
>
|
|
{listPortsSelected.length === listPorts.flat().length &&
|
|
listPorts.flat().length > 0
|
|
? "Clear All"
|
|
: "Select All"}
|
|
</Button>
|
|
<Button
|
|
className={classes.buttonMenuTool}
|
|
disabled={
|
|
isSubmit ||
|
|
listPorts.flat().length === 0 ||
|
|
listPortsSelected.length === 0 ||
|
|
(dataStation?.switch &&
|
|
(dataStation?.switch?.status === "DISCONNECTED" ||
|
|
dataStation?.switch?.status === "TIMEOUT"))
|
|
}
|
|
title={"Restart"}
|
|
// mt={'xs'}
|
|
miw={"80px"}
|
|
size="xs"
|
|
fz={"xs"}
|
|
variant="filled"
|
|
color="yellow"
|
|
onClick={() => {
|
|
const listPortsRestart =
|
|
listPortsSelected?.length > 0
|
|
? listPortsSelected
|
|
: listPorts.flat();
|
|
if (listPortsRestart.filter((el) => el.poe !== "ON").length > 0)
|
|
socket?.emit("control_switch", {
|
|
ports: listPortsRestart
|
|
.filter((el) => el.poe !== "ON")
|
|
.map((el) => el.name),
|
|
command: "restart",
|
|
station: { ...stationAPI, lines: [] },
|
|
ip: stationAPI?.switch_control_ip,
|
|
});
|
|
if (listPortsRestart.filter((el) => el.poe === "ON").length > 0)
|
|
socket?.emit("control_switch", {
|
|
ports: listPortsRestart
|
|
.filter((el) => el.poe === "ON")
|
|
.map((el) => el.name),
|
|
command: "restart-poe",
|
|
station: { ...stationAPI, lines: [] },
|
|
ip: stationAPI?.switch_control_ip,
|
|
});
|
|
setListPortsSelected([]);
|
|
setIsSubmit(true);
|
|
setTimeout(() => {
|
|
setIsSubmit(false);
|
|
}, 15000);
|
|
}}
|
|
>
|
|
Restart
|
|
</Button>
|
|
<Button
|
|
className={classes.buttonMenuTool}
|
|
disabled={
|
|
isSubmit ||
|
|
listPorts.flat().length === 0 ||
|
|
listPortsSelected.length === 0 ||
|
|
(dataStation?.switch &&
|
|
(dataStation?.switch?.status === "DISCONNECTED" ||
|
|
dataStation?.switch?.status === "TIMEOUT"))
|
|
}
|
|
title={"Turn On"}
|
|
// mt={'xs'}
|
|
miw={"80px"}
|
|
size="xs"
|
|
fz={"xs"}
|
|
variant="filled"
|
|
color="green"
|
|
onClick={() => {
|
|
const listPortsRestart =
|
|
listPortsSelected?.length > 0
|
|
? listPortsSelected
|
|
: listPorts.flat();
|
|
if (listPortsRestart.filter((el) => el.poe !== "ON").length > 0)
|
|
socket?.emit("control_switch", {
|
|
ports: listPortsRestart
|
|
.filter((el) => el.poe !== "ON")
|
|
.map((el) => el.name),
|
|
command: "on",
|
|
station: { ...stationAPI, lines: [] },
|
|
ip: stationAPI?.switch_control_ip,
|
|
});
|
|
if (listPortsRestart.filter((el) => el.poe === "ON").length > 0)
|
|
socket?.emit("control_switch", {
|
|
ports: listPortsRestart
|
|
.filter((el) => el.poe === "ON")
|
|
.map((el) => el.name),
|
|
command: "on-poe",
|
|
station: { ...stationAPI, lines: [] },
|
|
ip: stationAPI?.switch_control_ip,
|
|
});
|
|
setListPortsSelected([]);
|
|
setIsSubmit(true);
|
|
setTimeout(() => {
|
|
setIsSubmit(false);
|
|
}, 10000);
|
|
}}
|
|
>
|
|
Turn On
|
|
</Button>
|
|
<Button
|
|
className={classes.buttonMenuTool}
|
|
disabled={
|
|
isSubmit ||
|
|
listPorts.flat().length === 0 ||
|
|
listPortsSelected.length === 0 ||
|
|
(dataStation?.switch &&
|
|
(dataStation?.switch?.status === "DISCONNECTED" ||
|
|
dataStation?.switch?.status === "TIMEOUT"))
|
|
}
|
|
title={"Turn Off"}
|
|
// mt={'xs'}
|
|
miw={"80px"}
|
|
size="xs"
|
|
fz={"xs"}
|
|
variant="filled"
|
|
color="red"
|
|
onClick={() => {
|
|
const listPortsRestart =
|
|
listPortsSelected?.length > 0
|
|
? listPortsSelected
|
|
: listPorts.flat();
|
|
if (listPortsRestart.filter((el) => el.poe !== "ON").length > 0)
|
|
socket?.emit("control_switch", {
|
|
ports: listPortsRestart
|
|
.filter((el) => el.poe !== "ON")
|
|
.map((el) => el.name),
|
|
command: "off",
|
|
station: { ...stationAPI, lines: [] },
|
|
ip: stationAPI?.switch_control_ip,
|
|
});
|
|
if (listPortsRestart.filter((el) => el.poe === "ON").length > 0)
|
|
socket?.emit("control_switch", {
|
|
ports: listPortsRestart
|
|
.filter((el) => el.poe === "ON")
|
|
.map((el) => el.name),
|
|
command: "off-poe",
|
|
station: { ...stationAPI, lines: [] },
|
|
ip: stationAPI?.switch_control_ip,
|
|
});
|
|
setListPortsSelected([]);
|
|
setIsSubmit(true);
|
|
setTimeout(() => {
|
|
setIsSubmit(false);
|
|
}, 10000);
|
|
}}
|
|
>
|
|
Turn Off
|
|
</Button>
|
|
<Group ms="xs" style={{ display: "flex", gap: "8px" }}>
|
|
<Radio
|
|
value="all"
|
|
label="All"
|
|
checked={checkedActive === "all"}
|
|
onChange={() => changeShowPort("all")}
|
|
/>
|
|
<Radio
|
|
value="on"
|
|
label="On"
|
|
checked={checkedActive === "on"}
|
|
onChange={() => changeShowPort("on")}
|
|
/>
|
|
<Radio
|
|
value="off"
|
|
label="Off"
|
|
checked={checkedActive === "off"}
|
|
onChange={() => changeShowPort("off")}
|
|
/>
|
|
<Radio
|
|
value="config"
|
|
label="Config interface"
|
|
checked={checkedActive === "config"}
|
|
onChange={() => changeShowPort("config")}
|
|
/>
|
|
</Group>
|
|
</div>
|
|
</div>
|
|
|
|
<Grid.Col span={12} pt={"4px"}>
|
|
{listPorts?.length > 0 && (
|
|
<Box>
|
|
<Grid>
|
|
{listPorts?.map((group, key) => {
|
|
const isMini = group?.length > 0 && group?.length < 4;
|
|
const isLarge = group?.length > 20;
|
|
return (
|
|
<Grid.Col
|
|
pt={0}
|
|
key={key}
|
|
span={isLarge ? 11 : isMini ? 1 : 12}
|
|
>
|
|
{isLarge ? (
|
|
<fieldset
|
|
style={{
|
|
padding: "0px 4px",
|
|
}}
|
|
>
|
|
<legend>
|
|
<Text fw={700} c={"#514d4d"} fz={"xs"}>
|
|
{group[0]?.name.substring(0, 2) || ""}
|
|
</Text>
|
|
</legend>
|
|
<ScrollArea h={"11vh"} w={"67vw"}>
|
|
<Flex gap={"8px"} wrap={"nowrap"}>
|
|
{sortedPorts(group)
|
|
.slice(0, sortedPorts(group).length / 2)
|
|
.filter((el) =>
|
|
checkFilterPort(el.status, el.name)
|
|
)
|
|
?.map((port, i) => (
|
|
<Card
|
|
key={i}
|
|
shadow="sm"
|
|
padding="xs"
|
|
radius="md"
|
|
withBorder
|
|
style={{
|
|
flex: "0 0 auto",
|
|
position: "relative",
|
|
width: "50px",
|
|
height: "40px",
|
|
backgroundColor:
|
|
port.poe === "ON"
|
|
? "#f2dcf8"
|
|
: port.status === "ON"
|
|
? "#d4f1d3"
|
|
: "#f5f5f5",
|
|
cursor: "pointer",
|
|
border: listPortsSelected.find(
|
|
(el) => el.name === port.name
|
|
)?.name
|
|
? "1px solid #0018ff"
|
|
: "",
|
|
}}
|
|
className={`${
|
|
isSubmit ? classes.isDisabled : ""
|
|
}`}
|
|
onClick={() => {
|
|
toggleSelect(port);
|
|
}}
|
|
>
|
|
<Box
|
|
style={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: "2px",
|
|
flexDirection: "column",
|
|
flexWrap: "wrap",
|
|
}}
|
|
>
|
|
<Text fw={500} fz={"11px"}>
|
|
{normalizePortName(port.name)}
|
|
</Text>
|
|
</Box>
|
|
<Box
|
|
style={{
|
|
display:
|
|
port.poe === "ON" ? "block" : "none",
|
|
position: "absolute",
|
|
bottom: "2px",
|
|
right: "2px",
|
|
zIndex: 10,
|
|
}}
|
|
>
|
|
<Text size="10px" fs={"italic"}>
|
|
poe
|
|
</Text>
|
|
</Box>
|
|
</Card>
|
|
))}
|
|
</Flex>
|
|
<Flex gap={"8px"} wrap={"nowrap"} mt={"8px"}>
|
|
{sortedPorts(group)
|
|
.slice(
|
|
sortedPorts(group).length / 2,
|
|
sortedPorts(group).length
|
|
)
|
|
.filter((el) =>
|
|
checkFilterPort(el.status, el.name)
|
|
)
|
|
?.map((port, i) => (
|
|
<Card
|
|
key={i}
|
|
shadow="sm"
|
|
padding="xs"
|
|
radius="md"
|
|
withBorder
|
|
style={{
|
|
flex: "0 0 auto",
|
|
position: "relative",
|
|
width: "50px",
|
|
height: "40px",
|
|
backgroundColor:
|
|
port.poe === "ON"
|
|
? "#f2dcf8"
|
|
: port.status === "ON"
|
|
? "#d4f1d3"
|
|
: "#f5f5f5",
|
|
cursor: "pointer",
|
|
border: listPortsSelected.find(
|
|
(el) => el.name === port.name
|
|
)?.name
|
|
? "1px solid #0018ff"
|
|
: "",
|
|
}}
|
|
className={`${
|
|
isSubmit ? classes.isDisabled : ""
|
|
}`}
|
|
onClick={() => {
|
|
toggleSelect(port);
|
|
}}
|
|
>
|
|
<Box
|
|
style={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: "2px",
|
|
flexDirection: "column",
|
|
flexWrap: "wrap",
|
|
}}
|
|
>
|
|
<Text fw={500} fz={"11px"}>
|
|
{normalizePortName(port.name)}
|
|
</Text>
|
|
</Box>
|
|
<Box
|
|
style={{
|
|
display:
|
|
port.poe === "ON" ? "block" : "none",
|
|
position: "absolute",
|
|
bottom: "2px",
|
|
right: "2px",
|
|
zIndex: 10,
|
|
}}
|
|
>
|
|
<Text size="10px" fs={"italic"}>
|
|
poe
|
|
</Text>
|
|
</Box>
|
|
</Card>
|
|
))}
|
|
</Flex>
|
|
</ScrollArea>
|
|
</fieldset>
|
|
) : (
|
|
<fieldset
|
|
style={{
|
|
padding: "0px 4px",
|
|
}}
|
|
>
|
|
<legend>
|
|
<Text fw={700} c={"#514d4d"} fz={"xs"}>
|
|
{group[0]?.name.substring(0, 2) || ""}
|
|
</Text>
|
|
</legend>
|
|
<Box
|
|
style={{
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
// flexWrap: "wrap",
|
|
// justifyContent: "center",
|
|
alignItems: "center",
|
|
gap: "8px",
|
|
overflow: "auto",
|
|
height: "11vh",
|
|
maxWidth: "70vw",
|
|
}}
|
|
>
|
|
{sortedPorts(group)
|
|
?.filter((el) =>
|
|
checkFilterPort(el.status, el.name)
|
|
)
|
|
?.map((port, i) => (
|
|
<Card
|
|
key={i}
|
|
shadow="sm"
|
|
padding="xs"
|
|
radius="md"
|
|
withBorder
|
|
style={{
|
|
position: "relative",
|
|
width: "50px",
|
|
height: "40px",
|
|
backgroundColor:
|
|
port.poe === "ON"
|
|
? "#f2dcf8"
|
|
: port.status === "ON"
|
|
? "#d4f1d3"
|
|
: "#f5f5f5",
|
|
cursor: "pointer",
|
|
border: listPortsSelected.find(
|
|
(el) => el.name === port.name
|
|
)?.name
|
|
? "1px solid #0018ff"
|
|
: "",
|
|
}}
|
|
className={`${
|
|
isSubmit ? classes.isDisabled : ""
|
|
}`}
|
|
onClick={() => {
|
|
toggleSelect(port);
|
|
}}
|
|
>
|
|
<Box
|
|
style={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: "2px",
|
|
flexDirection: "column",
|
|
flexWrap: "wrap",
|
|
}}
|
|
>
|
|
{/* <IconSection
|
|
size={"12px"}
|
|
color={
|
|
port.poe === "ON"
|
|
? "#b722d4"
|
|
: port.status === "ON"
|
|
? "#40c057"
|
|
: "#b8b8b8"
|
|
}
|
|
/> */}
|
|
<Text fw={500} fz={"11px"}>
|
|
{normalizePortName(port.name)}
|
|
</Text>
|
|
</Box>
|
|
<Box
|
|
style={{
|
|
display:
|
|
port.poe === "ON" ? "block" : "none",
|
|
position: "absolute",
|
|
bottom: "2px",
|
|
right: "2px",
|
|
zIndex: 10,
|
|
}}
|
|
>
|
|
<Text size="10px" fs={"italic"}>
|
|
poe
|
|
</Text>
|
|
</Box>
|
|
</Card>
|
|
))}
|
|
</Box>
|
|
</fieldset>
|
|
)}
|
|
</Grid.Col>
|
|
);
|
|
})}
|
|
</Grid>
|
|
</Box>
|
|
)}
|
|
</Grid.Col>
|
|
</Grid>
|
|
);
|
|
};
|