(options = {})
| 4853 | const pmtu = d.pmtu != null ? d.pmtu + ' bytes' : 'unknown'; |
| 4854 | const verdict = d.pmtu == null |
| 4855 | ? '<span class="text-amber-300">⚠ could not measure</span>' |
| 4856 | : d.reduced |
| 4857 | ? '<span class="text-amber-300">⚠ below 1500 — MTU black-hole / tunnel overhead likely</span>' |
| 4858 | : '<span class="text-green-400">✓ full 1500-byte path</span>'; |
| 4859 | out.innerHTML = `<p class="mb-1">Path MTU: <strong>${escapeHtml(pmtu)}</strong> — ${verdict}</p> |
| 4860 | <p class="text-xs text-gray-400">${escapeHtml(d.note || '')}</p>`; |
| 4861 | } catch (e) { |
| 4862 | out.innerHTML = '<p class="text-sm text-red-400">Failed: ' + escapeHtml(e.message) + '</p>'; |
| 4863 | } finally { |
| 4864 | _ndBusy(btn, false); |
| 4865 | } |
| 4866 | } |
| 4867 | |
| 4868 | async function runCaptivePortal() { |
| 4869 | const out = document.getElementById('captive-results'); |
| 4870 | const btn = event && event.target ? event.target : null; |
| 4871 | _ndBusy(btn, true, 'Checking…'); |
| 4872 | out.classList.remove('hidden'); |
| 4873 | out.innerHTML = '<p class="text-sm text-gray-400">Probing connectivity endpoints…</p>'; |
| 4874 | try { |
| 4875 | const d = await fetchAPI('/api/net/captive-portal'); |
| 4876 | if (!d.success) { |
| 4877 | out.innerHTML = d.missing_tool ? _ndMissingTool(d, 'runCaptivePortal') |
| 4878 | : '<p class="text-sm text-red-400">Error: ' + escapeHtml(d.error || 'failed') + '</p>'; |
| 4879 | return; |
| 4880 | } |
| 4881 | const verdict = d.captive_portal |
| 4882 | ? '<span class="text-amber-300">⚠ captive portal / HTTP interception detected</span>' |
| 4883 | : '<span class="text-green-400">✓ no captive portal — direct internet</span>'; |
| 4884 | const rows = (d.checks || []).map(c => `<tr class="border-t border-slate-800"> |
| 4885 | <td class="px-3 py-1.5 font-mono text-xs">${escapeHtml(c.url)}</td> |
| 4886 | <td class="px-3 py-1.5">${escapeHtml(String(c.code || '—'))}</td> |
| 4887 | <td class="px-3 py-1.5">${c.ok ? '<span class="text-green-400">ok</span>' : '<span class="text-amber-300">intercepted</span>'}${c.redirect ? ' → ' + escapeHtml(c.redirect) : ''}</td> |
| 4888 | </tr>`).join(''); |
| 4889 | out.innerHTML = `<p class="mb-2">${verdict}</p> |
| 4890 | <table class="min-w-full text-sm text-gray-300"><tbody>${rows}</tbody></table>`; |
| 4891 | } catch (e) { |
| 4892 | out.innerHTML = '<p class="text-sm text-red-400">Failed: ' + escapeHtml(e.message) + '</p>'; |
| 4893 | } finally { |
| 4894 | _ndBusy(btn, false); |
| 4895 | } |
| 4896 | } |
| 4897 | |
| 4898 | async function runIperf() { |
| 4899 | const input = document.getElementById('iperf-server'); |
| 4900 | const out = document.getElementById('iperf-results'); |
| 4901 | const server = (input.value || '').trim(); |
| 4902 | if (!server) { input.focus(); return; } |
| 4903 | const reverse = document.getElementById('iperf-dir').value === 'down'; |
| 4904 | let dur = parseInt(document.getElementById('iperf-dur').value, 10); |
| 4905 | if (!Number.isFinite(dur)) dur = 5; |
| 4906 | const btn = event && event.target ? event.target : null; |
| 4907 | _ndBusy(btn, true, 'Testing…'); |
| 4908 | out.classList.remove('hidden'); |
| 4909 | out.innerHTML = '<p class="text-sm text-gray-400">Running iperf3 ' + (reverse ? 'download' : 'upload') + ' for ' + dur + 's…</p>'; |
| 4910 | try { |
| 4911 | const d = await postAPI('/api/net/iperf3', { server: server, duration: dur, reverse: reverse }); |
| 4912 | if (!d.success) { |
no test coverage detected