feat(devices): show icon + last-seen, icon picker in edit
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,8 @@
|
|||||||
// newly-seen devices. Kept SEPARATE from Little Blue's homelab-service band.
|
// newly-seen devices. Kept SEPARATE from Little Blue's homelab-service band.
|
||||||
import { el, mount, clear } from '../dom.js';
|
import { el, mount, clear } from '../dom.js';
|
||||||
import { api } from '../api.js';
|
import { api } from '../api.js';
|
||||||
|
import { resolveIcon, relativeTime, autoDefaultIcon } from './icon_util.js';
|
||||||
|
import { iconPicker } from './icon_picker.js';
|
||||||
|
|
||||||
let host;
|
let host;
|
||||||
const GROUPS = ['Smart Home', 'Entertainment', 'Personal', 'Network', 'Flagged'];
|
const GROUPS = ['Smart Home', 'Entertainment', 'Personal', 'Network', 'Flagged'];
|
||||||
@@ -13,26 +15,50 @@ function tile(d) {
|
|||||||
clear(t);
|
clear(t);
|
||||||
const edit = el('button', { class: 'dv-edit-btn', title: 'Edit device' }, '✎');
|
const edit = el('button', { class: 'dv-edit-btn', title: 'Edit device' }, '✎');
|
||||||
edit.onclick = editMode;
|
edit.onclick = editMode;
|
||||||
mount(t,
|
const ref = d.icon || autoDefaultIcon(d.grp);
|
||||||
|
const src = resolveIcon(ref);
|
||||||
|
const img = el('img', { class: 'dv-icon', src, alt: '' });
|
||||||
|
img.onerror = () => {
|
||||||
|
if (src && src.endsWith('.svg')) { img.src = src.replace(/\.svg$/, '.png'); return; }
|
||||||
|
img.replaceWith(el('div', { class: 'dv-icon-fb' }, (d.name?.[0] || '?').toUpperCase()));
|
||||||
|
};
|
||||||
|
const seen = d.present === false && d.last_seen
|
||||||
|
? el('span', { class: 'dv-seen' }, 'seen ' + relativeTime(d.last_seen)) : null;
|
||||||
|
mount(t, img,
|
||||||
el('span', { class: 'dv-nm' }, d.name || 'Unknown'),
|
el('span', { class: 'dv-nm' }, d.name || 'Unknown'),
|
||||||
el('span', { class: 'dv-ip' }, d.ip || ''),
|
el('span', { class: 'dv-ip' }, d.ip || ''),
|
||||||
d.mac ? el('span', { class: 'dv-mac' }, d.mac) : null,
|
d.mac ? el('span', { class: 'dv-mac' }, d.mac) : null,
|
||||||
el('span', { class: 'dv-vendor' },
|
el('span', { class: 'dv-vendor' },
|
||||||
(d.vendor || '') + (d.randomized ? ' · randomized' : '') + (d.present === false ? ' · absent' : '')),
|
(d.vendor || '') + (d.randomized ? ' · randomized' : '') + (d.present === false ? ' · absent' : '')),
|
||||||
|
seen,
|
||||||
d.mac ? edit : null);
|
d.mac ? edit : null);
|
||||||
}
|
}
|
||||||
function editMode() {
|
function editMode() {
|
||||||
clear(t);
|
clear(t);
|
||||||
|
let chosenIcon = d.icon || null;
|
||||||
const nameI = el('input', { class: 'dv-edit-name', value: d.name || '' });
|
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)));
|
const grpS = el('select', { class: 'dv-edit-grp' }, ...GROUPS.map(g => el('option', { value: g }, g)));
|
||||||
grpS.value = d.grp || 'Flagged';
|
grpS.value = d.grp || 'Flagged';
|
||||||
|
const pickerWrap = el('div', { class: 'dv-picker-wrap' });
|
||||||
|
pickerWrap.style.display = 'none';
|
||||||
|
const iconBtn = el('button', { class: 'ghost' }, 'Icon');
|
||||||
|
iconBtn.onclick = () => {
|
||||||
|
if (pickerWrap.style.display === 'none') {
|
||||||
|
clear(pickerWrap);
|
||||||
|
pickerWrap.append(iconPicker(chosenIcon, ref => { chosenIcon = ref; iconBtn.textContent = 'Icon ✓'; pickerWrap.style.display = 'none'; }));
|
||||||
|
pickerWrap.style.display = 'block';
|
||||||
|
} else pickerWrap.style.display = 'none';
|
||||||
|
};
|
||||||
const save = el('button', { class: 'dv-add' }, 'Save');
|
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(); };
|
save.onclick = async () => {
|
||||||
|
await api.patch('/api/devices/' + d.mac, { name: nameI.value.trim() || null, grp: grpS.value, icon: chosenIcon });
|
||||||
|
load();
|
||||||
|
};
|
||||||
const del = el('button', { class: 'ghost dv-ignore' }, 'Delete');
|
const del = el('button', { class: 'ghost dv-ignore' }, 'Delete');
|
||||||
del.onclick = async () => { await api.del('/api/devices/' + d.mac); load(); };
|
del.onclick = async () => { await api.del('/api/devices/' + d.mac); load(); };
|
||||||
const cancel = el('button', { class: 'ghost' }, 'Cancel');
|
const cancel = el('button', { class: 'ghost' }, 'Cancel');
|
||||||
cancel.onclick = view;
|
cancel.onclick = view;
|
||||||
mount(t, el('span', { class: 'dv-mac' }, d.mac), nameI, grpS, save, del, cancel);
|
mount(t, el('span', { class: 'dv-mac' }, d.mac), nameI, grpS, iconBtn, save, del, cancel, pickerWrap);
|
||||||
}
|
}
|
||||||
view();
|
view();
|
||||||
return t;
|
return t;
|
||||||
|
|||||||
Reference in New Issue
Block a user