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
+
+
+
+
+
+
+
+
Recent 10 Checks Log
+
+
+
+ | Timestamp |
+ Status |
+ HTTP Code |
+ Latency |
+ DNS Time |
+ Search 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))