diff --git a/apps/api/src/controllers/data.ts b/apps/api/src/controllers/data.ts index dc07d860e..af578e7fb 100644 --- a/apps/api/src/controllers/data.ts +++ b/apps/api/src/controllers/data.ts @@ -74,4 +74,24 @@ export function dataRoutes(fastify: FastifyInstance) { } } ); + + // Get all logs + fastify.get( + "/api/v1/data/logs", + async (request: FastifyRequest, reply: FastifyReply) => { + const bearer = request.headers.authorization!.split(" ")[1]; + const token = checkToken(bearer); + + if (token) { + try { + const logs = await import("fs/promises").then((fs) => + fs.readFile("logs.log", "utf-8") + ); + reply.send({ logs: logs }); + } catch (error) { + reply.code(500).send({ error: "Failed to read logs file" }); + } + } + } + ); } diff --git a/apps/client/layouts/adminLayout.tsx b/apps/client/layouts/adminLayout.tsx index 9141a306e..371c9421e 100644 --- a/apps/client/layouts/adminLayout.tsx +++ b/apps/client/layouts/adminLayout.tsx @@ -9,6 +9,7 @@ import { import { Button } from "@radix-ui/themes"; import { ContactIcon, + FileText, KeyRound, Mail, Mailbox, @@ -81,6 +82,12 @@ export default function AdminLayout({ children }: any) { current: location.pathname === "/admin/authentication", icon: KeyRound, }, + { + name: "Logs", + href: "/admin/logs", + current: location.pathname === "/admin/logs", + icon: FileText, + }, ]; return ( diff --git a/apps/client/pages/admin/logs.tsx b/apps/client/pages/admin/logs.tsx new file mode 100644 index 000000000..745bfead1 --- /dev/null +++ b/apps/client/pages/admin/logs.tsx @@ -0,0 +1,82 @@ +import { Card, CardContent, CardHeader, CardTitle } from "@/shadcn/ui/card"; +import { getCookie } from "cookies-next"; +import { useEffect, useState } from "react"; + +const Logs = () => { + const [logs, setLogs] = useState([]); + const [loading, setLoading] = useState(true); + + const fetchLogs = async () => { + const response = await fetch("/api/v1/data/logs", { + headers: { + Authorization: `Bearer ${getCookie("session")}`, + }, + }); + const data = await response.json(); + + // Split logs by newline and parse each line as JSON + const parsedLogs = data.logs + .split("\n") + .filter((line) => line.trim()) // Remove empty lines + .map((line) => { + try { + return JSON.parse(line); + } catch (e) { + console.error("Failed to parse log line:", e); + return null; + } + }) + .filter((log) => log !== null) // Remove any + .sort((a, b) => b.time - a.time); + + setLogs(parsedLogs); + setLoading(false); + }; + + useEffect(() => { + fetchLogs(); + }, []); + + if (loading) { + return
Loading...
; + } + + return ( +
+ + + + Logs + + + {logs.length === 0 ? ( +
No logs available
+ ) : ( +
    + {logs.map((log, index) => ( +
  • +
    + + {new Date(log.time).toLocaleString()} + + {log.msg} +
    +
  • + ))} +
+ )} +
+
+
+ ); +}; + +export default Logs;