Detect mobile viewport (window.innerWidth < 768) at startup and show a centered 'Not yet available on mobile' message instead of the full monitoring UI. All polling, gateway detection, and network requests are skipped entirely on mobile viewports. Desktop behavior is completely unchanged — the mobile check is the very first thing in init() and returns early before any other setup runs.
5.3 KiB
NetWatch is an MIT-licensed JavaScript single-page application by @sneak that provides real-time network latency monitoring to common internet hosts, displayed with color-coded figures and sparkline graphs, served from a static bucket or Docker container.
Getting Started
# Install dependencies
yarn install
# Development server
yarn dev
# Production build
yarn build
# Preview production build
yarn preview
# Docker
docker build -t netwatch .
docker run -p 8080:8080 netwatch
Rationale
When debugging network issues, it's useful to have a persistent at-a-glance view of latency and reachability to multiple well-known internet endpoints. NetWatch provides this as a zero-dependency SPA that can be deployed anywhere static files are served, with no backend required.
Design
The application is a single-page app built with Vite and Tailwind CSS v4. All
code lives in src/main.js with a class-based architecture:
CONFIG: Frozen configuration object (update interval, timeouts, axis ticks, etc.)HostState: Per-host state management — history buffer, latency tracking, status transitionsAppState: Top-level state container — WAN hosts, local hosts, pause state, aggregate statsSparklineRenderer: Canvas 2D sparkline drawing with fixed axes, color-coded line segments, error regions, and DPR-aware scaling- UI functions:
buildUI()constructs the DOM,updateHostRow()/updateSummary()/updateHealthBox()handle incremental updates tick(): Main loop — measures all hosts in parallel viaPromise.all, pushes samples, redraws UI. When paused, pushes blank markers (no probes, no false outage)
Monitoring targets
- 22 WAN hosts: datavi.be, Anthropic API, OpenAI API, AWS Console, GCP Console, Azure, Cloudflare, Fastly, Akamai, GitHub, B2, 7 S3 regional endpoints (Cape Town, London, Bahrain, Tokyo, Sydney, Oregon, São Paulo), 4 GCS locational endpoints (Iowa, Belgium, Singapore, Sydney)
- Local CPE: Cable modem at 192.168.100.1 (always monitored)
- Local Gateway: Auto-detected on startup by probing common default gateway addresses (192.168.1.1, 192.168.0.1, 192.168.8.1, 10.0.0.1); first responder wins. Note: modern browsers enforce Private Network Access restrictions that block public-origin pages from reaching RFC1918 addresses, so local targets only work when NetWatch is served from localhost or a private address.
Local hosts are tracked separately from WAN stats.
Latency measurement
HEAD requests with mode: 'no-cors' and cache: 'no-store', timed with
performance.now(). 1-second timeout; anything over 1000ms is clamped to
unreachable. IPv4 only.
Color coding
| Latency | Color |
|---|---|
| < 50ms | Green |
| < 100ms | Lime |
| < 200ms | Yellow |
| < 500ms | Orange |
| >= 500ms | Red |
| Unreachable | Gray |
Output structure
dist/
├── index.html
└── assets/
├── index-*.css
└── index-*.js
Features
- Real-time monitoring with 2s update interval and 300s history sparklines
- Health indicator: green (HEALTHY) or red (DEGRADED) based on WAN reachability
- Summary stats: reachable count, min/max/avg latency across WAN hosts only
- Fixed chart axes: Y-axis 0–1000ms, X-axis 0–300s
- Color-coded latency figures and sparkline line segments
- Play/pause: pause stops probes but history keeps scrolling (blank gaps, no false outage)
- Clickable service URLs
- Canvas-based sparkline rendering with devicePixelRatio scaling
- Mobile detection: viewports narrower than 768px show a friendly "not yet available on mobile" message instead of the monitoring UI (no polling or network requests on mobile)
- Zero runtime dependencies: all resources bundled into build artifacts
Deployment
After running yarn build, deploy the contents of the dist/ directory to any
static file host (S3, GCS, Cloudflare Pages, Vercel, Netlify, GitHub Pages) or
use the Docker image behind a reverse proxy.
The Docker image:
- Listens on port 8080 by default (override with
PORTenv var) - Trusts
X-Forwarded-Forfrom RFC1918 reverse proxies (10/8, 172.16/12, 192.168/16) - Sends access logs to stdout
- Caches static assets with immutable headers
Browser Compatibility
Requires a modern browser with ES modules, Fetch API, Canvas API, and CSS custom properties.
Limitations
- Mobile: Viewports below 768px wide show a static "not yet available" message. The full monitoring UI requires a desktop-width browser.
- CORS: Some hosts may block cross-origin HEAD requests. The app uses
no-corsmode which allows the request but provides opaque responses. Latency is still measurable based on request timing. - Local gateway: The 192.168.100.1 endpoint requires the host to be accessible from your network.
- Network conditions: Measurements reflect browser-to-endpoint latency, which includes your local network, ISP, and internet routing.
TODO
- Add unit tests
- Add eslint for JS linting (currently lint target runs prettier only)
- Add configurable host list (environment variable or config file)
- Add latency history export (CSV/JSON)
- Add notification/alert when status changes to DEGRADED
License
MIT. See LICENSE.