1use super::ApiResponse;
124use hammerwork::queue::DatabaseQueue;
125use serde::{Deserialize, Serialize};
126use std::sync::Arc;
127use warp::{Filter, Reply};
128
129#[derive(Debug, Serialize)]
131pub struct SystemInfo {
132 pub version: String,
133 pub build_info: BuildInfo,
134 pub runtime_info: RuntimeInfo,
135 pub database_info: DatabaseInfo,
136 pub features: Vec<String>,
137 pub uptime_seconds: u64,
138 pub started_at: chrono::DateTime<chrono::Utc>,
139}
140
141#[derive(Debug, Serialize)]
143pub struct BuildInfo {
144 pub version: String,
145 pub git_commit: Option<String>,
146 pub build_date: Option<String>,
147 pub rust_version: String,
148 pub target_triple: String,
149}
150
151#[derive(Debug, Serialize)]
153pub struct RuntimeInfo {
154 pub process_id: u32,
155 pub memory_usage_bytes: Option<u64>,
156 pub cpu_usage_percent: Option<f64>,
157 pub thread_count: Option<usize>,
158 pub gc_collections: Option<u64>,
159}
160
161#[derive(Debug, Serialize)]
163pub struct DatabaseInfo {
164 pub database_type: String,
165 pub connection_url: String, pub pool_size: u32,
167 pub active_connections: Option<u32>,
168 pub connection_health: bool,
169 pub last_migration: Option<String>,
170}
171
172#[derive(Debug, Serialize)]
174pub struct ServerConfig {
175 pub bind_address: String,
176 pub port: u16,
177 pub authentication_enabled: bool,
178 pub cors_enabled: bool,
179 pub websocket_max_connections: usize,
180 pub static_assets_path: String,
181}
182
183#[derive(Debug, Serialize)]
185pub struct MetricsInfo {
186 pub prometheus_enabled: bool,
187 pub metrics_endpoint: String,
188 pub custom_metrics_count: u32,
189 pub last_scrape: Option<chrono::DateTime<chrono::Utc>>,
190}
191
192#[derive(Debug, Deserialize)]
194pub struct ConfigUpdateRequest {
195 pub setting: String,
196 pub value: serde_json::Value,
197}
198
199#[derive(Debug, Deserialize)]
201pub struct MaintenanceRequest {
202 pub operation: String, pub target: Option<String>, pub dry_run: Option<bool>,
205}
206
207pub fn routes<T>(
209 queue: Arc<T>,
210) -> impl Filter<Extract = impl Reply, Error = warp::Rejection> + Clone
211where
212 T: DatabaseQueue + Send + Sync + 'static,
213{
214 let queue_filter = warp::any().map(move || queue.clone());
215
216 let info = warp::path("system")
217 .and(warp::path("info"))
218 .and(warp::path::end())
219 .and(warp::get())
220 .and(queue_filter.clone())
221 .and_then(system_info_handler);
222
223 let config = warp::path("system")
224 .and(warp::path("config"))
225 .and(warp::path::end())
226 .and(warp::get())
227 .and_then(system_config_handler);
228
229 let metrics_info = warp::path("system")
230 .and(warp::path("metrics"))
231 .and(warp::path::end())
232 .and(warp::get())
233 .and_then(metrics_info_handler);
234
235 let maintenance = warp::path("system")
236 .and(warp::path("maintenance"))
237 .and(warp::path::end())
238 .and(warp::post())
239 .and(queue_filter.clone())
240 .and(warp::body::json())
241 .and_then(maintenance_handler);
242
243 let version = warp::path("version")
244 .and(warp::path::end())
245 .and(warp::get())
246 .and_then(version_handler);
247
248 info.or(config).or(metrics_info).or(maintenance).or(version)
249}
250
251async fn system_info_handler<T>(queue: Arc<T>) -> Result<impl Reply, warp::Rejection>
253where
254 T: DatabaseQueue + Send + Sync,
255{
256 let database_healthy = queue.get_all_queue_stats().await.is_ok();
258
259 let build_info = BuildInfo {
260 version: env!("CARGO_PKG_VERSION").to_string(),
261 git_commit: option_env!("GIT_COMMIT").map(|s| s.to_string()),
262 build_date: option_env!("BUILD_DATE").map(|s| s.to_string()),
263 rust_version: get_rust_version(),
264 target_triple: get_target_triple(),
265 };
266
267 let runtime_info = RuntimeInfo {
268 process_id: std::process::id(),
269 memory_usage_bytes: get_memory_usage(),
270 cpu_usage_percent: None, thread_count: None, gc_collections: None, };
274
275 let database_info = DatabaseInfo {
276 database_type: "PostgreSQL/MySQL".to_string(), connection_url: "***masked***".to_string(),
278 pool_size: 5, active_connections: None, connection_health: database_healthy,
281 last_migration: None, };
283
284 let features = vec![
285 #[cfg(feature = "postgres")]
286 "postgres".to_string(),
287 #[cfg(feature = "mysql")]
288 "mysql".to_string(),
289 #[cfg(feature = "auth")]
290 "auth".to_string(),
291 ];
292
293 let system_info = SystemInfo {
294 version: env!("CARGO_PKG_VERSION").to_string(),
295 build_info,
296 runtime_info,
297 database_info,
298 features,
299 uptime_seconds: 0, started_at: chrono::Utc::now(), };
302
303 Ok(warp::reply::json(&ApiResponse::success(system_info)))
304}
305
306async fn system_config_handler() -> Result<impl Reply, warp::Rejection> {
308 let config = ServerConfig {
309 bind_address: "127.0.0.1".to_string(), port: 8080, authentication_enabled: false, cors_enabled: false, websocket_max_connections: 100, static_assets_path: "./assets".to_string(), };
316
317 Ok(warp::reply::json(&ApiResponse::success(config)))
318}
319
320async fn metrics_info_handler() -> Result<impl Reply, warp::Rejection> {
322 let metrics_info = MetricsInfo {
323 prometheus_enabled: true, metrics_endpoint: "/metrics".to_string(),
325 custom_metrics_count: 0, last_scrape: None, };
328
329 Ok(warp::reply::json(&ApiResponse::success(metrics_info)))
330}
331
332async fn maintenance_handler<T>(
334 queue: Arc<T>,
335 request: MaintenanceRequest,
336) -> Result<impl Reply, warp::Rejection>
337where
338 T: DatabaseQueue + Send + Sync,
339{
340 let dry_run = request.dry_run.unwrap_or(false);
341
342 match request.operation.as_str() {
343 "cleanup" => {
344 if dry_run {
345 let response = ApiResponse::success(serde_json::json!({
346 "operation": "cleanup",
347 "dry_run": true,
348 "message": "Dry run: Would clean up old completed and dead jobs",
349 "estimated_deletions": 0
350 }));
351 Ok(warp::reply::json(&response))
352 } else {
353 let older_than = chrono::Utc::now() - chrono::Duration::days(7); match queue.purge_dead_jobs(older_than).await {
356 Ok(count) => {
357 let response = ApiResponse::success(serde_json::json!({
358 "operation": "cleanup",
359 "dry_run": false,
360 "message": format!("Cleaned up {} dead jobs", count),
361 "deletions": count
362 }));
363 Ok(warp::reply::json(&response))
364 }
365 Err(e) => {
366 let response = ApiResponse::<()>::error(format!("Cleanup failed: {}", e));
367 Ok(warp::reply::json(&response))
368 }
369 }
370 }
371 }
372 "vacuum" => {
373 let response =
375 ApiResponse::<()>::error("Vacuum operation not yet implemented".to_string());
376 Ok(warp::reply::json(&response))
377 }
378 "reindex" => {
379 let response =
381 ApiResponse::<()>::error("Reindex operation not yet implemented".to_string());
382 Ok(warp::reply::json(&response))
383 }
384 "optimize" => {
385 let response =
387 ApiResponse::<()>::error("Optimize operation not yet implemented".to_string());
388 Ok(warp::reply::json(&response))
389 }
390 _ => {
391 let response = ApiResponse::<()>::error(format!(
392 "Unknown maintenance operation: {}",
393 request.operation
394 ));
395 Ok(warp::reply::json(&response))
396 }
397 }
398}
399
400async fn version_handler() -> Result<impl Reply, warp::Rejection> {
402 let version_info = serde_json::json!({
403 "version": env!("CARGO_PKG_VERSION"),
404 "name": env!("CARGO_PKG_NAME"),
405 "description": env!("CARGO_PKG_DESCRIPTION"),
406 "authors": env!("CARGO_PKG_AUTHORS").split(':').collect::<Vec<_>>(),
407 "repository": env!("CARGO_PKG_REPOSITORY"),
408 "license": env!("CARGO_PKG_LICENSE"),
409 "rust_version": get_rust_version(),
410 "build_target": get_target_triple(),
411 });
412
413 Ok(warp::reply::json(&ApiResponse::success(version_info)))
414}
415
416fn get_rust_version() -> String {
418 option_env!("RUSTC_VERSION")
419 .unwrap_or("unknown")
420 .to_string()
421}
422
423fn get_target_triple() -> String {
425 std::env::consts::ARCH.to_string() + "-" + std::env::consts::OS
426}
427
428fn get_memory_usage() -> Option<u64> {
430 #[cfg(target_os = "linux")]
431 {
432 use std::fs;
433 if let Ok(contents) = fs::read_to_string("/proc/self/status") {
434 for line in contents.lines() {
435 if line.starts_with("VmRSS:") {
436 if let Some(kb) = line
437 .split_whitespace()
438 .nth(1)
439 .and_then(|s| s.parse::<u64>().ok())
440 {
441 return Some(kb * 1024); }
443 }
444 }
445 }
446 }
447
448 #[cfg(target_os = "macos")]
449 {
450 }
453
454 #[cfg(target_os = "windows")]
455 {
456 }
459
460 None
461}
462
463#[cfg(test)]
464mod tests {
465 use super::*;
466
467 #[test]
468 fn test_maintenance_request_deserialization() {
469 let json = r#"{
470 "operation": "cleanup",
471 "target": "completed_jobs",
472 "dry_run": true
473 }"#;
474
475 let request: MaintenanceRequest = serde_json::from_str(json).unwrap();
476 assert_eq!(request.operation, "cleanup");
477 assert_eq!(request.target, Some("completed_jobs".to_string()));
478 assert_eq!(request.dry_run, Some(true));
479 }
480
481 #[test]
482 fn test_build_info_creation() {
483 let build_info = BuildInfo {
484 version: "1.0.0".to_string(),
485 git_commit: Some("abc123".to_string()),
486 build_date: Some("2024-01-01".to_string()),
487 rust_version: "1.70.0".to_string(),
488 target_triple: "x86_64-unknown-linux-gnu".to_string(),
489 };
490
491 let json = serde_json::to_string(&build_info).unwrap();
492 assert!(json.contains("1.0.0"));
493 assert!(json.contains("abc123"));
494 }
495
496 #[test]
497 fn test_get_rust_version() {
498 let version = get_rust_version();
499 assert!(!version.is_empty());
500 }
501
502 #[test]
503 fn test_get_target_triple() {
504 let target = get_target_triple();
505 assert!(!target.is_empty());
506 assert!(target.contains("-"));
507 }
508}