This commit is contained in:
@@ -197,6 +197,7 @@
|
|||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<h2>Devices</h2>
|
<h2>Devices</h2>
|
||||||
<button class="btn btn-primary" id="btn-discover" onclick="discoverDevices()">⟳ Scan</button>
|
<button class="btn btn-primary" id="btn-discover" onclick="discoverDevices()">⟳ Scan</button>
|
||||||
|
<button class="btn btn-secondary btn-sm" id="btn-manual-add" onclick="openManualModal()" title="Add device manually" style="width:auto;padding:9px 12px;">+</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="page-header-devices" class="page-header">
|
<div id="page-header-devices" class="page-header">
|
||||||
<span class="ws-dot" id="ws-dot-d"></span>
|
<span class="ws-dot" id="ws-dot-d"></span>
|
||||||
@@ -436,6 +437,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ── Manual Add Modal ── -->
|
||||||
|
<div class="modal-backdrop" id="modal-manual" onclick="closeManualModal(event)">
|
||||||
|
<div class="modal" onclick="event.stopPropagation()">
|
||||||
|
<div class="modal-header">
|
||||||
|
<span class="modal-title">Add Device Manually</span>
|
||||||
|
<button class="modal-close" onclick="closeManualModal()">✕</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">IP Address</label>
|
||||||
|
<input class="form-input" id="f-manual-host" type="text" placeholder="192.168.1.100" autofocus />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">Port</label>
|
||||||
|
<input class="form-input" id="f-manual-port" type="number" placeholder="49153" value="49153" />
|
||||||
|
</div>
|
||||||
|
<div id="modal-manual-error" style="display:none;color:var(--danger);font-size:13px;margin-bottom:10px;"></div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn btn-ghost" onclick="closeManualModal()">Cancel</button>
|
||||||
|
<button class="btn btn-primary" id="btn-add-manual" onclick="addManualDevice()">Add</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- ── Delete Confirm ── -->
|
<!-- ── Delete Confirm ── -->
|
||||||
<div class="confirm-backdrop" id="confirm-delete">
|
<div class="confirm-backdrop" id="confirm-delete">
|
||||||
<div class="confirm-box">
|
<div class="confirm-box">
|
||||||
@@ -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 ─────────────────────────────────────────────────────────────
|
// ── Rules list ─────────────────────────────────────────────────────────────
|
||||||
async function loadRules() {
|
async function loadRules() {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -94,6 +94,10 @@ async function handleRequest(req, res) {
|
|||||||
if (url === '/api/devices/discover' && method === 'POST') {
|
if (url === '/api/devices/discover' && method === 'POST') {
|
||||||
const saved = store.getDevices();
|
const saved = store.getDevices();
|
||||||
const manual = saved.map((d) => ({ host: d.host, port: d.port }));
|
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);
|
const devs = await wemo.discoverDevices(8000, manual);
|
||||||
store.saveDevices(devs);
|
store.saveDevices(devs);
|
||||||
return json(res, devs);
|
return json(res, devs);
|
||||||
|
|||||||
Reference in New Issue
Block a user