weft_core/api/
services.rs1use crate::api::openai_compat::AppState;
2use axum::extract::{Path, State};
3use axum::http::StatusCode;
4use axum::response::IntoResponse;
5use axum::Json;
6
7fn service_request_url(name: &str, health_url: &str) -> String {
8 let base_url = health_url.trim_end_matches("/health");
9 let _ = name;
10 format!("{}/webhook", base_url)
11}
12
13fn service_request_body(name: &str, body: &serde_json::Value) -> serde_json::Value {
14 let _ = name;
15 body.clone()
16}
17
18pub async fn list_services(State(state): State<AppState>) -> Json<serde_json::Value> {
19 state.process_manager.run_health_checks().await;
20 let statuses = state.process_manager.all_statuses().await;
21 let service_names: Vec<String> = statuses.keys().cloned().collect();
22
23 let services: Vec<serde_json::Value> = service_names
24 .iter()
25 .filter_map(|name| {
26 let config = state.process_manager.service_config_sync(name)?;
27 let status = statuses
28 .get(name)
29 .map(|st| st.to_string())
30 .unwrap_or_else(|| "unknown".into());
31 Some(serde_json::json!({
32 "name": config.name,
33 "command": config.command,
34 "auto_start": config.auto_start,
35 "status": status,
36 }))
37 })
38 .collect();
39
40 Json(serde_json::json!({ "services": services }))
41}
42
43pub async fn proxy_webhook(
46 State(state): State<AppState>,
47 Path(name): Path<String>,
48 Json(body): Json<serde_json::Value>,
49) -> impl IntoResponse {
50 let health_url = match state
51 .process_manager
52 .service_config_sync(&name)
53 .and_then(|svc| svc.health_url)
54 {
55 Some(u) => u.clone(),
56 None => {
57 return (
58 StatusCode::NOT_FOUND,
59 Json(serde_json::json!({ "error": format!("Service '{}' not found or has no health_url", name) })),
60 )
61 .into_response();
62 }
63 };
64
65 let request_url = service_request_url(&name, &health_url);
66 let request_body = service_request_body(&name, &body);
67
68 let client = reqwest::Client::new();
70 match client
71 .post(&request_url)
72 .json(&request_body)
73 .timeout(std::time::Duration::from_secs(300))
74 .send()
75 .await
76 {
77 Ok(resp) => {
78 let status = resp.status();
79 match resp.text().await {
80 Ok(text) => {
81 let axum_status = StatusCode::from_u16(status.as_u16())
82 .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
83 (axum_status, text).into_response()
84 }
85 Err(e) => (
86 StatusCode::BAD_GATEWAY,
87 Json(serde_json::json!({ "error": format!("Failed to read response: {}", e) })),
88 )
89 .into_response(),
90 }
91 }
92 Err(e) => (
93 StatusCode::BAD_GATEWAY,
94 Json(serde_json::json!({ "error": format!("Failed to proxy webhook: {}", e) })),
95 )
96 .into_response(),
97 }
98}
99
100pub async fn start_service(
101 State(state): State<AppState>,
102 Path(name): Path<String>,
103) -> impl IntoResponse {
104 match state.process_manager.start(&name).await {
105 Ok(()) => Json(serde_json::json!({ "ok": true })).into_response(),
106 Err(e) => (
107 StatusCode::BAD_REQUEST,
108 Json(serde_json::json!({ "error": e.to_string() })),
109 )
110 .into_response(),
111 }
112}
113
114pub async fn stop_service(
115 State(state): State<AppState>,
116 Path(name): Path<String>,
117) -> impl IntoResponse {
118 match state.process_manager.stop(&name).await {
119 Ok(()) => Json(serde_json::json!({ "ok": true })).into_response(),
120 Err(e) => (
121 StatusCode::BAD_REQUEST,
122 Json(serde_json::json!({ "error": e.to_string() })),
123 )
124 .into_response(),
125 }
126}
127
128pub async fn restart_service(
129 State(state): State<AppState>,
130 Path(name): Path<String>,
131) -> impl IntoResponse {
132 match state.process_manager.restart(&name).await {
133 Ok(()) => Json(serde_json::json!({ "ok": true })).into_response(),
134 Err(e) => (
135 StatusCode::BAD_REQUEST,
136 Json(serde_json::json!({ "error": e.to_string() })),
137 )
138 .into_response(),
139 }
140}