Files
wiki/tasks/flightradar24/frontend/static/monitoring.html
2026-04-20 02:10:01 +03:00

273 lines
6.9 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FR24 Monitoring</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: #0d1117;
color: #c9d1d9;
font-family: 'Segoe UI', system-ui, sans-serif;
font-size: 14px;
min-height: 100vh;
padding: 24px;
}
header {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 28px;
}
header a {
color: #58a6ff;
text-decoration: none;
font-size: 13px;
opacity: 0.8;
transition: opacity 0.15s;
}
header a:hover { opacity: 1; }
header h1 {
font-size: 18px;
font-weight: 600;
color: #e6edf3;
}
#last-updated {
margin-left: auto;
font-size: 12px;
color: #6e7681;
}
.metrics {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 16px;
margin-bottom: 32px;
}
.card {
background: #161b22;
border: 1px solid #30363d;
border-radius: 8px;
padding: 20px;
}
.card .label {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #6e7681;
margin-bottom: 8px;
}
.card .value {
font-size: 32px;
font-weight: 700;
line-height: 1;
}
.card .unit {
font-size: 13px;
color: #6e7681;
margin-top: 4px;
}
.green { color: #3fb950; }
.yellow { color: #d29922; }
.red { color: #f85149; }
.neutral { color: #e6edf3; }
h2 {
font-size: 14px;
font-weight: 600;
color: #8b949e;
text-transform: uppercase;
letter-spacing: 0.06em;
margin-bottom: 12px;
}
.table-wrap {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
thead th {
text-align: left;
padding: 8px 12px;
color: #6e7681;
font-weight: 500;
border-bottom: 1px solid #21262d;
white-space: nowrap;
}
tbody tr {
border-bottom: 1px solid #161b22;
transition: background 0.1s;
}
tbody tr:hover { background: #161b22; }
tbody td {
padding: 7px 12px;
white-space: nowrap;
}
#error-banner {
display: none;
background: #2d1b1b;
border: 1px solid #f85149;
border-radius: 6px;
color: #f85149;
padding: 10px 16px;
margin-bottom: 20px;
font-size: 13px;
}
</style>
</head>
<body>
<header>
<a href="/">← Карта</a>
<h1>FR24 Monitoring</h1>
<span id="last-updated"></span>
</header>
<div id="error-banner"></div>
<div class="metrics">
<div class="card">
<div class="label">Disk Usage</div>
<div class="value neutral" id="m-disk"></div>
<div class="unit">%</div>
</div>
<div class="card">
<div class="label">DB Size</div>
<div class="value neutral" id="m-db"></div>
<div class="unit" id="m-db-unit">MB</div>
</div>
<div class="card">
<div class="label">Capture Lag</div>
<div class="value neutral" id="m-lag"></div>
<div class="unit">сек</div>
</div>
<div class="card">
<div class="label">Throughput 5min</div>
<div class="value neutral" id="m-tput"></div>
<div class="unit">пакетов / 5 мин</div>
</div>
<div class="card">
<div class="label">Обработано</div>
<div class="value neutral" id="m-pending"></div>
<div class="unit" id="m-pending-unit">пакетов</div>
</div>
</div>
<h2>История (последние 20)</h2>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Время</th>
<th>Disk %</th>
<th>DB Size</th>
<th>Lag (сек)</th>
<th>Throughput</th>
</tr>
</thead>
<tbody id="history-body">
<tr><td colspan="5" style="color:#6e7681;padding:16px 12px">Загрузка…</td></tr>
</tbody>
</table>
</div>
<script>
function diskColor(v) { return v < 70 ? 'green' : v <= 80 ? 'yellow' : 'red'; }
function lagColor(v) { return v < 60 ? 'green' : v <= 300 ? 'yellow' : 'red'; }
function fmtDb(mb) {
if (mb >= 1024) return [(mb / 1024).toFixed(2), 'GB'];
return [mb.toFixed(1), 'MB'];
}
function fmtTime(iso) {
try {
return new Date(iso).toLocaleString('ru-RU', {
month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit', second: '2-digit'
});
} catch { return iso; }
}
function setMetric(id, value, colorClass) {
const el = document.getElementById(id);
el.textContent = value;
el.className = 'value ' + (colorClass || 'neutral');
}
function renderLatest(d, unprocessed) {
setMetric('m-disk', d.disk_pct, diskColor(d.disk_pct));
const [dbVal, dbUnit] = fmtDb(d.db_size_mb);
setMetric('m-db', dbVal);
document.getElementById('m-db-unit').textContent = dbUnit;
setMetric('m-lag', d.capture_lag_sec, lagColor(d.capture_lag_sec));
setMetric('m-tput', d.throughput_5min.toLocaleString());
if (unprocessed) {
const pct = 100 - unprocessed.pending_pct;
const color = pct >= 95 ? 'green' : pct >= 80 ? 'yellow' : 'red';
setMetric('m-pending', pct.toFixed(1) + '%', color);
document.getElementById('m-pending-unit').textContent =
`обработано (${(unprocessed.total - unprocessed.pending).toLocaleString()} из ${unprocessed.total.toLocaleString()})`;
}
}
function renderHistory(rows) {
const tbody = document.getElementById('history-body');
if (!rows || !rows.length) {
tbody.innerHTML = '<tr><td colspan="5" style="color:#6e7681;padding:16px 12px">Нет данных</td></tr>';
return;
}
tbody.innerHTML = rows.map(r => {
const [dbVal, dbUnit] = fmtDb(r.db_size_mb);
return `<tr>
<td>${fmtTime(r.collected_at)}</td>
<td class="${diskColor(r.disk_pct)}">${r.disk_pct}%</td>
<td>${dbVal} ${dbUnit}</td>
<td class="${lagColor(r.capture_lag_sec)}">${r.capture_lag_sec}</td>
<td>${r.throughput_5min.toLocaleString()}</td>
</tr>`;
}).join('');
}
async function refresh() {
const banner = document.getElementById('error-banner');
try {
const res = await fetch('/api/monitoring/status');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
banner.style.display = 'none';
if (data.latest) renderLatest(data.latest, data.unprocessed);
if (data.history) renderHistory(data.history);
document.getElementById('last-updated').textContent =
'Обновлено: ' + new Date().toLocaleTimeString('ru-RU');
} catch (e) {
banner.textContent = 'Ошибка загрузки данных: ' + e.message;
banner.style.display = 'block';
}
}
refresh();
setInterval(refresh, 30000);
</script>
</body>
</html>