Update controller get list log, lưu cache redis, fix UI
This commit is contained in:
parent
7ca2503508
commit
a69e904afa
|
|
@ -104,4 +104,57 @@ export default class LogsController {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async listSystemLogFiles({ request, response }: HttpContext) {
|
||||||
|
try {
|
||||||
|
const filename = request.input('filename', '')
|
||||||
|
const fromDate = request.input('from_date', '')
|
||||||
|
const toDate = request.input('to_date', '')
|
||||||
|
|
||||||
|
const systemLogsDir = path.join(DIRNAME, '..', '..', 'storage', 'system_logs')
|
||||||
|
|
||||||
|
// Nếu thư mục không tồn tại, trả về danh sách rỗng
|
||||||
|
if (!fs.existsSync(systemLogsDir)) {
|
||||||
|
return response.ok({
|
||||||
|
status: true,
|
||||||
|
data: [],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let files = fs.readdirSync(systemLogsDir)
|
||||||
|
|
||||||
|
// Lọc theo tên file nếu có
|
||||||
|
if (filename) {
|
||||||
|
files = files.filter((f) => f.includes(filename))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lọc theo khoảng thời gian nếu có
|
||||||
|
if (fromDate || toDate) {
|
||||||
|
const fromTime = fromDate ? new Date(fromDate).getTime() : 0
|
||||||
|
// Cộng thêm 24h để bao gồm cả ngày cuối cùng (end of day 23:59:59)
|
||||||
|
const toTime = toDate ? new Date(toDate).getTime() + 24 * 60 * 60 * 1000 : Date.now()
|
||||||
|
|
||||||
|
files = files.filter((f) => {
|
||||||
|
const filePath = path.join(systemLogsDir, f)
|
||||||
|
const stat = fs.statSync(filePath)
|
||||||
|
const fileTime = stat.mtime.getTime()
|
||||||
|
return fileTime >= fromTime && fileTime < toTime
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lấy 100 tên file đầu tiên từ danh sách lọc được
|
||||||
|
const result = files.slice(0, 100)
|
||||||
|
|
||||||
|
return response.ok({
|
||||||
|
status: true,
|
||||||
|
data: result,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
return response.internalServerError({
|
||||||
|
status: false,
|
||||||
|
message: 'Failed to list system log files',
|
||||||
|
error: error.message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -655,6 +655,21 @@ export default class LineConnection {
|
||||||
},
|
},
|
||||||
data,
|
data,
|
||||||
})
|
})
|
||||||
|
this.addHistory(
|
||||||
|
this.config.stationId,
|
||||||
|
this.config.id,
|
||||||
|
{
|
||||||
|
id: this.config.id,
|
||||||
|
number: this.config.lineNumber,
|
||||||
|
stationId: this.config.stationId,
|
||||||
|
pid: this.config.inventory?.pid || '',
|
||||||
|
sn: this.config.inventory?.sn || '',
|
||||||
|
vid: this.config.inventory?.vid || '',
|
||||||
|
scenario: '',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
},
|
||||||
|
this.outputTestLog,
|
||||||
|
)
|
||||||
// if (script?.send_result || script?.sendResult) {
|
// if (script?.send_result || script?.sendResult) {
|
||||||
this.dataDPELP = result
|
this.dataDPELP = result
|
||||||
console.log(
|
console.log(
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,9 @@ const parseLog = (data: string) => {
|
||||||
// Update current record with matched fields
|
// Update current record with matched fields
|
||||||
Object.keys(currentRecord).forEach((key) => {
|
Object.keys(currentRecord).forEach((key) => {
|
||||||
if (item && item[key] !== undefined) {
|
if (item && item[key] !== undefined) {
|
||||||
|
if (key === "pid") {
|
||||||
|
item[key] = item[key].replace("(", '').replace(")", '')
|
||||||
|
}
|
||||||
currentRecord[key] = item[key].trim()
|
currentRecord[key] = item[key].trim()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ router
|
||||||
router.get('list', '#controllers/logs_controller.list')
|
router.get('list', '#controllers/logs_controller.list')
|
||||||
router.post('viewLog', '#controllers/logs_controller.viewLog')
|
router.post('viewLog', '#controllers/logs_controller.viewLog')
|
||||||
router.post('downloadLog', '#controllers/logs_controller.downloadLog')
|
router.post('downloadLog', '#controllers/logs_controller.downloadLog')
|
||||||
|
router.get('listSystemLogFiles', '#controllers/logs_controller.listSystemLogFiles')
|
||||||
})
|
})
|
||||||
.prefix('api/logs')
|
.prefix('api/logs')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import {
|
||||||
Loader,
|
Loader,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { DateInput } from "@mantine/dates";
|
import { DateInput } from "@mantine/dates";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState, useRef } from "react";
|
||||||
import type { ISystemLog } from "../../untils/types";
|
import type { ISystemLog } from "../../untils/types";
|
||||||
import {
|
import {
|
||||||
IconDownload,
|
IconDownload,
|
||||||
|
|
@ -24,6 +24,7 @@ import classes from "../Component.module.css";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import type { Socket } from "socket.io-client";
|
import type { Socket } from "socket.io-client";
|
||||||
import ModalLog from "../Modal/ModalLog";
|
import ModalLog from "../Modal/ModalLog";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
function DrawerLogs({
|
function DrawerLogs({
|
||||||
socket,
|
socket,
|
||||||
|
|
@ -41,45 +42,73 @@ function DrawerLogs({
|
||||||
const [opened, { open, close }] = useDisclosure(false);
|
const [opened, { open, close }] = useDisclosure(false);
|
||||||
const [systemLogs, setSystemLogs] = useState<ISystemLog[]>([]);
|
const [systemLogs, setSystemLogs] = useState<ISystemLog[]>([]);
|
||||||
const [isDownloadLog, setIsDownloadLog] = useState(false);
|
const [isDownloadLog, setIsDownloadLog] = useState(false);
|
||||||
// const [testLogContent, setTestLogContent] = useState("");
|
const [loading, setLoading] = useState(false);
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [downloadName, setDownloadName] = useState("");
|
const [downloadName, setDownloadName] = useState("");
|
||||||
const [searchFileName, setSearchFileName] = useState("");
|
const [searchFileName, setSearchFileName] = useState("");
|
||||||
const [fromDate, setFromDate] = useState<Date | null>(null);
|
const [fromDate, setFromDate] = useState<Date | null>(null);
|
||||||
const [toDate, setToDate] = useState<Date | null>(null);
|
const [toDate, setToDate] = useState<Date | null>(null);
|
||||||
|
|
||||||
const [filteredLogs, setFilteredLogs] = useState<ISystemLog[]>([]);
|
const [filteredLogs, setFilteredLogs] = useState<ISystemLog[]>([]);
|
||||||
|
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
|
const apiUrl = import.meta.env.VITE_BACKEND_URL;
|
||||||
|
|
||||||
|
const fetchSystemLogFiles = async (
|
||||||
|
filename: string,
|
||||||
|
fromDateVal: Date | null,
|
||||||
|
toDateVal: Date | null,
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const params: Record<string, string> = {};
|
||||||
|
|
||||||
|
if (filename) {
|
||||||
|
params.filename = filename;
|
||||||
|
}
|
||||||
|
if (fromDateVal) {
|
||||||
|
params.from_date = moment(fromDateVal).format("YYYY-MM-DD");
|
||||||
|
}
|
||||||
|
if (toDateVal) {
|
||||||
|
params.to_date = moment(toDateVal).format("YYYY-MM-DD");
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await axios.get(`${apiUrl}api/logs/listSystemLogFiles`, {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data && response.data.data) {
|
||||||
|
const list: ISystemLog[] = response.data.data.map((file: string) => {
|
||||||
|
const filename = file.replace(/^.*[\\/]/, "");
|
||||||
|
const createAt = filename.match(/\d{8}/);
|
||||||
|
return {
|
||||||
|
fileName:
|
||||||
|
file.split("/")[3] ||
|
||||||
|
file.split("/")[2] ||
|
||||||
|
file.split("/")[1] ||
|
||||||
|
file.split("/")[0],
|
||||||
|
createdAt: createAt ? createAt[0] : "N/A",
|
||||||
|
path: file,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setSystemLogs(
|
||||||
|
list.sort(
|
||||||
|
(a: ISystemLog, b: ISystemLog) =>
|
||||||
|
parseInt(b.createdAt) - parseInt(a.createdAt),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch system log files:", error);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (opened) {
|
if (opened) {
|
||||||
socket?.emit("get_list_logs");
|
fetchSystemLogFiles("", null, null);
|
||||||
}
|
}
|
||||||
}, [opened]);
|
}, [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,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
setTimeout(() => {
|
|
||||||
setLoading(false);
|
|
||||||
}, 1000);
|
|
||||||
setSystemLogs(
|
|
||||||
list.sort(
|
|
||||||
(a: ISystemLog, b: ISystemLog) =>
|
|
||||||
parseInt(b.createdAt) - parseInt(a.createdAt)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}, [socket]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isDownloadLog && testLogContent && downloadName) {
|
if (isDownloadLog && testLogContent && downloadName) {
|
||||||
const blob = new Blob([testLogContent], { type: "text/plain" });
|
const blob = new Blob([testLogContent], { type: "text/plain" });
|
||||||
|
|
@ -98,40 +127,27 @@ function DrawerLogs({
|
||||||
}, [testLogContent]);
|
}, [testLogContent]);
|
||||||
|
|
||||||
useEffect(() => {
|
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ử
|
// Clear previous debounce timer
|
||||||
const trimmedSearch = searchFileName.trim().toLowerCase();
|
if (debounceTimerRef.current) {
|
||||||
const hasSearch = trimmedSearch.length > 0;
|
clearTimeout(debounceTimerRef.current);
|
||||||
const fromMoment = fromDate ? moment(fromDate).startOf("day") : null;
|
}
|
||||||
const toMoment = toDate ? moment(toDate).endOf("day") : null;
|
|
||||||
|
|
||||||
const delayDebounceFn = setTimeout(() => {
|
// Set new debounce timer for API call
|
||||||
// Nếu không có filter nào, tránh filter tốn công, gán thẳng
|
debounceTimerRef.current = setTimeout(() => {
|
||||||
if (!hasSearch && !fromMoment && !toMoment) {
|
fetchSystemLogFiles(searchFileName, fromDate, toDate);
|
||||||
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);
|
}, 500);
|
||||||
|
|
||||||
return () => clearTimeout(delayDebounceFn);
|
return () => {
|
||||||
}, [searchFileName, fromDate, toDate, systemLogs]);
|
if (debounceTimerRef.current) {
|
||||||
|
clearTimeout(debounceTimerRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [searchFileName, fromDate, toDate]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// API already returns filtered results, so just update filteredLogs
|
||||||
|
setFilteredLogs(systemLogs);
|
||||||
|
}, [systemLogs]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -289,7 +305,7 @@ function DrawerLogs({
|
||||||
setDownloadName(
|
setDownloadName(
|
||||||
element.path.split("/")[3] ||
|
element.path.split("/")[3] ||
|
||||||
element.path.split("/")[2] ||
|
element.path.split("/")[2] ||
|
||||||
element.path.split("/")[1]
|
element.path.split("/")[1],
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
width={20}
|
width={20}
|
||||||
|
|
|
||||||
|
|
@ -888,7 +888,7 @@ const ModalTerminal = ({
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
|
|
||||||
<Flex direction={"column"} h={"75%"}>
|
<Flex direction={"column"} h={"80vh"}>
|
||||||
<Box>
|
<Box>
|
||||||
<Flex gap={"sm"} justify={"center"} align={"center"}>
|
<Flex gap={"sm"} justify={"center"} align={"center"}>
|
||||||
<Text size="xl">
|
<Text size="xl">
|
||||||
|
|
@ -1087,7 +1087,7 @@ const ModalTerminal = ({
|
||||||
</Flex>
|
</Flex>
|
||||||
</Card>
|
</Card>
|
||||||
</Box>
|
</Box>
|
||||||
<Tabs.Panel value="general" h={"95%"}>
|
<Tabs.Panel value="general" h={"70vh"}>
|
||||||
<Flex
|
<Flex
|
||||||
justify={"space-between"}
|
justify={"space-between"}
|
||||||
direction={"column"}
|
direction={"column"}
|
||||||
|
|
@ -1249,6 +1249,7 @@ const ModalTerminal = ({
|
||||||
withBorder
|
withBorder
|
||||||
h={"fit-content"}
|
h={"fit-content"}
|
||||||
mt={"4px"}
|
mt={"4px"}
|
||||||
|
pb={10}
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue