Local CPE (192.168.100.1) is always monitored. On startup, probe 192.168.1.1, 192.168.0.1, 192.168.8.1, and 10.0.0.1 in parallel and add whichever responds first as "Local Gateway".
149 lines
4.8 KiB
Markdown
149 lines
4.8 KiB
Markdown
NetWatch is an MIT-licensed JavaScript single-page application by
|
||
[@sneak](https://sneak.berlin) 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
|
||
|
||
```bash
|
||
# 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 transitions
|
||
- **`AppState`**: Top-level state container — WAN hosts, local hosts, pause
|
||
state, aggregate stats
|
||
- **`SparklineRenderer`**: 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 via `Promise.all`,
|
||
pushes samples, redraws UI. When paused, pushes blank markers (no probes, no
|
||
false outage)
|
||
|
||
### Monitoring targets
|
||
|
||
- **11 WAN hosts**: datavi.be, Anthropic API, OpenAI API, AWS, GCP, Azure,
|
||
DigitalOcean, Cloudflare, Fastly, Akamai, GitHub
|
||
- **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
|
||
- 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 `PORT` env var)
|
||
- Trusts `X-Forwarded-For` from 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
|
||
|
||
- **CORS**: Some hosts may block cross-origin HEAD requests. The app uses
|
||
`no-cors` mode 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](LICENSE).
|
||
|
||
## Author
|
||
|
||
[@sneak](https://sneak.berlin)
|