Refactor code structure for improved readability and maintainability

هذا الالتزام موجود في:
2026-07-28 00:15:47 +03:00
الأصل 439a31ba93
التزام 04a73e93fa
14 ملفات معدلة مع 461 إضافات و77 حذوفات

13
Q1/Dashboard/Dockerfile Normal file
عرض الملف

@@ -0,0 +1,13 @@
# Minimal static-file server for just the dashboard, so it can be deployed
# as its own service on Ghaymah, separate from the API container.
FROM nginx:alpine
# nginx's default port is 80; most container platforms (Ghaymah included,
# per its port-configuration step) let you pick which port the service
# listens on, so we keep nginx's default here and just set that port
# when creating the service.
COPY index.html /usr/share/nginx/html/index.html
EXPOSE 80

213
Q1/Dashboard/index.html Normal file
عرض الملف

@@ -0,0 +1,213 @@
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>لوحة مراقبة الـ API</title>
<style>
:root{
--bg:#f4f7f6;
--card:#ffffff;
--border:#e3e8ec;
--text:#25313c;
--muted:#7a8894;
--accent:#0056b3;
--up:#28a745;
--down:#dc3545;
--warn:#f0ad4e;
}
*{ box-sizing:border-box; }
body{
font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background:var(--bg);
display:flex; justify-content:center; align-items:center;
min-height:100vh; margin:0; padding:24px;
}
.dashboard{
background:var(--card);
padding:30px;
border-radius:14px;
box-shadow:0 8px 24px rgba(15,35,55,0.08);
text-align:center;
width:420px;
max-width:100%;
border:1px solid var(--border);
}
h2{
color:var(--text);
margin:0 0 22px;
display:flex;
align-items:center;
justify-content:center;
gap:10px;
font-size:1.25rem;
}
.status-dot{
width:12px; height:12px; border-radius:50%;
background:#9aa5ad;
box-shadow:0 0 0 0 rgba(154,165,173,0.5);
flex-shrink:0;
}
.status-dot.online{ background:var(--up); animation:pulse-up 2s infinite; }
.status-dot.offline{ background:var(--down); animation:pulse-down 2s infinite; }
@keyframes pulse-up{
0%{ box-shadow:0 0 0 0 rgba(40,167,69,0.55); }
70%{ box-shadow:0 0 0 9px rgba(40,167,69,0); }
100%{ box-shadow:0 0 0 0 rgba(40,167,69,0); }
}
@keyframes pulse-down{
0%{ box-shadow:0 0 0 0 rgba(220,53,69,0.55); }
70%{ box-shadow:0 0 0 9px rgba(220,53,69,0); }
100%{ box-shadow:0 0 0 0 rgba(220,53,69,0); }
}
.metric{
background:#eef2f5;
margin:10px 0;
padding:14px 16px;
border-radius:9px;
font-size:1.05rem;
display:flex;
justify-content:space-between;
align-items:center;
}
.metric span{ font-weight:700; color:var(--accent); font-variant-numeric:tabular-nums; }
.metric span.status-online{ color:var(--up); }
.metric span.status-offline{ color:var(--down); }
.meta-row{
margin-top:16px;
font-size:0.75rem;
color:var(--muted);
display:flex;
justify-content:space-between;
}
.api-row{
margin-top:18px;
padding-top:14px;
border-top:1px dashed var(--border);
text-align:right;
}
.api-row label{
display:block;
font-size:0.72rem;
color:var(--muted);
margin-bottom:6px;
}
.api-row .input-group{
display:flex;
gap:6px;
}
.api-row input{
flex:1;
padding:8px 10px;
border:1px solid var(--border);
border-radius:7px;
font-size:0.8rem;
direction:ltr;
text-align:left;
}
.api-row button{
padding:8px 14px;
border:none;
border-radius:7px;
background:var(--accent);
color:#fff;
font-size:0.8rem;
cursor:pointer;
}
.api-row button:hover{ opacity:0.9; }
</style>
</head>
<body>
<div class="dashboard">
<h2>مراقبة النظام <span id="statusDot" class="status-dot"></span></h2>
<div class="metric">
الحالة: <span id="statusText">جاري التحميل...</span>
</div>
<div class="metric">
زمن الاستجابة: <span id="responseTime">— ms</span>
</div>
<div class="metric">
عدد الطلبات الإجمالي: <span id="requestCount"></span>
</div>
<div class="meta-row">
<span id="uptimeText">التشغيل: —</span>
<span id="lastUpdated">آخر تحديث: —</span>
</div>
<div class="api-row">
<label for="apiInput">عنوان الـ API (health endpoint):</label>
<div class="input-group">
<input id="apiInput" type="text" placeholder="https://backend-06febdb2d9f4.hosted.ghaymah.systems/health">
<button id="apiSave">حفظ</button>
</div>
</div>
</div>
<script>
// This copy of the dashboard is meant to be hosted on its own — separately
// from the API — so it needs to be told where the API lives. Resolution
// order:
// 1. ?api=<url> query-string param, e.g. index.html?api=https://foo/health
// 2. the value typed into the input box below (kept only in memory /
// in the URL — nothing is written to disk or browser storage)
// 3. falls back to the hosted backend URL provided for this project.
const POLL_MS = 5000;
const DEFAULT_HEALTH_URL = 'https://backend-06febdb2d9f4.hosted.ghaymah.systems/health';
let healthUrl = new URLSearchParams(window.location.search).get('api') || DEFAULT_HEALTH_URL;
const apiInput = document.getElementById('apiInput');
apiInput.value = healthUrl === '/health' ? '' : healthUrl;
document.getElementById('apiSave').addEventListener('click', () => {
const val = apiInput.value.trim();
if (val) {
healthUrl = val;
const url = new URL(window.location.href);
url.searchParams.set('api', val);
window.history.replaceState({}, '', url); // shareable link, no storage used
fetchMetrics();
}
});
function fmtUptime(seconds){
if (seconds === undefined || seconds === null) return '—';
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = Math.floor(seconds % 60);
return `${h}h ${m}m ${s}s`;
}
async function fetchMetrics(){
try {
const res = await fetch(healthUrl, { cache: 'no-store' });
if (!res.ok) throw new Error('HTTP ' + res.status);
const data = await res.json();
const statusEl = document.getElementById('statusText');
statusEl.innerText = data.status || 'Online';
statusEl.className = 'status-online';
document.getElementById('statusDot').className = 'status-dot online';
document.getElementById('responseTime').innerText = (data.response_time_ms ?? 0) + ' ms';
document.getElementById('requestCount').innerText = data.request_count ?? '—';
document.getElementById('uptimeText').innerText = 'التشغيل: ' + fmtUptime(data.uptime_seconds);
document.getElementById('lastUpdated').innerText =
'آخر تحديث: ' + new Date().toLocaleTimeString('ar-EG');
} catch (error) {
const statusEl = document.getElementById('statusText');
statusEl.innerText = 'انقطاع الاتصال (Offline)';
statusEl.className = 'status-offline';
document.getElementById('statusDot').className = 'status-dot offline';
document.getElementById('responseTime').innerText = '—';
}
}
fetchMetrics();
setInterval(fetchMetrics, POLL_MS);
</script>
</body>
</html>