The prefix length links for IPv6 prefixes were incorrectly pointing to /prefixlength/<len> which would show IPv4 prefixes. Added a new route /prefixlength6/<len> specifically for IPv6 prefixes and updated the template to use the correct URL based on whether displaying IPv4 or IPv6 prefix distributions. Also refactored handlePrefixLength to explicitly handle only IPv4 prefixes and created handlePrefixLength6 for IPv6 prefixes, removing the ambiguous auto-detection based on mask length value.
394 lines
17 KiB
HTML
394 lines
17 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;
|
|
}
|
|
.metric-value.metric-link {
|
|
text-decoration: underline;
|
|
text-decoration-style: dashed;
|
|
text-underline-offset: 2px;
|
|
cursor: pointer;
|
|
}
|
|
.metric-value.metric-link:hover {
|
|
color: #0066cc;
|
|
text-decoration-style: solid;
|
|
}
|
|
.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>RouteWatch</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 class="metric">
|
|
<span class="metric-label">Go Version</span>
|
|
<span class="metric-value" id="go_version">-</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-label">Goroutines</span>
|
|
<span class="metric-value" id="goroutines">-</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-label">Memory Usage</span>
|
|
<span class="metric-value" id="memory_usage">-</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">Peerings</span>
|
|
<span class="metric-value" id="peerings">-</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-label">Peers</span>
|
|
<span class="metric-value" id="peers">-</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 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">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 formatProcessingTime(ms) {
|
|
if (ms < 0.001) {
|
|
return (ms * 1000).toFixed(0) + ' µs';
|
|
} else if (ms < 0.01) {
|
|
return (ms * 1000).toFixed(1) + ' µs';
|
|
} else if (ms < 1) {
|
|
return ms.toFixed(3) + ' ms';
|
|
} else {
|
|
return ms.toFixed(2) + ' ms';
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
// Determine the URL path based on whether this is IPv4 or IPv6
|
|
const isIPv6 = elementId.includes('ipv6');
|
|
const urlPath = isIPv6 ? '/prefixlength6/' : '/prefixlength/';
|
|
|
|
distribution.forEach(item => {
|
|
const metric = document.createElement('div');
|
|
metric.className = 'metric';
|
|
metric.innerHTML = `
|
|
<span class="metric-label">/${item.mask_length}</span>
|
|
<a href="${urlPath}${item.mask_length}" class="metric-value metric-link">${formatNumber(item.count)}</a>
|
|
`;
|
|
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">High Water Mark</span>
|
|
<span class="metric-value">${handler.queue_high_water_mark}/${handler.queue_capacity} (${Math.round(handler.queue_high_water_mark * 100 / 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">${formatProcessingTime(handler.avg_process_time_ms)}</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-label">Min/Max Time</span>
|
|
<span class="metric-value">${formatProcessingTime(handler.min_process_time_ms)} / ${formatProcessingTime(handler.max_process_time_ms)}</span>
|
|
</div>
|
|
`;
|
|
|
|
container.appendChild(card);
|
|
});
|
|
}
|
|
|
|
function resetAllFields() {
|
|
// Reset all metric fields to '-'
|
|
document.getElementById('connected').textContent = '-';
|
|
document.getElementById('connected').className = 'metric-value';
|
|
document.getElementById('uptime').textContent = '-';
|
|
document.getElementById('go_version').textContent = '-';
|
|
document.getElementById('goroutines').textContent = '-';
|
|
document.getElementById('memory_usage').textContent = '-';
|
|
document.getElementById('total_messages').textContent = '-';
|
|
document.getElementById('messages_per_sec').textContent = '-';
|
|
document.getElementById('total_bytes').textContent = '-';
|
|
document.getElementById('mbits_per_sec').textContent = '-';
|
|
document.getElementById('asns').textContent = '-';
|
|
document.getElementById('prefixes').textContent = '-';
|
|
document.getElementById('ipv4_prefixes').textContent = '-';
|
|
document.getElementById('ipv6_prefixes').textContent = '-';
|
|
document.getElementById('peerings').textContent = '-';
|
|
document.getElementById('peers').textContent = '-';
|
|
document.getElementById('database_size').textContent = '-';
|
|
document.getElementById('live_routes').textContent = '-';
|
|
document.getElementById('ipv4_routes').textContent = '-';
|
|
document.getElementById('ipv6_routes').textContent = '-';
|
|
document.getElementById('ipv4_updates_per_sec').textContent = '-';
|
|
document.getElementById('ipv6_updates_per_sec').textContent = '-';
|
|
|
|
// Clear handler stats
|
|
document.getElementById('handler-stats-container').innerHTML = '';
|
|
|
|
// Clear prefix distributions
|
|
document.getElementById('ipv4-prefix-distribution').innerHTML = '<div class="metric"><span class="metric-label">No data</span></div>';
|
|
document.getElementById('ipv6-prefix-distribution').innerHTML = '<div class="metric"><span class="metric-label">No data</span></div>';
|
|
}
|
|
|
|
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';
|
|
resetAllFields();
|
|
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('go_version').textContent = data.go_version;
|
|
document.getElementById('goroutines').textContent = formatNumber(data.goroutines);
|
|
document.getElementById('memory_usage').textContent = data.memory_usage;
|
|
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('peers').textContent = formatNumber(data.peers);
|
|
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';
|
|
resetAllFields();
|
|
});
|
|
}
|
|
|
|
// Update immediately and then every 2 seconds
|
|
updateStatus();
|
|
setInterval(updateStatus, 2000);
|
|
</script>
|
|
</body>
|
|
</html> |