feat(api): parallel group sends and batch identify

- asyncio.gather for group brightness and driver-config Wi-Fi pushes
- Batch identify envelope for group members

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-13 00:44:13 +12:00
parent cad0aa7e59
commit c64dd736f2
4 changed files with 255 additions and 76 deletions

View File

@@ -326,6 +326,16 @@ function renderGroupsList(groups) {
}
});
const identifyBtn = document.createElement('button');
identifyBtn.className = 'btn btn-secondary btn-small';
identifyBtn.type = 'button';
identifyBtn.textContent = 'Identify';
identifyBtn.title =
'Identify all devices in this group at once (red blink at 10 Hz)';
identifyBtn.addEventListener('click', async () => {
await identifyGroupById(gid);
});
const delBtn = document.createElement('button');
delBtn.className = 'btn btn-danger btn-small';
delBtn.textContent = 'Delete';
@@ -348,11 +358,40 @@ function renderGroupsList(groups) {
row.appendChild(editBtn);
row.appendChild(brightBtn);
row.appendChild(applyBtn);
row.appendChild(identifyBtn);
row.appendChild(delBtn);
container.appendChild(row);
});
}
async function identifyGroupById(gid) {
if (!gid) return;
try {
const res = await fetch(`/groups/${encodeURIComponent(gid)}/identify`, {
method: 'POST',
credentials: 'same-origin',
headers: { Accept: 'application/json' },
});
const data = await res.json().catch(() => ({}));
if (!res.ok) {
alert(data.error || 'Identify failed');
return;
}
const n = typeof data.sent === 'number' ? data.sent : 0;
const errs = Array.isArray(data.errors) ? data.errors : [];
const failed = errs.filter((e) => e && e.error).length;
let msg = n ? `Identify sent to ${n} device(s).` : 'No devices received identify.';
if (failed) {
msg += ` ${failed} failed — see console for details.`;
console.warn('Group identify errors', errs);
}
alert(msg);
} catch (e) {
console.error(e);
alert('Identify failed');
}
}
document.addEventListener('DOMContentLoaded', () => {
const groupsBtn = document.getElementById('groups-btn');
const groupsModal = document.getElementById('groups-modal');
@@ -381,6 +420,16 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
const editIdentifyBtn = document.getElementById('edit-group-identify-btn');
if (editIdentifyBtn) {
editIdentifyBtn.addEventListener('click', async () => {
const idInput = document.getElementById('edit-group-id');
const gid = idInput && idInput.value;
if (!gid) return;
await identifyGroupById(gid);
});
}
const createHandler = async () => {
const name = newNameInput && newNameInput.value.trim();
if (!name) return;
@@ -449,4 +498,15 @@ document.addEventListener('DOMContentLoaded', () => {
if (editCloseBtn && editModal) {
editCloseBtn.addEventListener('click', () => editModal.classList.remove('active'));
}
window.openDeviceGroupsModal = async () => {
const gm = document.getElementById('groups-modal');
if (!gm) return;
gm.classList.add('active');
try {
await loadGroupsModal();
} catch (e) {
console.error('openDeviceGroupsModal', e);
}
};
});