الملفات

240 أسطر
8.9 KiB
Markdown
خام الرابط الدائم اللوم التاريخ

هذا الملف يحتوي على أحرف Unicode غامضة

هذا الملف يحتوي على أحرف Unicode قد تُخلط مع أحرف أخرى. إذا كنت تعتقد أن هذا مقصود، يمكنك تجاهل هذا التحذير بأمان. استخدم زر الهروب للكشف عنها.

# Q5 — Monitoring dashboard for mithal.space
A single container that continuously measures the availability and performance of
**https://mithal.space** and serves a monitoring 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 **200399** |
| `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 <http://localhost:8080/index.html>.
> 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 830, 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 `<script>`:
```js
const DATA_URL = 'data/metrics.json';
```
---
## 4. Build the image
The container runs the collector in the background and serves the dashboard in the
foreground, both from one process tree (`start.sh`):
```bash
docker build -t mithal-monitor:latest ./q5-mithal-dashboard
```
```bash
docker run --rm -p 8080:8080 --name mithal-monitor mithal-monitor:latest
```
Open <http://localhost:8080/index.html>. The first record appears within a few
seconds of startup, then one per minute.
Image notes:
- `python:3.12-slim`, non-root user `appuser` (uid 10001) owning the served tree
so the collector can write into it.
- `requirements.txt` installed before the code is copied, for layer caching.
- `start.sh` restarts the collector if it ever exits, and traps `SIGTERM`/`SIGINT`
so the container stops promptly.
- `HEALTHCHECK` fetches the dashboard page itself.
- Metrics live inside the container's filesystem, so **history resets on redeploy**.
That is intentional for this exercise; to keep history across restarts, mount a
volume at `/app/dashboard/data` (this is exactly what Ghaymah Block Storage is
for — see Q4).
---
## 5. Deploy to Ghaymah
This deployment uses Docker Hub namespace `agamy74`.
```bash
docker login
```
```bash
docker tag mithal-monitor:latest docker.io/agamy74/mithal-monitor:latest
```
```bash
docker push docker.io/agamy74/mithal-monitor:latest
```
> On Apple Silicon / ARM, build for the platform Ghaymah runs:
> ```bash
> docker buildx build --platform linux/amd64 -t docker.io/agamy74/mithal-monitor:latest --push ./q5-mithal-dashboard
> ```
Make sure the Docker Hub repository is **public** — Ghaymah pulls it anonymously.
Then, in the Ghaymah dashboard:
| Field | Value |
|---|---|
| Container Image URL | `docker.io/agamy74/mithal-monitor:8bc563f` |
| Application Name | `mithal-monitor` |
| Port Number | `8080` (matches `EXPOSE`) |
| Public Access | **enabled** |
| Environment Variables | *(optional)* `INTERVAL_S=60`, `TARGET_URL=https://mithal.space`, `SEARCH_URL=https://mithal.space/search?q=test` |
Click **Deploy**, then open `https://mithal-monitor-292f00f076b1.hosted.ghaymah.systems/index.html` and
screenshot the deployed dashboard. Leave it running so the 24 h uptime tile and the
hourly chart fill with real history before submission.
---
## 6. Local verification performed
Measured against the live site on 2026-07-26:
| Check | Result |
|---|---|
| `collect.py --once` against mithal.space | `UP code=200 latency=2693ms dns=7.07ms search=866ms ssl=50d` |
| 60 s loop (run at 6 s for testing) | 5 further records appended, pruning and atomic writes working |
| Failure path (unreachable host) | recorded `up:false` with `code/latency_ms/dns_ms/ssl_days_left/search_ms` all `null`, exit 0, no crash |
| Dashboard against real `metrics.json` | badge UP, uptime 100.0% (6/6), TLS card "50 days — Healthy" in green, chart with both series, 6-row table; no console errors |
| Dashboard with the data file removed | "no data yet" state on every tile + banner, no crash |
| `docker build` | image builds clean (200 MB) |
| `docker run` | `start.sh` launches the collector and the static server; container reports `(healthy)`; the collector wrote records into the served directory and the dashboard rendered them |
### Against the Ghaymah deployment
Checked on 2026-07-26 at 17:23 UTC, ~30 minutes after deployment:
| Check | Result |
|---|---|
| `GET /index.html` | HTTP 200, full page (16,931 bytes) |
| `GET /data/metrics.json` | **31 records**, first `16:53:16`, last `17:23:33` — exactly one per minute, no gaps |
| Latest record | `up: true`, `ssl_days_left: 50` |
This confirms the container's two-process design works in production: the collector
keeps writing into the directory the static server is serving, with no external
storage or second service involved.
The `dashboard/data/metrics.json` in this repo holds those first real samples; the
collector prunes anything older than 48 h automatically.
### Post-hibernation collection
The application was woken for final submission verification. `/index.html`
returned HTTP 200 at **2026-07-26 20:46:07 UTC**. The collector's new
in-container history began at **2026-07-26 20:45:34.818046 UTC** and grew from
3 to 4 records by **2026-07-26 20:48:44 UTC**, confirming the one-record-per-minute
loop resumed.
This history restarted at wake time because `/app/dashboard/data/metrics.json`
is on the container's ephemeral filesystem, not an attached volume. Hibernation
and restart therefore demonstrate the persistence boundary described in Q4:
production history that must survive container replacement belongs on Ghaymah
Block Storage or another external durable store.