(state, statusData = {})
| 8473 | const st = bad ? `<span class="text-red-300">${escapeHtml(p.status)}</span>` : `<span class="text-gray-500">${escapeHtml(p.status)}</span>`; |
| 8474 | return `<tr class="border-t border-slate-800"> |
| 8475 | <td class="px-2 py-1 font-mono ${bad ? 'text-red-300' : ''}">${escapeHtml(p.pfx)}</td> |
| 8476 | <td class="px-2 py-1 text-gray-400">${escapeHtml(p.origin_name || p.origin)}</td> |
| 8477 | <td class="px-2 py-1 font-mono">${p.metric == null ? '—' : p.metric}</td> |
| 8478 | <td class="px-2 py-1">${st}</td> |
| 8479 | </tr>`; |
| 8480 | }).join('') + |
| 8481 | '</tbody></table>'; |
| 8482 | } |
| 8483 | if (d.reasons && d.reasons.length) { |
| 8484 | html += '<ul class="text-xs text-gray-400 mt-2 list-disc pl-5">' + |
| 8485 | d.reasons.map(r => '<li>' + escapeHtml(r) + '</li>').join('') + '</ul>'; |
| 8486 | } |
| 8487 | (d.advisories || []).forEach(a => { |
| 8488 | html += `<div class="mt-2 px-3 py-2 rounded border text-xs text-gray-400 border-slate-700 bg-slate-900/40">${escapeHtml(a)}</div>`; |
| 8489 | }); |
| 8490 | out.innerHTML = html; |
| 8491 | } catch (e) { |
| 8492 | out.innerHTML = '<p class="text-sm text-red-400">Failed: ' + escapeHtml(e.message) + '</p>'; |
| 8493 | } finally { |
| 8494 | _ndBusy(btn, false); |
| 8495 | } |
| 8496 | } |
| 8497 | async function isisTrustBaseline() { |
| 8498 | try { |
| 8499 | await postAPI('/api/net/isis-baseline', { action: 'reset' }); |
| 8500 | addConsoleMessage('IS-IS baseline reset — re-learning current routers & prefixes', 'info'); |
| 8501 | await runIsisWatch(); |
| 8502 | } catch (e) { |
| 8503 | addConsoleMessage('Failed to reset IS-IS baseline: ' + e.message, 'error'); |
| 8504 | } |
| 8505 | } |
| 8506 | |
| 8507 | // ---- EIGRP Watch (passive Cisco IGP routing-security scanner) --------------- |
| 8508 | const _EIGRP_VERDICT_STYLE = { |
| 8509 | clean: ['bg-green-950/40 border-green-900 text-green-400', '✓ No EIGRP anomalies detected'], |
| 8510 | 'weak-auth': ['bg-amber-950/50 border-amber-800 text-amber-300', '⚠ EIGRP without HMAC authentication — exposed to route injection'], |
| 8511 | anomaly: ['bg-amber-950/50 border-amber-800 text-amber-300', '⚠ EIGRP anomaly — K-value / AS mismatch'], |
| 8512 | storm: ['bg-red-950/60 border-red-800 text-red-300', '🛑 EIGRP flooding storm detected'], |
| 8513 | 'rogue-router': ['bg-red-950/60 border-red-800 text-red-300', '🛑 Rogue EIGRP speaker — adjacency spoofing'], |
| 8514 | injection: ['bg-red-950/60 border-red-800 text-red-300', '🛑 EIGRP route/next-hop injection detected'], |
| 8515 | unknown: ['bg-slate-800 border-slate-700 text-slate-400', '— Could not determine'], |
| 8516 | }; |
| 8517 | function _eigrpFillIfaces() { |
| 8518 | const sel = document.getElementById('eigrp-iface'); |
| 8519 | if (!sel || sel.dataset.filled === '1') return Promise.resolve(); |
| 8520 | return fetchAPI('/api/net/interfaces').then(x => { |
| 8521 | (x.interfaces || []).forEach(i => { |
| 8522 | const o = document.createElement('option'); |
| 8523 | o.value = i.name; |
| 8524 | const tag = i.type === 'wifi' ? ' (WiFi)' : i.type === 'ethernet' ? ' (LAN)' : (i.type ? ' (' + i.type + ')' : ''); |
| 8525 | o.textContent = i.name + tag; |
| 8526 | sel.appendChild(o); |
| 8527 | }); |
| 8528 | sel.dataset.filled = '1'; |
| 8529 | }).catch(() => {}); |
| 8530 | } |
| 8531 | async function runEigrpWatch() { |
| 8532 | const out = document.getElementById('eigrp-results'); |
no test coverage detected