diff --git a/apps/desktop/resources/web/index.html b/apps/desktop/resources/web/index.html index 4805d33..4f664a2 100644 --- a/apps/desktop/resources/web/index.html +++ b/apps/desktop/resources/web/index.html @@ -197,6 +197,7 @@

Devices

+
+ + +
@@ -603,6 +627,67 @@ async function toggleDevice(i, e) { } } +// ── Manual Add ───────────────────────────────────────────────────────────── +function openManualModal() { + document.getElementById('f-manual-host').value = ''; + document.getElementById('f-manual-port').value = '49153'; + document.getElementById('modal-manual-error').style.display = 'none'; + document.getElementById('modal-manual').classList.add('open'); + setTimeout(() => document.getElementById('f-manual-host').focus(), 100); +} + +function closeManualModal(e) { + if (e && e.target !== document.getElementById('modal-manual')) return; + document.getElementById('modal-manual').classList.remove('open'); +} + +async function addManualDevice() { + const errEl = document.getElementById('modal-manual-error'); + errEl.style.display = 'none'; + + const host = document.getElementById('f-manual-host').value.trim(); + const port = parseInt(document.getElementById('f-manual-port').value, 10) || 49153; + + if (!host) { + errEl.textContent = '⚠ Enter an IP address'; + errEl.style.display = 'block'; + return; + } + + const btn = document.getElementById('btn-add-manual'); + btn.disabled = true; btn.textContent = 'Adding…'; + + try { + const result = await api('POST', '/api/devices/discover', { + manualEntries: [{ host, port }] + }); + + if (!result || result.length === 0) { + errEl.textContent = '⚠ No device found at that address'; + errEl.style.display = 'block'; + btn.disabled = false; btn.textContent = 'Add'; + return; + } + + const device = result.find((d) => d.host === host); + if (device) { + const friendlyName = device.friendlyName || device.name || host; + toast(`Added ${friendlyName}`, 'success'); + devices = result; + renderDevices(); + closeManualModal(); + } else { + errEl.textContent = '⚠ Device added but not found in results'; + errEl.style.display = 'block'; + } + } catch (err) { + errEl.textContent = `⚠ ${err.message}`; + errEl.style.display = 'block'; + } finally { + btn.disabled = false; btn.textContent = 'Add'; + } +} + // ── Rules list ───────────────────────────────────────────────────────────── async function loadRules() { try { diff --git a/docker/server.js b/docker/server.js index a360b5a..567292b 100644 --- a/docker/server.js +++ b/docker/server.js @@ -94,6 +94,10 @@ async function handleRequest(req, res) { if (url === '/api/devices/discover' && method === 'POST') { const saved = store.getDevices(); const manual = saved.map((d) => ({ host: d.host, port: d.port })); + // Add any manual entries from the request body + if (body.manualEntries && Array.isArray(body.manualEntries)) { + manual.push(...body.manualEntries); + } const devs = await wemo.discoverDevices(8000, manual); store.saveDevices(devs); return json(res, devs);