"use client"; import { useState, useEffect, useMemo } from "react"; import { usePoll } from "@/lib/use-poll"; import { fetchAPI } from "@/lib/api"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { ScrollArea } from "@/components/ui/scroll-area"; import { cn } from "@/lib/utils"; interface LogFile { name: string; size?: string; modified?: string; } export default function LogsPage() { const { data: logsRaw } = usePoll("/api/logs", 60000); const logFiles = Array.isArray(logsRaw) ? logsRaw : (logsRaw?.files ?? []); const [selected, setSelected] = useState(null); const [content, setContent] = useState(""); const [loadingContent, setLoadingContent] = useState(false); const [search, setSearch] = useState(""); useEffect(() => { if (!selected) { setContent(""); return; } let cancelled = false; setLoadingContent(true); fetchAPI<{ content: string } | string>(`/api/logs/${encodeURIComponent(selected)}?tail=200`) .then((data) => { if (cancelled) return; setContent(typeof data === "string" ? data : data.content ?? ""); }) .catch((err) => { if (cancelled) return; setContent(`Error loading log: ${err}`); }) .finally(() => { if (!cancelled) setLoadingContent(false); }); return () => { cancelled = true; }; }, [selected]); const filteredLines = useMemo(() => { if (!content) return []; const lines = content.split("\n"); if (!search.trim()) return lines; const lower = search.toLowerCase(); return lines.filter(line => line.toLowerCase().includes(lower)); }, [content, search]); return (

Logs

{/* Left sidebar: log file list */} Log Files {logFiles.length === 0 ? (

Loading...

) : (
{logFiles.map((file) => ( ))}
)}
{/* Right: log content viewer */} {selected ?? "Select a log file"} {selected && ( setSearch(e.target.value)} placeholder="Filter lines..." className="rounded-md border border-border bg-background px-2 py-1 text-xs focus:outline-none focus:ring-1 focus:ring-primary w-48" /> )} {!selected ? (

Select a log file from the sidebar

) : loadingContent ? (

Loading...

) : (
                  {filteredLines.join("\n") || "No matching lines"}
                
)}
); }