feat(devices): manually add a device by MAC (offline pre-register) → 2.1.3
'+ Add by MAC' in the band header → POST /api/devices → lan_devices.addManual (status=known, present=false; enriched on next scan). Repo + API + frontend tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -569,6 +569,9 @@ body.drawer-open #scrim { opacity: 1; pointer-events: auto; }
|
||||
.dv-edit-btn:hover { color: var(--accent); border-color: var(--accent-dim); }
|
||||
.dv-tile .dv-edit-name, .dv-tile .dv-edit-grp { margin: 2px 0; width: 100%; }
|
||||
.dv-tile .dv-add, .dv-tile .dv-ignore, .dv-tile .ghost { margin-top: 4px; margin-right: 4px; font-size: 11px; padding: 2px 8px; }
|
||||
.dv-addtoggle { margin-left: auto; font-size: 11px; padding: 2px 8px; white-space: nowrap; }
|
||||
.dv-addform { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin: 8px 0; padding: 8px 10px; border: 1px solid var(--accent-dim); border-radius: 6px; background: var(--accent-soft); }
|
||||
.dv-addform .dv-edit-name { flex: 1 1 9rem; }
|
||||
.dv-tile .dv-vendor { font-family: var(--font-ui); font-size: 11px; color: var(--muted); opacity: .7; }
|
||||
.dv-tile.flag { border-color: var(--bad); background: #1a1012; }
|
||||
.dv-tile.flag .dv-nm { color: var(--bad); }
|
||||
|
||||
@@ -55,6 +55,22 @@ function discoveredRow(d, onDone) {
|
||||
nameI, grpS, add, ignore);
|
||||
}
|
||||
|
||||
// Manual "add by MAC" form — for offline devices whose MAC you know.
|
||||
function manualAddForm() {
|
||||
const macI = el('input', { class: 'dv-edit-name', placeholder: 'aa:bb:cc:dd:ee:ff' });
|
||||
const nameI = el('input', { class: 'dv-edit-name', placeholder: 'name (optional)' });
|
||||
const grpS = el('select', { class: 'dv-edit-grp' }, ...GROUPS.map(g => el('option', { value: g }, g)));
|
||||
const err = el('span', { class: 'muted', style: { fontSize: '11px' } }, '');
|
||||
const add = el('button', { class: 'dv-add' }, 'Add');
|
||||
add.onclick = async () => {
|
||||
const mac = macI.value.trim().toLowerCase();
|
||||
if (!/^[0-9a-f]{2}(:[0-9a-f]{2}){5}$/.test(mac)) { err.textContent = 'MAC must look like aa:bb:cc:dd:ee:ff'; return; }
|
||||
try { await api.post('/api/devices', { mac, name: nameI.value.trim() || undefined, grp: grpS.value }); load(); }
|
||||
catch { err.textContent = 'add failed'; }
|
||||
};
|
||||
return el('div', { class: 'dv-addform' }, macI, nameI, grpS, add, err);
|
||||
}
|
||||
|
||||
async function load() {
|
||||
if (!host) return;
|
||||
let data, discovered = [];
|
||||
@@ -76,11 +92,18 @@ async function load() {
|
||||
...discovered.map(d => discoveredRow(d, load)))
|
||||
: null;
|
||||
|
||||
const addForm = manualAddForm();
|
||||
addForm.style.display = 'none';
|
||||
const addToggle = el('button', { class: 'ghost dv-addtoggle' }, '+ Add by MAC');
|
||||
addToggle.onclick = () => { addForm.style.display = addForm.style.display === 'none' ? 'flex' : 'none'; };
|
||||
|
||||
clear(host);
|
||||
mount(host,
|
||||
el('div', { class: 'dv-hd' },
|
||||
el('div', { class: 'dv-title' }, 'Network · Devices'),
|
||||
el('span', { class: 'dv-count' }, `${total} known${discovered.length ? ` · ${discovered.length} new` : ''}`)),
|
||||
el('span', { class: 'dv-count' }, `${total} known${discovered.length ? ` · ${discovered.length} new` : ''}`),
|
||||
addToggle),
|
||||
addForm,
|
||||
...sections,
|
||||
discPanel);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user