pub mod api;
pub mod error;
pub mod models;
pub mod state;
pub mod websocket;
use axum::{
routing::{get, post},
Router,
};
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::sync::RwLock;
use tower_http::cors::{Any, CorsLayer};
use tower_http::services::ServeDir;
use tower_http::trace::TraceLayer;
use tracing::info;
pub use error::{Result, WebError};
pub use models::*;
pub use state::AppState;
use api::{
create_memory, create_namespace, delete_memory, get_agent_stats, get_memory, get_namespace,
get_stats, health_check, list_memories, list_namespaces, search_memories, update_memory,
};
use websocket::websocket_handler;
pub struct WebDashboard {
router: Router,
state: Arc<RwLock<AppState>>,
}
impl WebDashboard {
pub async fn new(
storage: nexus_storage::StorageManager,
orchestrator: nexus_orchestrator::Orchestrator,
) -> Result<Self> {
let state = Arc::new(RwLock::new(AppState::new(storage, orchestrator).await?));
let router = Self::build_router(state.clone());
Ok(Self { router, state })
}
fn build_router(state: Arc<RwLock<AppState>>) -> Router {
let cors = CorsLayer::new()
.allow_origin(Any)
.allow_methods(Any)
.allow_headers(Any);
let api_routes = Router::new()
.route("/memories", get(list_memories).post(create_memory))
.route(
"/memories/:id",
get(get_memory).put(update_memory).delete(delete_memory),
)
.route("/memories/search", post(search_memories))
.route("/namespaces", get(list_namespaces).post(create_namespace))
.route("/namespaces/:id", get(get_namespace))
.route("/stats", get(get_stats))
.route("/stats/:agent", get(get_agent_stats))
.route("/health", get(health_check));
let ws_route = Router::new().route("/ws", get(websocket_handler));
Router::new()
.nest("/api", api_routes)
.merge(ws_route)
.fallback_service(ServeDir::new("src/static").append_index_html_on_directories(true))
.layer(cors)
.layer(TraceLayer::new_for_http())
.with_state(state)
}
pub async fn serve(self, addr: SocketAddr) -> Result<()> {
info!("Starting Nexus Web Dashboard on {}", addr);
let listener = tokio::net::TcpListener::bind(addr)
.await
.map_err(|e| WebError::ServerStart(e.to_string()))?;
info!("Web Dashboard listening on http://{}", addr);
axum::serve(listener, self.router)
.await
.map_err(|e| WebError::ServerStart(e.to_string()))?;
Ok(())
}
pub fn state(&self) -> Arc<RwLock<AppState>> {
self.state.clone()
}
}
pub async fn create_dashboard(
storage: nexus_storage::StorageManager,
orchestrator: nexus_orchestrator::Orchestrator,
) -> Result<WebDashboard> {
WebDashboard::new(storage, orchestrator).await
}
pub async fn run_default(
storage: nexus_storage::StorageManager,
orchestrator: nexus_orchestrator::Orchestrator,
) -> Result<()> {
let dashboard = WebDashboard::new(storage, orchestrator).await?;
let addr = SocketAddr::from(([0, 0, 0, 0], 8768));
dashboard.serve(addr).await
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_web_error_display() {
let err = WebError::ServerStart("test error".to_string());
assert!(err.to_string().contains("test error"));
}
}