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()]