diff --git a/worker/src/components/dashboard/dashboard-shell.tsx b/worker/src/components/dashboard/dashboard-shell.tsx index 1f9854c..58c4e75 100644 --- a/worker/src/components/dashboard/dashboard-shell.tsx +++ b/worker/src/components/dashboard/dashboard-shell.tsx @@ -1,6 +1,5 @@ import { useEffect, useMemo, useState } from "react"; -import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; @@ -49,12 +48,6 @@ type AppVersionEntry = { packets: number; }; -type ColoEntry = { - colo: string; - reporters: number; - packets: number; -}; - type TrafficComposition = { human: number; overhead: number; @@ -90,7 +83,6 @@ type DashboardResponse = { chartPoints: ChartPoint[]; locationPoints: LocationPoint[]; appVersions: AppVersionEntry[]; - coloDistribution: ColoEntry[]; trafficComposition: TrafficComposition; multiHopRatio: MultiHopRatio; compositionOverTime: CompositionPoint[]; @@ -186,6 +178,8 @@ export function DashboardShell() { const comp = summary?.trafficComposition; const compTotal = comp ? comp.human + comp.overhead + comp.acks : 0; const humanPct = compTotal > 0 ? ((comp!.human / compTotal) * 100).toFixed(1) : "0"; + const nodeCount = summary?.uniqueDevices ?? 0; + const avgPerNode = nodeCount > 0 ? Math.round(totalPackets / nodeCount) : 0; return (
@@ -198,7 +192,8 @@ export function DashboardShell() {

MeshCore SAR

- Anonymous mesh network traffic overview. Location is derived from Cloudflare ingress metadata. + Aggregated observations from opt-in mesh nodes. Counts reflect what reporting nodes observed, not unique network packets. + Ratios and percentages are statistically valid; absolute numbers scale with reporter count.

@@ -228,14 +223,14 @@ export function DashboardShell() { {/* Key metrics */}
{/* Map + Traffic trend */} -
+
Reporter Locations @@ -275,8 +270,11 @@ export function DashboardShell() { - Traffic Over Time - Packets per {summary?.filter.bucket ?? "time"} bucket + Observations Over Time + + Per-node average observations per {summary?.filter.bucket ?? "time"} bucket. + Normalizing by reporter count removes the bias of more nodes = higher numbers. + {isLoading && !summary ? ( @@ -291,7 +289,7 @@ export function DashboardShell() {
{/* Protocol breakdown */} -
+
Protocol Packet Types @@ -329,7 +327,7 @@ export function DashboardShell() { style={{ width: `${pct}%` }} />
- {entry.total.toLocaleString()} + {entry.total.toLocaleString()} obs. @@ -342,7 +340,6 @@ export function DashboardShell() { -
Path Routing Modes @@ -374,31 +371,10 @@ export function DashboardShell() { - - - Reporting Regions - Geographic distribution of mesh nodes - - - {summary?.locationPoints.length ? ( -
- {dedupeLocations(summary.locationPoints).map((loc) => ( - - {loc.city} - {loc.country} - - ))} -
- ) : ( - - )} -
-
-
{/* Messages vs Overhead over time */} -
+
Messages vs Protocol Overhead @@ -418,7 +394,7 @@ export function DashboardShell() { Traffic Breakdown - What the mesh is carrying + Observed traffic composition across all reporting nodes {comp && compTotal > 0 ? ( @@ -454,12 +430,12 @@ export function DashboardShell() {
- {/* App versions + CF edge */} -
+ {/* App versions */} +
App Versions - Distribution of reporting app versions + Distribution of reporting app versions by node count and observations {summary?.appVersions.length ? ( @@ -471,7 +447,7 @@ export function DashboardShell() {
{entry.version} - {entry.reporters} {entry.reporters === 1 ? "node" : "nodes"} / {entry.packets.toLocaleString()} pkts + {entry.reporters} {entry.reporters === 1 ? "node" : "nodes"} / {entry.packets.toLocaleString()} obs.
@@ -489,32 +465,6 @@ export function DashboardShell() { )} - - - - Cloudflare Edge PoPs - Which Cloudflare datacenters are serving mesh traffic - - - {summary?.coloDistribution.length ? ( -
- {summary.coloDistribution.map((entry) => ( -
-
{entry.colo}
-
- {entry.reporters} {entry.reporters === 1 ? "node" : "nodes"} -
-
- {entry.packets.toLocaleString()} pkts -
-
- ))} -
- ) : ( - - )} -
-
@@ -641,27 +591,29 @@ function buildStackedAreaPath(xs: number[], topYs: number[], bottomYs: number[], const CHART_H = 240; const CHART_PAD = { top: 20, right: 16, bottom: 32, left: 48 }; -function TrafficChart({ points, maxValue }: { points: ChartPoint[]; maxValue: number }) { +function TrafficChart({ points, maxValue: _rawMax }: { points: ChartPoint[]; maxValue: number }) { const [hover, setHover] = useState(null); const count = points.length; if (count === 0) return null; - const innerW = 100; // we use viewBox percentages + // Normalize: per-node average per bucket + const normalized = points.map((p) => ({ + ...p, + perNode: p.reports > 0 ? Math.round(p.totalPackets / p.reports) : 0, + })); + const maxValue = Math.max(...normalized.map((p) => p.perNode), 1); + + const innerW = 100; const innerH = CHART_H - CHART_PAD.top - CHART_PAD.bottom; - // find peak - const peakIdx = points.reduce((best, p, i) => (p.totalPackets > points[best].totalPackets ? i : best), 0); + const peakIdx = normalized.reduce((best, p, i) => (p.perNode > normalized[best].perNode ? i : best), 0); - // Y axis grid: 4 nice lines const gridLines = niceGridLines(maxValue, 4); - // point positions (normalized 0-1) - const xs = points.map((_, i) => i / Math.max(count - 1, 1)); - const ys = points.map((p) => 1 - p.totalPackets / maxValue); + const xs = normalized.map((_, i) => i / Math.max(count - 1, 1)); + const ys = normalized.map((p) => 1 - p.perNode / maxValue); - // SVG path for the line const linePath = xs.map((x, i) => `${i === 0 ? "M" : "L"}${x * innerW},${ys[i] * innerH}`).join(" "); - // area path (closed to bottom) const areaPath = `${linePath} L${xs[xs.length - 1] * innerW},${innerH} L0,${innerH} Z`; // X-axis labels: show ~6 evenly spaced @@ -731,7 +683,7 @@ function TrafficChart({ points, maxValue }: { points: ChartPoint[]; maxValue: nu fontWeight={600} fontFamily="var(--font-sans)" > - {points[peakIdx].totalPackets.toLocaleString()} + {normalized[peakIdx].perNode.toLocaleString()} {/* X-axis labels */} @@ -797,8 +749,9 @@ function TrafficChart({ points, maxValue }: { points: ChartPoint[]; maxValue: nu left: `${(CHART_PAD.left + xs[hover] * innerW) / (innerW + CHART_PAD.left + CHART_PAD.right) * 100}%`, }} > -
{points[hover].totalPackets.toLocaleString()} packets
-
{points[hover].label}
+
{normalized[hover].perNode.toLocaleString()} avg/node
+
{normalized[hover].totalPackets.toLocaleString()} total from {normalized[hover].reports} {normalized[hover].reports === 1 ? "node" : "nodes"}
+
{normalized[hover].label}
)} @@ -942,16 +895,6 @@ function LeafletMap({ locations }: { locations: LocationPoint[] }) { ); } -function dedupeLocations(locations: LocationPoint[]) { - const seen = new Set(); - return locations.filter((loc) => { - const key = `${loc.city}-${loc.country}`; - if (seen.has(key)) return false; - seen.add(key); - return true; - }); -} - function formatRelative(value: string) { const diffMs = Date.now() - new Date(value).getTime(); const diffMin = Math.floor(diffMs / 60000); diff --git a/worker/worker/stats.ts b/worker/worker/stats.ts index 49dde03..331bfbe 100644 --- a/worker/worker/stats.ts +++ b/worker/worker/stats.ts @@ -177,12 +177,6 @@ export interface AppVersionEntry { packets: number; } -export interface ColoEntry { - colo: string; - reporters: number; - packets: number; -} - export interface TrafficComposition { human: number; overhead: number; @@ -212,7 +206,6 @@ export interface DashboardSummary { chartPoints: ChartPoint[]; locationPoints: LocationPoint[]; appVersions: AppVersionEntry[]; - coloDistribution: ColoEntry[]; trafficComposition: TrafficComposition; multiHopRatio: MultiHopRatio; compositionOverTime: CompositionPoint[]; @@ -328,7 +321,6 @@ export function summarizeRows( // per-version and per-colo accumulators const versionMap = new Map; packets: number }>(); - const coloMap = new Map; packets: number }>(); const compositionBuckets = new Map(); for (const row of rows) { @@ -381,16 +373,6 @@ export function summarizeRows( versionMap.set(ver, { reporters: new Set([row.device_key6]), packets: packetTotal }); } - // CF colo - const colo = row.cf_colo ?? "unknown"; - const coloEntry = coloMap.get(colo); - if (coloEntry) { - coloEntry.reporters.add(row.device_key6); - coloEntry.packets += packetTotal; - } else { - coloMap.set(colo, { reporters: new Set([row.device_key6]), packets: packetTotal }); - } - // composition over time (human = text + group_text, overhead = rest) const humanPackets = row.pt_02 + row.pt_05; const overheadPackets = packetTotal - humanPackets; @@ -446,9 +428,6 @@ export function summarizeRows( appVersions: [...versionMap.entries()] .map(([version, entry]) => ({ version, reporters: entry.reporters.size, packets: entry.packets })) .sort((left, right) => right.packets - left.packets), - coloDistribution: [...coloMap.entries()] - .map(([colo, entry]) => ({ colo, reporters: entry.reporters.size, packets: entry.packets })) - .sort((left, right) => right.packets - left.packets), trafficComposition: { human: humanTotal, overhead: overheadTotal, acks: acksTotal }, multiHopRatio: { direct: directTotal, multiHop: multiHopTotal }, compositionOverTime: [...compositionBuckets.entries()]