()
| 6106 | _ndBusy(btn, false); |
| 6107 | } |
| 6108 | } |
| 6109 | |
| 6110 | // Blink a wired link in a pattern so its switch LED reveals the port. POSTs to |
| 6111 | // /api/net/locate-port; on the default-route guard it asks for confirmation and |
| 6112 | // re-sends with force. |
| 6113 | function _locateFillIfaces() { |
| 6114 | const sel = document.getElementById('locate-iface'); |
| 6115 | if (!sel || sel.dataset.filled === '1') return Promise.resolve(); |
| 6116 | return fetchAPI('/api/net/interfaces').then(x => { |
| 6117 | (x.interfaces || []).forEach(i => { |
| 6118 | const o = document.createElement('option'); |
| 6119 | o.value = i.name; |
| 6120 | const tag = i.type === 'wifi' ? ' (WiFi)' : i.type === 'ethernet' ? ' (LAN)' : (i.type ? ' (' + i.type + ')' : ''); |
| 6121 | o.textContent = i.name + tag; |
| 6122 | sel.appendChild(o); |
| 6123 | }); |
| 6124 | sel.dataset.filled = '1'; |
| 6125 | }).catch(() => {}); |
| 6126 | } |
| 6127 | async function locatePort(force) { |
| 6128 | const ifaceEl = document.getElementById('locate-iface'); |
| 6129 | const out = document.getElementById('locate-results'); |
| 6130 | const iface = ifaceEl && ifaceEl.value ? ifaceEl.value : ''; |
| 6131 | let count = parseInt(document.getElementById('locate-count').value, 10); |
| 6132 | if (!Number.isFinite(count)) count = 6; |
| 6133 | const methEl = document.getElementById('locate-method'); |
| 6134 | const method = (methEl && methEl.value) || 'flap'; |
| 6135 | const btn = event && event.target ? event.target : null; |
| 6136 | _ndBusy(btn, true, method === 'burst' ? 'Bursting…' : 'Flashing…'); |
| 6137 | out.classList.remove('hidden'); |
| 6138 | try { |
| 6139 | _locateFillIfaces(); |
| 6140 | const data = await postAPI('/api/net/locate-port', { interface: iface, count: count, method: method, force: !!force }); |
| 6141 | if (!data.success) { |
| 6142 | if (data.needs_force) { |
| 6143 | _ndBusy(btn, false); |
| 6144 | if (confirm(data.error + '\n\nFlash it anyway?')) return locatePort(true); |
| 6145 | out.innerHTML = '<p class="text-gray-400">Cancelled — tip: the Traffic-burst method locates it without dropping the link.</p>'; |
| 6146 | return; |
| 6147 | } |
| 6148 | out.innerHTML = '<p class="text-red-400">Error: ' + escapeHtml(data.error || 'failed') + '</p>'; |
| 6149 | return; |
| 6150 | } |
| 6151 | out.innerHTML = '<p class="text-green-400">⚡ ' + escapeHtml(data.message) + '</p>'; |
| 6152 | } catch (e) { |
| 6153 | // A flap on the interface serving this page can abort the request — that's |
| 6154 | // expected. (Burst never drops the link, so this path is flap-only.) |
| 6155 | out.innerHTML = '<p class="text-amber-300">Flash started on ' + escapeHtml(iface || 'the wired port') |
| 6156 | + '. If the UI dropped, that\'s the link flapping — watch the switch LED; it auto-restores.</p>'; |
no test coverage detected