(state)
| 8530 | } |
| 8531 | if (!out) return; |
| 8532 | if (!d.success) { out.innerHTML = '<span class="text-red-300">' + escapeHtml(d.error || 'failed') + '</span>'; return; } |
| 8533 | if (action === 'rib') { |
| 8534 | const rib = d.rib || { total: 0, routes: [] }; |
| 8535 | let h = '<div class="text-xs text-gray-400 mb-1">RIB: ' + rib.total + ' prefixes · session ' + escapeHtml(d.state || '?') + '</div>'; |
| 8536 | if (rib.routes && rib.routes.length) { |
| 8537 | h += '<table class="w-full text-xs"><tr class="text-gray-500 text-left"><th class="pr-3">Prefix</th><th class="pr-3">Origin AS</th><th class="pr-3">AS-path</th><th>Flapping</th></tr>'; |
| 8538 | for (const r of rib.routes) { |
| 8539 | h += '<tr><td class="pr-3 font-mono">' + escapeHtml(r.prefix) + '</td><td class="pr-3">' + escapeHtml(String(r.origin_as ?? '?')) + '</td><td class="pr-3 font-mono text-gray-400">' + escapeHtml((r.as_path || []).join(' ')) + '</td><td>' + (r.flapping ? '<span class="text-red-300">yes</span>' : '<span class="text-gray-500">no</span>') + '</td></tr>'; |
| 8540 | } |
| 8541 | h += '</table>'; |
| 8542 | } else { h += '<div class="text-gray-500 text-xs">no prefixes learned yet</div>'; } |
| 8543 | out.innerHTML = h; |
| 8544 | return; |
| 8545 | } |
| 8546 | const st = (d.status || {}); |
| 8547 | const cls = _BGP_STATE_STYLE[st.state] || 'text-gray-400'; |
| 8548 | let h = '<div>Session: <span class="' + cls + ' font-semibold">' + escapeHtml(st.state || 'Idle') + '</span>'; |
| 8549 | if (st.peer_ip) h += ' <span class="text-gray-400">peer ' + escapeHtml(st.peer_ip) + ' AS' + escapeHtml(String(st.peer_as ?? '?')) + '</span>'; |
| 8550 | if (st.rib && typeof st.rib.total === 'number') h += ' · <span class="text-gray-400">' + st.rib.total + ' prefixes' + (st.rib.flapping ? ', ' + st.rib.flapping + ' flapping' : '') + '</span>'; |
| 8551 | h += '</div>'; |
| 8552 | if (action === 'start') addConsoleMessage('BGP collector starting toward ' + (st.peer_ip || '?') + ' (receive-only)', 'info'); |
| 8553 | if (action === 'stop') addConsoleMessage('BGP collector stopped', 'info'); |
| 8554 | out.innerHTML = h; |
| 8555 | } catch (e) { |
| 8556 | if (out) out.innerHTML = '<span class="text-red-300">' + escapeHtml(e.message) + '</span>'; |
| 8557 | } |
| 8558 | } |
| 8559 | async function owdReflector(action) { |
| 8560 | const span = document.getElementById('owd-reflector-status'); |
| 8561 | try { |
| 8562 | const d = await postAPI('/api/net/owd-reflector', { action }); |
| 8563 | if (!span) return; |
| 8564 | if (!d.success) { span.innerHTML = '<span class="text-red-300">' + escapeHtml(d.error || 'failed') + '</span>'; return; } |
| 8565 | if (d.running || d.already_running) span.innerHTML = '<span class="text-green-400">reflecting on udp/' + (d.port || '?') + '</span>'; |
| 8566 | else span.innerHTML = '<span class="text-gray-500">stopped</span>'; |
| 8567 | } catch (e) { if (span) span.innerHTML = '<span class="text-red-300">' + escapeHtml(e.message) + '</span>'; } |
| 8568 | } |
| 8569 | async function runPathAsymmetry() { |
| 8570 | const out = document.getElementById('pa-results'); |
| 8571 | if (!out) return; |
| 8572 | const target = (document.getElementById('pa-target').value || '').trim(); |
| 8573 | if (!target) { out.classList.remove('hidden'); out.innerHTML = '<span class="text-amber-300">enter a target</span>'; return; } |
| 8574 | out.classList.remove('hidden'); |
| 8575 | out.innerHTML = '<span class="text-gray-400">measuring one-way delay…</span>'; |
| 8576 | try { |
| 8577 | const body = { |
| 8578 | target, |
| 8579 | count: parseInt(document.getElementById('pa-count').value, 10) || 20, |
| 8580 | clock_synced: document.getElementById('pa-synced').checked, |
| 8581 | }; |
| 8582 | const d = await postAPI('/api/net/path-asymmetry', body); |
| 8583 | if (!d.success) { out.innerHTML = '<span class="text-red-300">' + escapeHtml(d.error || 'failed') + '</span>'; return; } |
| 8584 | const s = d.summary || {}; |
| 8585 | let h = '<div class="text-xs text-gray-400 mb-1">' + d.replies + '/' + d.probes_sent + ' replies · state <span class="' + (s.state === 'asymmetric' ? 'text-red-300' : 'text-green-400') + '">' + escapeHtml(s.state || '?') + '</span>'; |
| 8586 | if (d.rib_correlation) h += ' · <span class="text-cyan-400">RIB-correlated</span>'; |
| 8587 | h += '</div>'; |
| 8588 | if (d.note) h += '<div class="text-amber-300 text-xs mb-1">' + escapeHtml(d.note) + '</div>'; |
| 8589 | if (s.last) { |
no test coverage detected