Files
routewatch/internal/templates/as_detail.html
T
clawbot 203a4e13a5
check / check (push) Canceled after 0s
Let the AS page's prefix lists fold (closes #155)
The IPv4 and IPv6 prefix lists on the AS page are each a <details>
element. Its <summary> holds the list's heading and count, so a click on
the heading folds or unfolds the list without a script. Both lists start
open. The peer list is unchanged.

The summary keeps the look of the other section headings. It hides the
browser's own marker, which not every browser shows on a flex element,
and draws a triangle before the heading that points down while the list
is open.

Model: opus-5-5
2026-10-27 04:56:58 +01:00

368 lines
11 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AS{{.ASN.ASN}} - {{.ASN.Handle}} - RouteWatch</title>
<style nonce="{{.CSPNonce}}">
* {
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 0;
padding: 0;
background: #f5f5f5;
color: #333;
}
/* Navbar styles */
.navbar {
background: #2c3e50;
padding: 0 20px;
display: flex;
align-items: center;
justify-content: space-between;
height: 56px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.navbar-brand {
display: flex;
align-items: center;
gap: 8px;
font-size: 18px;
font-weight: 600;
}
.navbar-brand a {
color: white;
text-decoration: none;
}
.navbar-brand a:hover {
color: #ecf0f1;
}
.navbar-brand .by {
font-weight: normal;
color: #95a5a6;
font-size: 14px;
}
.navbar-brand .author {
color: #3498db;
font-weight: normal;
}
.navbar-brand .author:hover {
text-decoration: underline;
}
.navbar-links {
display: flex;
gap: 20px;
}
.navbar-links a {
color: #ecf0f1;
text-decoration: none;
font-size: 14px;
padding: 8px 12px;
border-radius: 4px;
transition: background-color 0.2s;
}
.navbar-links a:hover {
background: rgba(255,255,255,0.1);
}
.navbar-links a.active {
background: rgba(255,255,255,0.15);
}
/* Main content */
.main-content {
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
h1 {
margin: 0 0 10px 0;
color: #2c3e50;
}
.subtitle {
color: #7f8c8d;
margin-bottom: 30px;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.info-card {
background: #f8f9fa;
padding: 20px;
border-radius: 6px;
border-left: 4px solid #3498db;
}
.info-label {
font-size: 14px;
color: #7f8c8d;
margin-bottom: 5px;
}
.info-value {
font-size: 24px;
font-weight: bold;
color: #2c3e50;
}
.prefix-section {
margin-top: 30px;
}
.prefix-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.prefix-header h2 {
margin: 0;
color: #2c3e50;
}
/* A click on a prefix list's heading folds or unfolds the list. Not
every browser shows its own marker on a flex element, so the page
hides it and draws a triangle before the heading. The triangle
points down while the list is open. */
summary.prefix-header {
cursor: pointer;
list-style: none;
}
summary.prefix-header::-webkit-details-marker {
display: none;
}
summary.prefix-header h2::before {
content: "\25B8";
display: inline-block;
margin-right: 10px;
transition: transform 0.2s;
}
details[open] > summary.prefix-header h2::before {
transform: rotate(90deg);
}
.prefix-count {
background: #e74c3c;
color: white;
padding: 5px 12px;
border-radius: 20px;
font-size: 14px;
font-weight: bold;
}
.prefix-table {
width: 100%;
border-collapse: collapse;
background: white;
border: 1px solid #e0e0e0;
border-radius: 6px;
overflow: hidden;
}
.prefix-table th {
background: #34495e;
color: white;
padding: 12px;
text-align: left;
font-weight: 600;
}
.prefix-table td {
padding: 12px;
border-bottom: 1px solid #e0e0e0;
}
.prefix-table tr:hover {
background: #f8f9fa;
}
.prefix-table tr:last-child td {
border-bottom: none;
}
.prefix-link {
color: #3498db;
text-decoration: none;
font-family: monospace;
}
.prefix-link:hover {
text-decoration: underline;
}
.age {
color: #7f8c8d;
font-size: 14px;
}
.nav-link {
display: inline-block;
margin-bottom: 20px;
color: #3498db;
text-decoration: none;
}
.nav-link:hover {
text-decoration: underline;
}
.empty-state {
text-align: center;
padding: 40px;
color: #7f8c8d;
}
@media (max-width: 768px) {
.container {
padding: 20px;
}
.info-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<nav class="navbar">
<div class="navbar-brand">
<a href="/">routewatch</a>
<span class="by">by</span>
<a href="https://sneak.berlin" class="author">@sneak</a>
</div>
<div class="navbar-links">
<a href="/">Home</a>
<a href="/status">Status</a>
</div>
</nav>
<main class="main-content">
<div class="container">
<h1>AS{{.ASN.ASN}}{{if .ASN.Handle}} - {{.ASN.Handle}}{{end}}</h1>
{{if .ASN.Description}}
<p class="subtitle">{{.ASN.Description}}</p>
{{end}}
<div class="info-grid">
<div class="info-card">
<div class="info-label">Total Prefixes</div>
<div class="info-value">{{.TotalCount}}</div>
</div>
<div class="info-card">
<div class="info-label">IPv4 Prefixes</div>
<div class="info-value">{{.IPv4Count}}</div>
</div>
<div class="info-card">
<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>
{{if not .ASN.FirstSeen.IsZero}}
<div class="info-card">
<div class="info-label">First Seen</div>
<div class="info-value">{{.ASN.FirstSeen.Format "2006-01-02"}}</div>
</div>
{{end}}
</div>
{{if .IPv4Prefixes}}
<details class="prefix-section" open>
<summary class="prefix-header">
<h2>IPv4 Prefixes</h2>
<span class="prefix-count">{{.IPv4Count}}</span>
</summary>
<table class="prefix-table">
<thead>
<tr>
<th>Prefix</th>
<th>Mask Length</th>
<th>Last Updated</th>
<th>Age</th>
</tr>
</thead>
<tbody>
{{range .IPv4Prefixes}}
<tr>
<td><a href="{{.Prefix | prefixURL}}" class="prefix-link">{{.Prefix}}</a></td>
<td>/{{.MaskLength}}</td>
<td>{{.LastUpdated.Format "2006-01-02 15:04:05"}}</td>
<td class="age">{{.LastUpdated | timeSince}}</td>
</tr>
{{end}}
</tbody>
</table>
</details>
{{end}}
{{if .IPv6Prefixes}}
<details class="prefix-section" open>
<summary class="prefix-header">
<h2>IPv6 Prefixes</h2>
<span class="prefix-count">{{.IPv6Count}}</span>
</summary>
<table class="prefix-table">
<thead>
<tr>
<th>Prefix</th>
<th>Mask Length</th>
<th>Last Updated</th>
<th>Age</th>
</tr>
</thead>
<tbody>
{{range .IPv6Prefixes}}
<tr>
<td><a href="{{.Prefix | prefixURL}}" class="prefix-link">{{.Prefix}}</a></td>
<td>/{{.MaskLength}}</td>
<td>{{.LastUpdated.Format "2006-01-02 15:04:05"}}</td>
<td class="age">{{.LastUpdated | timeSince}}</td>
</tr>
{{end}}
</tbody>
</table>
</details>
{{end}}
{{if eq .TotalCount 0}}
<div class="empty-state">
<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>
</main>
</body>
</html>