()
| 7714 | if (!d || d.success === false) { |
| 7715 | const msg = (d && d.error) || 'failed'; |
| 7716 | let extra = ''; |
| 7717 | if (d && d.missing_tool) extra = ' <button onclick="installNetTool(\'tcpdump\', this, runOspfWatch)" class="ml-2 underline text-cyan-400">Install tcpdump</button>'; |
| 7718 | out.innerHTML = '<p class="text-sm text-red-400">Error: ' + escapeHtml(msg) + extra + '</p>'; |
| 7719 | return; |
| 7720 | } |
| 7721 | const [cls, label] = _OSPF_VERDICT_STYLE[d.verdict] || _OSPF_VERDICT_STYLE.unknown; |
| 7722 | let html = `<div class="mb-2 px-3 py-2 rounded border ${cls} text-sm">${label}</div>`; |
| 7723 | html += `<p class="text-xs text-gray-500 mb-2">Interface: ${escapeHtml(d.interface || '—')} · ${d.seconds}s · ${d.packets} OSPF pkts (${d.hellos} hello, ${d.ls_updates} LSU @ ${d.lsu_per_s}/s)${d.learned ? ' · <span class="text-gray-400">baseline learned now</span>' : ''}</p>`; |
| 7724 | if (d.note) html += `<p class="text-xs text-gray-500 mb-2">${escapeHtml(d.note)}</p>`; |
| 7725 | const auth = (d.auth_types || []).map(a => _OSPF_AUTH_NAME[a] || a); |
| 7726 | if (auth.length) { |
| 7727 | const bad = (d.auth_types || []).some(a => a < 2); |
| 7728 | html += `<p class="text-xs mb-1 ${bad ? 'text-amber-300' : 'text-gray-400'}">Authentication: ${escapeHtml(auth.join(', '))}${bad ? ' — no cryptographic auth' : ''}</p>`; |
| 7729 | } |
| 7730 | // Advisories (CVE / OSV) |
| 7731 | (d.advisories || []).forEach(a => { |
| 7732 | 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'; |
| 7733 | const refs = (a.refs || []).map(r => r.startsWith('http') |
| 7734 | ? `<a href="${escapeHtml(r)}" target="_blank" rel="noopener" class="underline text-cyan-400">OSV</a>` |
| 7735 | : `<span class="font-mono">${escapeHtml(r)}</span>`).join(', '); |
| 7736 | 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>`; |
| 7737 | }); |
| 7738 | // Routers table |
| 7739 | const routers = d.routers || [], tr = d.trusted_routers || []; |
| 7740 | if (routers.length) { |
| 7741 | html += '<p class="text-xs uppercase text-gray-400 mt-2 mb-1">OSPF routers seen (' + routers.length + ')</p>' + |
| 7742 | '<table class="min-w-full text-xs text-gray-300 whitespace-nowrap"><thead>' + |
| 7743 | '<tr class="text-left text-gray-500"><th class="px-2 py-1">Router-ID</th><th class="px-2 py-1">Source(s)</th><th class="px-2 py-1">Status</th></tr>' + |
| 7744 | '</thead><tbody>' + |
| 7745 | routers.map(r => { |
| 7746 | const badge = r.new ? '<span class="text-amber-300">? new</span>' : (tr.indexOf(r.router_id) >= 0 ? '<span class="text-green-400">✓ known</span>' : '<span class="text-gray-500">—</span>'); |
| 7747 | return `<tr class="border-t border-slate-800"> |
| 7748 | <td class="px-2 py-1 font-mono ${r.new ? 'text-amber-300' : ''}">${escapeHtml(r.router_id)}</td> |
| 7749 | <td class="px-2 py-1 font-mono text-gray-400">${escapeHtml((r.sources || []).join(', ') || '—')}</td> |
| 7750 | <td class="px-2 py-1">${badge}</td> |
| 7751 | </tr>`; |
| 7752 | }).join('') + |
| 7753 | '</tbody></table>'; |
| 7754 | } |
| 7755 | const lc = d.lsa_counts || {}; |
| 7756 | const lcKeys = Object.keys(lc); |
| 7757 | if (lcKeys.length) { |
| 7758 | html += '<p class="text-xs text-gray-500 mt-2">LSAs: ' + lcKeys.map(k => `${escapeHtml(k)}×${lc[k]}`).join(', ') + '</p>'; |
| 7759 | } |
| 7760 | if (d.reasons && d.reasons.length) { |
| 7761 | html += '<ul class="text-xs text-gray-400 mt-2 list-disc pl-5">' + |
| 7762 | d.reasons.map(r => '<li>' + escapeHtml(r) + '</li>').join('') + '</ul>'; |
| 7763 | } |
| 7764 | out.innerHTML = html; |
nothing calls this directly
no test coverage detected