import Link from "next/link"; import { BarChart3 } from "lucide-react"; import { COUNTRIES } from "@fetcha/core"; import { getWorkspace } from "@/lib/session"; import { formatMs, formatNumber, formatPercent } from "@/lib/format"; import { cn } from "@/lib/utils"; import { getCountries, getErrorBreakdown, getRequestSeries, getRequestStats, getSuccessByNetwork, getTopDomains, type Scope } from "@/lib/queries/dashboard"; import { PageHeader } from "@/components/ui/page-header"; import { Stat, StatGrid } from "@/components/ui/stat"; import { Card } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { EmptyState } from "@/components/ui/empty-state"; import { Table, TableBody, TableCell, TableEmpty, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { ChartFrame } from "@/components/dashboard/charts/chart-frame"; import { RequestsBarChart } from "@/components/dashboard/charts/requests-bar-chart"; import { LatencyLineChart } from "@/components/dashboard/charts/latency-line-chart"; import { HorizontalBarChart } from "@/components/dashboard/charts/horizontal-bar-chart"; import { CHART_COLORS, NETWORK_COLORS } from "@/components/dashboard/charts/chart-theme"; export const dynamic = "force-dynamic"; const RANGES = [ { key: "24h", label: "24 hours", hours: 24, bucket: "hour" as const }, { key: "7d", label: "7 days", hours: 24 * 7, bucket: "day" as const }, { key: "30d", label: "30 days", hours: 24 * 30, bucket: "day" as const }, ]; export default async function AnalyticsPage({ searchParams }: { searchParams: Promise> }) { const [ws, sp] = await Promise.all([getWorkspace(), searchParams]); const scope: Scope = { organizationId: ws.organization.id, projectId: ws.project.id }; const rangeKey = Array.isArray(sp.range) ? sp.range[0] : sp.range; const range = RANGES.find((r) => r.key === rangeKey) ?? RANGES[1]!; const now = new Date(); const from = range.bucket === "hour" ? new Date(now.getTime() - range.hours * 3_600_000) : new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() - (range.hours / 24 - 1))); const [stats, series, byNetwork, domains, errors, countries] = await Promise.all([ getRequestStats(scope, from), getRequestSeries(scope, from, range.bucket), getSuccessByNetwork(scope, from), getTopDomains(scope, from, 10), getErrorBreakdown(scope, from, 8), getCountries(scope, from, 10), ]); const hasData = stats.total > 0; const latencyHasData = series.some((p) => p.p50 !== null); return (
{RANGES.map((r) => ( {r.label} ))} } /> {!hasData ? ( {range.key !== "30d" ? ( ) : null} } /> ) : null}
p.success + p.failed > 0).map((p) => [range.bucket === "hour" ? p.t.slice(0, 16).replace("T", " ") : p.t.slice(0, 10), p.success, p.failed]) }} > p.p50 !== null).map((p) => [range.bucket === "hour" ? p.t.slice(0, 16).replace("T", " ") : p.t.slice(0, 10), formatMs(p.p50), formatMs(p.p95)]) }} >
n.requests > 0)} table={{ columns: ["Network", "Requests", "Success rate"], rows: byNetwork.map((n) => [n.network, n.requests, n.successRate === null ? "—" : formatPercent(n.successRate)]) }} > ({ label: n.network === "isp" ? "ISP" : n.network[0]!.toUpperCase() + n.network.slice(1), value: n.successRate ?? 0, color: NETWORK_COLORS[n.network] ?? CHART_COLORS.accent, hint: `${formatNumber(n.requests)} requests` }))} max={100} format="percent" labelWidth={100} /> [e.code, e.requests, formatPercent(e.percent, 1)]) }} > ({ label: e.code, value: e.requests, hint: `${formatPercent(e.percent, 1)} of failures` }))} color={CHART_COLORS.danger} format="number" labelWidth={168} />

Top domains

Most requested targets and how they behaved.

Domain Requests Success Avg latency Blocked {domains.length === 0 ? ( No domains in this period. ) : ( domains.map((d) => ( {d.domain} {formatNumber(d.requests)} {d.successRate === null ? "—" : formatPercent(d.successRate, 1)} {formatMs(d.avgLatencyMs)} 0 && "text-danger")}>{d.blocked || "—"} )) )}

Countries

Requested exit country.

Country Requests Success Latency {countries.length === 0 ? ( No requests in this period. ) : ( countries.map((c) => ( {c.country ? ( {c.country} {COUNTRIES[c.country] ?? ""} ) : ( Any (not specified) )} {formatNumber(c.requests)} {c.successRate === null ? "—" : formatPercent(c.successRate, 1)} {formatMs(c.avgLatencyMs)} )) )}
); }