use super::web_dashboard_state::DashboardState;
use super::web_dashboard_types::{AnalysisQuery, StorageOperation};
use axum::{
extract::{Query, State},
http::StatusCode,
response::{Html, IntoResponse},
routing::{get, post},
Json, Router,
};
use serde_json::json;
use std::path::PathBuf;
use std::time::{Duration, SystemTime};
use tower::ServiceBuilder;
use tower_http::{
cors::{Any, CorsLayer},
trace::TraceLayer,
};
use tracing::{debug, error, info};
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn create_dashboard_router(state: DashboardState) -> Router {
Router::new()
.route("/", get(dashboard_index))
.route("/dashboard", get(dashboard_index))
.route("/api/metrics", get(get_metrics))
.route("/api/health", get(get_health))
.route("/api/storage/stats", get(get_storage_stats))
.route("/api/storage/operation", post(storage_operation))
.route("/api/analysis", get(run_analysis))
.route("/api/diagnostics", get(get_diagnostics))
.route("/api/events", get(metrics_stream))
.layer(
ServiceBuilder::new()
.layer(TraceLayer::new_for_http())
.layer(
CorsLayer::new()
.allow_origin(Any)
.allow_methods(Any)
.allow_headers(Any),
),
)
.with_state(state)
}
async fn dashboard_index() -> impl IntoResponse {
let html = include_str!("../../assets/dashboard.html");
Html(html)
}
async fn get_metrics(State(state): State<DashboardState>) -> impl IntoResponse {
if let Err(e) = state.update_metrics().await {
error!("Failed to update metrics: {}", e);
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({
"error": "Failed to update metrics"
})),
)
.into_response();
}
let metrics = state.metrics_cache.read().await.clone();
Json(metrics).into_response()
}
async fn get_health(State(state): State<DashboardState>) -> impl IntoResponse {
let metrics = state.metrics_cache.read().await;
Json(&metrics.health_status).into_response()
}
async fn get_storage_stats(State(state): State<DashboardState>) -> impl IntoResponse {
let stats = state.storage.get_statistics();
Json(json!({
"total_entries": stats.total_entries,
"cache_hit_ratio": 0.85, "compression_ratio": stats.compression_ratio,
"backend_type": stats.warm_backend,
"hot_memory_kb": stats.hot_memory_kb,
"hot_entries": stats.hot_entries,
"warm_entries": stats.warm_entries,
"cold_entries": stats.cold_entries,
"detailed": true
}))
.into_response()
}
async fn storage_operation(
State(state): State<DashboardState>,
Json(operation): Json<StorageOperation>,
) -> impl IntoResponse {
debug!("Executing storage operation: {}", operation.action);
match operation.action.as_str() {
"flush" => {
Json(json!({
"status": "completed",
"message": "Cache flushed successfully",
"action": "flush"
}))
.into_response()
}
"cleanup" => {
Json(json!({
"status": "completed",
"message": "Cleanup completed",
"action": "cleanup",
"entries_cleaned": 0
}))
.into_response()
}
"stats" => {
let stats = state.storage.get_statistics();
Json(json!({
"status": "completed",
"action": "stats",
"data": stats
}))
.into_response()
}
_ => (
StatusCode::BAD_REQUEST,
Json(json!({
"error": "Unsupported operation",
"supported": ["flush", "cleanup", "stats"]
})),
)
.into_response(),
}
}
async fn run_analysis(
State(state): State<DashboardState>,
Query(params): Query<AnalysisQuery>,
) -> impl IntoResponse {
info!("Running TDG analysis on: {}", params.path);
let path = PathBuf::from(params.path);
if !path.exists() {
return (
StatusCode::NOT_FOUND,
Json(json!({
"error": "File or path not found"
})),
)
.into_response();
}
match state.analyzer.analyze_file(&path).await {
Ok(score) => {
Json(json!({
"status": "completed",
"path": path.to_string_lossy(),
"score": score.total,
"grade": score.grade,
"confidence": score.confidence,
"language": score.language,
"analysis_time_ms": 50, "cached": false
}))
.into_response()
}
Err(e) => {
error!("Analysis failed for {}: {}", path.display(), e);
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({
"error": "Analysis failed",
"message": e.to_string()
})),
)
.into_response()
}
}
}
async fn get_diagnostics(State(state): State<DashboardState>) -> impl IntoResponse {
if let Err(e) = state.update_metrics().await {
error!("Failed to update diagnostics: {}", e);
}
let metrics = state.metrics_cache.read().await;
let storage_stats = state.storage.get_statistics();
Json(json!({
"timestamp": SystemTime::now(),
"components": {
"storage": {
"status": "healthy",
"metrics": storage_stats,
"backend": storage_stats.warm_backend
},
"performance": {
"status": if metrics.performance_stats.avg_analysis_time_ms < 500.0 { "healthy" } else { "warning" },
"avg_analysis_time_ms": metrics.performance_stats.avg_analysis_time_ms,
"active_operations": metrics.performance_stats.active_operations
},
"health": {
"overall": metrics.health_status.overall,
"issues": metrics.health_status.issues,
"recommendations": metrics.health_status.recommendations
}
}
})).into_response()
}
async fn metrics_stream(State(state): State<DashboardState>) -> impl IntoResponse {
let _ = state.update_metrics().await;
let metrics = state.metrics_cache.read().await.clone();
(
StatusCode::OK,
[
("Content-Type", "application/json"),
("Cache-Control", "no-cache"),
("Connection", "keep-alive"),
],
Json(json!({
"type": "metrics_update",
"data": metrics,
"timestamp": SystemTime::now()
})),
)
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub async fn start_dashboard_server(
addr: std::net::SocketAddr,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
info!("Starting TDG Dashboard server on {}", addr);
let state = DashboardState::new().await?;
let metrics_state = state.clone();
tokio::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(10));
loop {
interval.tick().await;
if let Err(e) = metrics_state.update_metrics().await {
error!("Background metrics update failed: {}", e);
}
}
});
let app = create_dashboard_router(state);
let listener = tokio::net::TcpListener::bind(&addr).await?;
info!("TDG Dashboard listening on http://{}", addr);
axum::serve(listener, app).await?;
Ok(())
}