pub mod audit;
pub mod auth;
pub mod handlers;
pub mod pillar_tracking_init;
pub mod rbac;
#[cfg(feature = "registry-admin")]
pub mod registry_admin;
pub mod routes;
pub mod models;
pub mod prometheus_client;
pub mod time_travel_handlers;
pub use models::{RequestLog, RouteInfo, ServerStatus, SystemInfo};
pub use routes::create_admin_router;
use std::net::SocketAddr;
#[allow(clippy::too_many_arguments)]
pub async fn start_admin_server(
addr: SocketAddr,
http_server_addr: Option<SocketAddr>,
ws_server_addr: Option<SocketAddr>,
grpc_server_addr: Option<SocketAddr>,
graphql_server_addr: Option<SocketAddr>,
api_enabled: bool,
prometheus_url: String,
chaos_api_state: Option<std::sync::Arc<mockforge_chaos::api::ChaosApiState>>,
latency_injector: Option<
std::sync::Arc<tokio::sync::RwLock<mockforge_foundation::latency::LatencyInjector>>,
>,
mockai: Option<
std::sync::Arc<tokio::sync::RwLock<mockforge_core::intelligent_behavior::MockAI>>,
>,
continuum_config: Option<mockforge_core::ContinuumConfig>,
virtual_clock: Option<std::sync::Arc<mockforge_core::VirtualClock>>,
recorder: Option<std::sync::Arc<mockforge_recorder::Recorder>>,
federation: Option<std::sync::Arc<mockforge_federation::Federation>>,
vbr_engine: Option<std::sync::Arc<mockforge_vbr::VbrEngine>>,
resilience_api_state: Option<mockforge_chaos::resilience_api::ResilienceApiState>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
start_admin_server_notify(
addr,
http_server_addr,
ws_server_addr,
grpc_server_addr,
graphql_server_addr,
api_enabled,
prometheus_url,
chaos_api_state,
latency_injector,
mockai,
continuum_config,
virtual_clock,
recorder,
federation,
vbr_engine,
resilience_api_state,
None,
)
.await
}
#[allow(clippy::too_many_arguments)]
pub async fn start_admin_server_notify(
addr: SocketAddr,
http_server_addr: Option<SocketAddr>,
ws_server_addr: Option<SocketAddr>,
grpc_server_addr: Option<SocketAddr>,
graphql_server_addr: Option<SocketAddr>,
api_enabled: bool,
prometheus_url: String,
chaos_api_state: Option<std::sync::Arc<mockforge_chaos::api::ChaosApiState>>,
latency_injector: Option<
std::sync::Arc<tokio::sync::RwLock<mockforge_foundation::latency::LatencyInjector>>,
>,
mockai: Option<
std::sync::Arc<tokio::sync::RwLock<mockforge_core::intelligent_behavior::MockAI>>,
>,
continuum_config: Option<mockforge_core::ContinuumConfig>,
virtual_clock: Option<std::sync::Arc<mockforge_core::VirtualClock>>,
recorder: Option<std::sync::Arc<mockforge_recorder::Recorder>>,
federation: Option<std::sync::Arc<mockforge_federation::Federation>>,
vbr_engine: Option<std::sync::Arc<mockforge_vbr::VbrEngine>>,
resilience_api_state: Option<mockforge_chaos::resilience_api::ResilienceApiState>,
bound_port_tx: Option<tokio::sync::oneshot::Sender<u16>>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut app = create_admin_router(
http_server_addr,
ws_server_addr,
grpc_server_addr,
graphql_server_addr,
api_enabled,
addr.port(),
prometheus_url,
chaos_api_state,
latency_injector,
mockai,
continuum_config,
virtual_clock,
recorder,
federation,
vbr_engine,
resilience_api_state,
);
#[cfg(feature = "registry-admin")]
if let Ok(db_url) = std::env::var("MOCKFORGE_REGISTRY_DB_URL") {
match registry_admin::init_sqlite_registry_store(&db_url).await {
Ok(store) => {
if let Err(e) = registry_admin::bootstrap_admin_user_from_env(&store).await {
tracing::error!("Registry admin bootstrap failed: {} — continuing startup", e);
}
let jwt_secret = std::env::var("MOCKFORGE_ADMIN_JWT_SECRET").unwrap_or_else(|_| {
tracing::warn!(
"MOCKFORGE_ADMIN_JWT_SECRET not set — using empty secret. \
Tokens issued by /api/admin/registry/auth/* will be \
trivially forgeable. Set the env var in production."
);
String::new()
});
let state = registry_admin::CoreAppState::with_jwt_secret(
std::sync::Arc::new(store),
jwt_secret,
);
app = app.merge(registry_admin::router(state));
tracing::info!(
"Registry admin (SQLite) enabled at /api/admin/registry/* — db: {}",
db_url
);
}
Err(e) => {
tracing::error!(
"Failed to initialize registry admin SQLite store at '{}': {} — \
/api/admin/registry/* routes will not be available",
db_url,
e
);
}
}
}
tracing::info!("Starting MockForge Admin UI on {}", addr);
let listener = tokio::net::TcpListener::bind(addr).await.map_err(|e| {
format!(
"Failed to bind Admin UI server to port {}: {}\n\
Hint: The port may already be in use. Try using a different port with --admin-port or check if another process is using this port with: lsof -i :{} or netstat -tulpn | grep {}",
addr.port(), e, addr.port(), addr.port()
)
})?;
if let Some(tx) = bound_port_tx {
let actual_port = listener.local_addr().map(|a| a.port()).unwrap_or(addr.port());
let _ = tx.send(actual_port);
}
axum::serve(listener, app).await?;
Ok(())
}
include!(concat!(env!("OUT_DIR"), "/ui_content.rs"));
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_admin_html() {
let html = get_admin_html();
assert!(!html.is_empty());
assert!(html.contains("<!DOCTYPE html>") || html.contains("<html"));
}
#[test]
fn test_get_admin_css() {
let _css = get_admin_css();
}
#[test]
fn test_get_admin_js() {
let _js = get_admin_js();
}
}