diff --git a/public/index.html b/public/index.html index 446a675..f53a8be 100644 --- a/public/index.html +++ b/public/index.html @@ -322,9 +322,18 @@ pre.raw-line { background: var(--bs-tertiary-bg); padding: .75rem; border-radius @@ -629,7 +638,10 @@ async function loadAlerts() { ${esc(a.message)} ${esc(a.source_name || '—')} ${new Date(a.created_at).toLocaleString()} - ${a.status === 'open' ? `` : ''} + + ${a.status === 'open' ? `` : ''} + ${a.status !== 'resolved' ? `` : ''} + `).join(''); } const label = query ? 'search results' : 'alerts'; @@ -655,21 +667,30 @@ function showAlert(id) {
Message
${esc(a.message)}
Raw Line
${esc(a.raw_line)}
`; - document.getElementById('ackBtn').style.display = a.status === 'open' ? '' : 'none'; + document.getElementById('statusSelect').value = a.status; new bootstrap.Modal(document.getElementById('detailModal')).show(); } -document.getElementById('ackBtn').addEventListener('click', async () => { - if (currentAlertId) await ackAlert(currentAlertId); +document.getElementById('updateStatusBtn').addEventListener('click', async () => { + const newStatus = document.getElementById('statusSelect').value; + if (currentAlertId) await updateAlertStatus(currentAlertId, newStatus); bootstrap.Modal.getInstance(document.getElementById('detailModal')).hide(); }); -async function ackAlert(id) { +async function updateAlertStatus(id, status) { try { - await api(`/alerts/${id}/ack`, { method: 'POST' }); - toast('Alert #' + id + ' acknowledged'); + await api(`/alerts/${id}/status`, { method: 'POST', body: JSON.stringify({ status }) }); + toast('Alert #' + id + ' ' + status); loadPage(document.querySelector('.sidebar .nav-link.active')?.dataset.page || 'dashboard'); - } catch (e) { toast('Failed to acknowledge', 'danger'); } + } catch (e) { toast('Failed to update status', 'danger'); } +} + +async function quickAction(id, status) { + try { + await api(`/alerts/${id}/status`, { method: 'POST', body: JSON.stringify({ status }) }); + toast('Alert #' + id + ' ' + status); + loadPage(document.querySelector('.sidebar .nav-link.active')?.dataset.page || 'dashboard'); + } catch (e) { toast('Failed', 'danger'); } } document.getElementById('filterSeverity').addEventListener('change', loadAlerts); diff --git a/src/Api/Router.php b/src/Api/Router.php index 2fc6d4f..05cbdb8 100644 --- a/src/Api/Router.php +++ b/src/Api/Router.php @@ -70,6 +70,8 @@ class Router $path === '/alerts/search' && $method === 'GET' => $this->searchAlerts(), preg_match('#^/alerts/(\d+)/ack$#', $path, $m) && $method === 'POST' => $this->ackAlert((int) $m[1]), + preg_match('#^/alerts/(\d+)/status$#', $path, $m) && $method === 'POST' + => $this->updateAlertStatus((int) $m[1]), preg_match('#^/alerts/counts$#', $path) && $method === 'GET' => $this->repo->getAlertCounts(), @@ -198,6 +200,18 @@ class Router return ['status' => 'acknowledged', 'id' => $id]; } + private function updateAlertStatus(int $id): array + { + $body = json_decode(file_get_contents('php://input'), true); + $status = AlertStatus::tryFrom($body['status'] ?? ''); + if (!$status) { + http_response_code(400); + return ['error' => 'Invalid status. Use: open, acknowledged, resolved']; + } + $this->repo->updateAlertStatus($id, $status); + return ['status' => $status->value, 'id' => $id]; + } + private function searchAlerts(): array { $query = $_GET['q'] ?? '';