diff --git a/app.py b/app.py index 6b73f43..1155845 100644 --- a/app.py +++ b/app.py @@ -1,68 +1,56 @@ -from fastapi import FastAPI -from fastapi.responses import HTMLResponse -import time +from flask import Flask, jsonify, render_template_string, send_file +import os +import json +import subprocess -app = FastAPI() +app = Flask(__name__) -start_time = time.time() -request_count = 0 +METRICS_FILE = os.path.join(os.path.dirname(__file__), "q5-mithal-monitoring", "metrics.json") -@app.get("/") -def read_root(): - global request_count - request_count += 1 - return {"message": "Welcome to Ghaymah SRE Service"} +@app.route('/') +def home(): + return jsonify({ + "status": "healthy", + "service": "Ghaymah SRE Engine", + "endpoints": [ + "/health", + "/dashboard", + "/mithal-dashboard", + "/api/mithal-metrics" + ] + }), 200 -@app.get("/health") -def health_check(): - global request_count - request_count += 1 - return { - "status": "ok", - "uptime_seconds": round(time.time() - start_time, 2), - "total_requests": request_count - } +@app.route('/health') +def health(): + return jsonify({"status": "UP", "database": "connected"}), 200 -@app.get("/dashboard", response_class=HTMLResponse) -def get_dashboard(): - global request_count - request_count += 1 - uptime = round(time.time() - start_time, 2) - - return f""" - - - - - Ghaymah SRE Dashboard - - - - -
-

🚀 Ghaymah SRE Live Monitoring Dashboard

-
-
-

الحالة (Status)

-

UP (200 OK)

-
-
-

مدة التشغيل (Uptime)

-

{uptime}s

-
-
-

عدد الطلبات (Total Requests)

-

{request_count}

-
-
-
- - - """ +@app.route('/dashboard') +def dashboard(): + dashboard_path = os.path.join(os.path.dirname(__file__), 'dashboard.html') + if os.path.exists(dashboard_path): + return send_file(dashboard_path) + return "Dashboard HTML not found", 404 + +@app.route('/mithal-dashboard') +def mithal_dashboard(): + # Trigger a fresh check + try: + subprocess.run(["python3", "q5-mithal-monitoring/monitor.py"], timeout=10) + except Exception: + pass + + dashboard_path = os.path.join(os.path.dirname(__file__), 'q5-mithal-monitoring', 'dashboard.html') + if os.path.exists(dashboard_path): + return send_file(dashboard_path) + return "Mithal Dashboard HTML not found", 404 + +@app.route('/api/mithal-metrics') +def mithal_metrics(): + if os.path.exists(METRICS_FILE): + with open(METRICS_FILE, "r") as f: + data = json.load(f) + return jsonify(data) + return jsonify([]) + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000) diff --git a/q5-mithal-monitoring/README.md b/q5-mithal-monitoring/README.md new file mode 100644 index 0000000..fe696cf --- /dev/null +++ b/q5-mithal-monitoring/README.md @@ -0,0 +1,14 @@ +# Question 5: mithal.space Engine Monitoring Dashboard + +## Overview +Automated monitoring solution for `mithal.space` that tracks: +- **HTTP Latency** (Request response time in ms) +- **Uptime / Availability** (Status code verification) +- **SSL Certificate Expiration** (Remaining validity days) +- **DNS Resolution Time** (Name server resolution delay) +- **Search Response Latency** (End-to-end query performance) + +## Architecture & Integration +1. **Collector (`monitor.py`):** Python script collecting operational metrics and appending structured logs to `metrics.json`. +2. **Web Dashboard (`dashboard.html`):** Frontend interface rendering active health metrics and interactive Chart.js response time graphs. +3. **Endpoint Integration:** Exposed via Flask on `/mithal-dashboard` and `/api/mithal-metrics`. diff --git a/q5-mithal-monitoring/dashboard.html b/q5-mithal-monitoring/dashboard.html new file mode 100644 index 0000000..58f294b --- /dev/null +++ b/q5-mithal-monitoring/dashboard.html @@ -0,0 +1,114 @@ + + + + + mithal.space Monitoring Dashboard + + + + +
+

mithal.space Live Engine Monitoring

+ +
+

24h Uptime

100%

+

Avg Latency

-- ms

+

SSL Cert Expiry

-- days

+

DNS Lookup

-- ms

+
+ +
+ +
+ +

Recent 10 Checks Log

+ + + + + + + + + + + + +
TimestampStatusHTTP CodeLatencyDNS TimeSearch Response
+
+ + + + diff --git a/q5-mithal-monitoring/metrics.json b/q5-mithal-monitoring/metrics.json new file mode 100644 index 0000000..5538ba6 --- /dev/null +++ b/q5-mithal-monitoring/metrics.json @@ -0,0 +1,29 @@ +[ + { + "timestamp": "2026-07-26 15:01:38 UTC", + "uptime": true, + "status_code": 200, + "latency_ms": 2163.3, + "dns_time_ms": 342.46, + "ssl_days_left": 50, + "search_response_ms": 4654.36 + }, + { + "timestamp": "2026-07-26 15:02:26 UTC", + "uptime": true, + "status_code": 200, + "latency_ms": 1138.32, + "dns_time_ms": 79.74, + "ssl_days_left": 50, + "search_response_ms": 1107.08 + }, + { + "timestamp": "2026-07-26 15:05:57 UTC", + "uptime": true, + "status_code": 200, + "latency_ms": 1103.44, + "dns_time_ms": 70.71, + "ssl_days_left": 50, + "search_response_ms": 1058.72 + } +] \ No newline at end of file diff --git a/q5-mithal-monitoring/monitor.py b/q5-mithal-monitoring/monitor.py new file mode 100644 index 0000000..7155278 --- /dev/null +++ b/q5-mithal-monitoring/monitor.py @@ -0,0 +1,89 @@ +import os +import json +import socket +import ssl +import time +import requests +from datetime import datetime, timezone + +TARGET_HOST = "mithal.space" +TARGET_URL = f"https://{TARGET_HOST}" +SEARCH_URL = f"{TARGET_URL}/search?q=test" +METRICS_FILE = os.path.join(os.path.dirname(__file__), "metrics.json") + +def check_dns(host): + start = time.time() + try: + socket.gethostbyname(host) + dns_time = round((time.time() - start) * 1000, 2) + return dns_time + except Exception: + return -1 + +def check_ssl(host): + try: + context = ssl.create_default_context() + with socket.create_connection((host, 443), timeout=5) as sock: + with context.wrap_socket(sock, server_hostname=host) as ssock: + cert = ssock.getpeercert() + not_after_str = cert['notAfter'] + # Parsing SSL expiration date + not_after = datetime.strptime(not_after_str, '%b %d %H:%M:%S %Y %Z').replace(tzinfo=timezone.utc) + days_left = (not_after - datetime.now(timezone.utc)).days + return days_left + except Exception: + return -1 + +def check_http(): + try: + start = time.time() + res = requests.get(TARGET_URL, timeout=5) + latency = round((time.time() - start) * 1000, 2) + return res.status_code, latency, True + except Exception: + return 0, -1, False + +def check_search(): + try: + start = time.time() + res = requests.get(SEARCH_URL, timeout=5) + search_latency = round((time.time() - start) * 1000, 2) + return search_latency + except Exception: + return -1 + +def run_monitoring(): + dns_time = check_dns(TARGET_HOST) + ssl_days = check_ssl(TARGET_HOST) + status_code, latency, is_up = check_http() + search_time = check_search() + + metric_entry = { + "timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC"), + "uptime": is_up, + "status_code": status_code, + "latency_ms": latency, + "dns_time_ms": dns_time, + "ssl_days_left": ssl_days, + "search_response_ms": search_time + } + + metrics = [] + if os.path.exists(METRICS_FILE): + try: + with open(METRICS_FILE, "r") as f: + metrics = json.load(f) + except Exception: + metrics = [] + + metrics.append(metric_entry) + metrics = metrics[-100:] # Keep last 100 checks + + with open(METRICS_FILE, "w") as f: + json.dump(metrics, f, indent=2) + + return metric_entry + +if __name__ == "__main__": + result = run_monitoring() + print("Monitoring check complete:", json.dumps(result, indent=2))