import { useCallback, useEffect, useRef, useState } from "react"; import { keyServerClient } from "../api/keyServerClient"; export interface ClientErrorItem { id: number; message: string; stack?: string; source: string; url: string; user_agent?: string; user_id?: number; count: number; first_seen: string; last_seen: string; } const STORAGE_KEY = "omniai:admin-monitor-open"; const POLL_INTERVAL = 30000; function formatTime(iso: string) { const d = new Date(iso); return d.toLocaleString("zh-CN", { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit" }); } function AdminMonitor() { const [open, setOpen] = useState(() => { try { return sessionStorage.getItem(STORAGE_KEY) === "1"; } catch { return false; } }); const [errors, setErrors] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [loading, setLoading] = useState(false); const intervalRef = useRef>(); const fetchErrors = useCallback(async (p = 1) => { setLoading(true); try { const data = await keyServerClient.getClientErrors(p); setErrors(data.items); setTotal(data.total); setPage(p); } catch { /* silent */ } setLoading(false); }, []); useEffect(() => { if (!open) return; void fetchErrors(1); intervalRef.current = setInterval(() => fetchErrors(1), POLL_INTERVAL); return () => clearInterval(intervalRef.current); }, [open, fetchErrors]); useEffect(() => { try { sessionStorage.setItem(STORAGE_KEY, open ? "1" : "0"); } catch { /* */ } }, [open]); const maxPage = Math.max(1, Math.ceil(total / 50)); if (!open) { return ( ); } return (
客户端错误 ({total})
{errors.length === 0 ? (
暂无错误
) : ( errors.map((err) => (
{err.source} {err.message.slice(0, 120)} {err.count}
URL: {err.url}
User: {err.user_id || "匿名"}
{err.stack ?
{err.stack.slice(0, 1000)}
: null}
)) )}
{maxPage > 1 ? ( ) : null}
); } export default AdminMonitor;