(interfaceName, options = {})
| 8989 | _ndBusy(btn, true, 'Listening…'); |
| 8990 | out.classList.remove('hidden'); |
| 8991 | out.innerHTML = '<p class="text-sm text-gray-400">Passively capturing BGP (TCP/179) on the segment…</p>'; |
| 8992 | try { |
| 8993 | _bgpFillIfaces(); |
| 8994 | const qs = '?seconds=' + encodeURIComponent(secs) + (iface ? '&interface=' + encodeURIComponent(iface) : ''); |
| 8995 | const d = await fetchAPI('/api/net/bgp-watch' + qs); |
| 8996 | if (!d || d.success === false) { |
| 8997 | const msg = (d && d.error) || 'failed'; |
| 8998 | let extra = ''; |
| 8999 | if (d && d.missing_tool) extra = ' <button onclick="installNetTool(\'tcpdump\', this, runBgpWatch)" class="ml-2 underline text-cyan-400">Install tcpdump</button>'; |
| 9000 | out.innerHTML = '<p class="text-sm text-red-400">Error: ' + escapeHtml(msg) + extra + '</p>'; |
| 9001 | return; |
| 9002 | } |
| 9003 | const [cls, label] = _BGP_VERDICT_STYLE[d.verdict] || _BGP_VERDICT_STYLE.unknown; |
| 9004 | let html = `<div class="mb-2 px-3 py-2 rounded border ${cls} text-sm">${label}</div>`; |
| 9005 | html += `<p class="text-xs text-gray-500 mb-2">Interface: ${escapeHtml(d.interface || '—')} · ${d.seconds}s · ${d.messages} BGP msgs (${d.updates} UPDATE @ ${d.update_per_s}/s, ${d.notifications} NOTIFICATION) · ${d.prefix_total} prefixes${d.learned ? ' · <span class="text-gray-400">baseline learned now</span>' : ''}</p>`; |
| 9006 | if (d.note) html += `<p class="text-xs text-gray-500 mb-2">${escapeHtml(d.note)}</p>`; |
| 9007 | if (d.enrich_note) html += `<p class="text-xs text-gray-500 mb-2">${escapeHtml(d.enrich_note)}</p>`; |
| 9008 | const asnNames = d.asn_names || {}; |
| 9009 | if (d.peers && d.peers.length) { |
| 9010 | const tp = d.trusted_peers || []; |
| 9011 | html += `<p class="text-xs mb-1 text-gray-400">Peers: ` + |
| 9012 | d.peers.map(a => { |
| 9013 | const nm = asnNames[a] ? ` <span class="text-gray-500">(${escapeHtml(asnNames[a])})</span>` : ''; |
| 9014 | return `<span class="font-mono ${tp.length && tp.indexOf(a) < 0 ? 'text-amber-300' : 'text-gray-200'}">AS${a}</span>${nm}`; |
| 9015 | }).join(', ') + |
| 9016 | ` · TCP-MD5: <span class="${d.md5 ? 'text-green-400' : 'text-amber-300'}">${d.md5 ? 'yes' : 'not seen'}</span></p>`; |
| 9017 | } |
| 9018 | (d.advisories || []).forEach(a => { |
| 9019 | const sev = a.severity === 'high' ? 'text-red-300 border-red-800 bg-red-950/40' : 'text-amber-300 border-amber-800 bg-amber-950/30'; |
| 9020 | const refs = (a.refs || []).map(r => r.startsWith('http') |
| 9021 | ? `<a href="${escapeHtml(r)}" target="_blank" rel="noopener" class="underline text-cyan-400">OSV</a>` |
| 9022 | : `<span class="font-mono">${escapeHtml(r)}</span>`).join(', '); |
| 9023 | html += `<div class="mt-2 px-3 py-2 rounded border text-xs ${sev}"><strong>${escapeHtml(a.title)}</strong><br>${escapeHtml(a.detail)}${refs ? '<br><span class="text-gray-400">Refs:</span> ' + refs : ''}</div>`; |
| 9024 | }); |
| 9025 | const prefixes = d.prefixes || []; |
| 9026 | if (prefixes.length) { |
| 9027 | html += '<p class="text-xs uppercase text-gray-400 mt-2 mb-1">Announced prefixes (' + d.prefix_total + (d.prefix_total > prefixes.length ? ', showing ' + prefixes.length : '') + ')</p>' + |
| 9028 | '<table class="min-w-full text-xs text-gray-300 whitespace-nowrap"><thead>' + |
| 9029 | '<tr class="text-left text-gray-500"><th class="px-2 py-1">Prefix</th><th class="px-2 py-1">Origin AS</th><th class="px-2 py-1">Baseline</th><th class="px-2 py-1">Status</th></tr>' + |
| 9030 | '</thead><tbody>' + |
| 9031 | prefixes.map(p => { |
| 9032 | const badge = p.hijack ? '<span class="text-red-300">🛑 hijack</span>' : (p.bogon ? '<span class="text-amber-300">bogon</span>' : '<span class="text-gray-500">—</span>'); |
| 9033 | const oname = p.origin_name ? ` <span class="text-gray-500">${escapeHtml(p.origin_name)}</span>` : ''; |
| 9034 | return `<tr class="border-t border-slate-800"> |
| 9035 | <td class="px-2 py-1 font-mono ${p.hijack || p.bogon ? 'text-amber-300' : ''}">${escapeHtml(p.prefix)}</td> |
| 9036 | <td class="px-2 py-1 font-mono ${p.hijack ? 'text-red-300' : ''}">${p.origin_as != null ? 'AS' + p.origin_as : '—'}${oname}</td> |
| 9037 | <td class="px-2 py-1 font-mono text-gray-500">${p.baseline_as != null ? 'AS' + p.baseline_as : '—'}</td> |
no test coverage detected