# Q5 — Monitoring dashboard for mithal.space A single container that continuously measures the availability and performance of **https://mithal.space** and serves a live dashboard of the results on port 8080. ``` q5-mithal-dashboard/ ├── collector/ │ ├── collect.py # measures latency, uptime, DNS, TLS, search — every 60s │ └── requirements.txt # requests (pinned); everything else is stdlib ├── dashboard/ │ ├── index.html # self-contained dashboard (Chart.js from CDN) │ └── data/metrics.json # rolling 48h JSON array, written by the collector ├── start.sh # entrypoint: collector in background + static server ├── Dockerfile └── README.md ``` **Why `data/` lives inside `dashboard/`:** the dashboard directory *is* the web root, so the page fetches `data/metrics.json` from its own origin. One container, one port, no CORS, no API layer. --- ## 1. What is collected Every 60 seconds `collector/collect.py` appends one record to the JSON array: ```json { "ts": "2026-07-26T15:46:40.381676+00:00", "up": true, "code": 200, "latency_ms": 848.58, "dns_ms": 0.67, "ssl_days_left": 50, "search_ms": 1056.08 } ``` | Field | How it is measured | |---|---| | `latency_ms` | timed `GET https://mithal.space`, 10 s timeout, redirects followed | | `up` | `true` when the status code is **200–399** | | `code` | the HTTP status code — `null` when the connection itself failed | | `dns_ms` | timed `socket.getaddrinfo("mithal.space", 443)` | | `ssl_days_left` | TLS handshake to `mithal.space:443`, cert `notAfter` parsed → days remaining | | `search_ms` | timed `GET https://mithal.space/search?q=test` (`null` if it errors or returns ≥ 400) | Every measurement is independent: a failure records `null` for that field only and never aborts the run or crashes the loop. Records older than **48 hours** are pruned on each write, and the file is written atomically (temp file + rename) so the dashboard never reads a half-written array. **Configuration** — constants at the top of `collect.py`, all overridable by env var: | Variable | Default | Meaning | |---|---|---| | `TARGET_URL` | `https://mithal.space` | site under test | | `SEARCH_URL` | `https://mithal.space/search?q=test` | search endpoint to time | | `INTERVAL_S` | `60` | seconds between collections | | `TIMEOUT_S` | `10` | per-request timeout | | `RETENTION_HOURS` | `48` | how much history to keep | | `METRICS_FILE` | `dashboard/data/metrics.json` | output path | --- ## 2. Run locally (without Docker) ```bash cd q5-mithal-dashboard python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate pip install -r collector/requirements.txt ``` One-shot collection (useful as a smoke test or under cron): ```bash python collector/collect.py --once ``` Continuous collection every 60 s — leave this running: ```bash python collector/collect.py ``` In a second terminal, serve the dashboard (the `data/` directory must be inside the served root, which it is): ```bash cd q5-mithal-dashboard/dashboard && python -m http.server 8080 ``` Open . > Opening `index.html` straight from disk does **not** work — browsers block > `fetch()` over `file://`. The page detects this and shows an explanatory banner > instead of failing silently. --- ## 3. The dashboard | Element | Detail | |---|---| | Status badge | green `UP` / red `DOWN` from the newest record, with HTTP code and timestamp | | Uptime tile | `up_checks / total_checks × 100` over the **last 24 h**, one decimal | | TLS card | "*X* days remaining" — green > 30, yellow 8–30, red ≤ 7 (`Expired` at ≤ 0) | | Latest response | newest `latency_ms`, with `search_ms` and `dns_ms` underneath | | Chart | `latency_ms` (solid blue) and `search_ms` (dashed purple) for the **last hour**; failed checks draw gaps, red points mark down checks | | Table | last 10 checks — time, ✅/❌, code, latency, DNS, search | | Refresh | re-fetches every 60 s; last-updated clock in the header | Empty, missing, or corrupt data renders a "no data yet" state on every tile plus a banner explaining what to do — it never throws. To point the page elsewhere, edit the one constant at the top of the `