check / check (push) Canceled after 0s
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
368 lines
11 KiB
HTML
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> |