use axum::{extract::State, response::Html, routing::get, Router}; use chrono::Local; use std::sync::Arc; #[cfg(feature = "monitoring")] use sysinfo::{Disks, Networks, System}; use crate::core::urls::ApiUrls; use crate::shared::state::AppState; pub mod real_time; pub mod tracing; pub fn configure() -> Router> { Router::new() .route(ApiUrls::MONITORING_DASHBOARD, get(dashboard)) .route(ApiUrls::MONITORING_SERVICES, get(services)) .route(ApiUrls::MONITORING_RESOURCES, get(resources)) .route(ApiUrls::MONITORING_LOGS, get(logs)) .route(ApiUrls::MONITORING_LLM, get(llm_metrics)) .route(ApiUrls::MONITORING_HEALTH, get(health)) // Additional endpoints expected by the frontend .route("/api/ui/monitoring/timestamp", get(timestamp)) .route("/api/ui/monitoring/bots", get(bots)) .route("/api/ui/monitoring/services/status", get(services_status)) .route("/api/ui/monitoring/resources/bars", get(resources_bars)) .route("/api/ui/monitoring/activity/latest", get(activity_latest)) .route("/api/ui/monitoring/metric/sessions", get(metric_sessions)) .route("/api/ui/monitoring/metric/messages", get(metric_messages)) .route("/api/ui/monitoring/metric/response_time", get(metric_response_time)) .route("/api/ui/monitoring/trend/sessions", get(trend_sessions)) .route("/api/ui/monitoring/rate/messages", get(rate_messages)) // Aliases for frontend compatibility .route("/api/ui/monitoring/sessions", get(sessions_panel)) .route("/api/ui/monitoring/messages", get(messages_panel)) } async fn dashboard(State(state): State>) -> Html { #[cfg(feature = "monitoring")] let (cpu_usage, total_memory, used_memory, memory_percent, uptime_str) = { let mut sys = System::new_all(); sys.refresh_all(); let cpu_usage = sys.global_cpu_usage(); let total_memory = sys.total_memory(); let used_memory = sys.used_memory(); let memory_percent = if total_memory > 0 { (used_memory as f64 / total_memory as f64) * 100.0 } else { 0.0 }; let uptime = System::uptime(); let uptime_str = format_uptime(uptime); (cpu_usage, total_memory, used_memory, memory_percent, uptime_str) }; #[cfg(not(feature = "monitoring"))] let (cpu_usage, total_memory, used_memory, memory_percent, uptime_str) = ( 0.0, 0, 0, 0.0, "N/A".to_string() ); let active_sessions = state .session_manager .try_lock() .map(|sm| sm.active_count()) .unwrap_or(0); Html(format!( r##"
CPU Usage {cpu_usage:.1}%
{cpu_usage:.1}%
Memory {memory_percent:.1}%
{used_gb:.1} GB / {total_gb:.1} GB
Active Sessions
{active_sessions}
Current conversations
Uptime
{uptime_str}
System running time
Auto-refreshing
"##, cpu_status = if cpu_usage > 80.0 { "danger" } else if cpu_usage > 60.0 { "warning" } else { "success" }, mem_status = if memory_percent > 80.0 { "danger" } else if memory_percent > 60.0 { "warning" } else { "success" }, used_gb = used_memory as f64 / 1_073_741_824.0, total_gb = total_memory as f64 / 1_073_741_824.0, )) } async fn services(State(_state): State>) -> Html { let services = vec![ ("PostgreSQL", check_postgres(), "Database"), ("Redis", check_redis(), "Cache"), ("MinIO", check_minio(), "Storage"), ("LLM Server", check_llm(), "AI Backend"), ]; let mut rows = String::new(); for (name, status, desc) in services { let (status_class, status_text) = if status { ("success", "Running") } else { ("danger", "Stopped") }; rows.push_str(&format!( r##"
{name}
{desc} {status_text} "##, name_lower = name.to_lowercase().replace(' ', "-"), )); } Html(format!( r##"

Services Status

{rows}
Service Description Status Actions
"## )) } async fn resources(State(_state): State>) -> Html { #[cfg(feature = "monitoring")] let (disk_rows, net_rows) = { let mut sys = System::new_all(); sys.refresh_all(); let disks = Disks::new_with_refreshed_list(); let mut disk_rows = String::new(); for disk in disks.list() { let total = disk.total_space(); let available = disk.available_space(); let used = total - available; let percent = if total > 0 { (used as f64 / total as f64) * 100.0 } else { 0.0 }; disk_rows.push_str(&format!( r##" {mount} {used_gb:.1} GB {total_gb:.1} GB
{percent:.1}% "##, mount = disk.mount_point().display(), used_gb = used as f64 / 1_073_741_824.0, total_gb = total as f64 / 1_073_741_824.0, status = if percent > 90.0 { "danger" } else if percent > 70.0 { "warning" } else { "success" }, )); } let networks = Networks::new_with_refreshed_list(); let mut net_rows = String::new(); for (name, data) in networks.list() { net_rows.push_str(&format!( r##" {name} {rx:.2} MB {tx:.2} MB "##, rx = data.total_received() as f64 / 1_048_576.0, tx = data.total_transmitted() as f64 / 1_048_576.0, )); } (disk_rows, net_rows) }; #[cfg(not(feature = "monitoring"))] let (disk_rows, net_rows) = ( String::new(), String::new() ); Html(format!( r##"

System Resources

Disk Usage

{disk_rows}
Mount Used Total Usage

Network

{net_rows}
Interface Received Transmitted
"## )) } async fn logs(State(_state): State>) -> Html { Html( r##"

System Logs

System ready INFO Monitoring initialized
"## .to_string(), ) } async fn llm_metrics(State(_state): State>) -> Html { Html( r##"

LLM Metrics

Total Requests
--
Cache Hit Rate
--
Avg Latency
--
Total Tokens
--
"## .to_string(), ) } async fn health(State(state): State>) -> Html { let db_ok = state.conn.get().is_ok(); let status = if db_ok { "healthy" } else { "degraded" }; Html(format!( r##"
{status} "## )) } fn format_uptime(seconds: u64) -> String { let days = seconds / 86400; let hours = (seconds % 86400) / 3600; let minutes = (seconds % 3600) / 60; if days > 0 { format!("{}d {}h {}m", days, hours, minutes) } else if hours > 0 { format!("{}h {}m", hours, minutes) } else { format!("{}m", minutes) } } fn check_postgres() -> bool { true } fn check_redis() -> bool { true } fn check_minio() -> bool { true } fn check_llm() -> bool { true } async fn timestamp(State(_state): State>) -> Html { let now = Local::now(); Html(format!("Last updated: {}", now.format("%H:%M:%S"))) } async fn bots(State(state): State>) -> Html { let active_sessions = state .session_manager .try_lock() .map(|sm| sm.active_count()) .unwrap_or(0); Html(format!( r##"
Active Sessions {active_sessions}
"## )) } async fn services_status(State(_state): State>) -> Html { let services = vec![ ("postgresql", check_postgres()), ("redis", check_redis()), ("minio", check_minio()), ("llm", check_llm()), ]; let mut status_updates = String::new(); for (name, running) in services { let status = if running { "running" } else { "stopped" }; status_updates.push_str(&format!( r##""## )); } Html(status_updates) } async fn resources_bars(State(_state): State>) -> Html { #[cfg(feature = "monitoring")] let (cpu_usage, memory_percent) = { let mut sys = System::new_all(); sys.refresh_all(); let cpu_usage = sys.global_cpu_usage(); let total_memory = sys.total_memory(); let used_memory = sys.used_memory(); let memory_percent = if total_memory > 0 { (used_memory as f64 / total_memory as f64) * 100.0 } else { 0.0 }; (cpu_usage, memory_percent) }; #[cfg(not(feature = "monitoring"))] let (cpu_usage, memory_percent): (f32, f32) = (0.0, 0.0); Html(format!( r##" CPU {cpu_usage:.0}% MEM {memory_percent:.0}% "##, cpu_width = cpu_usage.min(100.0f32), mem_width = memory_percent.min(100.0f32), )) } async fn activity_latest(State(_state): State>) -> Html { Html("System monitoring active...".to_string()) } async fn metric_sessions(State(state): State>) -> Html { let active_sessions = state .session_manager .try_lock() .map(|sm| sm.active_count()) .unwrap_or(0); Html(active_sessions.to_string()) } async fn metric_messages(State(_state): State>) -> Html { Html("--".to_string()) } async fn metric_response_time(State(_state): State>) -> Html { Html("--".to_string()) } async fn trend_sessions(State(_state): State>) -> Html { Html("↑ 0%".to_string()) } async fn rate_messages(State(_state): State>) -> Html { Html("0/hr".to_string()) } async fn sessions_panel(State(state): State>) -> Html { let active_sessions = state .session_manager .try_lock() .map(|sm| sm.active_count()) .unwrap_or(0); Html(format!( r##"

Active Sessions

{active_sessions}

No active sessions

"## )) } async fn messages_panel(State(_state): State>) -> Html { Html( r##"

Recent Messages

No recent messages

"## .to_string(), ) }