315 lines
10 KiB
TypeScript
315 lines
10 KiB
TypeScript
import { useDisclosure } from "@mantine/hooks";
|
|
import {
|
|
Button,
|
|
Box,
|
|
Drawer,
|
|
Grid,
|
|
Table,
|
|
Text,
|
|
ScrollArea,
|
|
Tooltip,
|
|
TextInput,
|
|
} from "@mantine/core";
|
|
import { DateInput } from "@mantine/dates";
|
|
import { useEffect, useState } from "react";
|
|
import type { ISystemLog } from "../../untils/types";
|
|
import {
|
|
IconDownload,
|
|
IconEye,
|
|
IconInfoCircle,
|
|
IconX,
|
|
} from "@tabler/icons-react";
|
|
import classes from "../Component.module.css";
|
|
import moment from "moment";
|
|
import type { Socket } from "socket.io-client";
|
|
import ModalLog from "../Modal/ModalLog";
|
|
|
|
function DrawerLogs({
|
|
socket,
|
|
isLogModalOpen,
|
|
setIsLogModalOpen,
|
|
testLogContent,
|
|
setTestLogContent,
|
|
}: {
|
|
socket: Socket | null;
|
|
isLogModalOpen: boolean;
|
|
setIsLogModalOpen: (value: React.SetStateAction<boolean>) => void;
|
|
testLogContent: string;
|
|
setTestLogContent: (value: React.SetStateAction<string>) => void;
|
|
}) {
|
|
const [opened, { open, close }] = useDisclosure(false);
|
|
const [systemLogs, setSystemLogs] = useState<ISystemLog[]>([]);
|
|
const [isDownloadLog, setIsDownloadLog] = useState(false);
|
|
// const [testLogContent, setTestLogContent] = useState("");
|
|
// const [isLogModalOpen, setIsLogModalOpen] = useState(false);
|
|
const [downloadName, setDownloadName] = useState("");
|
|
const [searchFileName, setSearchFileName] = useState("");
|
|
const [fromDate, setFromDate] = useState<Date | null>(null);
|
|
const [toDate, setToDate] = useState<Date | null>(null);
|
|
|
|
const [filteredLogs, setFilteredLogs] = useState<ISystemLog[]>([]);
|
|
|
|
useEffect(() => {
|
|
if (opened) {
|
|
socket?.emit("get_list_logs");
|
|
}
|
|
}, [opened]);
|
|
|
|
useEffect(() => {
|
|
socket?.on("list_logs", (files: string[]) => {
|
|
const list: ISystemLog[] = files.map((file) => {
|
|
const filename = file.replace(/^.*[\\/]/, "");
|
|
const createAt = filename.match(/\d{8}/);
|
|
return {
|
|
fileName:
|
|
file.split("/")[3] || file.split("/")[2] || file.split("/")[1],
|
|
createdAt: createAt ? createAt[0] : "N/A",
|
|
path: file,
|
|
};
|
|
});
|
|
setSystemLogs(
|
|
list.sort(
|
|
(a: ISystemLog, b: ISystemLog) =>
|
|
parseInt(b.createdAt) - parseInt(a.createdAt)
|
|
)
|
|
);
|
|
});
|
|
}, [socket]);
|
|
|
|
useEffect(() => {
|
|
if (isDownloadLog && testLogContent && downloadName) {
|
|
const blob = new Blob([testLogContent], { type: "text/plain" });
|
|
// Create a temporary link element
|
|
const link = document.createElement("a");
|
|
link.href = URL.createObjectURL(blob);
|
|
link.download = downloadName;
|
|
// Trigger the download by clicking the link
|
|
link.click();
|
|
// Clean up
|
|
URL.revokeObjectURL(link.href);
|
|
setIsDownloadLog(false);
|
|
setTestLogContent("");
|
|
setDownloadName("");
|
|
}
|
|
}, [testLogContent]);
|
|
|
|
useEffect(() => {
|
|
// Chuẩn bị trước các giá trị search/date để tránh tính lại trong filter cho từng phần tử
|
|
const trimmedSearch = searchFileName.trim().toLowerCase();
|
|
const hasSearch = trimmedSearch.length > 0;
|
|
const fromMoment = fromDate ? moment(fromDate).startOf("day") : null;
|
|
const toMoment = toDate ? moment(toDate).endOf("day") : null;
|
|
|
|
const delayDebounceFn = setTimeout(() => {
|
|
// Nếu không có filter nào, tránh filter tốn công, gán thẳng
|
|
if (!hasSearch && !fromMoment && !toMoment) {
|
|
setFilteredLogs(systemLogs);
|
|
return;
|
|
}
|
|
|
|
const next = systemLogs.filter((log) => {
|
|
if (hasSearch && !log.fileName.toLowerCase().includes(trimmedSearch)) {
|
|
return false;
|
|
}
|
|
|
|
const logDate = moment(log.createdAt, "YYYYMMDD");
|
|
if (fromMoment && !logDate.isSameOrAfter(fromMoment)) {
|
|
return false;
|
|
}
|
|
if (toMoment && !logDate.isSameOrBefore(toMoment)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
setFilteredLogs(next);
|
|
}, 500);
|
|
|
|
return () => clearTimeout(delayDebounceFn);
|
|
}, [searchFileName, fromDate, toDate, systemLogs]);
|
|
|
|
return (
|
|
<>
|
|
<Drawer
|
|
size={"50%"}
|
|
position="right"
|
|
style={{ position: "absolute", left: 0 }}
|
|
offset={8}
|
|
radius="md"
|
|
opened={opened}
|
|
onClose={close}
|
|
title={
|
|
<div>
|
|
<Tooltip
|
|
label={
|
|
<div>
|
|
Format:
|
|
<i style={{ marginLeft: "4px" }}>
|
|
YYYYMMDD-AUTO-Session.{`{Station name}`}-{`{Station ID}`}-
|
|
{`{Station IP}`}-{`{Line number}`}
|
|
.log
|
|
</i>
|
|
</div>
|
|
}
|
|
position="right"
|
|
>
|
|
<Text
|
|
fw={700}
|
|
style={{ display: "flex", alignItems: "center", gap: "6px" }}
|
|
>
|
|
List Logs <IconInfoCircle color="#3bb7e9" fontSize={"12px"} />
|
|
</Text>
|
|
</Tooltip>
|
|
</div>
|
|
}
|
|
>
|
|
<Grid>
|
|
<Grid.Col span={12}>
|
|
<Box mb="xs">
|
|
<Grid gutter="xs">
|
|
<Grid.Col span={6}>
|
|
<TextInput
|
|
placeholder="Search file name"
|
|
value={searchFileName}
|
|
onChange={(event) =>
|
|
setSearchFileName(event.currentTarget.value)
|
|
}
|
|
rightSection={
|
|
searchFileName ? (
|
|
<IconX
|
|
size={14}
|
|
style={{ cursor: "pointer" }}
|
|
onClick={() => setSearchFileName("")}
|
|
/>
|
|
) : null
|
|
}
|
|
rightSectionPointerEvents="auto"
|
|
size="xs"
|
|
/>
|
|
</Grid.Col>
|
|
<Grid.Col span={3}>
|
|
<DateInput
|
|
value={fromDate}
|
|
onChange={(value) => setFromDate(value as Date | null)}
|
|
placeholder="From date"
|
|
valueFormat="DD/MM/YYYY"
|
|
size="xs"
|
|
clearable
|
|
/>
|
|
</Grid.Col>
|
|
<Grid.Col span={3}>
|
|
<DateInput
|
|
value={toDate}
|
|
onChange={(value) => setToDate(value as Date | null)}
|
|
placeholder="To date"
|
|
valueFormat="DD/MM/YYYY"
|
|
size="xs"
|
|
clearable
|
|
/>
|
|
</Grid.Col>
|
|
</Grid>
|
|
</Box>
|
|
<ScrollArea h={"85vh"} style={{ marginTop: "15px" }}>
|
|
<Table
|
|
stickyHeader
|
|
striped
|
|
highlightOnHover
|
|
withRowBorders={true}
|
|
withTableBorder={true}
|
|
withColumnBorders={true}
|
|
>
|
|
<Table.Thead
|
|
style={{
|
|
top: 1,
|
|
}}
|
|
>
|
|
<Table.Tr>
|
|
<Table.Th style={{ width: "50%" }}>File name</Table.Th>
|
|
<Table.Th style={{ width: "30%" }}>Created at</Table.Th>
|
|
<Table.Th style={{ width: "10%" }}></Table.Th>
|
|
</Table.Tr>
|
|
</Table.Thead>
|
|
<Table.Tbody>
|
|
{filteredLogs.map((element) => (
|
|
<Table.Tr key={element.path}>
|
|
<Table.Td>{element.fileName}</Table.Td>
|
|
<Table.Td>
|
|
<Text>
|
|
{moment(element.createdAt).format("DD/MM/YYYY")}
|
|
</Text>
|
|
</Table.Td>
|
|
<Table.Td>
|
|
<Box
|
|
key={"action-" + element.fileName}
|
|
className={classes.optionIcon}
|
|
>
|
|
<IconEye
|
|
className={classes.viewIcon}
|
|
onClick={() => {
|
|
setTestLogContent("");
|
|
socket?.emit("get_content_log", {
|
|
line: { systemLogUrl: element.path },
|
|
});
|
|
setIsLogModalOpen(true);
|
|
}}
|
|
width={20}
|
|
/>
|
|
<IconDownload
|
|
className={[
|
|
classes.downloadIcon,
|
|
isDownloadLog ? classes.isDisabled : "",
|
|
].join(" ")}
|
|
onClick={() => {
|
|
socket?.emit("get_content_log", {
|
|
line: { systemLogUrl: element.path },
|
|
});
|
|
setIsDownloadLog(true);
|
|
setTestLogContent("");
|
|
setDownloadName(
|
|
element.path.split("/")[3] ||
|
|
element.path.split("/")[2] ||
|
|
element.path.split("/")[1]
|
|
);
|
|
}}
|
|
width={20}
|
|
/>
|
|
</Box>
|
|
</Table.Td>
|
|
</Table.Tr>
|
|
))}
|
|
</Table.Tbody>
|
|
</Table>
|
|
</ScrollArea>
|
|
</Grid.Col>
|
|
</Grid>
|
|
|
|
{isLogModalOpen && (
|
|
<ModalLog
|
|
opened={isLogModalOpen}
|
|
onClose={() => {
|
|
setIsLogModalOpen(false);
|
|
}}
|
|
testLogContent={testLogContent}
|
|
/>
|
|
)}
|
|
</Drawer>
|
|
|
|
<Button
|
|
fw={400}
|
|
style={{ height: "30px", width: "100px" }}
|
|
title="Add Scenario"
|
|
variant="outline"
|
|
// color="green"
|
|
onClick={() => {
|
|
open();
|
|
}}
|
|
>
|
|
List logs{" "}
|
|
</Button>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default DrawerLogs;
|