الملفات
ghaymah-exam-Moataz-Gamal-H…/q1-deploy-monitor/dashboard/index.html
2026-07-28 14:17:17 +03:00

221 أسطر
6.8 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>لوحة مراقبة التطبيق</title>
<style>
:root {
--bg: #0f1420;
--card: #171f30;
--border: #2a3448;
--text: #e7ecf5;
--muted: #8b96ab;
--green: #2ecc71;
--red: #e74c3c;
--accent: #4f8cff;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: 'Segoe UI', Tahoma, Arial, sans-serif;
background: var(--bg);
color: var(--text);
padding: 24px;
}
h1 { font-size: 20px; margin-bottom: 4px; }
.sub { color: var(--muted); font-size: 13px; margin-bottom: 20px; }
.controls {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.controls input {
flex: 1;
min-width: 220px;
padding: 10px 12px;
border-radius: 8px;
border: 1px solid var(--border);
background: var(--card);
color: var(--text);
font-size: 14px;
}
.controls button {
padding: 10px 18px;
border-radius: 8px;
border: none;
background: var(--accent);
color: white;
font-size: 14px;
cursor: pointer;
}
.controls button:hover { opacity: 0.9; }
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 16px;
margin-bottom: 20px;
}
.card {
background: var(--card);
border: 1px solid var(--border);
border-radius: 12px;
padding: 18px;
}
.card .label { color: var(--muted); font-size: 13px; margin-bottom: 8px; }
.card .value { font-size: 26px; font-weight: 600; }
.status-dot {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
margin-left: 8px;
}
.up { color: var(--green); }
.down { color: var(--red); }
.dot-up { background: var(--green); box-shadow: 0 0 8px var(--green); }
.dot-down { background: var(--red); box-shadow: 0 0 8px var(--red); }
.log {
background: var(--card);
border: 1px solid var(--border);
border-radius: 12px;
padding: 16px;
max-height: 260px;
overflow-y: auto;
font-family: 'Consolas', monospace;
font-size: 12.5px;
}
.log div { padding: 4px 0; border-bottom: 1px solid #202a3d; }
.log .ok { color: var(--green); }
.log .fail { color: var(--red); }
</style>
</head>
<body>
<h1>لوحة مراقبة التطبيق</h1>
<div class="sub">يفحص /health و /metrics مباشرة من التطبيق كل 10 ثوانٍ</div>
<div class="controls">
<input id="apiUrl" type="text" placeholder="https://your-app.ghaymah.systems" value="">
<button onclick="startMonitoring()">بدء المراقبة</button>
</div>
<div class="grid">
<div class="card">
<div class="label">الحالة</div>
<div class="value" id="statusValue"><span class="status-dot" id="statusDot"></span><span id="statusText">--</span></div>
</div>
<div class="card">
<div class="label">زمن الاستجابة</div>
<div class="value" id="latencyValue">-- ms</div>
</div>
<div class="card">
<div class="label">عدد الطلبات (على السيرفر)</div>
<div class="value" id="requestCountValue">--</div>
</div>
<div class="card">
<div class="label">مدة التشغيل</div>
<div class="value" id="uptimeValue">--</div>
</div>
</div>
<div class="log" id="log"></div>
<script>
let timer = null;
function fmtUptime(sec) {
if (sec == null) return '--';
const h = Math.floor(sec / 3600);
const m = Math.floor((sec % 3600) / 60);
const s = sec % 60;
return `${h}h ${m}m ${s}s`;
}
function addLogLine(text, ok) {
const log = document.getElementById('log');
const line = document.createElement('div');
line.className = ok ? 'ok' : 'fail';
line.textContent = text;
log.prepend(line);
while (log.children.length > 50) log.removeChild(log.lastChild);
}
async function checkOnce(baseUrl) {
const start = performance.now();
try {
const res = await fetch(baseUrl.replace(/\/$/, '') + '/health', { cache: 'no-store' });
const elapsed = Math.round(performance.now() - start);
const ok = res.ok;
// /health may return JSON (custom API) or plain text "ok" (static site) - handle both
const raw = await res.text();
let data = {};
try { data = JSON.parse(raw); } catch (e) { /* plain text health response, fine */ }
document.getElementById('statusDot').className = 'status-dot ' + (ok ? 'dot-up' : 'dot-down');
document.getElementById('statusText').textContent = ok ? 'متصل (UP)' : 'متوقف (DOWN)';
document.getElementById('statusText').className = ok ? 'up' : 'down';
document.getElementById('latencyValue').textContent = elapsed + ' ms';
document.getElementById('uptimeValue').textContent = data.uptime_seconds ? fmtUptime(data.uptime_seconds) : '--';
addLogLine(`[${new Date().toLocaleTimeString()}] ${ok ? 'OK' : 'FAIL'} - ${elapsed}ms`, ok);
// Try a JSON /metrics endpoint first (custom API apps like demo-api)
let gotMetrics = false;
try {
const mRes = await fetch(baseUrl.replace(/\/$/, '') + '/metrics', { cache: 'no-store' });
if (mRes.ok) {
const mData = await mRes.json();
document.getElementById('requestCountValue').textContent = mData.request_count ?? '--';
gotMetrics = true;
}
} catch (e) { /* no /metrics endpoint, try nginx_status next */ }
// Fallback: nginx stub_status (used by static sites served via nginx)
if (!gotMetrics) {
try {
const sRes = await fetch(baseUrl.replace(/\/$/, '') + '/nginx_status', { cache: 'no-store' });
if (sRes.ok) {
const text = await sRes.text();
// Format: "server accepts handled requests\n a b c "
const match = text.match(/\d+\s+\d+\s+(\d+)/);
document.getElementById('requestCountValue').textContent = match ? match[1] : '--';
}
} catch (e) { /* neither metrics source available */ }
}
} catch (err) {
document.getElementById('statusDot').className = 'status-dot dot-down';
document.getElementById('statusText').textContent = 'متوقف (DOWN)';
document.getElementById('statusText').className = 'down';
document.getElementById('latencyValue').textContent = '-- ms';
addLogLine(`[${new Date().toLocaleTimeString()}] FAIL - ${err.message}`, false);
}
}
function startMonitoring() {
const url = document.getElementById('apiUrl').value.trim();
if (!url) return;
if (timer) clearInterval(timer);
checkOnce(url);
timer = setInterval(() => checkOnce(url), 10000);
}
// Auto-fill with this site's own address (dashboard is served from the same app)
// and start monitoring immediately.
window.addEventListener('DOMContentLoaded', () => {
const input = document.getElementById('apiUrl');
if (!input.value) {
input.value = window.location.origin;
}
startMonitoring();
});
</script>
</body>
</html>