Initial mithal monitoring dashboard

هذا الالتزام موجود في:
2026-07-27 19:50:04 +03:00
التزام cd3079c6e2
6 ملفات معدلة مع 560 إضافات و0 حذوفات

336
dashboard.html Normal file
عرض الملف

@@ -0,0 +1,336 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>mithal.space Monitoring Dashboard</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f0f2f5;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px 30px;
border-radius: 12px;
margin-bottom: 20px;
}
.header h1 {
font-weight: 300;
font-size: 2rem;
}
.header .subtitle {
opacity: 0.9;
margin-top: 5px;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.card {
background: white;
padding: 20px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
transition: transform 0.2s;
}
.card:hover {
transform: translateY(-3px);
box-shadow: 0 4px 16px rgba(0,0,0,0.12);
}
.card-label {
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 0.5px;
color: #6c757d;
margin-bottom: 8px;
}
.card-value {
font-size: 2rem;
font-weight: 600;
}
.card-value .unit {
font-size: 1rem;
font-weight: 400;
color: #6c757d;
margin-left: 4px;
}
.status-up { color: #28a745; }
.status-down { color: #dc3545; }
.chart-container {
background: white;
padding: 20px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
margin-bottom: 20px;
height: 300px;
}
.table-container {
background: white;
padding: 20px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 0.9rem;
}
th {
text-align: left;
padding: 12px 8px;
border-bottom: 2px solid #dee2e6;
color: #495057;
font-weight: 600;
}
td {
padding: 10px 8px;
border-bottom: 1px solid #e9ecef;
}
.badge {
display: inline-block;
padding: 2px 12px;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 600;
}
.badge-up { background: #d4edda; color: #155724; }
.badge-down { background: #f8d7da; color: #721c24; }
.badge-ssl-ok { background: #d4edda; color: #155724; }
.badge-ssl-warning { background: #fff3cd; color: #856404; }
.badge-ssl-danger { background: #f8d7da; color: #721c24; }
.text-muted { color: #6c757d; font-size: 0.85rem; }
.text-center { text-align: center; }
.last-update {
margin-top: 20px;
padding: 12px;
background: white;
border-radius: 8px;
text-align: center;
color: #6c757d;
font-size: 0.9rem;
}
@media (max-width: 600px) {
.grid { grid-template-columns: 1fr 1fr; }
.card-value { font-size: 1.5rem; }
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>📊 mithal.space Monitoring Dashboard</h1>
<div class="subtitle">Real-time performance & availability monitoring</div>
</div>
<div class="grid">
<div class="card">
<div class="card-label">🟢 Uptime (24h)</div>
<div class="card-value" id="uptime">--</div>
</div>
<div class="card">
<div class="card-label">📡 Current Status</div>
<div class="card-value" id="status">--</div>
</div>
<div class="card">
<div class="card-label">⚡ Last Latency</div>
<div class="card-value" id="last-latency">-- <span class="unit">ms</span></div>
</div>
<div class="card">
<div class="card-label">🔒 SSL Certificate</div>
<div class="card-value" id="ssl-status">--</div>
<div class="text-muted" id="ssl-days">-- days remaining</div>
</div>
<div class="card">
<div class="card-label">🌐 DNS Resolution</div>
<div class="card-value" id="dns-latency">-- <span class="unit">ms</span></div>
</div>
<div class="card">
<div class="card-label">🔍 Search Response</div>
<div class="card-value" id="search-latency">-- <span class="unit">ms</span></div>
</div>
</div>
<div class="chart-container">
<canvas id="latencyChart"></canvas>
</div>
<div class="table-container">
<h3 style="margin-bottom: 15px; font-weight: 400;">📋 Recent Checks (last 10)</h3>
<table>
<thead>
<tr>
<th>Timestamp</th>
<th>Status</th>
<th>Latency (ms)</th>
<th>DNS (ms)</th>
<th>SSL</th>
<th>Search (ms)</th>
</tr>
</thead>
<tbody id="checks-body">
<tr><td colspan="6" class="text-muted text-center">Loading data...</td></tr>
</tbody>
</table>
</div>
<div class="last-update" id="last-update">📡 Last updated: Loading...</div>
</div>
<script>
let chart = null;
function computeUptime(data) {
if (!data || data.length === 0) return 0;
const now = new Date();
const last24h = data.filter(entry => {
const ts = new Date(entry.timestamp);
return (now - ts) <= 24 * 60 * 60 * 1000;
});
if (last24h.length === 0) return 0;
const up = last24h.filter(e => e.is_up === true).length;
return (up / last24h.length) * 100;
}
function getSslBadge(days) {
if (days === null || days === undefined)
return '<span class="badge badge-down">❌ Unknown</span>';
if (days > 30)
return '<span class="badge badge-ssl-ok">✅ Valid</span>';
else if (days > 7)
return '<span class="badge badge-ssl-warning">⚠️ Expiring soon</span>';
else
return '<span class="badge badge-ssl-danger">🚨 Expiring!</span>';
}
function updateDashboard(data) {
if (!data || data.length === 0) {
document.getElementById('uptime').textContent = '--';
document.getElementById('status').innerHTML = '--';
document.getElementById('last-latency').innerHTML = '-- <span class="unit">ms</span>';
document.getElementById('ssl-status').textContent = '--';
document.getElementById('ssl-days').textContent = '-- days remaining';
document.getElementById('dns-latency').innerHTML = '-- <span class="unit">ms</span>';
document.getElementById('search-latency').innerHTML = '-- <span class="unit">ms</span>';
document.getElementById('checks-body').innerHTML = '<tr><td colspan="6" class="text-muted text-center">No data</td></tr>';
return;
}
const latest = data[data.length - 1];
const statusEl = document.getElementById('status');
statusEl.innerHTML = latest.is_up ? '<span class="status-up">✅ UP</span>' : '<span class="status-down">❌ DOWN</span>';
document.getElementById('last-latency').innerHTML = `${latest.latency_ms || '—'} <span class="unit">ms</span>`;
const sslDays = latest.ssl_days_remaining;
document.getElementById('ssl-status').innerHTML = getSslBadge(sslDays);
document.getElementById('ssl-days').textContent = sslDays !== null ? `${sslDays} days remaining` : 'Unknown';
document.getElementById('dns-latency').innerHTML = `${latest.dns_ms || '—'} <span class="unit">ms</span>`;
document.getElementById('search-latency').innerHTML = `${latest.search_latency_ms || '—'} <span class="unit">ms</span>`;
const uptime = computeUptime(data);
document.getElementById('uptime').textContent = uptime.toFixed(2) + '%';
// Table (last 10)
const tbody = document.getElementById('checks-body');
tbody.innerHTML = '';
const last10 = data.slice(-10).reverse();
last10.forEach(entry => {
const row = document.createElement('tr');
const statusBadge = entry.is_up ? '<span class="badge badge-up">UP</span>' : '<span class="badge badge-down">DOWN</span>';
const sslBadge = getSslBadge(entry.ssl_days_remaining);
row.innerHTML = `
<td>${new Date(entry.timestamp).toLocaleString()}</td>
<td>${statusBadge}</td>
<td>${entry.latency_ms || '—'}</td>
<td>${entry.dns_ms || '—'}</td>
<td>${sslBadge}</td>
<td>${entry.search_latency_ms || '—'}</td>
`;
tbody.appendChild(row);
});
document.getElementById('last-update').textContent = `📡 Last updated: ${new Date().toLocaleString()}`;
// Chart (last 60)
const chartData = data.slice(-60);
const labels = chartData.map(e => new Date(e.timestamp).toLocaleTimeString());
const latencies = chartData.map(e => e.latency_ms || 0);
const statuses = chartData.map(e => e.is_up ? 1 : 0);
if (chart) {
chart.data.labels = labels;
chart.data.datasets[0].data = latencies;
chart.data.datasets[0].pointBackgroundColor = statuses.map(s => s === 1 ? '#28a745' : '#dc3545');
chart.update();
} else {
const ctx = document.getElementById('latencyChart').getContext('2d');
chart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: 'Latency (ms)',
data: latencies,
borderColor: '#667eea',
backgroundColor: 'rgba(102, 126, 234, 0.1)',
tension: 0.2,
fill: true,
pointRadius: 3,
pointBackgroundColor: statuses.map(s => s === 1 ? '#28a745' : '#dc3545'),
pointBorderColor: '#ffffff',
pointBorderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } },
scales: {
x: { grid: { display: false }, ticks: { maxTicksLimit: 15 } },
y: { beginAtZero: true, grid: { color: '#f0f0f0' }, title: { display: true, text: 'Latency (ms)' } }
}
}
});
}
}
async function fetchData() {
try {
const response = await fetch('monitoring-data.json');
if (!response.ok) throw new Error('Network error');
const data = await response.json();
return Array.isArray(data) ? data : [];
} catch (error) {
console.error('Failed to fetch data:', error);
return [];
}
}
async function refresh() {
const data = await fetchData();
updateDashboard(data);
}
refresh();
setInterval(refresh, 10000);
</script>
</body>
</html>