//! Remaining single-endpoint dashboard handlers: mark-all-read, //! spawn-request and hive-wide turn stats. use axum::{ extract::{Path as AxumPath, State}, http::StatusCode, response::{IntoResponse, Response}, }; use serde::{Deserialize, Serialize}; use utoipa::{IntoParams, ToSchema}; use super::{AppState, Ident, error_response}; use crate::hive_stats::HiveStats; #[derive(Deserialize, IntoParams)] pub(super) struct StatsHiveQuery { /// Stats window; defaults to `24h`. window: Option, } /// Hive-wide turn-stats rollup for the dashboard swarm-stats view. /// /// Aggregates every agent's `hyperhive-turn-stats.sqlite` read-only /// (skips missing/unreadable ones). #[utoipa::path( get, path = "/api/stats-hive", params(StatsHiveQuery), responses((status = 200, description = "hive-wide turn-stats rollup", body = HiveStats)), tag = "misc_api" )] pub(super) async fn api_stats_hive( State(state): State, axum::extract::Query(q): axum::extract::Query, ) -> Response { let window = crate::hive_stats::Window::parse(q.window.as_deref().unwrap_or("24h")); axum::Json(crate::hive_stats::hive_snapshot( window, &state.coord.model_prices, )) .into_response() } #[derive(Serialize, ToSchema)] pub(super) struct MarkAllReadBody { marked: u64, } /// Operator-driven "clear this agent's inbox" — backs the side-panel /// "mark all read" button. /// /// Marks every message addressed to the agent as acked (backfilling /// `delivered_at` for any still-pending rows so vacuum can collect /// them). `marked` lets the frontend show "cleared N messages" /// feedback without an extra fetch. #[utoipa::path( post, path = "/api/agent/{name}/mark-all-read", params(("name" = String, Path, description = "agent name")), responses( (status = 200, description = "count of messages marked read", body = MarkAllReadBody), (status = 400, description = "bad agent name"), (status = 500, description = "broker write failed"), ), tag = "misc_api" )] pub(super) async fn post_mark_all_read( State(state): State, AxumPath(name): AxumPath, ) -> Response { let name = match Ident::parse(&name) { Ok(n) => n, Err(reason) => { return (StatusCode::BAD_REQUEST, format!("bad agent name: {reason}")).into_response(); } }; match state.coord.broker.mark_all_read(name.as_str()) { Ok(marked) => { tracing::info!(%name, marked, "operator marked all messages read"); axum::Json(MarkAllReadBody { marked }).into_response() } Err(e) => error_response(&format!("mark-all-read {name} failed: {e:#}")), } }