clawbot 4a7bdf8f61
All checks were successful
check / check (push) Successful in 26s
chore: root .editorconfig and hardened .gitignore (closes #15)
Three root-level dotfiles diverged from the org models. Two are fixed
here; the third is deferred for a reason spelled out below.

Move backend/.editorconfig to the repo root. The file is byte-identical
to the org model, so this is a pure relocation with no content change.
It carries root = true, which one level down was actively harmful: it
stopped editors walking further up, leaving the entire frontend
(src/, index.html, vite.config.js, nginx.conf, script/) with no
indentation settings at all. At the root the same file covers the whole
tree, backend included, so the subdirectory copy is redundant.

Replace .gitignore with the org model verbatim, then re-append the two
repo-specific entries the model does not carry: dist/ (Vite output) and
*.log. This adds the OS entry Thumbs.db, the entire Editors section
(*.swp, *.swo, *~, *.bak, .idea/, .vscode/, *.sublime-*), and the
Environment / secrets section (.env, .env.*, *.pem, *.key).

The secrets section is the substantive part. The backend loads .env via
godotenv and only backend/.gitignore ignored it, so a .env at the repo
root was untracked but unignored -- one git add -A away from being
committed. Policy allows no exceptions there.

Not done here: excluding .git from .dockerignore. Both images read git
metadata at build time. Dockerfile.backend has an explicit
COPY .git /repo/.git feeding git describe in backend/Makefile, and the
frontend Dockerfile's make check runs vite, whose config calls
git rev-parse at config-eval time. Ignoring .git breaks both builds
outright rather than degrading them, and decoupling them from git
metadata belongs to the Dockerfile rework in #17. Deferred deliberately,
not overlooked; tracked separately so it is not lost.

No tracked file becomes ignored by the new patterns: git ls-files
differs only by the .editorconfig relocation, and check-ignore over the
full tracked set matches nothing.
2026-08-09 04:59:24 +00:00
2026-07-07 02:14:16 +02:00
2026-07-07 02:14:16 +02:00
2026-07-07 02:14:16 +02:00

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

Entrypoints

This repository adheres to the Scripts to Rule Them All standard: normalized scripts in script/ are the entrypoints for the development workflow, and the Makefile targets are thin shims that call them. We provide:

  • script/bootstrap — install all dependencies (pinned node via nvm if needed, yarn via corepack, yarn install --frozen-lockfile)
  • script/setup — make a fresh clone ready for development: bootstrap plus the git pre-commit hook
  • script/projectname — print the project name (used for the Docker image tag)
  • script/test — run the production build as the test (no unit tests yet)
  • script/lint — run prettier in check mode
  • script/fmt — format all files (writes)
  • script/fmt-check — check formatting (read-only)
  • script/check — run test, lint, and fmt-check
  • script/docker — build the Docker image tagged via script/projectname
  • script/cibuild — CI entrypoint: plain docker build .
  • script/precommit — run by the git pre-commit hook; runs script/check
  • script/install-precommit — install the git pre-commit hook

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

  • 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 01000ms, X-axis 0300s
  • 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.

Author

@sneak

Description
No description provided
Readme MIT 699 KiB
Languages
JavaScript 58.6%
Go 26.3%
Shell 8.4%
CSS 3.1%
Makefile 2%
Other 1.6%