feat(ui): add device from devices modal
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -76,6 +76,13 @@ function normalizeDeviceMacKey(mac) {
|
|||||||
.replace(/[:-]/g, '');
|
.replace(/[:-]/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeMacInput(raw) {
|
||||||
|
return String(raw || '')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[:-]/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
function findPingResponse(responses, deviceId) {
|
function findPingResponse(responses, deviceId) {
|
||||||
if (!responses || typeof responses !== 'object') return null;
|
if (!responses || typeof responses !== 'object') return null;
|
||||||
const want = normalizeDeviceMacKey(deviceId);
|
const want = normalizeDeviceMacKey(deviceId);
|
||||||
@@ -430,6 +437,69 @@ async function loadDevicesModal() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createDeviceFromModal() {
|
||||||
|
const nameEl = document.getElementById('devices-add-name');
|
||||||
|
const trEl = document.getElementById('devices-add-transport');
|
||||||
|
const macEl = document.getElementById('devices-add-mac');
|
||||||
|
const addrEl = document.getElementById('devices-add-address');
|
||||||
|
const statusEl = document.getElementById('devices-add-status');
|
||||||
|
const btn = document.getElementById('devices-add-btn');
|
||||||
|
const name = (nameEl && nameEl.value.trim()) || '';
|
||||||
|
const transport = (trEl && trEl.value) || 'espnow';
|
||||||
|
const mac = normalizeMacInput(macEl && macEl.value);
|
||||||
|
const address = (addrEl && addrEl.value.trim()) || '';
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
if (statusEl) statusEl.textContent = 'Name is required';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mac.length !== 12) {
|
||||||
|
if (statusEl) statusEl.textContent = 'MAC must be 12 hex characters';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (transport === 'wifi' && !address) {
|
||||||
|
if (statusEl) statusEl.textContent = 'Address is required for Wi-Fi devices';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (btn) {
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = 'Adding…';
|
||||||
|
}
|
||||||
|
if (statusEl) statusEl.textContent = 'Creating device…';
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
name,
|
||||||
|
transport,
|
||||||
|
type: 'led',
|
||||||
|
mac,
|
||||||
|
address: transport === 'wifi' ? address : mac,
|
||||||
|
};
|
||||||
|
const res = await fetch('/devices', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
const data = await res.json().catch(() => ({}));
|
||||||
|
if (!res.ok) {
|
||||||
|
if (statusEl) statusEl.textContent = data.error || 'Create failed';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (statusEl) statusEl.textContent = 'Device added';
|
||||||
|
if (nameEl) nameEl.value = '';
|
||||||
|
if (macEl) macEl.value = '';
|
||||||
|
if (addrEl) addrEl.value = '';
|
||||||
|
await loadDevicesModal();
|
||||||
|
} catch (e) {
|
||||||
|
if (statusEl) statusEl.textContent = e.message || 'Create failed';
|
||||||
|
} finally {
|
||||||
|
if (btn) {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.textContent = 'Add device';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function renderDevicesList(devices) {
|
function renderDevicesList(devices) {
|
||||||
const container = document.getElementById('devices-list-modal');
|
const container = document.getElementById('devices-list-modal');
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
@@ -750,6 +820,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const editForm = document.getElementById('edit-device-form');
|
const editForm = document.getElementById('edit-device-form');
|
||||||
const editCloseBtn = document.getElementById('edit-device-close-btn');
|
const editCloseBtn = document.getElementById('edit-device-close-btn');
|
||||||
const editDeviceModal = document.getElementById('edit-device-modal');
|
const editDeviceModal = document.getElementById('edit-device-modal');
|
||||||
|
const addTransport = document.getElementById('devices-add-transport');
|
||||||
|
const addAddress = document.getElementById('devices-add-address');
|
||||||
|
const addBtn = document.getElementById('devices-add-btn');
|
||||||
|
|
||||||
if (devicesBtn && devicesModal) {
|
if (devicesBtn && devicesModal) {
|
||||||
devicesBtn.addEventListener('click', () => {
|
devicesBtn.addEventListener('click', () => {
|
||||||
@@ -768,6 +841,17 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (addTransport && addAddress) {
|
||||||
|
const syncAddAddress = () => {
|
||||||
|
addAddress.hidden = addTransport.value !== 'wifi';
|
||||||
|
};
|
||||||
|
addTransport.addEventListener('change', syncAddAddress);
|
||||||
|
syncAddAddress();
|
||||||
|
}
|
||||||
|
if (addBtn) {
|
||||||
|
addBtn.addEventListener('click', () => createDeviceFromModal());
|
||||||
|
}
|
||||||
|
|
||||||
const devicesPingBtn = document.getElementById('devices-ping-btn');
|
const devicesPingBtn = document.getElementById('devices-ping-btn');
|
||||||
if (devicesPingBtn) {
|
if (devicesPingBtn) {
|
||||||
devicesPingBtn.addEventListener('click', () => {
|
devicesPingBtn.addEventListener('click', () => {
|
||||||
|
|||||||
@@ -166,6 +166,19 @@
|
|||||||
<div id="devices-modal" class="modal">
|
<div id="devices-modal" class="modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<h2>Devices</h2>
|
<h2>Devices</h2>
|
||||||
|
<div class="form-group" style="margin-bottom:0.75rem;">
|
||||||
|
<div class="profiles-actions" style="gap:0.5rem;flex-wrap:wrap;">
|
||||||
|
<input type="text" id="devices-add-name" placeholder="Device name" autocomplete="off" style="min-width:10rem;">
|
||||||
|
<select id="devices-add-transport">
|
||||||
|
<option value="espnow">ESP-NOW</option>
|
||||||
|
<option value="wifi">Wi-Fi</option>
|
||||||
|
</select>
|
||||||
|
<input type="text" id="devices-add-mac" placeholder="MAC (12 hex)" autocomplete="off" style="min-width:10rem;">
|
||||||
|
<input type="text" id="devices-add-address" placeholder="Address (IP/host for Wi-Fi)" autocomplete="off" style="min-width:12rem;" hidden>
|
||||||
|
<button type="button" class="btn btn-primary btn-small" id="devices-add-btn">Add device</button>
|
||||||
|
</div>
|
||||||
|
<small id="devices-add-status" class="muted-text" aria-live="polite"></small>
|
||||||
|
</div>
|
||||||
<div id="devices-list-modal" class="profiles-list"></div>
|
<div id="devices-list-modal" class="profiles-list"></div>
|
||||||
<div class="modal-actions" style="align-items:center;gap:0.75rem;flex-wrap:wrap;">
|
<div class="modal-actions" style="align-items:center;gap:0.75rem;flex-wrap:wrap;">
|
||||||
<button type="button" class="btn btn-secondary" id="devices-ping-btn" title="ESP-NOW broadcast ping (3 s)">Ping drivers</button>
|
<button type="button" class="btn btn-secondary" id="devices-ping-btn" title="ESP-NOW broadcast ping (3 s)">Ping drivers</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user