feat(devices): edit known devices (rename/regroup/delete) → 2.1.2

Known device tiles get a ✎ edit affordance using the existing PATCH/DELETE
/api/devices/:mac endpoints. Previously devices could only be named at promote time.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
root
2026-06-08 23:02:06 +10:00
parent 2284a88bd2
commit 7a5fd88c07
6 changed files with 59 additions and 10 deletions

View File

@@ -563,6 +563,12 @@ body.drawer-open #scrim { opacity: 1; pointer-events: auto; }
.dv-tile .dv-nm { font-family: var(--font-ui); font-size: 13px; color: var(--text); }
.dv-tile .dv-ip { font-family: var(--font-mono); font-size: 12px; color: var(--muted); }
.dv-tile .dv-mac { font-family: var(--font-mono); font-size: 10px; color: var(--muted); opacity: .6; letter-spacing: .02em; }
.dv-tile { position: relative; }
.dv-edit-btn { position: absolute; top: 5px; right: 5px; background: transparent; border: 1px solid var(--border); color: var(--muted); border-radius: 3px; font-size: 11px; line-height: 1; padding: 2px 5px; cursor: pointer; opacity: 0; }
.dv-tile:hover .dv-edit-btn { opacity: 1; }
.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-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); }

View File

@@ -8,12 +8,34 @@ let host;
const GROUPS = ['Smart Home', 'Entertainment', 'Personal', 'Network', 'Flagged'];
function tile(d) {
return el('div', { class: 'dv-tile' + (d.flagged ? ' flag' : '') + (d.present === false ? ' absent' : '') },
el('span', { class: 'dv-nm' }, d.name || 'Unknown'),
el('span', { class: 'dv-ip' }, d.ip || ''),
d.mac ? el('span', { class: 'dv-mac' }, d.mac) : null,
el('span', { class: 'dv-vendor' },
(d.vendor || '') + (d.randomized ? ' · randomized' : '') + (d.present === false ? ' · absent' : '')));
const t = el('div', { class: 'dv-tile' + (d.flagged ? ' flag' : '') + (d.present === false ? ' absent' : '') });
function view() {
clear(t);
const edit = el('button', { class: 'dv-edit-btn', title: 'Edit device' }, '✎');
edit.onclick = editMode;
mount(t,
el('span', { class: 'dv-nm' }, d.name || 'Unknown'),
el('span', { class: 'dv-ip' }, d.ip || ''),
d.mac ? el('span', { class: 'dv-mac' }, d.mac) : null,
el('span', { class: 'dv-vendor' },
(d.vendor || '') + (d.randomized ? ' · randomized' : '') + (d.present === false ? ' · absent' : '')),
d.mac ? edit : null);
}
function editMode() {
clear(t);
const nameI = el('input', { class: 'dv-edit-name', value: d.name || '' });
const grpS = el('select', { class: 'dv-edit-grp' }, ...GROUPS.map(g => el('option', { value: g }, g)));
grpS.value = d.grp || 'Flagged';
const save = el('button', { class: 'dv-add' }, 'Save');
save.onclick = async () => { await api.patch('/api/devices/' + d.mac, { name: nameI.value.trim() || null, grp: grpS.value }); load(); };
const del = el('button', { class: 'ghost dv-ignore' }, 'Delete');
del.onclick = async () => { await api.del('/api/devices/' + d.mac); load(); };
const cancel = el('button', { class: 'ghost' }, 'Cancel');
cancel.onclick = view;
mount(t, el('span', { class: 'dv-mac' }, d.mac), nameI, grpS, save, del, cancel);
}
view();
return t;
}
function discoveredRow(d, onDone) {