Complete Q5: Add mithal.space monitoring script, dashboard, and Flask API integration
هذا الالتزام موجود في:
114
app.py
114
app.py
@@ -1,68 +1,56 @@
|
|||||||
from fastapi import FastAPI
|
from flask import Flask, jsonify, render_template_string, send_file
|
||||||
from fastapi.responses import HTMLResponse
|
import os
|
||||||
import time
|
import json
|
||||||
|
import subprocess
|
||||||
|
|
||||||
app = FastAPI()
|
app = Flask(__name__)
|
||||||
|
|
||||||
start_time = time.time()
|
METRICS_FILE = os.path.join(os.path.dirname(__file__), "q5-mithal-monitoring", "metrics.json")
|
||||||
request_count = 0
|
|
||||||
|
|
||||||
@app.get("/")
|
@app.route('/')
|
||||||
def read_root():
|
def home():
|
||||||
global request_count
|
return jsonify({
|
||||||
request_count += 1
|
"status": "healthy",
|
||||||
return {"message": "Welcome to Ghaymah SRE Service"}
|
"service": "Ghaymah SRE Engine",
|
||||||
|
"endpoints": [
|
||||||
|
"/health",
|
||||||
|
"/dashboard",
|
||||||
|
"/mithal-dashboard",
|
||||||
|
"/api/mithal-metrics"
|
||||||
|
]
|
||||||
|
}), 200
|
||||||
|
|
||||||
@app.get("/health")
|
@app.route('/health')
|
||||||
def health_check():
|
def health():
|
||||||
global request_count
|
return jsonify({"status": "UP", "database": "connected"}), 200
|
||||||
request_count += 1
|
|
||||||
return {
|
|
||||||
"status": "ok",
|
|
||||||
"uptime_seconds": round(time.time() - start_time, 2),
|
|
||||||
"total_requests": request_count
|
|
||||||
}
|
|
||||||
|
|
||||||
@app.get("/dashboard", response_class=HTMLResponse)
|
@app.route('/dashboard')
|
||||||
def get_dashboard():
|
def dashboard():
|
||||||
global request_count
|
dashboard_path = os.path.join(os.path.dirname(__file__), 'dashboard.html')
|
||||||
request_count += 1
|
if os.path.exists(dashboard_path):
|
||||||
uptime = round(time.time() - start_time, 2)
|
return send_file(dashboard_path)
|
||||||
|
return "Dashboard HTML not found", 404
|
||||||
return f"""
|
|
||||||
<!DOCTYPE html>
|
@app.route('/mithal-dashboard')
|
||||||
<html lang="ar" dir="rtl">
|
def mithal_dashboard():
|
||||||
<head>
|
# Trigger a fresh check
|
||||||
<meta charset="UTF-8">
|
try:
|
||||||
<title>Ghaymah SRE Dashboard</title>
|
subprocess.run(["python3", "q5-mithal-monitoring/monitor.py"], timeout=10)
|
||||||
<style>
|
except Exception:
|
||||||
body {{ font-family: system-ui, sans-serif; background: #0f172a; color: #fff; padding: 40px; text-align: center; }}
|
pass
|
||||||
.container {{ max-width: 800px; margin: 0 auto; }}
|
|
||||||
.grid {{ display: flex; gap: 20px; margin-top: 30px; }}
|
dashboard_path = os.path.join(os.path.dirname(__file__), 'q5-mithal-monitoring', 'dashboard.html')
|
||||||
.card {{ background: #1e293b; padding: 25px; border-radius: 12px; flex: 1; border: 1px solid #334155; }}
|
if os.path.exists(dashboard_path):
|
||||||
.status-up {{ color: #22c55e; font-weight: bold; }}
|
return send_file(dashboard_path)
|
||||||
h1 {{ font-size: 2.5rem; color: #38bdf8; }}
|
return "Mithal Dashboard HTML not found", 404
|
||||||
</style>
|
|
||||||
<script>setTimeout(() => {{ window.location.reload(); }}, 5000);</script>
|
@app.route('/api/mithal-metrics')
|
||||||
</head>
|
def mithal_metrics():
|
||||||
<body>
|
if os.path.exists(METRICS_FILE):
|
||||||
<div class="container">
|
with open(METRICS_FILE, "r") as f:
|
||||||
<h2>🚀 Ghaymah SRE Live Monitoring Dashboard</h2>
|
data = json.load(f)
|
||||||
<div class="grid">
|
return jsonify(data)
|
||||||
<div class="card">
|
return jsonify([])
|
||||||
<h3>الحالة (Status)</h3>
|
|
||||||
<h1 class="status-up">UP (200 OK)</h1>
|
if __name__ == '__main__':
|
||||||
</div>
|
app.run(host='0.0.0.0', port=5000)
|
||||||
<div class="card">
|
|
||||||
<h3>مدة التشغيل (Uptime)</h3>
|
|
||||||
<h1>{uptime}s</h1>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<h3>عدد الطلبات (Total Requests)</h3>
|
|
||||||
<h1>{request_count}</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
|
|||||||
14
q5-mithal-monitoring/README.md
Normal file
14
q5-mithal-monitoring/README.md
Normal file
@@ -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`.
|
||||||
114
q5-mithal-monitoring/dashboard.html
Normal file
114
q5-mithal-monitoring/dashboard.html
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>mithal.space Monitoring Dashboard</title>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<style>
|
||||||
|
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #0f172a; color: #f8fafc; margin: 0; padding: 20px; }
|
||||||
|
.container { max-width: 1100px; margin: auto; }
|
||||||
|
h1 { text-align: center; color: #38bdf8; margin-bottom: 30px; }
|
||||||
|
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 20px; margin-bottom: 30px; }
|
||||||
|
.card { background: #1e293b; padding: 20px; border-radius: 12px; border: 1px solid #334155; text-align: center; }
|
||||||
|
.card h3 { margin: 0; font-size: 14px; color: #94a3b8; }
|
||||||
|
.card p { margin: 10px 0 0; font-size: 28px; font-weight: bold; color: #38bdf8; }
|
||||||
|
.chart-container { background: #1e293b; padding: 20px; border-radius: 12px; border: 1px solid #334155; margin-bottom: 30px; }
|
||||||
|
table { width: 100%; border-collapse: collapse; background: #1e293b; border-radius: 12px; overflow: hidden; }
|
||||||
|
th, td { padding: 12px 15px; text-align: left; border-bottom: 1px solid #334155; }
|
||||||
|
th { background-color: #0f172a; color: #38bdf8; }
|
||||||
|
.badge-up { background: #166534; color: #4ade80; padding: 4px 8px; border-radius: 4px; font-weight: bold; }
|
||||||
|
.badge-down { background: #991b1b; color: #f87171; padding: 4px 8px; border-radius: 4px; font-weight: bold; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>mithal.space Live Engine Monitoring</h1>
|
||||||
|
|
||||||
|
<div class="cards">
|
||||||
|
<div class="card"><h3>24h Uptime</h3><p id="uptime-val">100%</p></div>
|
||||||
|
<div class="card"><h3>Avg Latency</h3><p id="latency-val">-- ms</p></div>
|
||||||
|
<div class="card"><h3>SSL Cert Expiry</h3><p id="ssl-val">-- days</p></div>
|
||||||
|
<div class="card"><h3>DNS Lookup</h3><p id="dns-val">-- ms</p></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="chart-container">
|
||||||
|
<canvas id="latencyChart" height="100"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Recent 10 Checks Log</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Timestamp</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>HTTP Code</th>
|
||||||
|
<th>Latency</th>
|
||||||
|
<th>DNS Time</th>
|
||||||
|
<th>Search Response</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="logs-body"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
async function fetchMetrics() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/mithal-metrics');
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if(!data || data.length === 0) return;
|
||||||
|
|
||||||
|
// Uptime calc
|
||||||
|
const upCount = data.filter(d => d.uptime).length;
|
||||||
|
const uptimePct = ((upCount / data.length) * 100).toFixed(1);
|
||||||
|
document.getElementById('uptime-val').innerText = `${uptimePct}%`;
|
||||||
|
|
||||||
|
const latest = data[data.length - 1];
|
||||||
|
document.getElementById('latency-val').innerText = `${latest.latency_ms} ms`;
|
||||||
|
document.getElementById('ssl-val').innerText = `${latest.ssl_days_left} Days`;
|
||||||
|
document.getElementById('dns-val').innerText = `${latest.dns_time_ms} ms`;
|
||||||
|
|
||||||
|
// Render Logs Table (Last 10)
|
||||||
|
const logs = data.slice(-10).reverse();
|
||||||
|
const tbody = document.getElementById('logs-body');
|
||||||
|
tbody.innerHTML = logs.map(l => `
|
||||||
|
<tr>
|
||||||
|
<td>${l.timestamp}</td>
|
||||||
|
<td><span class="${l.uptime ? 'badge-up' : 'badge-down'}">${l.uptime ? 'UP' : 'DOWN'}</span></td>
|
||||||
|
<td>${l.status_code}</td>
|
||||||
|
<td>${l.latency_ms} ms</td>
|
||||||
|
<td>${l.dns_time_ms} ms</td>
|
||||||
|
<td>${l.search_response_ms} ms</td>
|
||||||
|
</tr>
|
||||||
|
`).join('');
|
||||||
|
|
||||||
|
// Render Chart
|
||||||
|
const labels = data.map(d => d.timestamp.split(' ')[1]);
|
||||||
|
const latencies = data.map(d => d.latency_ms);
|
||||||
|
|
||||||
|
if (window.myChart) window.myChart.destroy();
|
||||||
|
const ctx = document.getElementById('latencyChart').getContext('2d');
|
||||||
|
window.myChart = new Chart(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: labels,
|
||||||
|
datasets: [{
|
||||||
|
label: 'HTTP Latency (ms)',
|
||||||
|
data: latencies,
|
||||||
|
borderColor: '#38bdf8',
|
||||||
|
backgroundColor: 'rgba(56, 189, 248, 0.1)',
|
||||||
|
fill: true,
|
||||||
|
tension: 0.3
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: { responsive: true, scales: { y: { beginAtZero: true } } }
|
||||||
|
});
|
||||||
|
} catch(e) { console.error("Error loading metrics:", e); }
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchMetrics();
|
||||||
|
setInterval(fetchMetrics, 30000);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
29
q5-mithal-monitoring/metrics.json
Normal file
29
q5-mithal-monitoring/metrics.json
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
89
q5-mithal-monitoring/monitor.py
Normal file
89
q5-mithal-monitoring/monitor.py
Normal file
@@ -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))
|
||||||
المرجع في مشكلة جديدة
حظر مستخدم