"use client"; import { Suspense, useCallback, useEffect, useState } from "react"; import { useSearchParams } from "next/navigation"; import { AdminPager, AdminSegmented } from "@/components/admin"; import { AdminChartCard } from "@/components/admin/charts"; import AdminSearchField from "@/components/admin/AdminSearchField"; import { apiStatsEvents, type VisitEventItem, type VisitEventsResult } from "@/lib/api"; import { StatsState, TableEmpty, deviceLabel, kindLabel, refKindLabel } from "../ui"; const PAGE_SIZE = 50; const KIND_OPTIONS = [ { key: "", label: "全部" }, { key: "pageview", label: "浏览" }, { key: "bot", label: "爬虫" }, { key: "probe", label: "探测" }, ]; /** 本地日期(yyyy-mm-dd)→ RFC3339,覆盖全天 */ function dayToRFC3339(date: string, endOfDay = false): string { if (!date) return ""; return new Date(`${date}T${endOfDay ? "23:59:59" : "00:00:00"}`).toISOString(); } function kindBadge(kind: string) { const map: Record = { pageview: { color: "var(--accent)", soft: "var(--accent-soft)" }, bot: { color: "var(--gold)", soft: "var(--gold-soft)" }, probe: { color: "var(--danger)", soft: "var(--danger-soft, var(--panel-2))" }, }; const s = map[kind] ?? { color: "var(--ink-3)", soft: "var(--panel-2)" }; return ( {kindLabel(kind)} ); } function DetailsInner() { const searchParams = useSearchParams(); // 初始筛选只读一次(如从「爬虫与安全」跳转 ?ip=x.x.x.x) const [initialIp] = useState(() => searchParams.get("ip") ?? ""); const [kind, setKind] = useState(""); const [path, setPath] = useState(""); const [ip, setIp] = useState(initialIp); const [bot, setBot] = useState(""); const [start, setStart] = useState(""); const [end, setEnd] = useState(""); const [page, setPage] = useState(1); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); const load = useCallback( (f: { kind: string; path: string; ip: string; bot: string; start: string; end: string }, p: number) => { setLoading(true); apiStatsEvents({ kind: f.kind || undefined, path: f.path || undefined, ip: f.ip || undefined, bot: f.bot || undefined, start: dayToRFC3339(f.start) || undefined, end: dayToRFC3339(f.end, true) || undefined, page: p, size: PAGE_SIZE, }) .then((d) => { setData(d); setError(""); }) .catch(() => setError("加载失败,请稍后重试")) .finally(() => setLoading(false)); }, [] ); useEffect(() => { load({ kind, path, ip, bot, start, end }, page); // eslint-disable-next-line react-hooks/exhaustive-deps }, [load, kind, path, ip, bot, start, end, page]); const resetPage = () => setPage(1); return (

记录含浏览、爬虫与异常请求;访客标识仅存哈希,IP 与登录日志口径一致,仅超管可见。

{ setKind(k); resetPage(); }} ariaLabel="事件类型筛选" role="group" size="sm" />
{ setPath(v); }} onSubmit={resetPage} onClear={() => { setPath(""); resetPage(); }} placeholder="路径模糊匹配,回车应用" ariaLabel="按路径筛选" />
{ setIp(""); resetPage(); }} placeholder="按 IP 筛选,回车应用" ariaLabel="按 IP 筛选" />
{ setBot(""); resetPage(); }} placeholder="按爬虫名筛选" ariaLabel="按爬虫名筛选" />
{error ? : null} {data && data.items.length ? ( <>
{data.items.map((e: VisitEventItem) => ( ))}
时间 类型 路径 来源 设备 / 系统 / 浏览器 用户 IP 状态
{new Date(e.created_at).toLocaleString("zh-CN", { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit" })} {kindBadge(e.kind)} {e.bot ? ( {e.bot} ) : null} {e.path || "/"} {e.ref_host ? ( <> {e.ref_host} {refKindLabel(e.ref_kind)} ) : ( refKindLabel(e.ref_kind) )} {deviceLabel(e.device)} · {e.os || "?"} · {e.browser || "?"} {e.username || 游客} {e.ip || "—"} = 400 ? "var(--danger)" : "var(--ink-3)" }}> {e.status > 0 ? e.status : "—"}
) : ( )}
); } export default function DetailsPage() { return ( }> ); }