greentic_runner_host/http/
admin.rs1use axum::Json;
2use axum::extract::State;
3use axum::http::StatusCode;
4use axum::response::IntoResponse;
5use serde_json::json;
6use time::format_description::well_known::Rfc3339;
7
8use crate::http::auth::AdminGuard;
9use crate::runner::ServerState;
10
11pub async fn status(AdminGuard: AdminGuard, State(state): State<ServerState>) -> impl IntoResponse {
12 let snapshot = state.active.snapshot();
13 let tenants = snapshot
14 .iter()
15 .map(|(tenant, runtime)| {
16 let pack = runtime.pack();
17 let metadata = pack.metadata();
18 let overlays = runtime
19 .overlays()
20 .into_iter()
21 .zip(runtime.overlay_digests().into_iter())
22 .map(|(overlay, digest)| {
23 let meta = overlay.metadata();
24 json!({
25 "pack_id": meta.pack_id,
26 "version": meta.version,
27 "digest": digest,
28 })
29 })
30 .collect::<Vec<_>>();
31 json!({
32 "tenant": tenant,
33 "pack_id": metadata.pack_id,
34 "version": metadata.version,
35 "digest": runtime.digest(),
36 "overlays": overlays,
37 })
38 })
39 .collect::<Vec<_>>();
40
41 let health = state.health.snapshot();
42 let last_reload = health.last_reload.and_then(|ts| ts.format(&Rfc3339).ok());
43
44 Json(json!({
45 "tenants": tenants,
46 "active": snapshot.len(),
47 "last_reload": last_reload,
48 "last_error": health.last_error,
49 }))
50}
51
52pub async fn reload(AdminGuard: AdminGuard, State(state): State<ServerState>) -> impl IntoResponse {
53 if let Some(handle) = &state.reload {
54 match handle.trigger().await {
55 Ok(()) => {
56 tracing::info!("pack.reload.requested");
57 (
58 StatusCode::ACCEPTED,
59 Json(json!({ "status": "reload requested" })),
60 )
61 }
62 Err(err) => {
63 tracing::warn!(error = %err, "reload trigger failed");
64 (
65 StatusCode::INTERNAL_SERVER_ERROR,
66 Json(json!({ "error": err.to_string() })),
67 )
68 }
69 }
70 } else {
71 (
72 StatusCode::NOT_IMPLEMENTED,
73 Json(json!({ "error": "reload handle unavailable" })),
74 )
75 }
76}