Add AS peers display to AS detail page

- Added GetASPeers method to database to fetch all peering relationships
- Updated AS detail handler to fetch and pass peers to template
- Added peers section to AS detail page showing all peer ASNs with their info
- Added peer count to the info cards at the top of the page
- Shows handle, description, and first/last seen dates for each peer
This commit is contained in:
2025-07-29 03:58:09 +02:00
parent deeedae180
commit 7aec01c499
6 changed files with 1487367 additions and 142555 deletions

View File

@@ -1435,6 +1435,65 @@ func (d *Database) GetASDetailsContext(ctx context.Context, asn int) (*ASN, []Li
return &asnInfo, allPrefixes, nil
}
// ASPeer represents a peering relationship with another AS
type ASPeer struct {
ASN int `json:"asn"`
Handle string `json:"handle"`
Description string `json:"description"`
FirstSeen time.Time `json:"first_seen"`
LastSeen time.Time `json:"last_seen"`
}
// GetASPeers returns all ASes that peer with the given AS
func (d *Database) GetASPeers(asn int) ([]ASPeer, error) {
return d.GetASPeersContext(context.Background(), asn)
}
// GetASPeersContext returns all ASes that peer with the given AS with context support
func (d *Database) GetASPeersContext(ctx context.Context, asn int) ([]ASPeer, error) {
query := `
SELECT
CASE
WHEN p.as_a = ? THEN p.as_b
ELSE p.as_a
END as peer_asn,
COALESCE(a.handle, '') as handle,
COALESCE(a.description, '') as description,
p.first_seen,
p.last_seen
FROM peerings p
LEFT JOIN asns a ON a.asn = CASE
WHEN p.as_a = ? THEN p.as_b
ELSE p.as_a
END
WHERE p.as_a = ? OR p.as_b = ?
ORDER BY peer_asn
`
rows, err := d.db.QueryContext(ctx, query, asn, asn, asn, asn)
if err != nil {
return nil, fmt.Errorf("failed to query AS peers: %w", err)
}
defer func() { _ = rows.Close() }()
var peers []ASPeer
for rows.Next() {
var peer ASPeer
err := rows.Scan(&peer.ASN, &peer.Handle, &peer.Description, &peer.FirstSeen, &peer.LastSeen)
if err != nil {
d.logger.Error("Failed to scan peer row", "error", err, "asn", asn)
continue
}
peers = append(peers, peer)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("error iterating AS peers: %w", err)
}
return peers, nil
}
// GetPrefixDetails returns detailed information about a prefix
func (d *Database) GetPrefixDetails(prefix string) ([]LiveRoute, error) {
return d.GetPrefixDetailsContext(context.Background(), prefix)

View File

@@ -60,6 +60,8 @@ type Store interface {
// AS and prefix detail operations
GetASDetails(asn int) (*ASN, []LiveRoute, error)
GetASDetailsContext(ctx context.Context, asn int) (*ASN, []LiveRoute, error)
GetASPeers(asn int) ([]ASPeer, error)
GetASPeersContext(ctx context.Context, asn int) ([]ASPeer, error)
GetPrefixDetails(prefix string) ([]LiveRoute, error)
GetPrefixDetailsContext(ctx context.Context, prefix string) ([]LiveRoute, error)
GetRandomPrefixesByLength(maskLength, ipVersion, limit int) ([]LiveRoute, error)