Initial Ghaymah tasks setup
هذا الالتزام موجود في:
171
task5-mithal-dashboard/dashboard.html
Normal file
171
task5-mithal-dashboard/dashboard.html
Normal file
@@ -0,0 +1,171 @@
|
||||
<!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>
|
||||
المرجع في مشكلة جديدة
حظر مستخدم