@@ -269,6 +278,7 @@ a{color:var(--blue);text-decoration:none;transition:color .2s}a:hover{color:var(
@@ -604,6 +614,9 @@ function renderDashboard(d) {
}
$('rpkiContent').innerHTML = rk;
+ // Atlas Probes
+ renderAtlas(d.atlas);
+
// Neighbours
let ne = '
';
ne += '
';
@@ -665,6 +678,54 @@ function renderDashboard(d) {
$('facContent').innerHTML = fh;
}
+
+function renderAtlas(atlas) {
+ if (!atlas) {
+ $('atlasContent').innerHTML = '
No RIPE Atlas data available.
';
+ return;
+ }
+
+ var h = '';
+
+ // Summary badges
+ h += '
';
+ h += '
' + atlas.total_probes + '
Total Probes
';
+ h += '
' + atlas.connected + '
Connected
';
+ h += '
' + atlas.disconnected + '
Disconnected
';
+ h += '
' + atlas.anchors + '
Anchors
';
+ h += '
';
+
+ if (atlas.total_probes > 0) {
+ // Connection ratio bar
+ var connPct = pct(atlas.connected, atlas.total_probes);
+ h += '
Connected ' + connPct + '% / Disconnected ' + (100 - connPct) + '%
';
+ h += '
';
+ }
+
+ if (atlas.probes && atlas.probes.length > 0) {
+ h += '
Show probe details (' + atlas.probes.length + (atlas.total_probes > atlas.probes.length ? ' of ' + atlas.total_probes : '') + ')
';
+ h += '
';
+ } else if (atlas.total_probes === 0) {
+ h += '
';
+ }
+
+ $('atlasContent').innerHTML = h;
+}
+
async function doCompare() {
if (!currentAsn) return;
const raw2 = $('compareAsn').value.trim().replace(/[^0-9]/g, '');
diff --git a/server.js b/server.js
index b9a8f3e..00020a9 100644
--- a/server.js
+++ b/server.js
@@ -383,12 +383,13 @@ const server = http.createServer(async (req, res) => {
const start = Date.now();
try {
- const [pdbNet, prefixData, neighbourData, overviewData, rirData] = await Promise.all([
+ const [pdbNet, prefixData, neighbourData, overviewData, rirData, atlasProbeData] = await Promise.all([
fetchJSON("https://www.peeringdb.com/api/net?asn=" + asn),
fetchJSON("https://stat.ripe.net/data/announced-prefixes/data.json?resource=AS" + asn),
fetchJSON("https://stat.ripe.net/data/asn-neighbours/data.json?resource=AS" + asn),
fetchJSON("https://stat.ripe.net/data/as-overview/data.json?resource=AS" + asn),
fetchJSON("https://stat.ripe.net/data/rir-stats-country/data.json?resource=AS" + asn),
+ fetchJSON("https://atlas.ripe.net/api/v2/probes/?asn_v4=" + asn + "&page_size=500"),
]);
const net = pdbNet?.data?.[0] || {};
@@ -398,6 +399,11 @@ const server = http.createServer(async (req, res) => {
const overview = overviewData?.data || {};
const rirEntries = rirData?.data?.located_resources || rirData?.data?.rir_stats || [];
+ // Atlas probes
+ const atlasProbes = atlasProbeData?.results || [];
+ const atlasConnected = atlasProbes.filter(p => p.status_name === "Connected");
+ const atlasAnchors = atlasProbes.filter(p => p.is_anchor === true);
+
// Phase 2: IX + Facilities + RPKI (batched 20 at a time)
const phase2Promises = [];
if (netId) {
@@ -531,6 +537,21 @@ const server = http.createServer(async (req, res) => {
total: facilities.length,
list: facilities,
},
+ atlas: {
+ total_probes: atlasProbes.length,
+ connected: atlasConnected.length,
+ disconnected: atlasProbes.length - atlasConnected.length,
+ anchors: atlasAnchors.length,
+ probes: atlasProbes.slice(0, 100).map(p => ({
+ id: p.id,
+ status: p.status_name || p.status || "Unknown",
+ is_anchor: p.is_anchor || false,
+ country: p.country_code || "",
+ prefix_v4: p.prefix_v4 || "",
+ prefix_v6: p.prefix_v6 || "",
+ description: p.description || "",
+ })),
+ },
};
res.end(JSON.stringify(result, null, 2));