FE log component implementation

This commit is contained in:
Alex Holliday committed 2025-06-23 12:09:30 +08:00
1 parent 0b6febbb82
commit 365ae08123
9 files changed
+401 -160

No files matched your search

+35
View File
@@ -0,0 +1,35 @@
import { useState, useEffect } from "react";
import { networkService } from "../main";
import { createToast } from "../Utils/toastUtils";
import { useTranslation } from "react-i18next";
const useFetchLogs = () => {
const { t } = useTranslation();
const [logs, setLogs] = useState(undefined);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(undefined);
useEffect(() => {
const fetchLogs = async () => {
try {
setIsLoading(true);
const response = await networkService.getLogs();
setLogs(response.data.data);
createToast({
body: t("logsPage.toast.fetchLogsSuccess"),
});
} catch (error) {
setError(error);
createToast({
message: error.message,
});
} finally {
setIsLoading(false);
}
};
fetchLogs();
}, [t]);
return [logs, isLoading, error];
};
export { useFetchLogs };
+93
View File
@@ -0,0 +1,93 @@
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import Select from "../../../Components/Inputs/Select";
import Typography from "@mui/material/Typography";
import { useFetchLogs } from "../../../Hooks/logHooks";
import { useTheme } from "@emotion/react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
const formatLog = (theme, log, idx) => {
const levelColors = {
info: theme.palette.success.main,
warn: theme.palette.warning.main,
error: theme.palette.error.main,
debug: theme.palette.accent.main,
};
const color = levelColors[log.level] || theme.palette.primary.contrastText;
return (
<span key={idx}>
<span>[{log.timestamp}]</span>{" "}
<span style={{ color, fontWeight: "bold" }}>{log.level.toUpperCase()}</span>
{": "}
{`(${log.service})`}
{`(${log.method})`}
{": "}
{log.message}
<br />
</span>
);
};
const Logs = () => {
// Local state
const [logLevel, setLogLevel] = useState("all");
// Hooks
const theme = useTheme();
const { t } = useTranslation();
const [logs, isLoading, error] = useFetchLogs();
// Setup
const LOG_LEVELS = [
{ _id: "all", name: t("logsPage.logLevelSelect.values.all") },
{ _id: "info", name: t("logsPage.logLevelSelect.values.info") },
{ _id: "warn", name: t("logsPage.logLevelSelect.values.warn") },
{ _id: "error", name: t("logsPage.logLevelSelect.values.error") },
{ _id: "debug", name: t("logsPage.logLevelSelect.values.debug") },
];
return (
<Stack>
<Stack
direction="row"
alignItems="center"
gap={theme.spacing(4)}
>
<Typography>{t("logsPage.logLevelSelect.title")}</Typography>
<Select
items={LOG_LEVELS}
value={logLevel}
onChange={(e) => {
setLogLevel(e.target.value);
}}
/>
</Stack>
<Box
component="pre"
sx={{
fontFamily: "monospace",
color: theme.palette.primary.contrastText,
padding: 2,
borderRadius: 1,
overflowX: "auto",
whiteSpace: "pre-wrap",
wordWrap: "break-word",
}}
>
<code>
{logs
?.filter((log) => {
if (logLevel === "all") return true;
return log.level === logLevel;
})
.reverse()
.map((log, idx) => formatLog(theme, log, idx))}
</code>
</Box>
</Stack>
);
};
export default Logs;
@@ -23,8 +23,6 @@ const Metrics = ({ metrics = {} }) => {
return { key, title: camelToTitle(key), value: metrics[key] };
});
console.log(JSON.stringify(data, null, 2));
return (
<Stack gap={theme.spacing(2)}>
<Typography variant="h2">{t("queuePage.metricsTable.title")}</Typography>
+3 -2
View File
@@ -4,6 +4,7 @@ import Breadcrumbs from "../../Components/Breadcrumbs";
import Tabs from "@mui/material/Tabs";
import Tab from "@mui/material/Tab";
import Queue from "./Queue";
import LogsComponent from "./Logs";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
@@ -14,7 +15,7 @@ const Logs = () => {
const theme = useTheme();
// Local state
const [value, setValue] = useState(0);
const [value, setValue] = useState(1);
// Handlers
const handleChange = (event, newValue) => {
@@ -33,7 +34,7 @@ const Logs = () => {
<Tab label={t("logsPage.tabs.logs")} />
</Tabs>
{value === 0 && <Queue />}
{value === 1 && <div>Not yet</div>}
{value === 1 && <LogsComponent />}
</Stack>
);
};
+8
View File
@@ -1041,6 +1041,14 @@ class NetworkService {
});
return response;
}
async getLogs() {
return this.axiosInstance.get(`/logs`, {
headers: {
"Content-Type": "application/json",
},
});
}
}
export default NetworkService;
+1 -1
View File
@@ -1,5 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M21 5C21 6.65685 16.9706 8 12 8C7.02944 8 3 6.65685 3 5M21 5C21 3.34315 16.9706 2 12 2C7.02944 2 3 3.34315 3 5M21 5V19C21 20.66 17 22 12 22C7 22 3 20.66 3 19V5M21 12C21 13.66 17 15 12 15C7 15 3 13.66 3 12"
stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
</svg>

Before

Width:  |  Height:  |  Size: 419 B

After

Width:  |  Height:  |  Size: 421 B

+14 -1
View File
@@ -758,7 +758,20 @@
"title": "Logs",
"tabs": {
"queue": "Job queue",
"logs": "Logs"
"logs": "Server logs"
},
"toast": {
"fetchLogsSuccess": "Logs fetched successfully"
},
"logLevelSelect": {
"title": "Log level",
"values": {
"all": "All",
"info": "Info",
"warn": "Warn",
"error": "Error",
"debug": "Debug"
}
}
},
"queuePage": {