import { useEffect, useMemo, useState } from 'react'; import { get } from '../lib/api'; import type { Player, PlayerLogEntry } from '../lib/types'; import { useApi } from '../lib/useApi'; import { PageHeader } from '../components/PageHeader'; import { Empty } from '../components/Empty'; import { absoluteTime, clip, relativeTime } from '../lib/format'; import { RefreshCw } from '../components/Icons'; export function PlayerLogs({ embedded }: { embedded?: boolean } = {}) { const { data: players } = useApi('/players?take=500'); const [pid, setPid] = useState(null); const [filter, setFilter] = useState(''); const [take, setTake] = useState(200); const [auto, setAuto] = useState(false); const [rows, setRows] = useState(null); const [loading, setLoading] = useState(false); const [err, setErr] = useState(null); const load = useMemo(() => async () => { if (pid === null) return; setLoading(true); setErr(null); try { const data = await get(`/players/${pid}/logs?take=${take}`); setRows(data); } catch (e) { setErr((e as Error).message); } finally { setLoading(false); } }, [pid, take]); useEffect(() => { load(); }, [load]); useEffect(() => { if (!auto) return; const id = setInterval(load, 5000); return () => clearInterval(id); }, [auto, load]); const filtered = (rows ?? []).filter(r => { if (!filter.trim()) return true; const f = filter.toLowerCase(); return r.path.toLowerCase().includes(f) || String(r.status).includes(f) || r.method.toLowerCase().includes(f) || r.host.toLowerCase().includes(f); }); return (
{!embedded && ( )}
{err &&
{err}
}
{pid === null && } {pid !== null && rows && filtered.length === 0 && ( )} {filtered.length > 0 && (
{filtered.map((r, i) => ( ))}
When Status Method Host Path ms
{relativeTime(r.timestamp)} = 500 ? 'text-danger' : r.status >= 400 ? 'text-warn' : 'text-success'}`}>{r.status} {r.method} {r.host} {clip(r.path + (r.query ? '?' + r.query : ''), 100)} {r.elapsedMs}
)}
); }