refactor: reorganize directory structure to match exam submission spec
هذا الالتزام موجود في:
163
q5-mithal-monitor/dashboard.html
Normal file
163
q5-mithal-monitor/dashboard.html
Normal file
@@ -0,0 +1,163 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Mithal.space Monitoring</title>
|
||||
<!-- Include Google Fonts and Chart.js -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@300;400;700&display=swap" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0a0e17; --card: rgba(16, 24, 39, 0.8);
|
||||
--text: #e2e8f0; --muted: #64748b;
|
||||
--accent: #38bdf8; --success: #34d399; --danger: #f87171;
|
||||
--border: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
body {
|
||||
font-family: 'Tajawal', sans-serif;
|
||||
background: var(--bg); color: var(--text);
|
||||
margin: 0; padding: 40px 20px;
|
||||
background-image: radial-gradient(circle at top right, rgba(56, 189, 248, 0.1), transparent 40%);
|
||||
}
|
||||
h1 { text-align: center; color: var(--accent); margin-bottom: 40px; }
|
||||
|
||||
.grid {
|
||||
display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px; max-width: 1200px; margin: 0 auto 30px auto;
|
||||
}
|
||||
.card {
|
||||
background: var(--card); border: 1px solid var(--border);
|
||||
border-radius: 16px; padding: 25px;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
|
||||
backdrop-filter: blur(10px); transition: transform 0.3s;
|
||||
}
|
||||
.card:hover { transform: translateY(-3px); border-color: rgba(56, 189, 248, 0.3); }
|
||||
.card-title { font-size: 1.1rem; color: var(--muted); margin-bottom: 15px; font-weight: bold; }
|
||||
.metric-value { font-size: 2.5rem; font-weight: 700; display: flex; align-items: baseline; gap: 8px;}
|
||||
.unit { font-size: 1.2rem; color: var(--muted); font-weight: normal; }
|
||||
|
||||
.good { color: var(--success); } .bad { color: var(--danger); }
|
||||
|
||||
table {
|
||||
width: 100%; border-collapse: collapse; margin-top: 15px;
|
||||
text-align: right;
|
||||
}
|
||||
th, td { padding: 12px 15px; border-bottom: 1px solid var(--border); }
|
||||
th { color: var(--muted); font-weight: normal; font-size: 0.9rem; }
|
||||
tr:hover { background: rgba(255,255,255,0.02); }
|
||||
|
||||
.chart-container {
|
||||
max-width: 1200px; margin: 0 auto; background: var(--card);
|
||||
border-radius: 16px; padding: 25px; border: 1px solid var(--border);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>لوحة مراقبة Mithal.space 🚀</h1>
|
||||
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<div class="card-title">نسبة التوافر (Uptime)</div>
|
||||
<div id="uptime" class="metric-value good">99.9 <span class="unit">%</span></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-title">صلاحية شهادة SSL</div>
|
||||
<div id="ssl-status" class="metric-value good">64 <span class="unit">يوم متبقي</span></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-title">متوسط الاستجابة</div>
|
||||
<div id="avg-latency" class="metric-value">120 <span class="unit">ms</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chart-container">
|
||||
<div class="card-title">زمن الاستجابة (آخر 60 دقيقة)</div>
|
||||
<canvas id="latencyChart" height="80"></canvas>
|
||||
</div>
|
||||
|
||||
<div class="grid" style="margin-top: 30px;">
|
||||
<div class="card" style="grid-column: 1 / -1;">
|
||||
<div class="card-title">سجل الفحوصات (آخر 10)</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>الوقت</th>
|
||||
<th>الحالة</th>
|
||||
<th>الاستجابة (ms)</th>
|
||||
<th>DNS (ms)</th>
|
||||
<th>بحث (ms)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="history-table">
|
||||
<tr><td colspan="5" style="text-align:center; color: var(--muted)">جاري جلب البيانات...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Mock data to demonstrate the beautiful UI (since we can't run the backend locally now)
|
||||
function generateMockData() {
|
||||
let labels = [];
|
||||
let data = [];
|
||||
let now = new Date();
|
||||
for(let i=60; i>=0; i--) {
|
||||
let d = new Date(now.getTime() - i*60000);
|
||||
labels.push(d.getHours() + ':' + d.getMinutes().toString().padStart(2, '0'));
|
||||
data.push(Math.floor(Math.random() * (150 - 80 + 1)) + 80);
|
||||
}
|
||||
return { labels, data };
|
||||
}
|
||||
|
||||
const mock = generateMockData();
|
||||
|
||||
const ctx = document.getElementById('latencyChart').getContext('2d');
|
||||
|
||||
// Gradient for chart
|
||||
let gradient = ctx.createLinearGradient(0, 0, 0, 400);
|
||||
gradient.addColorStop(0, 'rgba(56, 189, 248, 0.5)');
|
||||
gradient.addColorStop(1, 'rgba(56, 189, 248, 0.0)');
|
||||
|
||||
new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: mock.labels,
|
||||
datasets: [{
|
||||
label: 'Latency (ms)',
|
||||
data: mock.data,
|
||||
borderColor: '#38bdf8',
|
||||
backgroundColor: gradient,
|
||||
borderWidth: 2,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 5,
|
||||
fill: true,
|
||||
tension: 0.4
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: { legend: { display: false } },
|
||||
scales: {
|
||||
x: { grid: { color: 'rgba(255,255,255,0.05)' }, ticks: { color: '#64748b' } },
|
||||
y: { grid: { color: 'rgba(255,255,255,0.05)' }, ticks: { color: '#64748b' } }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Populate Table with mock data
|
||||
const tbody = document.getElementById('history-table');
|
||||
let rows = '';
|
||||
for(let i=0; i<10; i++) {
|
||||
rows += `
|
||||
<tr>
|
||||
<td>${mock.labels[60-i]}</td>
|
||||
<td class="good">200 OK</td>
|
||||
<td>${mock.data[60-i]}</td>
|
||||
<td>24.5</td>
|
||||
<td>310.2</td>
|
||||
</tr>`;
|
||||
}
|
||||
tbody.innerHTML = rows;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
95
q5-mithal-monitor/monitor.py
Normal file
95
q5-mithal-monitor/monitor.py
Normal file
@@ -0,0 +1,95 @@
|
||||
import requests
|
||||
import time
|
||||
import socket
|
||||
import ssl
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
import dns.resolver
|
||||
|
||||
TARGET_URL = "https://mithal.space"
|
||||
TARGET_DOMAIN = "mithal.space"
|
||||
DATA_FILE = "/Users/mac/a1/mithal_monitor/data/metrics.json"
|
||||
|
||||
def check_ssl(domain):
|
||||
context = ssl.create_default_context()
|
||||
try:
|
||||
with socket.create_connection((domain, 443), timeout=5) as sock:
|
||||
with context.wrap_socket(sock, server_hostname=domain) as ssock:
|
||||
cert = ssock.getpeercert()
|
||||
expiry_date = datetime.strptime(cert['notAfter'], "%b %d %H:%M:%S %Y %Z")
|
||||
days_remaining = (expiry_date - datetime.utcnow()).days
|
||||
return "Valid", days_remaining
|
||||
except Exception as e:
|
||||
return f"Error: {str(e)}", 0
|
||||
|
||||
def check_dns(domain):
|
||||
start = time.time()
|
||||
try:
|
||||
answers = dns.resolver.resolve(domain, 'A')
|
||||
return round((time.time() - start) * 1000, 2)
|
||||
except Exception:
|
||||
return -1
|
||||
|
||||
def measure():
|
||||
results = {
|
||||
"timestamp": datetime.utcnow().isoformat() + "Z",
|
||||
"latency_ms": -1,
|
||||
"status_code": 0,
|
||||
"uptime": False,
|
||||
"ssl_status": "Unknown",
|
||||
"ssl_days": 0,
|
||||
"dns_time_ms": -1,
|
||||
"search_time_ms": -1
|
||||
}
|
||||
|
||||
# DNS Check
|
||||
results["dns_time_ms"] = check_dns(TARGET_DOMAIN)
|
||||
|
||||
# SSL Check
|
||||
ssl_status, ssl_days = check_ssl(TARGET_DOMAIN)
|
||||
results["ssl_status"] = ssl_status
|
||||
results["ssl_days"] = ssl_days
|
||||
|
||||
# HTTP Check
|
||||
try:
|
||||
start = time.time()
|
||||
resp = requests.get(TARGET_URL, timeout=5)
|
||||
results["latency_ms"] = round((time.time() - start) * 1000, 2)
|
||||
results["status_code"] = resp.status_code
|
||||
results["uptime"] = resp.status_code < 400
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Mock Search Check (assuming a /search endpoint exists)
|
||||
try:
|
||||
start = time.time()
|
||||
requests.get(f"{TARGET_URL}/search?q=test", timeout=5)
|
||||
results["search_time_ms"] = round((time.time() - start) * 1000, 2)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return results
|
||||
|
||||
def save_data(data):
|
||||
os.makedirs(os.path.dirname(DATA_FILE), exist_ok=True)
|
||||
history = []
|
||||
if os.path.exists(DATA_FILE):
|
||||
try:
|
||||
with open(DATA_FILE, 'r') as f:
|
||||
history = json.load(f)
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
history.append(data)
|
||||
# Keep last 1440 checks (24 hours at 1/min)
|
||||
history = history[-1440:]
|
||||
|
||||
with open(DATA_FILE, 'w') as f:
|
||||
json.dump(history, f, indent=2)
|
||||
|
||||
if __name__ == "__main__":
|
||||
while True:
|
||||
data = measure()
|
||||
save_data(data)
|
||||
time.sleep(60)
|
||||
المرجع في مشكلة جديدة
حظر مستخدم