From 962259a8d6bd122503103dff59e843b584439e5e Mon Sep 17 00:00:00 2001 From: atlas Date: Sat, 13 Jun 2026 13:49:54 +0200 Subject: [PATCH] audit-log: expose total count in /api/audit-log response Per iris's dashboard-side ask: return { entries, total } instead of a bare array so the UI can show 'latest 500 of N' rather than silently capping at the clamp. Adds AuditLog::count_total(). --- hive-c0re/src/audit_log.rs | 9 +++++++++ hive-c0re/src/dashboard.rs | 19 ++++++++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/hive-c0re/src/audit_log.rs b/hive-c0re/src/audit_log.rs index 9fd4382c..9b098b33 100644 --- a/hive-c0re/src/audit_log.rs +++ b/hive-c0re/src/audit_log.rs @@ -168,6 +168,14 @@ impl AuditLog { Ok(out) } + /// Total row count, regardless of the `list_recent` clamp. Lets the + /// dashboard show "latest N of TOTAL" instead of silently capping. + pub fn count_total(&self) -> Result { + let conn = self.conn.lock().unwrap(); + let n: i64 = conn.query_row("SELECT COUNT(*) FROM audit_log", [], |r| r.get(0))?; + Ok(n) + } + /// Drop rows older than the retention window. Returns the number of /// rows deleted. Called from the hourly vacuum loop. pub fn vacuum(&self) -> Result { @@ -255,6 +263,7 @@ mod tests { assert!(rows[1].detail.is_none()); assert_eq!(rows[0].agent, "atlas"); assert_eq!(rows[0].action, "restart_infra"); + assert_eq!(db.count_total().expect("count"), 2); } #[test] diff --git a/hive-c0re/src/dashboard.rs b/hive-c0re/src/dashboard.rs index 30a889ee..be68a6db 100644 --- a/hive-c0re/src/dashboard.rs +++ b/hive-c0re/src/dashboard.rs @@ -1315,12 +1315,21 @@ async fn api_container_resources() -> Response { /// `GET /api/audit-log` — most-recent agent-initiated privileged-action /// audit entries, newest first (server-clamped to 500). Backs the -/// operator dashboard's audit view. Returns `Vec` JSON. +/// operator dashboard's audit view. Returns +/// `{ "entries": [AuditEntry…], "total": N }` so the UI can show +/// "latest 500 of N" rather than silently capping. `ts_unix` is in +/// **seconds**. async fn api_audit_log(State(state): State) -> Response { - match state.coord.audit_log.list_recent(500) { - Ok(rows) => axum::Json(rows).into_response(), - Err(e) => error_response(&format!("audit-log: {e:#}")), - } + const LIMIT: usize = 500; + let entries = match state.coord.audit_log.list_recent(LIMIT) { + Ok(rows) => rows, + Err(e) => return error_response(&format!("audit-log: {e:#}")), + }; + let total = match state.coord.audit_log.count_total() { + Ok(n) => n, + Err(e) => return error_response(&format!("audit-log count: {e:#}")), + }; + axum::Json(serde_json::json!({ "entries": entries, "total": total })).into_response() } /// Validate that a path-param agent name conforms to the hyperhive