Skip to content

IFM Relay — Mesh Operator Dashboard Web App

Status: ✅ Core demo implemented (packages/sdk/examples/relay/) — this document is the full design spec for the production app.

Implemented today: a live observer dashboard that boots the IFM web core (the Rust protocol core in WASM) as a passive mesh observer and joins the SAME mesh the desktop apps run on (entry = rendezvous manifest, exactly like production). It shows stations on the discovery plane, connected peers, per-frequency traffic, packets-by-type, the shared station-agnostic relay pool, counters and a live traffic feed. Relays are passive infrastructure run by operators — the dashboard only observes; it never spawns relays.

The Relay app is a live operational dashboard for running an IFM relay node. It provides real-time mesh visibility, traffic analytics, health monitoring, and operational controls — think Datadog + Grafana + Netdata purpose-built for the IFM mesh.

This is the only app with a live dashboard — Station and Listener are consumer/producer apps; Relay is the operator's mission control.


Target Audience

  • Relay operators — Running ifm-relay nodes (bootstrap, regional, edge)
  • Network admins — Monitoring mesh health, capacity planning
  • DevOps/SRE — Alerting, incident response, post-mortem replay
  • Protocol developers — Debugging gossip, DHT, transport behavior

The Live Dashboard (Core Differentiator)

The dashboard is a real-time, multi-panel operational view updating at sub-second intervals. It's not a static admin page — it's a live cockpit.

Dashboard Layout (Desktop / Wide)

Panel Specifications

PanelRefreshData SourceInteractions
Mesh Topology1speers.list + peers.get (latency)Pan/zoom, click peer → detail drawer, geo/logical toggle, filter by region/transport/state
Frequency Table2sfrequencies.listSort any column, filter (type, listeners, broadcaster), search, export CSV, click → frequency detail
Traffic Sparklines500mspackets.stream (aggregated)Time range selector, pause, voice/text/video/control breakdown, packets/sec + bytes/sec
Health Gauges2snode.healthClick gauge → detail modal (sparklines, thresholds), configurable warn/crit levels
Alert FeedEvent-drivenalerts.stream (SSE)Acknowledge, dismiss, filter by severity, webhook test button, silence rules

Full Feature Set

1. Mesh Topology (/relay/topology)

Real-time peer graph with geographic intelligence

  • Force-directed graph (D3.js / Canvas) — Peers as nodes, connections as edges
  • Layout modes:
    • Geographic — Nodes positioned by GeoIP (lat/long), edges curved by great-circle
    • Logical — Force-directed by connection strength, clusters = sub-meshes
    • Hierarchical — Bootstrap → regional → edge layers
  • Node encoding:
    • Size = bandwidth (total in+out)
    • Color = health (green=healthy, yellow=degraded, red=critical, gray=disconnected)
    • Border = transport (solid=QUIC, dashed=WebRTC, dotted=TCP)
    • Label = peer ID prefix + region flag
  • Edge encoding:
    • Width = traffic volume
    • Color = latency (green<50ms, yellow<150ms, red>150ms)
    • Animation = packet flow direction (subtle pulse)
  • Controls: Pan/zoom, search peer ID, filter by region/transport/state, time-travel slider (historical replay)
  • Peer detail drawer (click node): Full PeerInfo, connection history, per-frequency traffic, actions (disconnect, ban, force reconnect)

2. Frequencies (/relay/frequencies)

Registry of all known frequencies with operational controls

  • Columns: Name, namespace, channel, type (public/protected/hidden), listener count, broadcaster count, bandwidth (in/out), codec, bitrate, last activity, local subscription state
  • Actions per row: Announce (inject into DHT), Hide (remove from DHT), Config (access type, capacity, TTL), Delete (force purge), Subscribe (relay joins frequency)
  • Bulk actions: Multi-select → announce/hide/delete/set-access-type
  • Create frequency modal: Name, type, initial TTL, capacity limit, access control list (for protected)

3. Traffic Analytics (/relay/traffic)

Deep-dive packet flow analysis

  • Real-time charts (500ms update):
    • Packets/sec (in/out, stacked by type: voice/text/video/control/plugin)
    • Bytes/sec (in/out)
    • Unique peers/sec (in/out)
    • Frequency count active
  • Aggregation windows: 1m, 5m, 15m, 1h, 24h (pre-aggregated in node)
  • Breakdown tables (drill-down):
    • By frequency: top 20 by packets, bytes, peers
    • By peer: top 20 producers/consumers
    • By type: voice vs text vs video vs control ratios
    • By TTL: distribution histogram (0-255)
    • By hops: distribution histogram
  • Anomaly detection: Auto-flag spikes (3σ), new frequency bursts, peer traffic anomalies
  • Export: CSV (current view), PCAP (raw packets via packets.stream + local buffer)

4. Health Monitoring (/relay/health)

System + node metrics with thresholds

GaugeSourceWarnCritDetail Modal
CPU %node.health.cpu_percent70%90%Per-core, process vs system, sparkline 1h
Memory %node.health.memory_bytes / total75%90%Heap, RSS, cache, GC pauses
Network Innode.health.network.bytes_in_per_sec80% NIC95% NICPer-interface, peer breakdown
Network Outnode.health.network.bytes_out_per_sec80% NIC95% NICPer-interface, peer breakdown
Connectionsnode.health.network.connections80% max95% maxBy transport, by state
Gossip Hit Ratenode.health.gossip.hit_rate<80%<60%Cache size, dup rate, TTL dist
Gossip Cachenode.health.gossip.cache_size80% max95% maxEviction rate, age distribution
DHT Routing Tablenode.health.dht.routing_table_size<500<200Bucket distribution, lookup latency
Audio Encodersnode.health.audio.encoder_activeN/AN/ABitrate, complexity, FEC, packets
Audio Decodersnode.health.audio.decoder_activeN/AN/AJitter buffer, underruns/overruns
  • Threshold config: Per-gauge warn/crit, notification channels (webhook, email, PagerDuty, Slack)
  • SLO dashboard: Availability (99.9%), latency p50/p95/p99, peer churn rate

5. Peer Management (/relay/peers)

Operational peer control plane

  • Actions: Disconnect (graceful), Ban (add to blocklist, persist), Force Reconnect (drop + dial), Delete (forget peer)
  • Bulk: Select multiple → disconnect/ban
  • Peer detail: Full connection history, per-frequency traffic, capability negotiation, transport fallback history
  • Blocklist management: View/edit persistent ban list, CIDR ranges, expiry

6. Frequency Control (/relay/frequency-control)

Operator control over frequency lifecycle

  • Announce frequency — Inject into DHT with TTL, capacity, access type
  • Hide frequency — Remove from DHT (graceful: notify subscribers first)
  • Set access type — Public / Protected (ACL) / Hidden (invite-only)
  • Capacity limits — Max listeners, max broadcasters, bandwidth cap per frequency
  • TTL management — Default TTL for gossip, per-frequency override
  • Bridge config — Relay ↔ external (RTMP/SRT/ICEcast) — future

7. Alerting (/relay/alerts)

Threshold + anomaly alerting with notification routing

Alert Rules (Configurable)

RuleConditionSeverityCooldown
Peer churn>10 peers disconnect in 1mWarning5m
Gossip hit rate<70% for 5mWarning10m
Gossip hit rate<50% for 2mCritical5m
CPU sustained>85% for 10mWarning15m
Memory leakRSS growth >10%/hr for 1hWarning30m
Bandwidth saturation>90% NIC for 5mCritical5m
DHT routing table<200 peersWarning10m
Audio underruns>5/min sustainedWarning5m
New frequency burst>20 new freq in 1mInfo1m

Notification Channels

  • Webhook (generic JSON POST) — Slack, Discord, PagerDuty, Opsgenie, custom
  • Email (SMTP) — Digest + immediate
  • In-app — Alert feed panel, browser notification API
  • SSE stream/api/alerts/stream for external consumers

Alert Feed UI

  • Live streaming list (newest top), color-coded by severity
  • Group by rule, expand for context (metric values, peer list, graph snapshot)
  • Actions: Acknowledge (assign to user), Silence (1h/24h/custom), Dismiss, Create silencing rule
  • History: Searchable, filterable, exportable

8. Historical Replay (/relay/replay)

Time-travel debugging — the killer feature

  • Timeline scrubber — Date/time picker + relative (last 1h/6h/24h/7d)
  • Synchronized panels — All dashboard panels (topology, frequencies, traffic, health) show state at selected timestamp
  • Playback controls — Play/pause, speed (0.25x/0.5x/1x/2x/5x), loop range
  • Event markers — Alerts, peer joins/leaves, frequency announcements, config changes
  • Export — Snapshot (JSON), video (WebM screen capture), report (PDF)
  • Use cases:
    • Post-mortem: "What happened at 14:23 when traffic dropped?"
    • Capacity planning: "Show me peak hour last Tuesday"
    • Debug: "Replay the cascade when peer X disconnected"

Architecture

JSON-RPC API (Relay Node → Dashboard)

MethodParamsReturnsDescription
node.info{}NodeInfoIdentity, version, uptime, listen addrs
node.health{}HealthMetricsCPU, mem, net, gossip, DHT, audio
peers.list{}PeerInfo[]All peers with connection state
peers.get{peer_id}PeerInfoDetailed peer info + history
peers.connect{peer_id, addrs[]}boolDial peer
peers.disconnect{peer_id}boolGraceful disconnect
peers.ban{peer_id, duration?, reason?}boolAdd to blocklist
frequencies.list{}FrequencyInfo[]All known frequencies
frequencies.announce{freq, type, ttl, capacity, acl?}boolInject into DHT
frequencies.hide{freq, graceful?}boolRemove from DHT
frequencies.set_access{freq, type, acl?}boolChange access type
packets.stream{filter?, aggregate?}PacketStreamSSE: aggregated or raw packets
metrics.stream{interval_ms?}MetricsStreamSSE: health metrics push
alerts.stream{severity?}AlertStreamSSE: alert events
replay.query{start_ms, end_ms, panels[]}ReplaySnapshotHistorical state at timestamp
config.get{}RelayConfigCurrent runtime config
config.set{key, value}boolUpdate config (hot-reload)

Project Structure

packages/sdk/examples/relay/
├── index.html
├── package.json
├── tsconfig.json
├── vite.config.ts
├── tailwind.config.js
├── public/
│   ├── wasm/                    # @ifm/sdk (for types only)
│   └── icons/
├── src/
│   ├── main.tsx
│   ├── App.tsx                  # Router + providers + WebSocket manager
│   ├── styles/globals.css
│   ├── components/
│   │   ├── ui/                  # Radix primitives
│   │   ├── layout/              # Header, Sidebar, TabBar, StatusBar
│   │   ├── topology/
│   │   │   ├── MeshGraph.tsx        # D3/Canvas force-directed graph
│   │   │   ├── PeerNode.tsx         # Node rendering (color, size, border)
│   │   │   ├── PeerEdge.tsx         # Edge rendering (width, color, pulse)
│   │   │   ├── PeerDetailDrawer.tsx # Click peer → slide-out panel
│   │   │   └── TopologyControls.tsx # Layout toggle, filters, time-travel
│   │   ├── frequencies/
│   │   │   ├── FrequencyTable.tsx   # TanStack Table + virtualization
│   │   │   ├── FrequencyActions.tsx # Announce/Hide/Config/Delete buttons
│   │   │   ├── CreateFrequencyModal.tsx
│   │   │   └── BulkActionsBar.tsx
│   │   ├── traffic/
│   │   │   ├── TrafficCharts.tsx    # Recharts/Visx real-time charts
│   │   │   ├── BreakdownTables.tsx  # Drill-down tables
│   │   │   ├── TimeRangeSelector.tsx
│   │   │   └── ExportButtons.tsx
│   │   ├── health/
│   │   │   ├── GaugeGrid.tsx        # Gauge components (react-gauge-chart)
│   │   │   ├── GaugeDetailModal.tsx # Click gauge → sparkline + thresholds
│   │   │   ├── ThresholdConfig.tsx  # Warn/crit per metric
│   │   │   └── SLOPanel.tsx         # Availability, latency SLOs
│   │   ├── peers/
│   │   │   ├── PeerTable.tsx
│   │   │   ├── PeerDetailModal.tsx
│   │   │   ├── BlocklistManager.tsx
│   │   │   └── BulkPeerActions.tsx
│   │   ├── alerts/
│   │   │   ├── AlertFeed.tsx        # Live streaming list (SSE)
│   │   │   ├── AlertRuleEditor.tsx
│   │   │   ├── NotificationConfig.tsx
│   │   │   └── AlertHistory.tsx
│   │   ├── replay/
│   │   │   ├── TimelineScrubber.tsx
│   │   │   ├── PlaybackControls.tsx
│   │   │   ├── SyncedPanels.tsx     # All panels at timestamp
│   │   │   ├── EventMarkers.tsx
│   │   │   └── ExportReplay.tsx
│   │   └── config/
│   │       ├── ConfigEditor.tsx     # TOML editor + validation
│   │       └── HotReloadIndicator.tsx
│   ├── pages/
│   │   ├── Topology.tsx
│   │   ├── Frequencies.tsx
│   │   ├── Traffic.tsx
│   │   ├── Health.tsx
│   │   ├── Peers.tsx
│   │   ├── Alerts.tsx
│   │   ├── Replay.tsx
│   │   └── Config.tsx
│   ├── hooks/
│   │   ├── useJSONRPC.ts            # RPC client + caching (TanStack Query)
│   │   ├── useSSE.ts                # SSE connection manager
│   │   ├── useMesh.ts               # Peer list + topology computed
│   │   ├── useFrequencies.ts        # Frequency list + actions
│   │   ├── useTraffic.ts            # Aggregated traffic metrics
│   │   ├── useHealth.ts             # Health gauges + thresholds
│   │   ├── useAlerts.ts             # Alert feed + rules
│   │   ├── useReplay.ts             # Historical query + playback
│   │   └── useConfig.ts             # Config get/set + validation
│   ├── services/
│   │   ├── jsonrpc.ts               # WebSocket JSON-RPC client
│   │   ├── sse.ts                   # EventSource wrapper + reconnect
│   │   ├── replay.ts                # IndexedDB + Parquet (WASM) storage
│   │   └── notifications.ts         # Web Push + browser notifications
│   ├── stores/
│   │   ├── topologyStore.ts         # Peer positions, layout mode
│   │   ├── frequencyStore.ts
│   │   ├── trafficStore.ts          # Time-series buffers
│   │   ├── healthStore.ts
│   │   ├── alertStore.ts
│   │   └── replayStore.ts
│   └── types/
│       ├── rpc.ts                   # JSON-RPC types
│       ├── topology.ts
│       ├── frequency.ts
│       ├── traffic.ts
│       ├── health.ts
│       ├── alerts.ts
│       └── replay.ts

Real-Time Data Flow

JSON-RPC (Request/Response + Polling)

typescript
// Polling for state that changes at known intervals
const peerQuery = useQuery({
  queryKey: ['peers'],
  queryFn: () => rpc.call('peers.list', {}),
  refetchInterval: 1000,  // 1s for topology
});

// Push for high-frequency data
const trafficSSE = useSSE('/api/packets/stream', {
  filter: { aggregate: true },  // Server aggregates to 500ms buckets
});
trafficSSE.on('data', (point) => trafficStore.push(point));

SSE (Server-Sent Events) for Streams

  • packets.stream → aggregated traffic metrics (500ms)
  • metrics.stream → health gauges (2s)
  • alerts.stream → alert events (immediate)
  • logs.stream → structured logs (optional, high volume)

Replay Engine (Local-First)

  • Browser stores last 24h of aggregated metrics in IndexedDB (Parquet via WASM parquet-wasm)
  • replay.query RPC fetches historical snapshots from node (node retains 7d Parquet)
  • Timeline scrubber reads from local IndexedDB (instant) → falls back to RPC for older data
  • Playback: animates through local buffer at configurable speed

Key User Flows

1. Daily Operations Check

2. Incident Response

3. Capacity Planning

4. Frequency Lifecycle


Responsive Breakpoints

BreakpointWidthLayout
Mobile< 640pxSingle panel view, bottom tab bar, swipe between tabs; topology = peer list
Tablet640–1024px2-col: main panel + sidebar (alerts/health); topology graph simplified
Desktop1024–1440pxFull 5-panel dashboard grid (as shown above)
Wide / Ops Wall> 1440pxAll panels visible, larger graphs, additional: SLO panel, replay minimap

Designed for operations walls — Relay dashboard is the app you put on a 4K monitor in the NOC.


Configuration

toml
# relay-dashboard.config.toml

[dashboard]
refresh_intervals = { topology = 1000, frequencies = 2000, traffic = 500, health = 2000 }
timezone = "UTC"
theme = "dark"  # "dark" | "light" | "auto"

[topology]
layout = "geo"  # "geo" | "logical" | "hierarchical"
max_peers_render = 500
show_disconnected = false
geoip_database = "public/geoip/mmdb"

[frequencies]
default_sort = "listeners_desc"
page_size = 50
show_hidden = true

[traffic]
aggregation_windows = [60, 300, 900, 3600, 86400]  # seconds
chart_retention_ms = 3600000  # 1h in browser

[health]
thresholds = {
  cpu_warn = 70, cpu_crit = 90,
  mem_warn = 75, mem_crit = 90,
  net_in_warn = 80, net_in_crit = 95,
  net_out_warn = 80, net_out_crit = 95,
  gossip_hit_warn = 80, gossip_hit_crit = 60,
  dht_size_warn = 500, dht_size_crit = 200,
}

[alerts]
rules = [
  { name = "peer_churn", metric = "peer_disconnects_per_min", condition = ">10", severity = "warning", cooldown = 300 },
  { name = "gossip_degraded", metric = "gossip_hit_rate", condition = "<70", severity = "warning", cooldown = 600 },
  { name = "gossip_critical", metric = "gossip_hit_rate", condition = "<50", severity = "critical", cooldown = 300 },
  { name = "bandwidth_sat", metric = "nic_utilization", condition = ">90", severity = "critical", cooldown = 300 },
]
notifications = {
  webhook = { url = "", secret = "" },
  email = { smtp = "", from = "", to = [] },
  slack = { webhook_url = "" },
  pagerduty = { integration_key = "" },
}

[replay]
local_retention_hours = 24
remote_retention_days = 7
parquet_compression = "zstd"

[node_connection]
ws_url = "ws://localhost:8080/ifm/jsonrpc"
reconnect_interval_ms = 2000
max_reconnect_attempts = 10
request_timeout_ms = 5000

Dependencies (Key)

PackagePurpose
react, react-domUI framework
react-router-domRouting
@tanstack/react-queryRPC caching, polling
zustandClient state
@radix-ui/*Accessible primitives
tailwindcssStyling
d3-force, d3-geo, d3-scaleTopology graph
canvas / roughjsHigh-perf graph rendering
recharts / visxReal-time charts
react-gauge-chartHealth gauges
date-fns / date-fns-tzTime handling
parquet-wasmReplay storage (IndexedDB)
eventsource-parserSSE parsing
viteBuild tool
vitest, playwrightTesting

Implementation Phases

Phase 1: Foundation

  • [ ] Vite + React + TypeScript + Tailwind scaffold
  • [ ] JSON-RPC WebSocket client with reconnection
  • [ ] SSE client for streams
  • [ ] Basic routing + tab layout
  • [ ] Node connection config + status indicator

Phase 2: Core Dashboard Panels

  • [ ] Topology: Peer list → D3 force graph (logical layout first)
  • [ ] Frequencies: Table with sort/filter/actions
  • [ ] Traffic: Real-time charts (Recharts) from SSE
  • [ ] Health: Gauge grid from SSE metrics
  • [ ] Alerts: Feed from SSE + rule editor

Phase 3: Operational Controls

  • [ ] Peer management (disconnect/ban/reconnect)
  • [ ] Frequency control (announce/hide/set-access)
  • [ ] Config editor (TOML + validation + hot-reload)
  • [ ] Blocklist manager

Phase 4: Historical Replay

  • [ ] IndexedDB + Parquet storage
  • [ ] Timeline scrubber + playback controls
  • [ ] Synced panel rendering at timestamp
  • [ ] Node replay RPC integration
  • [ ] Export (snapshot, video, report)

Phase 5: Geo + Advanced

  • [ ] Geographic topology layout (GeoIP + D3 geo)
  • [ ] Anomaly detection on traffic/health
  • [ ] SLO dashboard + burn rate alerts
  • [ ] Multi-node federation view (cluster of relays)
  • [ ] Mobile/tablet responsive polish

Phase 6: Polish & Hardening

  • [ ] Themes, keyboard shortcuts, accessibility
  • [ ] Web Push notifications for critical alerts
  • [ ] Performance: virtualized tables, canvas graph, WebWorkers for Parquet
  • [ ] E2E tests with simulated node
  • [ ] Documentation + runbooks

  • Station App — Broadcaster studio (content originates here, appears in Relay topology/frequencies)
  • Listener App — End-user consumption (consumes frequencies Relay manages)
  • Node UI — Terminal equivalent for SSH/local access
  • CLIifm relay subcommands for automation (native core, on ifm-core)
  • SDK API — Core protocol (Relay dashboard connects to native node via JSON-RPC)

Released under the MIT License.