(networks)
| 6279 | } |
| 6280 | } |
| 6281 | |
| 6282 | async function checkVpnEgress() { |
| 6283 | const cell = document.getElementById('vpn-egress-result'); |
| 6284 | if (!cell) return; |
| 6285 | const sel = document.getElementById('vpn-egress-iface'); |
| 6286 | const iface = sel && sel.value ? sel.value : ''; |
| 6287 | const label = iface ? 'via ' + escapeHtml(iface) : 'default route'; |
| 6288 | cell.innerHTML = '<span class="text-gray-400">checking egress ' + label + '… (public IP, known-VPN IP list, Tor exit)</span>'; |
| 6289 | try { |
| 6290 | const d = await fetchAPI('/api/net/vpn-check' + (iface ? '?interface=' + encodeURIComponent(iface) : '')); |
| 6291 | const ifNote = d.interface ? ' <span class="text-gray-500">[' + escapeHtml(d.interface) + ']</span>' : ''; |
| 6292 | const why = (d.reasons && d.reasons.length) |
| 6293 | ? ' <span class="text-gray-500">(' + d.reasons.map(escapeHtml).join('; ') + ')</span>' : ''; |
| 6294 | if (d.verdict === 'vpn') { |
| 6295 | cell.innerHTML = '<span class="text-amber-300">yes</span>' + ifNote + why; |
| 6296 | } else if (d.verdict === 'likely') { |
| 6297 | cell.innerHTML = '<span class="text-amber-300">likely</span>' + ifNote + why; |
| 6298 | } else if (d.verdict === 'no') { |
| 6299 | const via = [d.isp, d.public_ip].filter(Boolean).map(escapeHtml).join(' · '); |
| 6300 | const note = d.ip_list_checked ? '; egress IP not in the known-VPN list' : ''; |
| 6301 | cell.innerHTML = '<span class="text-gray-400">no</span>' + ifNote |
| 6302 | + (via ? ' <span class="text-gray-500">(egress via ' + via + note + ')</span>' : ''); |
| 6303 | } else { |
| 6304 | cell.innerHTML = '<span class="text-gray-500">unknown</span>' + ifNote + why; |
| 6305 | } |
| 6306 | } catch (e) { |
| 6307 | cell.innerHTML = '<span class="text-red-400">check failed: ' + escapeHtml(e.message) + '</span>'; |
| 6308 | } |
| 6309 | } |
| 6310 | |
| 6311 | async function loadInterfaces() { |
| 6312 | const out = document.getElementById('interfaces-results'); |
| 6313 | out.innerHTML = '<p class="text-gray-400">Loading…</p>'; |
| 6314 | try { |
| 6315 | const data = await fetchAPI('/api/net/interfaces'); |
| 6316 | if (!data.success) { |
| 6317 | out.innerHTML = '<p class="text-red-400">' + escapeHtml(data.error || 'failed') + '</p>'; |
| 6318 | return; |
| 6319 | } |
| 6320 | _ndLastIfaces = data.interfaces || []; |
| 6321 | const methodBadge = (m) => { |
| 6322 | const map = { |
| 6323 | dhcp: 'bg-blue-900/50 text-blue-300', static: 'bg-purple-900/50 text-purple-300', |
| 6324 | 'dhcp-failed': 'bg-red-900/50 text-red-300', 'link-down': 'bg-slate-700 text-slate-400', |
| 6325 | unknown: 'bg-slate-700 text-slate-400' |
| 6326 | }; |
| 6327 | return `<span class="px-2 py-0.5 rounded text-xs ${map[m] || map.unknown}">${escapeHtml(m)}</span>`; |
| 6328 | }; |
| 6329 | const typeLabel = (i) => { |
| 6330 | if (i.type === 'vpn') return '<span class="px-2 py-0.5 rounded text-xs bg-amber-900/50 text-amber-300">🔒 ' + escapeHtml(i.vpn_kind || 'VPN') + '</span>'; |
| 6331 | if (i.type === 'wifi') return '<span class="px-2 py-0.5 rounded text-xs bg-sky-900/50 text-sky-300">wifi</span>'; |
| 6332 | return '<span class="px-2 py-0.5 rounded text-xs bg-slate-700 text-slate-300">ethernet</span>'; |
| 6333 | }; |
| 6334 | const rows = data.interfaces.map(i => { |
| 6335 | const link = (i.link_detected === true) ? '<span class="text-green-400">up</span>' |
| 6336 | : (i.link_detected === false) ? '<span class="text-gray-500">down</span>' |
| 6337 | : escapeHtml(String(i.operstate || '—')); |
| 6338 | const speed = i.speed ? escapeHtml(i.speed) + (i.duplex ? ' ' + escapeHtml(i.duplex) : '') : '—'; |
no test coverage detected