From aebdd1b23ed7ad8e7967ee4a46e96ab856253e3e Mon Sep 17 00:00:00 2001 From: sneak Date: Wed, 31 Dec 2025 15:47:57 -0800 Subject: [PATCH] Add oldest and newest route timestamps to status page Display oldest and newest route timestamps in the Routing Table card on the /status page. Timestamps are shown as relative times (e.g., "5m ago", "2h 30m ago"). Changes: - Add OldestRoute and NewestRoute fields to database.Stats - Query MIN/MAX of last_updated across live_routes_v4 and v6 tables - Include timestamps in both /status.json and /api/v1/stats responses - Add formatRelativeTime JavaScript function for display --- internal/database/database.go | 17 +++++++++++++++++ internal/database/interface.go | 2 ++ internal/server/handlers.go | 8 ++++++++ internal/templates/status.html | 30 +++++++++++++++++++++++++++++- 4 files changed, 56 insertions(+), 1 deletion(-) diff --git a/internal/database/database.go b/internal/database/database.go index a7da1b5..7234186 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -946,6 +946,23 @@ func (d *Database) GetStatsContext(ctx context.Context) (Stats, error) { } stats.LiveRoutes = v4Count + v6Count + // Get oldest and newest route timestamps + routeTimestampQuery := ` + SELECT MIN(last_updated), MAX(last_updated) FROM ( + SELECT last_updated FROM live_routes_v4 + UNION ALL + SELECT last_updated FROM live_routes_v6 + ) + ` + var oldestRoute, newestRoute *time.Time + err = d.db.QueryRowContext(ctx, routeTimestampQuery).Scan(&oldestRoute, &newestRoute) + if err != nil { + d.logger.Warn("Failed to get route timestamps", "error", err) + } else { + stats.OldestRoute = oldestRoute + stats.NewestRoute = newestRoute + } + // Get prefix distribution stats.IPv4PrefixDistribution, stats.IPv6PrefixDistribution, err = d.GetPrefixDistributionContext(ctx) if err != nil { diff --git a/internal/database/interface.go b/internal/database/interface.go index fb0f5c8..2c01173 100644 --- a/internal/database/interface.go +++ b/internal/database/interface.go @@ -18,6 +18,8 @@ type Stats struct { Peers int FileSizeBytes int64 LiveRoutes int + OldestRoute *time.Time + NewestRoute *time.Time IPv4PrefixDistribution []PrefixDistribution IPv6PrefixDistribution []PrefixDistribution } diff --git a/internal/server/handlers.go b/internal/server/handlers.go index 4464153..9aab1b1 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -164,6 +164,8 @@ func (s *Server) handleStatusJSON() http.HandlerFunc { LiveRoutes int `json:"live_routes"` IPv4Routes int `json:"ipv4_routes"` IPv6Routes int `json:"ipv6_routes"` + OldestRoute *time.Time `json:"oldest_route,omitempty"` + NewestRoute *time.Time `json:"newest_route,omitempty"` IPv4UpdatesPerSec float64 `json:"ipv4_updates_per_sec"` IPv6UpdatesPerSec float64 `json:"ipv6_updates_per_sec"` IPv4PrefixDistribution []database.PrefixDistribution `json:"ipv4_prefix_distribution"` @@ -258,6 +260,8 @@ func (s *Server) handleStatusJSON() http.HandlerFunc { LiveRoutes: dbStats.LiveRoutes, IPv4Routes: ipv4Routes, IPv6Routes: ipv6Routes, + OldestRoute: dbStats.OldestRoute, + NewestRoute: dbStats.NewestRoute, IPv4UpdatesPerSec: routeMetrics.IPv4UpdatesPerSec, IPv6UpdatesPerSec: routeMetrics.IPv6UpdatesPerSec, IPv4PrefixDistribution: dbStats.IPv4PrefixDistribution, @@ -369,6 +373,8 @@ func (s *Server) handleStats() http.HandlerFunc { LiveRoutes int `json:"live_routes"` IPv4Routes int `json:"ipv4_routes"` IPv6Routes int `json:"ipv6_routes"` + OldestRoute *time.Time `json:"oldest_route,omitempty"` + NewestRoute *time.Time `json:"newest_route,omitempty"` IPv4UpdatesPerSec float64 `json:"ipv4_updates_per_sec"` IPv6UpdatesPerSec float64 `json:"ipv6_updates_per_sec"` HandlerStats []HandlerStatsInfo `json:"handler_stats"` @@ -530,6 +536,8 @@ func (s *Server) handleStats() http.HandlerFunc { LiveRoutes: dbStats.LiveRoutes, IPv4Routes: ipv4Routes, IPv6Routes: ipv6Routes, + OldestRoute: dbStats.OldestRoute, + NewestRoute: dbStats.NewestRoute, IPv4UpdatesPerSec: routeMetrics.IPv4UpdatesPerSec, IPv6UpdatesPerSec: routeMetrics.IPv6UpdatesPerSec, HandlerStats: handlerStatsInfo, diff --git a/internal/templates/status.html b/internal/templates/status.html index bcdfbb5..86a38ff 100644 --- a/internal/templates/status.html +++ b/internal/templates/status.html @@ -321,6 +321,14 @@ IPv6 Updates/sec - +
+ Oldest Route + - +
+
+ Newest Route + - +
@@ -400,7 +408,23 @@ return ms.toFixed(2) + ' ms'; } } - + + function formatRelativeTime(isoString) { + if (!isoString) return '-'; + const date = new Date(isoString); + const now = new Date(); + const diffMs = now - date; + const diffSec = Math.floor(diffMs / 1000); + const diffMin = Math.floor(diffSec / 60); + const diffHour = Math.floor(diffMin / 60); + const diffDay = Math.floor(diffHour / 24); + + if (diffSec < 60) return diffSec + 's ago'; + if (diffMin < 60) return diffMin + 'm ago'; + if (diffHour < 24) return diffHour + 'h ' + (diffMin % 60) + 'm ago'; + return diffDay + 'd ' + (diffHour % 24) + 'h ago'; + } + function updatePrefixDistribution(elementId, distribution) { const container = document.getElementById(elementId); container.innerHTML = ''; @@ -506,6 +530,8 @@ document.getElementById('ipv6_routes').textContent = '-'; document.getElementById('ipv4_updates_per_sec').textContent = '-'; document.getElementById('ipv6_updates_per_sec').textContent = '-'; + document.getElementById('oldest_route').textContent = '-'; + document.getElementById('newest_route').textContent = '-'; document.getElementById('whois_fresh').textContent = '-'; document.getElementById('whois_stale').textContent = '-'; document.getElementById('whois_never').textContent = '-'; @@ -566,6 +592,8 @@ 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); + document.getElementById('oldest_route').textContent = formatRelativeTime(data.oldest_route); + document.getElementById('newest_route').textContent = formatRelativeTime(data.newest_route); // Update stream stats if (data.stream) {