diff --git a/q5-mithal-monitor/Dockerfile b/q5-mithal-monitor/Dockerfile new file mode 100644 index 0000000..f729810 --- /dev/null +++ b/q5-mithal-monitor/Dockerfile @@ -0,0 +1,5 @@ +FROM python:3.12-slim +WORKDIR /app +COPY monitor.py dashboard.html ./ +EXPOSE 5000 +CMD ["python", "monitor.py"] \ No newline at end of file diff --git a/q5-mithal-monitor/__pycache__/monitor.cpython-313.pyc b/q5-mithal-monitor/__pycache__/monitor.cpython-313.pyc new file mode 100644 index 0000000..90aad1b Binary files /dev/null and b/q5-mithal-monitor/__pycache__/monitor.cpython-313.pyc differ diff --git a/q5-mithal-monitor/dashboard.html b/q5-mithal-monitor/dashboard.html new file mode 100644 index 0000000..aa449d5 --- /dev/null +++ b/q5-mithal-monitor/dashboard.html @@ -0,0 +1,142 @@ + + + + + + mithal.space monitor + + + + +

mithal.space — Monitoring

+

loading...

+ +
+
Uptime (24h)
+
-
+
+ +
+
SSL (days left)
+
-
+
+
+ +
+
Latency last hour
+ +
+ +
+
Last 10 checks
+ + + + + + + + + + + + +
timelatencydnssearchstatusresult
+
+ + + + diff --git a/q5-mithal-monitor/metrics.csv b/q5-mithal-monitor/metrics.csv new file mode 100644 index 0000000..7e8bb1f --- /dev/null +++ b/q5-mithal-monitor/metrics.csv @@ -0,0 +1,2 @@ +timestamp,latency_ms,uptime,status_code,ssl_valid,ssl_expires,ssl_days_remaining,dns_ms,search_ms,search_status_code,overall +2026-07-29T01:55:40.917123+00:00,705.91,True,200,True,2026-09-15T13:10:47+00:00,48,0.54,1684.08,200,True diff --git a/q5-mithal-monitor/metrics.json b/q5-mithal-monitor/metrics.json new file mode 100644 index 0000000..fa7ade2 --- /dev/null +++ b/q5-mithal-monitor/metrics.json @@ -0,0 +1,41 @@ +[ + { + "timestamp": "2026-07-29T01:55:40.917123+00:00", + "latency_ms": 705.91, + "uptime": true, + "status_code": 200, + "ssl_valid": true, + "ssl_expires": "2026-09-15T13:10:47+00:00", + "ssl_days_remaining": 48, + "dns_ms": 0.54, + "search_ms": 1684.08, + "search_status_code": 200, + "overall": true + }, + { + "timestamp": "2026-07-29T02:06:20.105132Z", + "latency_ms": 515.49, + "uptime": true, + "status_code": 200, + "dns_ms": 10.14, + "ssl_valid": false, + "ssl_expires": "Sep 15 13:10:47 2026 GMT", + "ssl_days_remaining": null, + "search_ms": 476.59, + "search_status_code": 200, + "overall": false + }, + { + "timestamp": "2026-07-29T02:06:35.031662Z", + "latency_ms": 502.26, + "uptime": true, + "status_code": 200, + "dns_ms": 0.51, + "ssl_valid": false, + "ssl_expires": "Sep 15 13:10:47 2026 GMT", + "ssl_days_remaining": null, + "search_ms": 496.97, + "search_status_code": 200, + "overall": false + } +] \ No newline at end of file diff --git a/q5-mithal-monitor/monitor.py b/q5-mithal-monitor/monitor.py new file mode 100644 index 0000000..e4ecafa --- /dev/null +++ b/q5-mithal-monitor/monitor.py @@ -0,0 +1,122 @@ +import json +import os +import socket +import ssl +import threading +import time +from datetime import datetime +from http.server import HTTPServer, SimpleHTTPRequestHandler +from urllib.error import HTTPError +from urllib.request import urlopen + +URL = "https://mithal.space" +SEARCH_URL = "https://mithal.space/search?q=test" +HOST = "mithal.space" +METRICS_FILE = "metrics.json" + + +def check_http(url): + start = time.time() + status = 0 + ok = False + try: + res = urlopen(url, timeout=15) + status = res.status + ok = status == 200 + res.read(1024) + except HTTPError as e: + status = e.code + ok = status == 200 + except Exception: + ok = False + ms = round((time.time() - start) * 1000, 2) + return ms, status, ok + + +def check_dns(): + start = time.time() + ok = False + try: + socket.gethostbyname(HOST) + ok = True + except Exception: + ok = False + ms = round((time.time() - start) * 1000, 2) + return ms, ok + + +def check_ssl(): + valid = False + expires = None + days_left = None + try: + ctx = ssl.create_default_context() + with socket.create_connection((HOST, 443), timeout=10) as s: + with ctx.wrap_socket(s, server_hostname=HOST) as ss: + cert = ss.getpeercert() + expires = cert["notAfter"] + exp_date = datetime.strptime(expires, "%b %d %H:%M:%S %Y %GMT") + days_left = (exp_date - datetime.utcnow()).days + valid = True + except Exception: + pass + return valid, expires, days_left + + +def run_check(): + latency, status_code, uptime = check_http(URL) + dns_ms, dns_ok = check_dns() + ssl_ok, ssl_expires, ssl_days = check_ssl() + search_ms, search_code, search_ok = check_http(SEARCH_URL) + + row = { + "timestamp": datetime.utcnow().isoformat() + "Z", + "latency_ms": latency, + "uptime": uptime, + "status_code": status_code, + "dns_ms": dns_ms, + "ssl_valid": ssl_ok, + "ssl_expires": ssl_expires, + "ssl_days_remaining": ssl_days, + "search_ms": search_ms, + "search_status_code": search_code, + "overall": uptime and dns_ok and ssl_ok and search_ok, + } + return row + + +def save_row(row): + data = [] + if os.path.exists(METRICS_FILE): + with open(METRICS_FILE, "r", encoding="utf-8") as f: + data = json.load(f) + + data.append(row) + + # keep last 24 hours (1440 checks if every 1 min) + if len(data) > 1440: + data = data[-1440:] + + with open(METRICS_FILE, "w", encoding="utf-8") as f: + json.dump(data, f, indent=2) + + +def monitor_loop(): + while True: + row = run_check() + save_row(row) + print(row) + time.sleep(60) + + +def start_server(): + os.chdir(os.path.dirname(os.path.abspath(__file__))) + server = HTTPServer(("0.0.0.0", 5000), SimpleHTTPRequestHandler) + print("dashboard: http://0.0.0.0:5000/dashboard.html") + server.serve_forever() + + +if __name__ == "__main__": + save_row(run_check()) + threading.Thread(target=monitor_loop, daemon=True).start() + start_server()