الملفات
ghaymah-exam--Yousifhamdyam…/q5-mithal-monitor/dashboard (1).html

151 أسطر
5.9 KiB
HTML

<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>مراقبة mithal.space</title>
<style>
:root{
--bg:#0b0f14; --card:#121821; --line:#212a36;
--ok:#22c55e; --warn:#f59e0b; --bad:#ef4444; --text:#e6edf3; --muted:#8b98a8; --accent:#3b82f6;
}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--text);font-family:-apple-system,Segoe UI,Tahoma,Arial,sans-serif;padding:24px}
h1{font-size:20px;margin:0 0 4px}
.subtitle{color:var(--muted);font-size:13px;margin-bottom:20px}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;margin-bottom:20px}
.card{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:18px}
.label{color:var(--muted);font-size:13px;margin-bottom:8px}
.value{font-size:26px;font-weight:700}
.value.small{font-size:16px;font-weight:600}
.ring-wrap{display:flex;align-items:center;gap:14px}
svg{transform:rotate(-90deg)}
.chart-card{margin-bottom:20px}
canvas{width:100%;height:180px}
table{width:100%;border-collapse:collapse;background:var(--card);border:1px solid var(--line);border-radius:12px;overflow:hidden}
th,td{padding:10px 14px;text-align:right;border-bottom:1px solid var(--line);font-size:13px}
th{color:var(--muted);font-weight:600}
tr:last-child td{border-bottom:none}
.pill{padding:2px 8px;border-radius:6px;font-size:12px}
.pill.ok{background:rgba(34,197,94,.15);color:var(--ok)}
.pill.bad{background:rgba(239,68,68,.15);color:var(--bad)}
.pill.warn{background:rgba(245,158,11,.15);color:var(--warn)}
</style>
</head>
<body>
<h1>🔍 مراقبة mithal.space</h1>
<div class="subtitle">Uptime · Latency · SSL · DNS · Search — يُحدَّث كل 60 ثانية من <code>latest.json</code></div>
<div class="grid">
<div class="card">
<div class="label">Uptime (آخر 24 ساعة)</div>
<div class="ring-wrap">
<svg width="64" height="64" viewBox="0 0 64 64">
<circle cx="32" cy="32" r="28" stroke="#212a36" stroke-width="8" fill="none"/>
<circle id="uptimeRing" cx="32" cy="32" r="28" stroke="#22c55e" stroke-width="8" fill="none"
stroke-dasharray="176" stroke-dashoffset="176" stroke-linecap="round"/>
</svg>
<div class="value" id="uptimeValue">--%</div>
</div>
</div>
<div class="card">
<div class="label">آخر زمن استجابة (Latency)</div>
<div class="value" id="latencyValue">--</div>
</div>
<div class="card">
<div class="label">شهادة SSL — الأيام المتبقية</div>
<div class="value" id="sslValue">--</div>
</div>
<div class="card">
<div class="label">زمن تحليل DNS</div>
<div class="value" id="dnsValue">--</div>
</div>
<div class="card">
<div class="label">زمن استجابة البحث</div>
<div class="value" id="searchValue">--</div>
</div>
</div>
<div class="card chart-card">
<div class="label">زمن الاستجابة — آخر ساعة (ms)</div>
<canvas id="latencyChart"></canvas>
</div>
<table>
<thead><tr><th>الوقت</th><th>الحالة</th><th>زمن الاستجابة</th><th>DNS</th><th>بحث</th></tr></thead>
<tbody id="historyBody"></tbody>
</table>
<script>
const SNAPSHOT_URL = "latest.json";
const POLL_MS = 30000;
function drawChart(points) {
const canvas = document.getElementById("latencyChart");
const ctx = canvas.getContext("2d");
const dpr = window.devicePixelRatio || 1;
const w = canvas.clientWidth, h = 180;
canvas.width = w * dpr; canvas.height = h * dpr;
ctx.scale(dpr, dpr);
ctx.clearRect(0, 0, w, h);
if (!points.length) return;
const max = Math.max(...points, 10) * 1.2;
const stepX = w / Math.max(points.length - 1, 1);
ctx.beginPath();
ctx.strokeStyle = "#3b82f6";
ctx.lineWidth = 2;
points.forEach((p, i) => {
const x = i * stepX;
const y = h - (p / max) * h;
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
});
ctx.stroke();
}
async function refresh() {
try {
const res = await fetch(SNAPSHOT_URL + "?_=" + Date.now(), { cache: "no-store" });
const data = await res.json();
const last = data.last_check;
const history = data.history || [];
document.getElementById("uptimeValue").textContent = `${data.uptime_percent_24h}%`;
const circumference = 176;
const offset = circumference - (data.uptime_percent_24h / 100) * circumference;
document.getElementById("uptimeRing").setAttribute("stroke-dashoffset", offset);
document.getElementById("latencyValue").textContent = last.latency_ms != null ? `${last.latency_ms} ms` : "N/A";
document.getElementById("dnsValue").textContent = last.dns_ms != null ? `${last.dns_ms} ms` : "N/A";
document.getElementById("searchValue").textContent = last.search?.latency_ms != null ? `${last.search.latency_ms} ms` : "N/A";
if (last.ssl?.valid) {
document.getElementById("sslValue").textContent = `${last.ssl.days_remaining} يوم`;
} else {
document.getElementById("sslValue").innerHTML = `<span class="pill bad">غير صالحة</span>`;
}
const lastHour = history.slice(-60);
drawChart(lastHour.map(h => h.latency_ms || 0));
const rows = history.slice(-10).reverse().map(h => `
<tr>
<td>${new Date(h.timestamp).toLocaleTimeString("ar-EG")}</td>
<td><span class="pill ${h.uptime.up ? "ok" : "bad"}">${h.uptime.up ? "UP" : "DOWN"} (${h.uptime.status_code ?? "—"})</span></td>
<td>${h.latency_ms ?? "—"} ms</td>
<td>${h.dns_ms ?? "—"} ms</td>
<td>${h.search?.ok ? "✅" : "❌"} ${h.search?.latency_ms ?? ""}</td>
</tr>`).join("");
document.getElementById("historyBody").innerHTML = rows;
} catch (e) {
console.error("Failed to load snapshot:", e);
}
}
refresh();
setInterval(refresh, POLL_MS);
</script>
</body>
</html>