2025-12-04 09:03:03 -03:00
|
|
|
//! UI Server module for BotUI
|
|
|
|
|
//!
|
|
|
|
|
//! Serves the web UI (suite, minimal) and handles API proxying.
|
2025-12-03 18:42:22 -03:00
|
|
|
|
|
|
|
|
use axum::{
|
|
|
|
|
extract::State,
|
|
|
|
|
http::StatusCode,
|
|
|
|
|
response::{Html, IntoResponse},
|
|
|
|
|
routing::get,
|
|
|
|
|
Router,
|
|
|
|
|
};
|
|
|
|
|
use log::error;
|
2025-12-04 09:33:31 -03:00
|
|
|
use std::{fs, path::PathBuf};
|
2025-12-03 18:42:22 -03:00
|
|
|
|
2025-12-04 09:33:31 -03:00
|
|
|
use crate::shared::AppState;
|
2025-12-03 18:42:22 -03:00
|
|
|
|
2025-12-04 09:33:31 -03:00
|
|
|
/// Serve the index page (minimal UI)
|
2025-12-03 18:42:22 -03:00
|
|
|
pub async fn index() -> impl IntoResponse {
|
|
|
|
|
serve_minimal().await
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-04 09:33:31 -03:00
|
|
|
/// Handler for minimal UI
|
2025-12-03 18:42:22 -03:00
|
|
|
pub async fn serve_minimal() -> impl IntoResponse {
|
|
|
|
|
match fs::read_to_string("ui/minimal/index.html") {
|
|
|
|
|
Ok(html) => (StatusCode::OK, [("content-type", "text/html")], Html(html)),
|
|
|
|
|
Err(e) => {
|
|
|
|
|
error!("Failed to load minimal UI: {}", e);
|
|
|
|
|
(
|
|
|
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
|
[("content-type", "text/plain")],
|
|
|
|
|
Html("Failed to load minimal interface".to_string()),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-04 09:33:31 -03:00
|
|
|
/// Handler for suite UI
|
2025-12-03 18:42:22 -03:00
|
|
|
pub async fn serve_suite() -> impl IntoResponse {
|
|
|
|
|
match fs::read_to_string("ui/suite/index.html") {
|
|
|
|
|
Ok(html) => (StatusCode::OK, [("content-type", "text/html")], Html(html)),
|
|
|
|
|
Err(e) => {
|
|
|
|
|
error!("Failed to load suite UI: {}", e);
|
|
|
|
|
(
|
|
|
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
|
[("content-type", "text/plain")],
|
|
|
|
|
Html("Failed to load suite interface".to_string()),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-04 09:33:31 -03:00
|
|
|
/// Health check endpoint - checks BotServer connectivity
|
|
|
|
|
async fn health(State(state): State<AppState>) -> (StatusCode, axum::Json<serde_json::Value>) {
|
|
|
|
|
match state.health_check().await {
|
2025-12-03 18:42:22 -03:00
|
|
|
true => (
|
|
|
|
|
StatusCode::OK,
|
|
|
|
|
axum::Json(serde_json::json!({
|
|
|
|
|
"status": "healthy",
|
|
|
|
|
"service": "botui",
|
|
|
|
|
"mode": "web"
|
|
|
|
|
})),
|
|
|
|
|
),
|
|
|
|
|
false => (
|
|
|
|
|
StatusCode::SERVICE_UNAVAILABLE,
|
|
|
|
|
axum::Json(serde_json::json!({
|
|
|
|
|
"status": "unhealthy",
|
|
|
|
|
"service": "botui",
|
|
|
|
|
"error": "botserver unreachable"
|
|
|
|
|
})),
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-04 09:33:31 -03:00
|
|
|
/// API health check endpoint
|
2025-12-03 18:42:22 -03:00
|
|
|
async fn api_health() -> (StatusCode, axum::Json<serde_json::Value>) {
|
|
|
|
|
(
|
|
|
|
|
StatusCode::OK,
|
|
|
|
|
axum::Json(serde_json::json!({
|
|
|
|
|
"status": "ok",
|
2025-12-04 09:33:31 -03:00
|
|
|
"version": env!("CARGO_PKG_VERSION")
|
2025-12-03 18:42:22 -03:00
|
|
|
})),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-04 09:33:31 -03:00
|
|
|
/// Configure and return the main router
|
|
|
|
|
pub fn configure_router() -> Router {
|
|
|
|
|
let suite_path = PathBuf::from("./ui/suite");
|
|
|
|
|
let minimal_path = PathBuf::from("./ui/minimal");
|
|
|
|
|
let state = AppState::new();
|
|
|
|
|
|
|
|
|
|
Router::new()
|
|
|
|
|
// Health check endpoints
|
|
|
|
|
.route("/health", get(health))
|
|
|
|
|
.route("/api/health", get(api_health))
|
|
|
|
|
// UI routes
|
|
|
|
|
.route("/", get(index))
|
|
|
|
|
.route("/minimal", get(serve_minimal))
|
|
|
|
|
.route("/suite", get(serve_suite))
|
|
|
|
|
// Suite static assets (when accessing /suite/*)
|
|
|
|
|
.nest_service(
|
|
|
|
|
"/suite/js",
|
|
|
|
|
tower_http::services::ServeDir::new(suite_path.join("js")),
|
|
|
|
|
)
|
|
|
|
|
.nest_service(
|
|
|
|
|
"/suite/css",
|
|
|
|
|
tower_http::services::ServeDir::new(suite_path.join("css")),
|
|
|
|
|
)
|
|
|
|
|
.nest_service(
|
|
|
|
|
"/suite/public",
|
|
|
|
|
tower_http::services::ServeDir::new(suite_path.join("public")),
|
|
|
|
|
)
|
|
|
|
|
.nest_service(
|
|
|
|
|
"/suite/drive",
|
|
|
|
|
tower_http::services::ServeDir::new(suite_path.join("drive")),
|
|
|
|
|
)
|
|
|
|
|
.nest_service(
|
|
|
|
|
"/suite/chat",
|
|
|
|
|
tower_http::services::ServeDir::new(suite_path.join("chat")),
|
|
|
|
|
)
|
|
|
|
|
.nest_service(
|
|
|
|
|
"/suite/mail",
|
|
|
|
|
tower_http::services::ServeDir::new(suite_path.join("mail")),
|
|
|
|
|
)
|
|
|
|
|
.nest_service(
|
|
|
|
|
"/suite/tasks",
|
|
|
|
|
tower_http::services::ServeDir::new(suite_path.join("tasks")),
|
|
|
|
|
)
|
|
|
|
|
// Legacy paths for backward compatibility (serve suite assets)
|
|
|
|
|
.nest_service(
|
|
|
|
|
"/js",
|
|
|
|
|
tower_http::services::ServeDir::new(suite_path.join("js")),
|
|
|
|
|
)
|
|
|
|
|
.nest_service(
|
|
|
|
|
"/css",
|
|
|
|
|
tower_http::services::ServeDir::new(suite_path.join("css")),
|
|
|
|
|
)
|
|
|
|
|
.nest_service(
|
|
|
|
|
"/public",
|
|
|
|
|
tower_http::services::ServeDir::new(suite_path.join("public")),
|
|
|
|
|
)
|
|
|
|
|
.nest_service(
|
|
|
|
|
"/drive",
|
|
|
|
|
tower_http::services::ServeDir::new(suite_path.join("drive")),
|
|
|
|
|
)
|
|
|
|
|
.nest_service(
|
|
|
|
|
"/chat",
|
|
|
|
|
tower_http::services::ServeDir::new(suite_path.join("chat")),
|
|
|
|
|
)
|
|
|
|
|
.nest_service(
|
|
|
|
|
"/mail",
|
|
|
|
|
tower_http::services::ServeDir::new(suite_path.join("mail")),
|
|
|
|
|
)
|
|
|
|
|
.nest_service(
|
|
|
|
|
"/tasks",
|
|
|
|
|
tower_http::services::ServeDir::new(suite_path.join("tasks")),
|
|
|
|
|
)
|
|
|
|
|
// Fallback for other static files
|
|
|
|
|
.fallback_service(
|
|
|
|
|
tower_http::services::ServeDir::new(minimal_path.clone()).fallback(
|
|
|
|
|
tower_http::services::ServeDir::new(minimal_path)
|
|
|
|
|
.append_index_html_on_directories(true),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.with_state(state)
|
2025-12-03 18:42:22 -03:00
|
|
|
}
|