()
| 5186 | const d = await postAPI('/api/net/iperf3', { server: server, duration: dur, reverse: reverse }); |
| 5187 | if (!d.success) { |
| 5188 | out.innerHTML = d.missing_tool ? _ndMissingTool(d, 'runIperf') |
| 5189 | : '<p class="text-sm text-red-400">Error: ' + escapeHtml(d.error || 'failed') + '</p>'; |
| 5190 | return; |
| 5191 | } |
| 5192 | const card = (label, val, unit) => `<div class="bg-slate-800 rounded-lg p-4 text-center"> |
| 5193 | <div class="text-2xl font-bold text-Ragnar-400">${val}<span class="text-sm text-gray-400"> ${unit}</span></div> |
| 5194 | <div class="text-xs uppercase text-gray-500 mt-1">${label}</div></div>`; |
| 5195 | let extra = ''; |
| 5196 | if (d.protocol === 'TCP') extra = card('Retransmits', d.retransmits != null ? d.retransmits : '—', ''); |
| 5197 | else extra = card('Jitter', d.jitter_ms, 'ms') + card('Loss', d.lost_percent, '%'); |
| 5198 | out.innerHTML = `<div class="grid grid-cols-2 sm:grid-cols-3 gap-3"> |
| 5199 | ${card(d.direction === 'download' ? 'Download' : 'Upload', d.mbps, 'Mbps')}${extra}</div> |
| 5200 | <p class="text-xs text-gray-500 mt-2">${d.protocol} · ${escapeHtml(String(d.server))}:${d.port} · ${d.duration_s}s</p>`; |
| 5201 | } catch (e) { |
| 5202 | out.innerHTML = '<p class="text-sm text-red-400">Failed: ' + escapeHtml(e.message) + '</p>'; |
| 5203 | } finally { |
| 5204 | _ndBusy(btn, false); |
| 5205 | } |
| 5206 | } |
| 5207 | |
| 5208 | async function toggleIperfServer() { |
| 5209 | const btn = document.getElementById('iperf-server-btn'); |
| 5210 | const status = document.getElementById('iperf-server-status'); |
| 5211 | const starting = btn.textContent.indexOf('Start') !== -1; |
| 5212 | _ndBusy(btn, true, starting ? 'Starting…' : 'Stopping…'); |
| 5213 | try { |
| 5214 | const d = await postAPI('/api/net/iperf3-server', { action: starting ? 'start' : 'stop' }); |
| 5215 | _ndBusy(btn, false); |
| 5216 | if (!d.success) { |
| 5217 | status.classList.remove('hidden'); |
| 5218 | status.innerHTML = d.missing_tool |
| 5219 | ? _ndMissingTool(d, 'toggleIperfServer') |
| 5220 | : '<span class="text-red-400">' + escapeHtml(d.error || 'failed') + '</span>'; |
| 5221 | return; |
| 5222 | } |
| 5223 | btn.textContent = d.running ? 'Stop server' : 'Start server'; |
| 5224 | status.classList.toggle('hidden', !d.running); |
| 5225 | if (d.running) { |
| 5226 | const addrs = (d.addresses || []).map(escapeHtml).join(', '); |
| 5227 | status.innerHTML = '<span class="text-green-400">● listening on port ' + d.port + '</span> — ' |
| 5228 | + 'run <span class="font-mono">iperf3 -c ' + escapeHtml((d.addresses || [''])[0]) + '</span> from another device' |
| 5229 | + (addrs ? ' <span class="text-gray-500">(' + addrs + ')</span>' : ''); |
| 5230 | } |
| 5231 | } catch (e) { |
| 5232 | _ndBusy(btn, false); |
| 5233 | status.classList.remove('hidden'); |
| 5234 | status.innerHTML = '<span class="text-red-400">Failed: ' + escapeHtml(e.message) + '</span>'; |
| 5235 | } |
| 5236 | } |
no test coverage detected