diff --git a/server.js b/server.js index 24c1431..b9b3831 100644 --- a/server.js +++ b/server.js @@ -1160,7 +1160,7 @@ function buildPdfHtml(data, aspa, format) { /** * Build comparison PDF HTML for two ASNs. */ -function buildComparisonPdfHtml(d1, aspa1, d2, aspa2) { +function buildComparisonPdfHtml(d1, aspa1, l1, d2, aspa2, l2) { const ts = new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); function col(d, aspa) { @@ -1204,6 +1204,98 @@ function buildComparisonPdfHtml(d1, aspa1, d2, aspa2) { const colHtml = col(d1, aspa1) + col(d2, aspa2); + // ── Peering Intelligence: common IXPs + common facilities ───────────────── + const ixList1 = (l1?.ix_presence?.connections || []).map(x => ({ id: x.ix_id, name: x.ix_name, city: x.city || '', speed: x.speed_mbps })); + const ixList2 = (l2?.ix_presence?.connections || []).map(x => ({ id: x.ix_id, name: x.ix_name, city: x.city || '', speed: x.speed_mbps })); + const ixIds1 = new Set(ixList1.map(x => x.id)); + const ixIds2 = new Set(ixList2.map(x => x.id)); + const commonIxIds = [...ixIds1].filter(id => ixIds2.has(id)); + const commonIxps = commonIxIds.slice(0, 20).map(id => { + const x1 = ixList1.find(x => x.id === id); + const x2 = ixList2.find(x => x.id === id); + return { id, name: x1?.name || 'Unknown', city: x1?.city || '', speed1: x1?.speed, speed2: x2?.speed }; + }); + + const facList1 = (l1?.facilities?.list || []).map(f => ({ id: f.fac_id, name: f.name, city: f.city || '', country: f.country || '' })); + const facList2 = (l2?.facilities?.list || []).map(f => ({ id: f.fac_id, name: f.name, city: f.city || '', country: f.country || '' })); + const facIds1 = new Set(facList1.map(f => f.id)); + const facIds2 = new Set(facList2.map(f => f.id)); + const commonFacIds = [...facIds1].filter(id => facIds2.has(id)); + const commonFacs = commonFacIds.slice(0, 15).map(id => facList1.find(f => f.id === id)); + + // IXPs of ASN1 where ASN2 is NOT present (opportunity IXPs for ASN2) + const oppsFor2 = ixList1.filter(x => !ixIds2.has(x.id)).slice(0, 8); + const oppsFor1 = ixList2.filter(x => !ixIds1.has(x.id)).slice(0, 8); + + const commonIxHtml = commonIxps.length > 0 + ? `
| IXP Name | City | AS${d1.asn} Speed | AS${d2.asn} Speed |
|---|---|---|---|
| ${escHtml(x.name)} | +${escHtml(x.city)} | +${x.speed1 ? (x.speed1/1000).toLocaleString()+'G' : '—'} | +${x.speed2 ? (x.speed2/1000).toLocaleString()+'G' : '—'} | +
No common IXPs found — these networks do not share any Internet Exchange Points.
'; + + const commonFacHtml = commonFacs.length > 0 + ? `| Datacenter / Colocation Facility | City | Country |
|---|---|---|
| ${escHtml(f?.name||'')} | +${escHtml(f?.city||'')} | +${escHtml(f?.country||'')} | +
No shared colocation facilities found.
'; + + const oppsHtml = (asns, opps) => opps.length === 0 + ? `Already maximally peered on these exchanges — or no data available.
` + : opps.map(x => `Common Internet Exchange Points and colocation facilities — potential peering locations between AS${d1.asn} and AS${d2.asn}.
+ +