feat(alerts): always-visible dismiss/ack actions + Dismiss all (service, endpoint, UI)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 17:53:12 +02:00
parent 13efe177a5
commit 6c4490f653
7 changed files with 180 additions and 10 deletions

View File

@@ -120,6 +120,42 @@ export async function dismissAlert(alertId: string, portId: string, userId: stri
}
}
/**
* Bulk-dismiss every open (non-dismissed, non-resolved) alert for a port,
* optionally narrowed to a single rule and/or severity. Returns the count
* dismissed. Port-scoped so it can never touch another tenant's alerts.
*/
export async function dismissAllForPort(
portId: string,
userId: string,
filter: { ruleId?: AlertRuleId; severity?: AlertSeverity } = {},
): Promise<number> {
const conds = [eq(alerts.portId, portId), isNull(alerts.dismissedAt), isNull(alerts.resolvedAt)];
if (filter.ruleId) conds.push(eq(alerts.ruleId, filter.ruleId));
if (filter.severity) conds.push(eq(alerts.severity, filter.severity));
const rows = await db
.update(alerts)
.set({ dismissedAt: sql`now()`, dismissedBy: userId })
.where(and(...conds))
.returning({ id: alerts.id });
for (const r of rows) {
emitToRoom(`port:${portId}`, 'alert:dismissed', { alertId: r.id, portId });
}
if (rows.length > 0) {
void createAuditLog({
portId,
userId,
action: 'update',
entityType: 'alert',
entityId: portId, // port-wide bulk action — no single alert subject
metadata: { kind: 'dismiss_all', count: rows.length, filter },
});
}
return rows.length;
}
export async function acknowledgeAlert(
alertId: string,
portId: string,