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'] ?? '';