172 أسطر
6.0 KiB
HTML
172 أسطر
6.0 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ar" dir="rtl">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>لوحة مراقبة mithal.space</title>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.4/chart.umd.min.js"></script>
|
|
<style>
|
|
:root {
|
|
--up: #16a34a; --down: #dc2626; --warn: #f59e0b;
|
|
--bg: #0f172a; --card: #1e293b; --text: #e2e8f0; --muted: #94a3b8;
|
|
}
|
|
* { box-sizing: border-box; }
|
|
body { background: var(--bg); color: var(--text); font-family: "Segoe UI", Tahoma, sans-serif; margin:0; padding:24px; }
|
|
h1 { margin-bottom:4px; }
|
|
.sub { color: var(--muted); margin-bottom:24px; }
|
|
.grid { display:grid; grid-template-columns: repeat(auto-fit, minmax(220px,1fr)); gap:16px; margin-bottom:20px; }
|
|
.card { background: var(--card); border-radius:12px; padding:20px; border:1px solid #334155; }
|
|
.card h3 { margin:0 0 8px; color: var(--muted); font-size:14px; font-weight:500; }
|
|
.card .value { font-size:26px; font-weight:700; }
|
|
.badge { display:inline-block; padding:4px 12px; border-radius:999px; font-size:14px; font-weight:700; }
|
|
.badge.up { background:rgba(22,163,74,.15); color:var(--up); }
|
|
.badge.down { background:rgba(220,38,38,.15); color:var(--down); }
|
|
.badge.warn { background:rgba(245,158,11,.15); color:var(--warn); }
|
|
.chart-card { background:var(--card); border-radius:12px; padding:20px; border:1px solid #334155; margin-bottom:20px; }
|
|
table { width:100%; border-collapse: collapse; font-size:13px; }
|
|
th, td { padding:8px 10px; text-align:right; border-bottom:1px solid #334155; }
|
|
th { color: var(--muted); font-weight:600; }
|
|
.log-card { background:var(--card); border-radius:12px; padding:20px; border:1px solid #334155; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<h1>📡 لوحة مراقبة mithal.space</h1>
|
|
<div class="sub">تحديث تلقائي كل دقيقة (يقرأ من history.json / latest.json الناتجة عن collector.py)</div>
|
|
|
|
<div class="grid">
|
|
<div class="card">
|
|
<h3>Uptime (آخر 24 ساعة)</h3>
|
|
<div class="value" id="uptimePct">--%</div>
|
|
</div>
|
|
<div class="card">
|
|
<h3>الحالة الحالية</h3>
|
|
<div class="value"><span id="statusBadge" class="badge down">--</span></div>
|
|
</div>
|
|
<div class="card">
|
|
<h3>شهادة SSL</h3>
|
|
<div class="value" id="sslInfo">--</div>
|
|
</div>
|
|
<div class="card">
|
|
<h3>زمن تحليل DNS</h3>
|
|
<div class="value" id="dnsMs">-- ms</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="chart-card">
|
|
<h3 style="color:var(--muted); margin-top:0;">زمن الاستجابة — آخر ساعة</h3>
|
|
<canvas id="latencyChart" height="90"></canvas>
|
|
</div>
|
|
|
|
<div class="log-card">
|
|
<h3 style="color:var(--muted); margin-top:0;">آخر 10 فحوصات</h3>
|
|
<table>
|
|
<thead>
|
|
<tr><th>الوقت</th><th>الحالة</th><th>HTTP</th><th>زمن الاستجابة</th><th>DNS</th><th>البحث</th></tr>
|
|
</thead>
|
|
<tbody id="logBody"></tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<script>
|
|
let chart = null;
|
|
|
|
async function loadData() {
|
|
try {
|
|
const [historyRes, latestRes] = await Promise.all([
|
|
fetch('history.json', { cache: 'no-store' }),
|
|
fetch('latest.json', { cache: 'no-store' })
|
|
]);
|
|
const history = await historyRes.json();
|
|
const latest = await latestRes.json();
|
|
|
|
renderSummary(history, latest);
|
|
renderChart(history);
|
|
renderLog(history);
|
|
} catch (err) {
|
|
document.getElementById('statusBadge').textContent = 'لا يوجد اتصال بالبيانات';
|
|
console.error(err);
|
|
}
|
|
}
|
|
|
|
function renderSummary(history, latest) {
|
|
// Uptime % لآخر 24 ساعة
|
|
const last24h = history.slice(-1440);
|
|
const upCount = last24h.filter(r => r.up).length;
|
|
const pct = last24h.length ? ((upCount / last24h.length) * 100).toFixed(2) : '0.00';
|
|
document.getElementById('uptimePct').textContent = pct + '%';
|
|
|
|
// الحالة الحالية
|
|
const badge = document.getElementById('statusBadge');
|
|
badge.textContent = latest.up ? 'يعمل ✅' : 'متعطل ❌';
|
|
badge.className = 'badge ' + (latest.up ? 'up' : 'down');
|
|
|
|
// SSL
|
|
const sslEl = document.getElementById('sslInfo');
|
|
if (latest.ssl_valid) {
|
|
const days = latest.ssl_days_left;
|
|
sslEl.textContent = `صالحة (${days} يوم متبقي)`;
|
|
sslEl.parentElement.querySelector('.value').style.color = days < 14 ? 'var(--warn)' : 'var(--text)';
|
|
} else {
|
|
sslEl.textContent = 'غير صالحة ⚠️';
|
|
}
|
|
|
|
// DNS
|
|
document.getElementById('dnsMs').textContent = (latest.dns_ms ?? '--') + ' ms';
|
|
}
|
|
|
|
function renderChart(history) {
|
|
const lastHour = history.slice(-60); // فحص كل دقيقة → آخر 60 نقطة = ساعة
|
|
const labels = lastHour.map(r => new Date(r.timestamp).toLocaleTimeString('ar'));
|
|
const data = lastHour.map(r => r.latency_ms >= 0 ? r.latency_ms : null);
|
|
|
|
if (chart) { chart.destroy(); }
|
|
chart = new Chart(document.getElementById('latencyChart'), {
|
|
type: 'line',
|
|
data: {
|
|
labels,
|
|
datasets: [{
|
|
label: 'زمن الاستجابة (ms)',
|
|
data,
|
|
borderColor: '#2563eb',
|
|
backgroundColor: 'rgba(37,99,235,.15)',
|
|
tension: 0.3,
|
|
fill: true,
|
|
pointRadius: 2
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
scales: {
|
|
x: { ticks: { color: '#94a3b8' }, grid: { color: '#334155' } },
|
|
y: { ticks: { color: '#94a3b8' }, grid: { color: '#334155' } }
|
|
},
|
|
plugins: { legend: { labels: { color: '#e2e8f0' } } }
|
|
}
|
|
});
|
|
}
|
|
|
|
function renderLog(history) {
|
|
const last10 = history.slice(-10).reverse();
|
|
const body = document.getElementById('logBody');
|
|
body.innerHTML = '';
|
|
for (const r of last10) {
|
|
const tr = document.createElement('tr');
|
|
tr.innerHTML = `
|
|
<td>${new Date(r.timestamp).toLocaleString('ar')}</td>
|
|
<td>${r.up ? '✅ يعمل' : '❌ متعطل'}</td>
|
|
<td>${r.status_code ?? '--'}</td>
|
|
<td>${r.latency_ms >= 0 ? r.latency_ms + ' ms' : '--'}</td>
|
|
<td>${r.dns_ms >= 0 ? r.dns_ms + ' ms' : '--'}</td>
|
|
<td>${r.search_latency_ms >= 0 ? r.search_latency_ms + ' ms' : '--'}</td>
|
|
`;
|
|
body.appendChild(tr);
|
|
}
|
|
}
|
|
|
|
loadData();
|
|
setInterval(loadData, 60000);
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|