هذا الالتزام موجود في:
2026-07-27 00:05:12 +03:00
التزام 80980bbf16
19 ملفات معدلة مع 1912 إضافات و0 حذوفات

عرض الملف

@@ -0,0 +1,232 @@
<!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:#0b0f14;
--panel:#111823;
--panel-border:#1e2a38;
--text:#dbe4ee;
--muted:#7d8ba0;
--ok:#33d17a;
--bad:#f2495c;
--warn:#f2b632;
--accent:#3ea6ff;
--mono: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
}
*{box-sizing:border-box;}
body{
margin:0;
background:var(--bg);
color:var(--text);
font-family: var(--mono);
min-height:100vh;
padding:28px 20px 60px;
}
.wrap{max-width:960px;margin:0 auto;}
header{
display:flex;justify-content:space-between;align-items:flex-end;
margin-bottom:22px;flex-wrap:wrap;gap:12px;
}
h1{font-size:1.25rem;margin:0;letter-spacing:.02em;font-weight:600;}
.subtitle{color:var(--muted);font-size:.8rem;margin-top:4px;}
.config{display:flex;gap:8px;align-items:center;}
.config input{
background:#0f151d;border:1px solid var(--panel-border);color:var(--text);
padding:8px 10px;border-radius:6px;font-family:var(--mono);font-size:.8rem;width:280px;
}
.config button{
background:var(--accent);border:none;color:#04101c;font-weight:700;
padding:8px 14px;border-radius:6px;cursor:pointer;font-family:var(--mono);font-size:.8rem;
}
.config button:hover{filter:brightness(1.1);}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px;margin-bottom:16px;}
.card{
background:var(--panel);border:1px solid var(--panel-border);border-radius:10px;
padding:16px 18px;position:relative;overflow:hidden;
}
.label{color:var(--muted);font-size:.72rem;text-transform:uppercase;letter-spacing:.08em;margin-bottom:8px;}
.value{font-size:1.9rem;font-weight:700;line-height:1.1;}
.value.small{font-size:1.1rem;}
.status-row{display:flex;align-items:center;gap:10px;}
.dot{width:12px;height:12px;border-radius:50%;background:var(--muted);flex:0 0 auto;}
.dot.ok{background:var(--ok);box-shadow:0 0 0 0 rgba(51,209,122,.6);animation:pulse 2s infinite;}
.dot.bad{background:var(--bad);}
@keyframes pulse{
0%{box-shadow:0 0 0 0 rgba(51,209,122,.55);}
70%{box-shadow:0 0 0 8px rgba(51,209,122,0);}
100%{box-shadow:0 0 0 0 rgba(51,209,122,0);}
}
.chart-card{grid-column:1/-1;}
canvas{width:100%;height:140px;display:block;}
.log{
background:var(--panel);border:1px solid var(--panel-border);border-radius:10px;
padding:14px 18px;font-size:.76rem;color:var(--muted);max-height:170px;overflow-y:auto;
}
.log div{padding:2px 0;border-bottom:1px dashed #1a2431;}
.log .fail{color:var(--bad);}
.log .ok-line{color:var(--ok);}
footer{margin-top:18px;color:var(--muted);font-size:.7rem;text-align:center;}
</style>
</head>
<body>
<div class="wrap">
<header>
<div>
<h1>لوحة مراقبة التطبيق</h1>
<div class="subtitle">فحص كل <span id="intervalLabel">5</span> ثوانٍ · Ghaymah Containers</div>
</div>
<div class="config">
<input id="apiUrl" type="text" placeholder="https://your-app.ghaymah.systems" />
<button id="connectBtn">اتصال</button>
</div>
</header>
<div class="grid">
<div class="card">
<div class="label">الحالة</div>
<div class="status-row">
<div class="dot" id="statusDot"></div>
<div class="value small" id="statusText">غير متصل</div>
</div>
</div>
<div class="card">
<div class="label">زمن الاستجابة</div>
<div class="value" id="latencyValue">--</div>
</div>
<div class="card">
<div class="label">عدد الطلبات</div>
<div class="value" id="requestsValue">--</div>
</div>
<div class="card">
<div class="label">مدة التشغيل</div>
<div class="value small" id="uptimeValue">--</div>
</div>
</div>
<div class="card chart-card">
<div class="label">زمن الاستجابة (آخر 30 قراءة)</div>
<canvas id="chart"></canvas>
</div>
<div class="log" id="log"></div>
<footer>يعتمد على نقاط الوصول /health و /metrics · يعمل بالكامل من المتصفح</footer>
</div>
<script>
const POLL_MS = 5000;
const HISTORY_LEN = 30;
let latencyHistory = [];
let timer = null;
const els = {
dot: document.getElementById('statusDot'),
statusText: document.getElementById('statusText'),
latency: document.getElementById('latencyValue'),
requests: document.getElementById('requestsValue'),
uptime: document.getElementById('uptimeValue'),
log: document.getElementById('log'),
apiUrl: document.getElementById('apiUrl'),
connectBtn: document.getElementById('connectBtn'),
};
function fmtUptime(sec){
if(sec == null) return '--';
const h = Math.floor(sec/3600), m = Math.floor((sec%3600)/60), s = Math.floor(sec%60);
return `${h}س ${m}د ${s}ث`;
}
function addLog(msg, ok){
const line = document.createElement('div');
line.className = ok ? 'ok-line' : 'fail';
const time = new Date().toLocaleTimeString('ar-EG');
line.textContent = `[${time}] ${msg}`;
els.log.prepend(line);
while(els.log.children.length > 40) els.log.removeChild(els.log.lastChild);
}
function drawChart(){
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
const dpr = window.devicePixelRatio || 1;
const w = canvas.clientWidth, h = canvas.clientHeight;
canvas.width = w * dpr; canvas.height = h * dpr;
ctx.scale(dpr, dpr);
ctx.clearRect(0,0,w,h);
if(latencyHistory.length < 2){
ctx.fillStyle = '#3a4a5c';
ctx.font = '12px monospace';
ctx.fillText('بانتظار بيانات كافية...', 10, h/2);
return;
}
const max = Math.max(...latencyHistory, 10) * 1.2;
const stepX = w / (HISTORY_LEN - 1);
ctx.strokeStyle = '#3ea6ff';
ctx.lineWidth = 2;
ctx.beginPath();
latencyHistory.forEach((v,i) => {
const x = i * stepX;
const y = h - (v / max) * (h - 10) - 5;
i === 0 ? ctx.moveTo(x,y) : ctx.lineTo(x,y);
});
ctx.stroke();
ctx.lineTo((latencyHistory.length-1)*stepX, h);
ctx.lineTo(0, h);
ctx.closePath();
ctx.fillStyle = 'rgba(62,166,255,0.12)';
ctx.fill();
}
async function poll(){
const base = els.apiUrl.value.trim().replace(/\/$/, '');
if(!base) return;
const start = performance.now();
try{
const res = await fetch(base + '/metrics', {cache:'no-store'});
const elapsed = Math.round(performance.now() - start);
if(!res.ok) throw new Error('HTTP ' + res.status);
const data = await res.json();
els.dot.className = 'dot ok';
els.statusText.textContent = 'يعمل';
els.latency.textContent = (data.avg_response_time_ms ?? elapsed) + ' ms';
els.requests.textContent = data.requests_total ?? '--';
els.uptime.textContent = fmtUptime(data.uptime_seconds);
latencyHistory.push(elapsed);
if(latencyHistory.length > HISTORY_LEN) latencyHistory.shift();
drawChart();
addLog(`الفحص ناجح - زمن الاستجابة ${elapsed}ms`, true);
}catch(err){
els.dot.className = 'dot bad';
els.statusText.textContent = 'متوقف / لا يستجيب';
addLog('فشل الفحص: ' + err.message, false);
}
}
els.connectBtn.addEventListener('click', () => {
if(timer) clearInterval(timer);
latencyHistory = [];
poll();
timer = setInterval(poll, POLL_MS);
});
window.addEventListener('resize', drawChart);
drawChart();
</script>
</body>
</html>