import { useState } from "react"; import { useTranslation } from "react-i18next"; import { useQuery } from "@tanstack/react-query"; import { fetchLogs, type AppLogRecord, type LogLevel } from "./api.js"; import { formatRelativeDateTime } from "./lib/format.js"; // Diagnostic log viewer (app_logs) — backend warn+ and frontend errors in one place. // Gated by log:read server-side. Filter by level / source / since; each row expands to // the structured context + stack. Read-only — logs are an evidence/diagnostic stream, // never edited. See wiki/concepts/app-logs.md. const LEVELS: LogLevel[] = ["trace", "debug", "info", "warn", "error", "fatal"]; /** Terminal-theme colour per level. */ const LEVEL_COLOR: Record = { trace: "text-term-muted", debug: "text-term-muted", info: "text-term-cyan", warn: "text-term-amber", error: "text-term-red", fatal: "text-term-red", }; function LogRow({ log }: { log: AppLogRecord }) { const { t } = useTranslation(); const [open, setOpen] = useState(false); const hasDetail = (log.context && Object.keys(log.context).length > 0) || log.stack; return (
{open && hasDetail && (
{log.path && (
{t("logs.path")}: {log.path}
)} {log.context && Object.keys(log.context).length > 0 && (
              {JSON.stringify(log.context, null, 2)}
            
)} {log.stack && (
              {log.stack}
            
)}
)}
); } export function LogsViewer() { const { t } = useTranslation(); const [level, setLevel] = useState(""); const [source, setSource] = useState(""); const [since, setSince] = useState(""); const [applied, setApplied] = useState<{ level?: string; source?: string; since?: string }>({}); const q = useQuery({ queryKey: ["logs", applied], queryFn: () => fetchLogs({ ...applied, limit: 500 }), refetchInterval: 15_000, // keep the booth view roughly live without a WS }); const logs = q.data?.logs ?? []; function apply() { setApplied({ level: level || undefined, source: source || undefined, since: since ? new Date(`${since}T00:00:00`).toISOString() : undefined, }); } function clear() { setLevel(""); setSource(""); setSince(""); setApplied({}); } return (

{t("logs.title")}

{t("logs.level")}
{t("logs.source")}
{t("logs.since")} setSince(e.target.value)} />
{q.isLoading ? (
{t("common.loading")}
) : logs.length === 0 ? (
{t("logs.empty")}
) : ( <>
{t("logs.time")} {t("logs.level")} {t("logs.source")} {t("logs.message")} {t("logs.status")}
{logs.map((log) => ( ))} )}
); }