()
| 8119 | const iface = ifaceSel && ifaceSel.value ? ifaceSel.value : ''; |
| 8120 | const secsEl = document.getElementById('eigrp-secs'); |
| 8121 | const secs = secsEl && secsEl.value ? secsEl.value : '15'; |
| 8122 | _ndBusy(btn, true, 'Listening…'); |
| 8123 | out.classList.remove('hidden'); |
| 8124 | out.innerHTML = '<p class="text-sm text-gray-400">Passively capturing EIGRP on the segment…</p>'; |
| 8125 | try { |
| 8126 | _eigrpFillIfaces(); |
| 8127 | const qs = '?seconds=' + encodeURIComponent(secs) + (iface ? '&interface=' + encodeURIComponent(iface) : ''); |
| 8128 | const d = await fetchAPI('/api/net/eigrp-watch' + qs); |
| 8129 | if (!d || d.success === false) { |
| 8130 | const msg = (d && d.error) || 'failed'; |
| 8131 | let extra = ''; |
| 8132 | if (d && d.missing_tool) extra = ' <button onclick="installNetTool(\'tcpdump\', this, runEigrpWatch)" class="ml-2 underline text-cyan-400">Install tcpdump</button>'; |
| 8133 | out.innerHTML = '<p class="text-sm text-red-400">Error: ' + escapeHtml(msg) + extra + '</p>'; |
| 8134 | return; |
| 8135 | } |
| 8136 | const [cls, label] = _EIGRP_VERDICT_STYLE[d.verdict] || _EIGRP_VERDICT_STYLE.unknown; |
| 8137 | let html = `<div class="mb-2 px-3 py-2 rounded border ${cls} text-sm">${label}</div>`; |
| 8138 | html += `<p class="text-xs text-gray-500 mb-2">Interface: ${escapeHtml(d.interface || '—')} · ${d.seconds}s · ${d.packet_count} EIGRP pkts, ${d.router_count} router(s), ${d.prefix_count} prefix(es) @ ${d.rate}/s${d.learned ? ' · <span class="text-gray-400">baseline learned now</span>' : ''}</p>`; |
| 8139 | if ((d.routers || []).length) { |
| 8140 | html += '<p class="text-xs uppercase text-gray-400 mt-2 mb-1">EIGRP routers (' + d.routers.length + ')</p>' + |
| 8141 | '<table class="min-w-full text-xs text-gray-300 whitespace-nowrap"><thead>' + |
| 8142 | '<tr class="text-left text-gray-500"><th class="px-2 py-1">Router</th><th class="px-2 py-1">AS</th><th class="px-2 py-1">AF</th><th class="px-2 py-1">Auth</th><th class="px-2 py-1">Status</th></tr>' + |
| 8143 | '</thead><tbody>' + |
| 8144 | d.routers.map(r => { |
| 8145 | const badge = r.baseline ? '<span class="text-green-400">✓ known</span>' : '<span class="text-amber-300">? new</span>'; |
| 8146 | const auth = r.auth ? '<span class="text-green-400">yes</span>' : '<span class="text-amber-300">none</span>'; |
| 8147 | return `<tr class="border-t border-slate-800"> |
| 8148 | <td class="px-2 py-1 font-mono ${r.baseline ? '' : 'text-amber-300'}">${escapeHtml(r.src)}</td> |
| 8149 | <td class="px-2 py-1 font-mono">${(r.as || []).join(', ') || '?'}</td> |
| 8150 | <td class="px-2 py-1 text-gray-400">${escapeHtml(r.af)}</td> |
| 8151 | <td class="px-2 py-1">${auth}</td> |
| 8152 | <td class="px-2 py-1">${badge}</td> |
| 8153 | </tr>`; |
| 8154 | }).join('') + |
| 8155 | '</tbody></table>'; |
| 8156 | } |
| 8157 | if ((d.prefixes || []).length) { |
| 8158 | html += '<p class="text-xs uppercase text-gray-400 mt-2 mb-1">Advertised prefixes (' + d.prefixes.length + ')</p>' + |
| 8159 | '<table class="min-w-full text-xs text-gray-300 whitespace-nowrap"><thead>' + |
| 8160 | '<tr class="text-left text-gray-500"><th class="px-2 py-1">Prefix</th><th class="px-2 py-1">Next-hop</th><th class="px-2 py-1">Origin</th><th class="px-2 py-1">Kind</th><th class="px-2 py-1">Status</th></tr>' + |
| 8161 | '</thead><tbody>' + |
| 8162 | d.prefixes.map(p => { |
| 8163 | const bad = (p.status === 'new' || p.status === 'nexthop-changed'); |
| 8164 | const st = bad ? `<span class="text-red-300">${escapeHtml(p.status)}</span>` : `<span class="text-gray-500">${escapeHtml(p.status)}</span>`; |
| 8165 | return `<tr class="border-t border-slate-800"> |
| 8166 | <td class="px-2 py-1 font-mono ${bad ? 'text-red-300' : ''}">${escapeHtml(p.prefix)}</td> |
| 8167 | <td class="px-2 py-1 font-mono">${escapeHtml(p.nexthop)}</td> |
| 8168 | <td class="px-2 py-1 font-mono text-gray-400">${escapeHtml(p.origin)}</td> |
| 8169 | <td class="px-2 py-1 text-gray-400">${escapeHtml(p.kind)}</td> |
| 8170 | <td class="px-2 py-1">${st}</td> |
| 8171 | </tr>`; |
| 8172 | }).join('') + |
| 8173 | '</tbody></table>'; |
| 8174 | } |
| 8175 | if (d.reasons && d.reasons.length) { |
| 8176 | html += '<ul class="text-xs text-gray-400 mt-2 list-disc pl-5">' + |
| 8177 | d.reasons.map(r => '<li>' + escapeHtml(r) + '</li>').join('') + '</ul>'; |
| 8178 | } |
no test coverage detected