featherbit/admin/
status.rs1use std::sync::Arc;
5
6use axum::extract::State;
7use axum::http::StatusCode;
8use axum::response::IntoResponse;
9use axum::routing::{get, post};
10use axum::{Json, Router};
11
12use crate::state::SharedState;
13
14pub fn router() -> Router<Arc<SharedState>> {
17 Router::new()
18 .route("/healthz", get(healthz))
19 .route("/readyz", get(readyz))
20 .route("/api/status", get(status))
21 .route("/api/config/export", get(export_config))
22 .route("/api/config/reload", post(reload_config))
23 .route("/metrics", get(metrics))
24}
25
26async fn healthz() -> impl IntoResponse {
29 (
30 StatusCode::OK,
31 Json(serde_json::json!({"status": "healthy"})),
32 )
33}
34
35async fn readyz(State(state): State<Arc<SharedState>>) -> impl IntoResponse {
40 let routes = state.routes.read().await;
41 if routes.is_empty() {
42 (
43 StatusCode::SERVICE_UNAVAILABLE,
44 Json(serde_json::json!({"status": "not_ready", "reason": "no routes loaded"})),
45 )
46 } else {
47 (
48 StatusCode::OK,
49 Json(serde_json::json!({"status": "ready", "routes": routes.len()})),
50 )
51 }
52}
53
54async fn status(State(state): State<Arc<SharedState>>) -> impl IntoResponse {
60 let routes = state.routes.read().await;
61 let gw = state.gateway.read().await;
62 Json(serde_json::json!({
63 "version": env!("CARGO_PKG_VERSION"),
64 "routes": routes.len(),
65 "policies": gw.policies.len(),
66 }))
67}
68
69async fn export_config(State(state): State<Arc<SharedState>>) -> impl IntoResponse {
80 let gw = state.gateway.read().await;
81 match serde_yaml::to_string(&*gw) {
82 Ok(yaml) => (
83 StatusCode::OK,
84 [("content-type", "text/yaml; charset=utf-8")],
85 yaml,
86 )
87 .into_response(),
88 Err(e) => (
89 StatusCode::INTERNAL_SERVER_ERROR,
90 Json(serde_json::json!({"error": format!("failed to serialize config: {}", e)})),
91 )
92 .into_response(),
93 }
94}
95
96async fn metrics(State(state): State<Arc<SharedState>>) -> impl IntoResponse {
100 (
101 StatusCode::OK,
102 [("content-type", "text/plain; charset=utf-8")],
103 state.metrics.render(),
104 )
105}
106
107async fn reload_config(State(state): State<Arc<SharedState>>) -> impl IntoResponse {
114 match state.reload_from_disk().await {
115 Ok(_) => (
116 StatusCode::OK,
117 Json(serde_json::json!({"status": "reloaded"})),
118 )
119 .into_response(),
120 Err(e) => (
121 StatusCode::INTERNAL_SERVER_ERROR,
122 Json(serde_json::json!({"error": e})),
123 )
124 .into_response(),
125 }
126}