Show figures of the routing table on the home page (closes #154)
check / check (push) Canceled after 0s

The home page gets cards for the newest and oldest kept route, the newest
and oldest updated AS, the AS with the most prefixes, the ASes with a
single prefix and fifteen well-known networks. Two more cards count the
bogon announcements and the prefixes with several origin ASes. They link
to the new pages /bogons and /multiple-origins, which list up to 1,000
entries each.

The new package internal/bogon tells reserved prefixes and AS numbers,
from the IANA special-purpose and address space registries. Table.Overview
walks the table and lets the read lock go every 10,000 prefixes. The
server works the cards out once a minute and serves that copy. A new index
on asns(last_seen) finds the newest and oldest updated AS.

Model: opus-5-5
This commit was merged in pull request #196.
This commit is contained in:
2026-10-27 18:27:01 +01:00
parent e68be8254a
commit 281d162e27
23 changed files with 2240 additions and 6 deletions
@@ -1003,6 +1003,62 @@ func TestGetASReadsTheRecordOrTheBuiltInList(t *testing.T) {
}
}
// TestGetLastSeenASes stores three ASes, each last seen at another time, and
// expects the one last seen most recently and the one last seen longest ago.
// An empty asns table gives neither.
func TestGetLastSeenASes(t *testing.T) {
t.Parallel()
db, err := New(&config.Config{StateDir: t.TempDir()}, logger.New())
if err != nil {
t.Fatalf("failed to create database: %v", err)
}
defer func() { _ = db.Close() }()
newest, oldest, err := db.GetLastSeenASes(t.Context())
if err != nil || newest != nil || oldest != nil {
t.Fatalf("GetLastSeenASes() on an empty table = %+v, %+v, %v; want "+
"neither", newest, oldest, err)
}
const (
newestASN = 13335
middleASN = 15169
oldestASN = 3
)
start := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)
err = db.GetOrCreateASNBatch(map[int]SeenTimes{
middleASN: {First: start, Last: start.Add(time.Hour)},
newestASN: {First: start, Last: start.Add(2 * time.Hour)},
oldestASN: {First: start, Last: start.Add(time.Minute)},
})
if err != nil {
t.Fatalf("GetOrCreateASNBatch: %v", err)
}
newest, oldest, err = db.GetLastSeenASes(t.Context())
if err != nil {
t.Fatalf("GetLastSeenASes: %v", err)
}
listed, _ := asinfo.Get(newestASN)
if newest == nil || newest.ASN != newestASN ||
!newest.LastSeen.Equal(start.Add(2*time.Hour)) ||
newest.Description != listed.Description {
t.Errorf("newest = %+v, want AS%d (%s) last seen %v", newest, newestASN,
listed.Description, start.Add(2*time.Hour))
}
if oldest == nil || oldest.ASN != oldestASN ||
!oldest.LastSeen.Equal(start.Add(time.Minute)) {
t.Errorf("oldest = %+v, want AS%d last seen %v", oldest, oldestASN,
start.Add(time.Minute))
}
}
// TestGetASWHOISReadsTheStoredDetails stores an AS with WHOIS details and asks
// GetASWHOIS for it and for an AS the asns table does not hold. The first gets
// its stored handle, description and details, and the time they were fetched.