- Added new live_routes table with mask_length column for tracking CIDR prefix lengths - Updated PrefixHandler to maintain live routing table with additions and deletions - Added route expiration functionality (5 minute timeout) to in-memory routing table - Added prefix distribution stats showing count of prefixes by mask length - Added IPv4/IPv6 prefix distribution cards to status page - Updated database interface with UpsertLiveRoute, DeleteLiveRoute, and GetPrefixDistribution - Set all handler queue depths to 50000 for consistency - Doubled DBHandler batch size to 32000 for better throughput - Fixed withdrawal handling to delete routes when origin ASN is available
309 lines
13 KiB
HTML
309 lines
13 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>RouteWatch Status</title>
|
|
<style>
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
background: #f5f5f5;
|
|
}
|
|
h1 {
|
|
color: #333;
|
|
margin-bottom: 30px;
|
|
}
|
|
.status-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
gap: 20px;
|
|
margin-bottom: 30px;
|
|
}
|
|
.status-card {
|
|
background: white;
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
.status-card h2 {
|
|
margin: 0 0 15px 0;
|
|
font-size: 18px;
|
|
color: #666;
|
|
}
|
|
.metric {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: 8px 0;
|
|
border-bottom: 1px solid #eee;
|
|
}
|
|
.metric:last-child {
|
|
border-bottom: none;
|
|
}
|
|
.metric-label {
|
|
color: #666;
|
|
}
|
|
.metric-value {
|
|
font-family: 'SF Mono', Monaco, 'Cascadia Mono', 'Roboto Mono', Consolas, 'Courier New', monospace;
|
|
color: #333;
|
|
}
|
|
.connected {
|
|
color: #22c55e;
|
|
}
|
|
.disconnected {
|
|
color: #ef4444;
|
|
}
|
|
.error {
|
|
background: #fee;
|
|
color: #c00;
|
|
padding: 10px;
|
|
border-radius: 4px;
|
|
margin-top: 20px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>RouteWatch Status</h1>
|
|
<div id="error" class="error" style="display: none;"></div>
|
|
<div class="status-grid">
|
|
<div class="status-card">
|
|
<h2>Connection Status</h2>
|
|
<div class="metric">
|
|
<span class="metric-label">Status</span>
|
|
<span class="metric-value" id="connected">-</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-label">Uptime</span>
|
|
<span class="metric-value" id="uptime">-</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="status-card">
|
|
<h2>Stream Statistics</h2>
|
|
<div class="metric">
|
|
<span class="metric-label">Total Messages</span>
|
|
<span class="metric-value" id="total_messages">-</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-label">Messages/sec</span>
|
|
<span class="metric-value" id="messages_per_sec">-</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-label">Total Data</span>
|
|
<span class="metric-value" id="total_bytes">-</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-label">Throughput</span>
|
|
<span class="metric-value" id="mbits_per_sec">-</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="status-card">
|
|
<h2>Database Statistics</h2>
|
|
<div class="metric">
|
|
<span class="metric-label">ASNs</span>
|
|
<span class="metric-value" id="asns">-</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-label">Total Prefixes</span>
|
|
<span class="metric-value" id="prefixes">-</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-label">IPv4 Prefixes</span>
|
|
<span class="metric-value" id="ipv4_prefixes">-</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-label">IPv6 Prefixes</span>
|
|
<span class="metric-value" id="ipv6_prefixes">-</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-label">Peerings</span>
|
|
<span class="metric-value" id="peerings">-</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-label">Database Size</span>
|
|
<span class="metric-value" id="database_size">-</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="status-card">
|
|
<h2>Routing Table</h2>
|
|
<div class="metric">
|
|
<span class="metric-label">Live Routes</span>
|
|
<span class="metric-value" id="live_routes">-</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-label">IPv4 Routes</span>
|
|
<span class="metric-value" id="ipv4_routes">-</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-label">IPv6 Routes</span>
|
|
<span class="metric-value" id="ipv6_routes">-</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-label">IPv4 Updates/sec</span>
|
|
<span class="metric-value" id="ipv4_updates_per_sec">-</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-label">IPv6 Updates/sec</span>
|
|
<span class="metric-value" id="ipv6_updates_per_sec">-</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="status-grid">
|
|
<div class="status-card">
|
|
<h2>IPv4 Prefix Distribution</h2>
|
|
<div id="ipv4-prefix-distribution">
|
|
<!-- Will be populated dynamically -->
|
|
</div>
|
|
</div>
|
|
|
|
<div class="status-card">
|
|
<h2>IPv6 Prefix Distribution</h2>
|
|
<div id="ipv6-prefix-distribution">
|
|
<!-- Will be populated dynamically -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="handler-stats-container" class="status-grid">
|
|
<!-- Handler stats will be dynamically added here -->
|
|
</div>
|
|
|
|
<script>
|
|
function formatBytes(bytes) {
|
|
if (bytes === 0) return '0 B';
|
|
const k = 1024;
|
|
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
}
|
|
|
|
function formatNumber(num) {
|
|
return num.toLocaleString();
|
|
}
|
|
|
|
function updatePrefixDistribution(elementId, distribution) {
|
|
const container = document.getElementById(elementId);
|
|
container.innerHTML = '';
|
|
|
|
if (!distribution || distribution.length === 0) {
|
|
container.innerHTML = '<div class="metric"><span class="metric-label">No data</span></div>';
|
|
return;
|
|
}
|
|
|
|
// Sort by mask length
|
|
distribution.sort((a, b) => a.mask_length - b.mask_length);
|
|
|
|
distribution.forEach(item => {
|
|
const metric = document.createElement('div');
|
|
metric.className = 'metric';
|
|
metric.innerHTML = `
|
|
<span class="metric-label">/${item.mask_length}</span>
|
|
<span class="metric-value">${formatNumber(item.count)}</span>
|
|
`;
|
|
container.appendChild(metric);
|
|
});
|
|
}
|
|
|
|
function updateHandlerStats(handlerStats) {
|
|
const container = document.getElementById('handler-stats-container');
|
|
container.innerHTML = '';
|
|
|
|
handlerStats.forEach(handler => {
|
|
const card = document.createElement('div');
|
|
card.className = 'status-card';
|
|
|
|
// Extract handler name (remove package prefix)
|
|
const handlerName = handler.name.split('.').pop();
|
|
|
|
card.innerHTML = `
|
|
<h2>${handlerName}</h2>
|
|
<div class="metric">
|
|
<span class="metric-label">Queue</span>
|
|
<span class="metric-value">${handler.queue_length}/${handler.queue_capacity}</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-label">Processed</span>
|
|
<span class="metric-value">${formatNumber(handler.processed_count)}</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-label">Dropped</span>
|
|
<span class="metric-value ${handler.dropped_count > 0 ? 'disconnected' : ''}">${formatNumber(handler.dropped_count)}</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-label">Avg Time</span>
|
|
<span class="metric-value">${handler.avg_process_time_ms.toFixed(2)} ms</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-label">Min/Max Time</span>
|
|
<span class="metric-value">${handler.min_process_time_ms.toFixed(2)} / ${handler.max_process_time_ms.toFixed(2)} ms</span>
|
|
</div>
|
|
`;
|
|
|
|
container.appendChild(card);
|
|
});
|
|
}
|
|
|
|
function updateStatus() {
|
|
fetch('/api/v1/stats')
|
|
.then(response => response.json())
|
|
.then(response => {
|
|
// Check if response is an error
|
|
if (response.status === 'error') {
|
|
document.getElementById('error').textContent = 'Error: ' + response.error.msg;
|
|
document.getElementById('error').style.display = 'block';
|
|
return;
|
|
}
|
|
|
|
// Extract data from successful response
|
|
const data = response.data;
|
|
|
|
// Connection status
|
|
const connectedEl = document.getElementById('connected');
|
|
connectedEl.textContent = data.connected ? 'Connected' : 'Disconnected';
|
|
connectedEl.className = 'metric-value ' + (data.connected ? 'connected' : 'disconnected');
|
|
|
|
// Update all metrics
|
|
document.getElementById('uptime').textContent = data.uptime;
|
|
document.getElementById('total_messages').textContent = formatNumber(data.total_messages);
|
|
document.getElementById('messages_per_sec').textContent = data.messages_per_sec.toFixed(1);
|
|
document.getElementById('total_bytes').textContent = formatBytes(data.total_bytes);
|
|
document.getElementById('mbits_per_sec').textContent = data.mbits_per_sec.toFixed(2) + ' Mbps';
|
|
document.getElementById('asns').textContent = formatNumber(data.asns);
|
|
document.getElementById('prefixes').textContent = formatNumber(data.prefixes);
|
|
document.getElementById('ipv4_prefixes').textContent = formatNumber(data.ipv4_prefixes);
|
|
document.getElementById('ipv6_prefixes').textContent = formatNumber(data.ipv6_prefixes);
|
|
document.getElementById('peerings').textContent = formatNumber(data.peerings);
|
|
document.getElementById('database_size').textContent = formatBytes(data.database_size_bytes);
|
|
document.getElementById('live_routes').textContent = formatNumber(data.live_routes);
|
|
document.getElementById('ipv4_routes').textContent = formatNumber(data.ipv4_routes);
|
|
document.getElementById('ipv6_routes').textContent = formatNumber(data.ipv6_routes);
|
|
document.getElementById('ipv4_updates_per_sec').textContent = data.ipv4_updates_per_sec.toFixed(1);
|
|
document.getElementById('ipv6_updates_per_sec').textContent = data.ipv6_updates_per_sec.toFixed(1);
|
|
|
|
// Update handler stats
|
|
updateHandlerStats(data.handler_stats || []);
|
|
|
|
// Update prefix distribution
|
|
updatePrefixDistribution('ipv4-prefix-distribution', data.ipv4_prefix_distribution);
|
|
updatePrefixDistribution('ipv6-prefix-distribution', data.ipv6_prefix_distribution);
|
|
|
|
// Clear any errors
|
|
document.getElementById('error').style.display = 'none';
|
|
})
|
|
.catch(error => {
|
|
document.getElementById('error').textContent = 'Error fetching status: ' + error;
|
|
document.getElementById('error').style.display = 'block';
|
|
});
|
|
}
|
|
|
|
// Update immediately and then every 500ms
|
|
updateStatus();
|
|
setInterval(updateStatus, 500);
|
|
</script>
|
|
</body>
|
|
</html> |