669 lines
20 KiB
TypeScript
669 lines
20 KiB
TypeScript
import {
|
|
ActionIcon,
|
|
Box,
|
|
Button,
|
|
Flex,
|
|
Group,
|
|
Modal,
|
|
NumberInput,
|
|
PasswordInput,
|
|
Select,
|
|
Table,
|
|
TextInput,
|
|
} from "@mantine/core";
|
|
import { useForm } from "@mantine/form";
|
|
import { useEffect, useState } from "react";
|
|
import type { TLine, TStation } from "../untils/types";
|
|
import DialogConfirm from "./DialogConfirm";
|
|
import axios from "axios";
|
|
import { notifications } from "@mantine/notifications";
|
|
import { IconInputX } from "@tabler/icons-react";
|
|
|
|
const apiUrl = import.meta.env.VITE_BACKEND_URL;
|
|
|
|
const lineInit = {
|
|
port: 0,
|
|
lineNumber: 0,
|
|
lineClear: 0,
|
|
station_id: 0,
|
|
apc_name: "",
|
|
};
|
|
|
|
const StationSetting = ({
|
|
isOpen,
|
|
isEdit,
|
|
onClose,
|
|
dataStation,
|
|
setStations,
|
|
setActiveTab,
|
|
stations,
|
|
}: {
|
|
isOpen: boolean;
|
|
isEdit: boolean;
|
|
onClose: () => void;
|
|
dataStation?: TStation;
|
|
setStations: (value: React.SetStateAction<TStation[]>) => void;
|
|
setActiveTab: (value: string) => void;
|
|
stations: TStation[];
|
|
}) => {
|
|
const [lines, setLines] = useState<TLine[]>([lineInit]);
|
|
const [openConfirm, setOpenConfirm] = useState<boolean>(false);
|
|
|
|
const ipRegex =
|
|
/(\b25[0-5]|\b2[0-4][0-9]|\b1[0-9]{2}|\b[1-9]?[0-9])\.(\b25[0-5]|\b2[0-4][0-9]|\b1[0-9]{2}|\b[1-9]?[0-9])\.(\b25[0-5]|\b2[0-4][0-9]|\b1[0-9]{2}|\b[1-9]?[0-9])\.(\b25[0-5]|\b2[0-4][0-9]|\b1[0-9]{2}|\b[1-9]?[0-9])\b/g;
|
|
const form = useForm<TStation>({
|
|
initialValues: dataStation,
|
|
validate: (values: TStation) => ({
|
|
ip: !ipRegex.test(values.ip) ? "IP address invalid" : null,
|
|
}),
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (dataStation) {
|
|
form.setFieldValue("name", dataStation.name);
|
|
form.setFieldValue("ip", dataStation.ip);
|
|
form.setFieldValue("port", dataStation.port);
|
|
form.setFieldValue("netmask", dataStation.netmask);
|
|
form.setFieldValue("network", dataStation.network);
|
|
form.setFieldValue("gateway", dataStation.gateway);
|
|
form.setFieldValue("tftp_ip", dataStation.tftp_ip);
|
|
form.setFieldValue("apc_1_ip", dataStation.apc_1_ip);
|
|
form.setFieldValue("apc_1_port", dataStation.apc_1_port);
|
|
form.setFieldValue("apc_1_username", dataStation.apc_1_username);
|
|
form.setFieldValue("apc_1_password", dataStation.apc_1_password);
|
|
form.setFieldValue("apc_2_ip", dataStation.apc_2_ip);
|
|
form.setFieldValue("apc_2_port", dataStation.apc_2_port);
|
|
form.setFieldValue("apc_2_username", dataStation.apc_2_username);
|
|
form.setFieldValue("apc_2_password", dataStation.apc_2_password);
|
|
form.setFieldValue("switch_control_ip", dataStation.switch_control_ip);
|
|
form.setFieldValue(
|
|
"switch_control_port",
|
|
dataStation.switch_control_port
|
|
);
|
|
form.setFieldValue(
|
|
"switch_control_username",
|
|
dataStation.switch_control_username
|
|
);
|
|
form.setFieldValue(
|
|
"switch_control_password",
|
|
dataStation.switch_control_password
|
|
);
|
|
|
|
const dataLine = dataStation.lines.map((value) => ({
|
|
id: value.id,
|
|
lineNumber: value.line_number || 0,
|
|
port: value.port,
|
|
lineClear: value.line_clear || 0,
|
|
apc_name: value.apc_name,
|
|
outlet: value.outlet,
|
|
station_id: value.station_id,
|
|
}));
|
|
setLines(dataLine);
|
|
}
|
|
}, [dataStation]);
|
|
|
|
useEffect(() => {
|
|
if (lines.length > 0) {
|
|
const lastLine = lines[lines.length - 1];
|
|
if (lastLine?.lineNumber || lastLine?.port)
|
|
setLines((pre) => [...pre, lineInit]);
|
|
}
|
|
}, [lines]);
|
|
|
|
useEffect(() => {
|
|
if (!isOpen) {
|
|
setLines([lineInit]);
|
|
setOpenConfirm(false);
|
|
form.reset();
|
|
}
|
|
}, [isOpen]);
|
|
|
|
const renderLinesTable = () => {
|
|
const rows = lines?.map((row: TLine, index: number) => {
|
|
return (
|
|
<Table.Tr key={index}>
|
|
<Table.Td fz={"sm"} p="3px" ta={"center"} fw={700}>
|
|
<NumberInput
|
|
value={row?.lineNumber}
|
|
onChange={(e) =>
|
|
setLines((pre) =>
|
|
pre.map((value, i) =>
|
|
i === index ? { ...value, lineNumber: Number(e!) } : value
|
|
)
|
|
)
|
|
}
|
|
/>
|
|
</Table.Td>
|
|
<Table.Td fz={"sm"} p="3px" ta={"center"} fw={700}>
|
|
<NumberInput
|
|
value={row?.port}
|
|
onChange={(e) =>
|
|
setLines((pre) =>
|
|
pre.map((value, i) =>
|
|
i === index ? { ...value, port: Number(e!) } : value
|
|
)
|
|
)
|
|
}
|
|
/>
|
|
</Table.Td>
|
|
<Table.Td fz={"sm"} p="3px" ta={"center"} fw={700}>
|
|
<NumberInput
|
|
value={row?.lineClear}
|
|
onChange={(e) =>
|
|
setLines((pre) =>
|
|
pre.map((value, i) =>
|
|
i === index ? { ...value, lineClear: Number(e!) } : value
|
|
)
|
|
)
|
|
}
|
|
/>
|
|
</Table.Td>
|
|
<Table.Td fz={"sm"} p="3px" ta={"center"} fw={700}>
|
|
<Select
|
|
data={[
|
|
{ label: "APC1", value: "apc_1" },
|
|
{ label: "APC2", value: "apc_2" },
|
|
]}
|
|
value={row?.apc_name}
|
|
onChange={(e) =>
|
|
setLines((pre) =>
|
|
pre.map((value, i) =>
|
|
i === index ? { ...value, apc_name: e! } : value
|
|
)
|
|
)
|
|
}
|
|
/>
|
|
</Table.Td>
|
|
<Table.Td fz={"sm"} p="3px" ta={"center"} fw={700}>
|
|
<NumberInput
|
|
value={row?.outlet}
|
|
onChange={(e) =>
|
|
setLines((pre) =>
|
|
pre.map((value, i) =>
|
|
i === index ? { ...value, outlet: Number(e!) } : value
|
|
)
|
|
)
|
|
}
|
|
/>
|
|
</Table.Td>
|
|
<Table.Td fz={"sm"} p="3px" ta={"center"} fw={700}>
|
|
{row?.lineNumber ? (
|
|
<ActionIcon
|
|
title="Remove line"
|
|
variant="outline"
|
|
color="red"
|
|
onClick={() => {
|
|
setLines((pre) => [
|
|
...pre.slice(0, index),
|
|
...pre.slice(index + 1, lines.length),
|
|
]);
|
|
}}
|
|
>
|
|
<IconInputX />
|
|
</ActionIcon>
|
|
) : (
|
|
""
|
|
)}
|
|
</Table.Td>
|
|
</Table.Tr>
|
|
);
|
|
});
|
|
|
|
return rows;
|
|
};
|
|
|
|
const handleSave = async () => {
|
|
try {
|
|
const payload = {
|
|
...form.values,
|
|
lines: lines.filter((el) => el.lineNumber && el.port),
|
|
};
|
|
if (isEdit) payload.id = dataStation?.id || 0;
|
|
const url = isEdit ? "api/stations/update" : "api/stations/create";
|
|
const response = await axios.post(apiUrl + url, payload);
|
|
if (response.data.status) {
|
|
if (response.data.data) {
|
|
const station = response.data.data[0];
|
|
setStations((pre) =>
|
|
isEdit
|
|
? pre.map((el) =>
|
|
el.id === station.id ? { ...el, ...station } : el
|
|
)
|
|
: [...pre, station]
|
|
);
|
|
}
|
|
notifications.show({
|
|
title: "Success",
|
|
message: response.data.message,
|
|
color: "green",
|
|
});
|
|
onClose();
|
|
} else
|
|
notifications.show({
|
|
title: "Error",
|
|
message: response.data.message,
|
|
color: "red",
|
|
});
|
|
} catch (err) {
|
|
console.log(err);
|
|
notifications.show({
|
|
title: "Error",
|
|
message: "Error save station",
|
|
color: "red",
|
|
});
|
|
}
|
|
};
|
|
|
|
const handleDelete = async () => {
|
|
try {
|
|
const response = await axios.post(apiUrl + "api/stations/delete", {
|
|
id: dataStation?.id,
|
|
});
|
|
if (response.data.status) {
|
|
const listStations = stations.filter((el) => el.id !== dataStation?.id);
|
|
setStations(listStations);
|
|
setActiveTab(
|
|
listStations.length ? listStations[0]?.id.toString() : "0"
|
|
);
|
|
notifications.show({
|
|
title: "Success",
|
|
message: response.data.message,
|
|
color: "green",
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.log(error);
|
|
notifications.show({
|
|
title: "Error",
|
|
message: "Error delete station",
|
|
color: "red",
|
|
});
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Box>
|
|
<Modal
|
|
title={
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: "10px",
|
|
}}
|
|
>
|
|
{isEdit ? "Edit Station" : "Add Station"}{" "}
|
|
<Button
|
|
style={{ height: "30px" }}
|
|
color="green"
|
|
onClick={() => {
|
|
handleSave();
|
|
}}
|
|
>
|
|
Save
|
|
</Button>
|
|
{isEdit && (
|
|
<Button
|
|
style={{ height: "30px" }}
|
|
color="red"
|
|
onClick={() => {
|
|
setOpenConfirm(true);
|
|
}}
|
|
>
|
|
Delete
|
|
</Button>
|
|
)}
|
|
</div>
|
|
}
|
|
size={"80%"}
|
|
style={{ position: "absolute", left: 0 }}
|
|
centered
|
|
opened={isOpen}
|
|
onClose={() => {
|
|
onClose();
|
|
}}
|
|
>
|
|
<Flex justify={"space-between"} gap={"sm"}>
|
|
{/* Station info */}
|
|
<Box w={"40%"}>
|
|
<TextInput
|
|
required
|
|
name="name"
|
|
label="Name"
|
|
size="sm"
|
|
value={form.values.name || ""}
|
|
onChange={(e) => form.setFieldValue("name", e.target.value)}
|
|
/>
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "space-between",
|
|
width: "100%",
|
|
gap: "6px",
|
|
}}
|
|
>
|
|
<TextInput
|
|
required
|
|
label="IP"
|
|
size="sm"
|
|
value={form.values.ip || ""}
|
|
onChange={(e) => form.setFieldValue("ip", e.target.value)}
|
|
/>
|
|
<NumberInput
|
|
required
|
|
size="sm"
|
|
label="Port"
|
|
value={form.values.port || ""}
|
|
onChange={(e) => form.setFieldValue("port", Number(e!))}
|
|
/>
|
|
</div>
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "space-between",
|
|
width: "100%",
|
|
gap: "6px",
|
|
}}
|
|
>
|
|
<TextInput
|
|
value={form.values.netmask || ""}
|
|
label={"Netmask"}
|
|
onChange={(e) => {
|
|
form.setFieldValue("netmask", e.target.value);
|
|
}}
|
|
/>
|
|
<TextInput
|
|
value={form.values.network || ""}
|
|
label={"Network"}
|
|
onChange={(e) => {
|
|
form.setFieldValue("network", e.target.value);
|
|
}}
|
|
/>
|
|
</div>
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "space-between",
|
|
width: "100%",
|
|
gap: "6px",
|
|
}}
|
|
>
|
|
<TextInput
|
|
value={form.values.gateway || ""}
|
|
label={"Gateway"}
|
|
onChange={(e) => {
|
|
form.setFieldValue("gateway", e.target.value);
|
|
}}
|
|
/>
|
|
<TextInput
|
|
value={form.values.tftp_ip || ""}
|
|
label={"TFTP IP"}
|
|
onChange={(e) => {
|
|
form.setFieldValue("tftp_ip", e.target.value);
|
|
}}
|
|
/>
|
|
</div>
|
|
<div style={{}}>
|
|
<Group
|
|
mt={"md"}
|
|
title="APC 1"
|
|
style={{
|
|
border: "1px solid #e1e1e1",
|
|
padding: "10px",
|
|
borderRadius: "5px",
|
|
}}
|
|
>
|
|
<Box
|
|
w={"100%"}
|
|
style={{
|
|
display: "flex",
|
|
justifyContent: "space-between",
|
|
}}
|
|
>
|
|
<TextInput
|
|
label="APC1 IP"
|
|
size="xs"
|
|
w={"60%"}
|
|
value={form.values.apc_1_ip || ""}
|
|
onChange={(e) =>
|
|
form.setFieldValue("apc_1_ip", e.target.value)
|
|
}
|
|
/>
|
|
<NumberInput
|
|
label="APC1 Port"
|
|
w={"39%"}
|
|
size="xs"
|
|
value={form.values.apc_1_port || ""}
|
|
onChange={(e) =>
|
|
form.setFieldValue("apc_1_port", parseInt(e.toString()))
|
|
}
|
|
/>
|
|
</Box>
|
|
<Box
|
|
w={"100%"}
|
|
style={{
|
|
display: "flex",
|
|
justifyContent: "space-between",
|
|
}}
|
|
>
|
|
<TextInput
|
|
label="Username"
|
|
w={"49%"}
|
|
size="xs"
|
|
autoComplete="new-username"
|
|
value={form.values.apc_1_username || ""}
|
|
onChange={(e) =>
|
|
form.setFieldValue("apc_1_username", e.target.value)
|
|
}
|
|
/>
|
|
|
|
<PasswordInput
|
|
label="Password"
|
|
w={"49%"}
|
|
size="xs"
|
|
autoComplete="new-password"
|
|
value={form.values.apc_1_password || ""}
|
|
onChange={(e) =>
|
|
form.setFieldValue("apc_1_password", e.target.value)
|
|
}
|
|
/>
|
|
</Box>
|
|
</Group>
|
|
|
|
{/* APC2 configuration */}
|
|
<Group
|
|
mt={"md"}
|
|
title="APC 2"
|
|
style={{
|
|
border: "1px solid #e1e1e1",
|
|
padding: "10px",
|
|
borderRadius: "5px",
|
|
}}
|
|
>
|
|
<Box
|
|
w={"100%"}
|
|
style={{
|
|
display: "flex",
|
|
justifyContent: "space-between",
|
|
}}
|
|
>
|
|
<TextInput
|
|
label="APC2 IP"
|
|
w={"60%"}
|
|
size="xs"
|
|
value={form.values.apc_2_ip || ""}
|
|
onChange={(e) =>
|
|
form.setFieldValue("apc_2_ip", e.target.value)
|
|
}
|
|
/>
|
|
<NumberInput
|
|
label="APC2 Port"
|
|
w={"39%"}
|
|
size="xs"
|
|
value={form.values.apc_2_port || ""}
|
|
onChange={(e) =>
|
|
form.setFieldValue("apc_2_port", parseInt(e.toString()))
|
|
}
|
|
/>
|
|
</Box>
|
|
<Box
|
|
w={"100%"}
|
|
style={{
|
|
display: "flex",
|
|
justifyContent: "space-between",
|
|
}}
|
|
>
|
|
<TextInput
|
|
label="Username"
|
|
w={"49%"}
|
|
size="xs"
|
|
value={form.values.apc_2_username || ""}
|
|
onChange={(e) =>
|
|
form.setFieldValue("apc_2_username", e.target.value)
|
|
}
|
|
/>
|
|
|
|
<PasswordInput
|
|
label="Password"
|
|
w={"49%"}
|
|
size="xs"
|
|
value={form.values.apc_2_password || ""}
|
|
onChange={(e) =>
|
|
form.setFieldValue("apc_2_password", e.target.value)
|
|
}
|
|
/>
|
|
</Box>
|
|
</Group>
|
|
|
|
{/* APC1 configuration */}
|
|
<Group
|
|
mt={"md"}
|
|
title="Switch control"
|
|
style={{
|
|
border: "1px solid #e1e1e1",
|
|
padding: "10px",
|
|
borderRadius: "5px",
|
|
}}
|
|
>
|
|
<Box
|
|
w={"100%"}
|
|
style={{
|
|
display: "flex",
|
|
justifyContent: "space-between",
|
|
}}
|
|
>
|
|
<TextInput
|
|
label="Switch IP"
|
|
size="xs"
|
|
w={"60%"}
|
|
value={form.values.switch_control_ip || ""}
|
|
onChange={(e) =>
|
|
form.setFieldValue("switch_control_ip", e.target.value)
|
|
}
|
|
/>
|
|
<NumberInput
|
|
label="Switch Port"
|
|
w={"39%"}
|
|
size="xs"
|
|
value={form.values.switch_control_port || ""}
|
|
onChange={(e) =>
|
|
form.setFieldValue(
|
|
"switch_control_port",
|
|
parseInt(e.toString())
|
|
)
|
|
}
|
|
/>
|
|
</Box>
|
|
<Box
|
|
w={"100%"}
|
|
style={{
|
|
display: "flex",
|
|
justifyContent: "space-between",
|
|
}}
|
|
>
|
|
<TextInput
|
|
label="Username"
|
|
size="xs"
|
|
w={"49%"}
|
|
value={form.values.switch_control_username || ""}
|
|
onChange={(e) =>
|
|
form.setFieldValue(
|
|
"switch_control_username",
|
|
e.target.value
|
|
)
|
|
}
|
|
/>
|
|
<PasswordInput
|
|
label="Password"
|
|
size="xs"
|
|
w={"49%"}
|
|
value={form.values.switch_control_password || ""}
|
|
onChange={(e) =>
|
|
form.setFieldValue(
|
|
"switch_control_password",
|
|
e.target.value
|
|
)
|
|
}
|
|
/>
|
|
</Box>
|
|
</Group>
|
|
</div>
|
|
</Box>
|
|
{/* Lines */}
|
|
<Box w={"60%"}>
|
|
<Table
|
|
verticalSpacing="xs"
|
|
horizontalSpacing="lg"
|
|
striped
|
|
highlightOnHover
|
|
withTableBorder
|
|
withColumnBorders
|
|
>
|
|
<Table.Thead>
|
|
<Table.Tr>
|
|
<Table.Th fz={"sm"} w={"15%"} ta={"center"}>
|
|
Line number
|
|
</Table.Th>
|
|
<Table.Th fz={"sm"} w={"15%"} ta={"center"}>
|
|
Port
|
|
</Table.Th>
|
|
<Table.Th fz={"sm"} w={"15%"} ta={"center"}>
|
|
Clear line
|
|
</Table.Th>
|
|
<Table.Th fz={"sm"} w={"15%"} ta={"center"}>
|
|
APC
|
|
</Table.Th>
|
|
<Table.Th fz={"sm"} w={"15%"} ta={"center"}>
|
|
Outlet
|
|
</Table.Th>
|
|
<Table.Th fz={"sm"} w={"10%"} ta={"center"}></Table.Th>
|
|
</Table.Tr>
|
|
</Table.Thead>
|
|
<Table.Tbody>{renderLinesTable()}</Table.Tbody>
|
|
</Table>
|
|
</Box>
|
|
</Flex>
|
|
</Modal>
|
|
|
|
<DialogConfirm
|
|
opened={openConfirm}
|
|
close={() => {
|
|
setOpenConfirm(false);
|
|
}}
|
|
message={"Are you sure delete this station?"}
|
|
handle={() => {
|
|
handleDelete();
|
|
setOpenConfirm(false);
|
|
onClose();
|
|
}}
|
|
centered={true}
|
|
/>
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
export default StationSetting;
|