From a69e904afa92ded0fbb93e332056527360ad8652 Mon Sep 17 00:00:00 2001 From: "andrew.ng" Date: Fri, 5 Jun 2026 08:08:55 +0700 Subject: [PATCH] =?UTF-8?q?Update=20controller=20get=20list=20log,=20l?= =?UTF-8?q?=C6=B0u=20cache=20redis,=20fix=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BACKEND/app/controllers/logs_controller.ts | 53 +++++++ BACKEND/app/services/line_connection.ts | 15 ++ .../app/ultils/templates/show_inventory.ts | 3 + BACKEND/start/routes.ts | 1 + FRONTEND/src/components/Drawer/DrawerLogs.tsx | 138 ++++++++++-------- .../src/components/Modal/ModalTerminal.tsx | 5 +- 6 files changed, 152 insertions(+), 63 deletions(-) diff --git a/BACKEND/app/controllers/logs_controller.ts b/BACKEND/app/controllers/logs_controller.ts index b51f688..099ba79 100644 --- a/BACKEND/app/controllers/logs_controller.ts +++ b/BACKEND/app/controllers/logs_controller.ts @@ -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, + }) + } + } } diff --git a/BACKEND/app/services/line_connection.ts b/BACKEND/app/services/line_connection.ts index d3904ce..ee827b1 100644 --- a/BACKEND/app/services/line_connection.ts +++ b/BACKEND/app/services/line_connection.ts @@ -655,6 +655,21 @@ export default class LineConnection { }, 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) { this.dataDPELP = result console.log( diff --git a/BACKEND/app/ultils/templates/show_inventory.ts b/BACKEND/app/ultils/templates/show_inventory.ts index c3bbf38..fce5ecc 100644 --- a/BACKEND/app/ultils/templates/show_inventory.ts +++ b/BACKEND/app/ultils/templates/show_inventory.ts @@ -36,6 +36,9 @@ const parseLog = (data: string) => { // Update current record with matched fields Object.keys(currentRecord).forEach((key) => { if (item && item[key] !== undefined) { + if (key === "pid") { + item[key] = item[key].replace("(", '').replace(")", '') + } currentRecord[key] = item[key].trim() } }) diff --git a/BACKEND/start/routes.ts b/BACKEND/start/routes.ts index 9f62ac2..774726e 100644 --- a/BACKEND/start/routes.ts +++ b/BACKEND/start/routes.ts @@ -32,6 +32,7 @@ router router.get('list', '#controllers/logs_controller.list') router.post('viewLog', '#controllers/logs_controller.viewLog') router.post('downloadLog', '#controllers/logs_controller.downloadLog') + router.get('listSystemLogFiles', '#controllers/logs_controller.listSystemLogFiles') }) .prefix('api/logs') diff --git a/FRONTEND/src/components/Drawer/DrawerLogs.tsx b/FRONTEND/src/components/Drawer/DrawerLogs.tsx index 56fe95a..6321930 100644 --- a/FRONTEND/src/components/Drawer/DrawerLogs.tsx +++ b/FRONTEND/src/components/Drawer/DrawerLogs.tsx @@ -12,7 +12,7 @@ import { Loader, } from "@mantine/core"; import { DateInput } from "@mantine/dates"; -import { useEffect, useState } from "react"; +import { useEffect, useState, useRef } from "react"; import type { ISystemLog } from "../../untils/types"; import { IconDownload, @@ -24,6 +24,7 @@ import classes from "../Component.module.css"; import moment from "moment"; import type { Socket } from "socket.io-client"; import ModalLog from "../Modal/ModalLog"; +import axios from "axios"; function DrawerLogs({ socket, @@ -41,45 +42,73 @@ function DrawerLogs({ const [opened, { open, close }] = useDisclosure(false); const [systemLogs, setSystemLogs] = useState([]); const [isDownloadLog, setIsDownloadLog] = useState(false); - // const [testLogContent, setTestLogContent] = useState(""); - const [loading, setLoading] = useState(true); + const [loading, setLoading] = useState(false); const [downloadName, setDownloadName] = useState(""); const [searchFileName, setSearchFileName] = useState(""); const [fromDate, setFromDate] = useState(null); const [toDate, setToDate] = useState(null); - const [filteredLogs, setFilteredLogs] = useState([]); + const debounceTimerRef = useRef(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 = {}; + + 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(() => { if (opened) { - socket?.emit("get_list_logs"); + fetchSystemLogFiles("", null, null); } }, [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(() => { if (isDownloadLog && testLogContent && downloadName) { const blob = new Blob([testLogContent], { type: "text/plain" }); @@ -98,40 +127,27 @@ function DrawerLogs({ }, [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; + // Clear previous debounce timer + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + } - 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); + // Set new debounce timer for API call + debounceTimerRef.current = setTimeout(() => { + fetchSystemLogFiles(searchFileName, fromDate, toDate); }, 500); - return () => clearTimeout(delayDebounceFn); - }, [searchFileName, fromDate, toDate, systemLogs]); + return () => { + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + } + }; + }, [searchFileName, fromDate, toDate]); + + useEffect(() => { + // API already returns filtered results, so just update filteredLogs + setFilteredLogs(systemLogs); + }, [systemLogs]); return ( <> @@ -289,7 +305,7 @@ function DrawerLogs({ setDownloadName( element.path.split("/")[3] || element.path.split("/")[2] || - element.path.split("/")[1] + element.path.split("/")[1], ); }} width={20} diff --git a/FRONTEND/src/components/Modal/ModalTerminal.tsx b/FRONTEND/src/components/Modal/ModalTerminal.tsx index c0a3da9..f07ddfe 100644 --- a/FRONTEND/src/components/Modal/ModalTerminal.tsx +++ b/FRONTEND/src/components/Modal/ModalTerminal.tsx @@ -888,7 +888,7 @@ const ModalTerminal = ({ - + @@ -1087,7 +1087,7 @@ const ModalTerminal = ({ - +