import Link from "next/link"; import { requireAdmin } from "@/lib/session"; import { getRoutingAnalytics, parseRange, rangeLabel } from "@/lib/queries/admin"; import { formatBytes, formatMs, formatNumber, formatPercent, formatUsd } from "@/lib/format"; import { PageHeader } from "@/components/ui/page-header"; import { Table, TableBody, TableCell, TableEmpty, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { NetworkBadge, Panel, ProviderBadge, RangeTabs, numCell } from "@/components/admin/primitives"; import { ProviderSeriesChart } from "@/components/admin/charts"; export const dynamic = "force-dynamic"; /** Mirrors SCORE_WEIGHTS in packages/routing/src/engine.ts (kept in sync by hand; web does not depend on @fetcha/routing). */ const WEIGHTS: Array<{ key: string; weight: number; what: string; how: string }> = [ { key: "success", weight: 35, what: "Historical success on this domain × route", how: "Bayesian-smoothed toward the network prior (datacenter 70 %, isp 85 %, residential 93 %, mobile 95 %) with 5 pseudo-observations, so new routes are not punished." }, { key: "cost", weight: 20, what: "Cost efficiency", how: "1 − price_per_gb / $15. Cheaper networks score higher; the ceiling is the most expensive class." }, { key: "latency", weight: 15, what: "Observed latency", how: "300 ms → 1.0, 5 s → 0, linear. Uses route stats when present, else 500 ms (datacenter) / 1 200 ms (others)." }, { key: "health", weight: 15, what: "Provider health", how: "Circuit-breaker health factor for the route key (recent failure window)." }, { key: "geo", weight: 10, what: "Geography match", how: "1 when the provider supports the requested country (or no country requested), 0 otherwise." }, { key: "session", weight: 5, what: "Session stability", how: "1 unless a sticky session is required and the provider cannot honour it (direct)." }, ]; export default async function AdminRoutingPage({ searchParams }: { searchParams: Promise> }) { await requireAdmin(); const sp = await searchParams; const range = parseRange(sp.range); const a = await getRoutingAnalytics(range); const totals = a.matrix.reduce((acc, r) => ({ requests: acc.requests + r.requests, successes: acc.successes + r.successes, cost: acc.cost + r.cost }), { requests: 0, successes: 0, cost: 0 }); return ( <> } /> Provider Network Requests Successes Success Blocked Errors Avg latency GB Cost Cost / success {a.matrix.length === 0 ? ( No routing metrics in this range. Metrics are written hourly by the API as attempts complete. ) : ( a.matrix.map((r) => ( {formatNumber(r.requests)} {formatNumber(r.successes)} {formatPercent(r.successRate)} {formatNumber(r.blocked)} {formatNumber(r.errors)} {formatMs(r.avgLatency)} {formatBytes(r.bytes)} {formatUsd(r.cost, true)} {formatUsd(r.costPerSuccess, true)} )) )}
Factor Weight Measures Computation {WEIGHTS.map((w) => ( {w.key} {w.weight} % {w.what} {w.how} ))}

Auto escalation

For network: "auto" candidates are grouped by class in the order datacenter → isp → residential → mobile (limited to the plan's networks), then sorted by score inside each class. A class is skipped entirely for a domain once its route stats show ≥ 5 attempts with > 60 % blocks (residential is never skipped). Providers are de-duplicated so retries alternate upstreams before repeating one.

Domain profiles

domain_profiles.route_stats {" "} feed the success and latency factors per route key. The admin policy can pin an explicit route order (wins over score, still bounded by circuit state) or force a network for auto requests. Explicit customer network choices are never overridden.

Circuit breakers

Each route key has a breaker. An open circuit removes the route from the candidate list entirely; a half-open one lets a trickle through. The breaker's recent failure ratio is also the 15 % health factor, so a degrading route loses score before it is cut off. Reset circuits from Providers.

); }