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:
parent
deeedae180
commit
7aec01c499
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -291,6 +291,17 @@ func (m *mockStore) GetRandomPrefixesByLengthContext(ctx context.Context, maskLe
|
||||
return m.GetRandomPrefixesByLength(maskLength, ipVersion, limit)
|
||||
}
|
||||
|
||||
// GetASPeers mock implementation
|
||||
func (m *mockStore) GetASPeers(asn int) ([]database.ASPeer, error) {
|
||||
// Return empty peers for now
|
||||
return []database.ASPeer{}, nil
|
||||
}
|
||||
|
||||
// GetASPeersContext mock implementation with context support
|
||||
func (m *mockStore) GetASPeersContext(ctx context.Context, asn int) ([]database.ASPeer, error) {
|
||||
return m.GetASPeers(asn)
|
||||
}
|
||||
|
||||
// UpsertLiveRouteBatch mock implementation
|
||||
func (m *mockStore) UpsertLiveRouteBatch(routes []*database.LiveRoute) error {
|
||||
m.mu.Lock()
|
||||
|
@ -493,6 +493,14 @@ func (s *Server) handleASDetail() http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
// Get peers
|
||||
peers, err := s.db.GetASPeersContext(r.Context(), asn)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get AS peers", "error", err)
|
||||
// Continue without peers rather than failing the whole request
|
||||
peers = []database.ASPeer{}
|
||||
}
|
||||
|
||||
// Group prefixes by IP version
|
||||
const ipVersionV4 = 4
|
||||
var ipv4Prefixes, ipv6Prefixes []database.LiveRoute
|
||||
@ -549,6 +557,8 @@ func (s *Server) handleASDetail() http.HandlerFunc {
|
||||
TotalCount int
|
||||
IPv4Count int
|
||||
IPv6Count int
|
||||
Peers []database.ASPeer
|
||||
PeerCount int
|
||||
}{
|
||||
ASN: asInfo,
|
||||
IPv4Prefixes: ipv4Prefixes,
|
||||
@ -556,6 +566,8 @@ func (s *Server) handleASDetail() http.HandlerFunc {
|
||||
TotalCount: len(prefixes),
|
||||
IPv4Count: len(ipv4Prefixes),
|
||||
IPv6Count: len(ipv6Prefixes),
|
||||
Peers: peers,
|
||||
PeerCount: len(peers),
|
||||
}
|
||||
|
||||
// Check if context is still valid before writing response
|
||||
|
@ -154,6 +154,10 @@
|
||||
<div class="info-label">IPv6 Prefixes</div>
|
||||
<div class="info-value">{{.IPv6Count}}</div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<div class="info-label">Peer ASNs</div>
|
||||
<div class="info-value">{{.PeerCount}}</div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<div class="info-label">First Seen</div>
|
||||
<div class="info-value">{{.ASN.FirstSeen.Format "2006-01-02"}}</div>
|
||||
@ -223,6 +227,44 @@
|
||||
<p>No prefixes announced by this AS</p>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .Peers}}
|
||||
<div class="prefix-section">
|
||||
<div class="prefix-header">
|
||||
<h2>Peer ASNs</h2>
|
||||
<span class="prefix-count">{{.PeerCount}}</span>
|
||||
</div>
|
||||
<table class="prefix-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ASN</th>
|
||||
<th>Handle</th>
|
||||
<th>Description</th>
|
||||
<th>First Seen</th>
|
||||
<th>Last Seen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Peers}}
|
||||
<tr>
|
||||
<td><a href="/as/{{.ASN}}" class="prefix-link">AS{{.ASN}}</a></td>
|
||||
<td>{{if .Handle}}{{.Handle}}{{else}}-{{end}}</td>
|
||||
<td>{{if .Description}}{{.Description}}{{else}}-{{end}}</td>
|
||||
<td>{{.FirstSeen.Format "2006-01-02"}}</td>
|
||||
<td>{{.LastSeen.Format "2006-01-02"}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="prefix-section">
|
||||
<h2>Peer ASNs</h2>
|
||||
<div class="empty-state">
|
||||
<p>No peering relationships found for this AS</p>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user