feat(events): add SFM Event DB Manager for browsing, flagging, and deleting events
This commit is contained in:
@@ -379,6 +379,20 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Bulk false-trigger flagging -->
|
||||
<div id="ue-bulk-actions" class="flex flex-wrap items-center gap-2 mb-3 text-sm">
|
||||
<span id="ue-bulk-selected" class="text-gray-600 dark:text-gray-400">0 selected</span>
|
||||
<button id="ue-bulk-flag-ft" onclick="flagSelectedUnitEvents(true)" disabled
|
||||
class="px-3 py-1.5 text-sm rounded-lg border border-yellow-300 dark:border-yellow-700 text-yellow-700 dark:text-yellow-300 hover:bg-yellow-50 dark:hover:bg-yellow-900/30 disabled:opacity-40 disabled:cursor-not-allowed">
|
||||
🚩 Flag as false trigger
|
||||
</button>
|
||||
<button id="ue-bulk-clear-ft" onclick="flagSelectedUnitEvents(false)" disabled
|
||||
class="px-3 py-1.5 text-sm rounded-lg border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-40 disabled:cursor-not-allowed">
|
||||
✓ Clear false trigger
|
||||
</button>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400 ml-2">For deletion / DB cleanup, use the <a href="/admin/events" class="text-seismo-orange hover:underline">Event DB Manager</a>.</span>
|
||||
</div>
|
||||
|
||||
<!-- Event table -->
|
||||
<div id="ue-events-container" class="overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-700">
|
||||
<div class="text-center py-12 text-gray-500 dark:text-gray-400 text-sm">
|
||||
@@ -2652,7 +2666,13 @@ function renderUnitEventTable(events, total, container, bucket, assignmentsTotal
|
||||
? '<span class="px-2 py-0.5 rounded text-xs bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300">FT</span>'
|
||||
: '';
|
||||
|
||||
const checked = _ueSelectedEventIds.has(ev.id) ? 'checked' : '';
|
||||
return `<tr class="hover:bg-gray-50 dark:hover:bg-slate-700/50 cursor-pointer ${ev.attribution ? '' : 'bg-amber-50/40 dark:bg-amber-900/10'}" onclick="showEventDetail('${_dtEsc(ev.id)}')">
|
||||
<td class="px-3 py-2.5 text-sm" onclick="event.stopPropagation()">
|
||||
<input type="checkbox" class="ue-row-check rounded border-gray-300 dark:border-gray-600"
|
||||
data-event-id="${_dtEsc(ev.id)}" ${checked}
|
||||
onchange="onUnitEventRowCheck(this)">
|
||||
</td>
|
||||
<td class="px-4 py-2.5 text-sm text-gray-900 dark:text-white whitespace-nowrap">${ts}</td>
|
||||
<td class="px-4 py-2.5 text-sm font-mono ${_uePpvClass(ev.tran_ppv)}">${tran}</td>
|
||||
<td class="px-4 py-2.5 text-sm font-mono ${_uePpvClass(ev.vert_ppv)}">${vert}</td>
|
||||
@@ -2668,6 +2688,11 @@ function renderUnitEventTable(events, total, container, bucket, assignmentsTotal
|
||||
<table class="w-full text-left">
|
||||
<thead class="bg-gray-50 dark:bg-slate-700 border-b border-gray-200 dark:border-gray-600">
|
||||
<tr>
|
||||
<th class="px-3 py-2">
|
||||
<input type="checkbox" id="ue-check-all"
|
||||
class="rounded border-gray-300 dark:border-gray-600"
|
||||
onchange="toggleAllUnitEventRows(this.checked)">
|
||||
</th>
|
||||
${_ueSortableTh('Timestamp', 'timestamp')}
|
||||
${_ueSortableTh('Tran', 'tran_ppv')}
|
||||
${_ueSortableTh('Vert', 'vert_ppv')}
|
||||
@@ -2679,6 +2704,85 @@ function renderUnitEventTable(events, total, container, bucket, assignmentsTotal
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">${rows}</tbody>
|
||||
</table>`;
|
||||
_ueRefreshBulkButton();
|
||||
}
|
||||
|
||||
// ===== Bulk false-trigger flagging =====
|
||||
// Selection is keyed by event ID and persists across table re-renders, so
|
||||
// users can paginate / re-sort without losing their selection.
|
||||
const _ueSelectedEventIds = new Set();
|
||||
|
||||
function _ueRefreshBulkButton() {
|
||||
const n = _ueSelectedEventIds.size;
|
||||
const lbl = document.getElementById('ue-bulk-selected');
|
||||
const flag = document.getElementById('ue-bulk-flag-ft');
|
||||
const clr = document.getElementById('ue-bulk-clear-ft');
|
||||
if (lbl) lbl.textContent = `${n} selected`;
|
||||
if (flag) flag.disabled = (n === 0);
|
||||
if (clr) clr.disabled = (n === 0);
|
||||
}
|
||||
|
||||
function onUnitEventRowCheck(input) {
|
||||
const id = input.getAttribute('data-event-id');
|
||||
if (input.checked) {
|
||||
_ueSelectedEventIds.add(id);
|
||||
} else {
|
||||
_ueSelectedEventIds.delete(id);
|
||||
// If we just unchecked a row, the master "all" checkbox shouldn't stay checked.
|
||||
const master = document.getElementById('ue-check-all');
|
||||
if (master) master.checked = false;
|
||||
}
|
||||
_ueRefreshBulkButton();
|
||||
}
|
||||
|
||||
function toggleAllUnitEventRows(checked) {
|
||||
document.querySelectorAll('.ue-row-check').forEach(cb => {
|
||||
const id = cb.getAttribute('data-event-id');
|
||||
cb.checked = checked;
|
||||
if (checked) _ueSelectedEventIds.add(id);
|
||||
else _ueSelectedEventIds.delete(id);
|
||||
});
|
||||
_ueRefreshBulkButton();
|
||||
}
|
||||
|
||||
async function flagSelectedUnitEvents(value) {
|
||||
// value = true → flag as false trigger
|
||||
// value = false → clear false-trigger flag
|
||||
if (_ueSelectedEventIds.size === 0) return;
|
||||
const ids = Array.from(_ueSelectedEventIds);
|
||||
const verb = value ? 'flag as false trigger' : 'clear false-trigger flag on';
|
||||
if (!confirm(`${verb} ${ids.length} event${ids.length === 1 ? '' : 's'}?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// SFM exposes single-row PATCH only. Fan out concurrently with a
|
||||
// modest cap so we don't open hundreds of sockets at once.
|
||||
const concurrency = 8;
|
||||
let ok = 0, failed = 0;
|
||||
let cursor = 0;
|
||||
async function worker() {
|
||||
while (cursor < ids.length) {
|
||||
const i = cursor++;
|
||||
const id = ids[i];
|
||||
try {
|
||||
const resp = await fetch(
|
||||
`/api/sfm/db/events/${encodeURIComponent(id)}/false_trigger?value=${value ? 'true' : 'false'}`,
|
||||
{ method: 'PATCH' }
|
||||
);
|
||||
if (resp.ok) ok++;
|
||||
else failed++;
|
||||
} catch (_) {
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
await Promise.all(Array.from({ length: concurrency }, worker));
|
||||
|
||||
if (failed) {
|
||||
alert(`${ok} updated, ${failed} failed. Refreshing table.`);
|
||||
}
|
||||
_ueSelectedEventIds.clear();
|
||||
loadUnitEvents();
|
||||
}
|
||||
|
||||
// ===== Pair Device Modal Functions =====
|
||||
|
||||
Reference in New Issue
Block a user