"use client"; import { useMemo } from "react"; import useSWR from "swr"; import { Bar, Line, Pie } from "react-chartjs-2"; import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, PointElement, LineElement, ArcElement, Tooltip, Legend, TimeScale, } from "chart.js"; import Link from "next/link"; ChartJS.register( CategoryScale, LinearScale, BarElement, PointElement, LineElement, ArcElement, Tooltip, Legend, TimeScale, ); type Analysis = import("@/lib/analyzeLogs").LogAnalysisResult; const fetcher = (url: string) => fetch(url).then((r) => r.json()); export default function SiteDashboard({ params }: { params: { dir: string } }) { const { data, error, isLoading } = useSWR( `/api/sites/${encodeURIComponent(params.dir)}/analysis`, fetcher, { refreshInterval: 60_000 } ); const topUrlsData = useMemo(() => { const labels = data?.topUrls.map((x) => x.url) ?? []; const values = data?.topUrls.map((x) => x.count) ?? []; return { labels, datasets: [{ label: "URL 访问次数", data: values, backgroundColor: "#60a5fa" }], }; }, [data]); const statusPieData = useMemo(() => { const labels = Object.keys(data?.statusCodeDistribution || {}); const values = Object.values(data?.statusCodeDistribution || {}); return { labels, datasets: [{ data: values, backgroundColor: ["#34d399", "#60a5fa", "#fbbf24", "#f87171", "#a78bfa"] }], }; }, [data]); const timelineData = useMemo(() => { const labels = (data?.timeAccess || []).map((p) => new Date(p.time).toLocaleString()); const values = (data?.timeAccess || []).map((p) => p.count); return { labels, datasets: [{ label: "每小时请求量", data: values, borderColor: "#34d399", backgroundColor: "#34d39933" }], }; }, [data]); const devicePieData = useMemo(() => { const entries = Object.entries(data?.deviceTypes || {}); return { labels: entries.map(([k]) => k), datasets: [{ data: entries.map(([, v]) => v), backgroundColor: ["#60a5fa", "#f472b6", "#fbbf24", "#a78bfa"] }], }; }, [data]); const provinceData = useMemo(() => { const entries = Object.entries(data?.provinceCityProportions || {}).sort((a, b) => b[1] - a[1]).slice(0, 15); return { labels: entries.map(([k]) => k), datasets: [{ label: "区域占比%", data: entries.map(([, v]) => v), backgroundColor: "#a78bfa" }], }; }, [data]); return (

{decodeURIComponent(params.dir)} · 访问分析

版本 {data?.version ?? "-"}

返回列表 原始日志
{isLoading &&

加载中...

} {error &&

加载失败:{String(error)}

} {data && (
{data.recentRawLog.join("\n")}
)}
); } function Stat({ label, value }: { label: string; value: number | string }) { return (
{label}
{value}
); } function Card({ title, children }: { title: string; children: React.ReactNode }) { return (

{title}

{children}
); } function BrowserOsTable({ kind, data, }: { kind: "browser" | "os"; data: Record>; }) { const rows = useMemo(() => { const out: { group: string; version: string; count: number }[] = []; Object.entries(data || {}).forEach(([g, versions]) => { Object.entries(versions).forEach(([v, c]) => out.push({ group: g, version: v, count: c })); }); return out.sort((a, b) => b.count - a.count).slice(0, 10); }, [data]); return (
{rows.map((r, i) => ( ))}
{kind === "browser" ? "浏览器" : "操作系统"} 版本 次数
{r.group} {r.version} {r.count}
); }