mockforge_ui/
routes.rs

1//! Route definitions for the admin UI
2
3use axum::{
4    routing::{delete, get, post},
5    Router,
6};
7use tower_http::{compression::CompressionLayer, cors::CorsLayer};
8
9use crate::handlers::analytics::AnalyticsState;
10use crate::handlers::AdminState;
11use crate::handlers::*;
12use crate::time_travel_handlers;
13use mockforge_core::{get_global_logger, init_global_logger};
14
15/// Create the admin router with static assets and optional API endpoints
16pub fn create_admin_router(
17    http_server_addr: Option<std::net::SocketAddr>,
18    ws_server_addr: Option<std::net::SocketAddr>,
19    grpc_server_addr: Option<std::net::SocketAddr>,
20    graphql_server_addr: Option<std::net::SocketAddr>,
21    api_enabled: bool,
22    admin_port: u16,
23    prometheus_url: String,
24) -> Router {
25    // Initialize global logger if not already initialized
26    let _logger = get_global_logger().unwrap_or_else(|| init_global_logger(1000));
27
28    let state = AdminState::new(
29        http_server_addr,
30        ws_server_addr,
31        grpc_server_addr,
32        graphql_server_addr,
33        api_enabled,
34        admin_port,
35    );
36
37    // Start system monitoring background task to poll CPU, memory, and thread metrics
38    let state_clone = state.clone();
39    tokio::spawn(async move {
40        state_clone.start_system_monitoring().await;
41    });
42    let mut router = Router::new()
43        .route("/", get(serve_admin_html))
44        .route("/assets/index.css", get(serve_admin_css))
45        .route("/assets/index.js", get(serve_admin_js))
46        .route("/api-docs", get(serve_api_docs))
47        .route("/mockforge-icon.png", get(serve_icon))
48        .route("/mockforge-icon-32.png", get(serve_icon_32))
49        .route("/mockforge-icon-48.png", get(serve_icon_48))
50        .route("/mockforge-logo.png", get(serve_logo))
51        .route("/mockforge-logo-40.png", get(serve_logo_40))
52        .route("/mockforge-logo-80.png", get(serve_logo_80));
53
54    router = router
55        .route("/__mockforge/dashboard", get(get_dashboard))
56        .route("/_mf", get(get_dashboard))  // Short alias for dashboard
57        .route("/__mockforge/health", get(get_health))
58        .route("/admin/server-info", get(get_server_info))
59        .route("/__mockforge/server-info", get(get_server_info))
60        .route("/__mockforge/routes", get(get_routes))
61        .route("/__mockforge/logs", get(get_logs))
62        .route("/__mockforge/logs/sse", get(logs_sse))
63        .route("/__mockforge/metrics", get(get_metrics))
64        .route("/__mockforge/config", get(get_config))
65        .route("/__mockforge/config/latency", post(update_latency))
66        .route("/__mockforge/config/faults", post(update_faults))
67        .route("/__mockforge/config/proxy", post(update_proxy))
68        .route("/__mockforge/config/traffic-shaping", post(update_traffic_shaping))
69        .route("/__mockforge/logs", delete(clear_logs))
70        .route("/__mockforge/restart", post(restart_servers))
71        .route("/__mockforge/restart/status", get(get_restart_status))
72        .route("/__mockforge/fixtures", get(get_fixtures))
73        .route("/__mockforge/fixtures/{id}", delete(delete_fixture))
74        .route("/__mockforge/fixtures/bulk", delete(delete_fixtures_bulk))
75        .route("/__mockforge/fixtures/{id}/download", get(download_fixture))
76        .route("/__mockforge/fixtures/{id}/rename", post(rename_fixture))
77        .route("/__mockforge/fixtures/{id}/move", post(move_fixture))
78        .route("/__mockforge/import/insomnia", post(import_insomnia))
79        // Plugin management routes
80        .route("/__mockforge/plugins", get(get_plugins))
81        .route("/__mockforge/plugins/status", get(get_plugin_status))
82        .route("/__mockforge/plugins/{id}", get(get_plugin_details))
83        .route("/__mockforge/plugins/{id}", delete(delete_plugin))
84        .route("/__mockforge/plugins/reload", post(reload_plugin))
85        // Workspace management routes
86        .route("/__mockforge/workspaces", get(get_workspaces))
87        .route("/__mockforge/workspaces", post(create_workspace))
88        .route("/__mockforge/workspaces/{workspace_id}", get(get_workspace))
89        .route("/__mockforge/workspaces/{workspace_id}", delete(delete_workspace))
90        .route("/__mockforge/workspaces/{workspace_id}/activate", post(set_active_workspace))
91        // Environment management routes
92        .route("/__mockforge/workspaces/{workspace_id}/environments", get(get_environments))
93        .route("/__mockforge/workspaces/{workspace_id}/environments", post(create_environment))
94        .route("/__mockforge/workspaces/{workspace_id}/environments/order", axum::routing::put(update_environments_order))
95        .route("/__mockforge/workspaces/{workspace_id}/environments/{environment_id}", axum::routing::put(update_environment))
96        .route("/__mockforge/workspaces/{workspace_id}/environments/{environment_id}", delete(delete_environment))
97        .route("/__mockforge/workspaces/{workspace_id}/environments/{environment_id}/activate", post(set_active_environment))
98        .route("/__mockforge/workspaces/{workspace_id}/environments/{environment_id}/variables", get(get_environment_variables))
99        .route("/__mockforge/workspaces/{workspace_id}/environments/{environment_id}/variables", post(set_environment_variable))
100        .route("/__mockforge/workspaces/{workspace_id}/environments/{environment_id}/variables/{variable_name}", delete(remove_environment_variable))
101        // Chain management routes - proxy to main HTTP server
102        .route("/__mockforge/chains", get(proxy_chains_list))
103        .route("/__mockforge/chains", post(proxy_chains_create))
104        .route("/__mockforge/chains/{id}", get(proxy_chain_get))
105        .route("/__mockforge/chains/{id}", axum::routing::put(proxy_chain_update))
106        .route("/__mockforge/chains/{id}", delete(proxy_chain_delete))
107        .route("/__mockforge/chains/{id}/execute", post(proxy_chain_execute))
108        .route("/__mockforge/chains/{id}/validate", post(proxy_chain_validate))
109        .route("/__mockforge/chains/{id}/history", get(proxy_chain_history))
110        // Validation configuration routes
111        .route("/__mockforge/validation", get(get_validation))
112        .route("/__mockforge/validation", post(update_validation))
113        // Environment variables routes
114        .route("/__mockforge/env", get(get_env_vars))
115        .route("/__mockforge/env", post(update_env_var))
116        // File management routes
117        .route("/__mockforge/files/content", post(get_file_content))
118        .route("/__mockforge/files/save", post(save_file_content))
119        // Smoke test routes
120        .route("/__mockforge/smoke", get(get_smoke_tests))
121        .route("/__mockforge/smoke/run", get(run_smoke_tests_endpoint))
122        // Time travel / temporal testing routes
123        .route("/__mockforge/time-travel/status", get(time_travel_handlers::get_time_travel_status))
124        .route("/__mockforge/time-travel/enable", post(time_travel_handlers::enable_time_travel))
125        .route("/__mockforge/time-travel/disable", post(time_travel_handlers::disable_time_travel))
126        .route("/__mockforge/time-travel/advance", post(time_travel_handlers::advance_time))
127        .route("/__mockforge/time-travel/scale", post(time_travel_handlers::set_time_scale))
128        .route("/__mockforge/time-travel/reset", post(time_travel_handlers::reset_time_travel))
129        .route("/__mockforge/time-travel/schedule", post(time_travel_handlers::schedule_response))
130        .route("/__mockforge/time-travel/scheduled", get(time_travel_handlers::list_scheduled_responses))
131        .route("/__mockforge/time-travel/scheduled/{id}", delete(time_travel_handlers::cancel_scheduled_response))
132        .route("/__mockforge/time-travel/scheduled/clear", post(time_travel_handlers::clear_scheduled_responses))
133        // Health check endpoints for Kubernetes probes
134        .route("/health/live", get(health::liveness_probe))
135        .route("/health/ready", get(health::readiness_probe))
136        .route("/health/startup", get(health::startup_probe))
137        .route("/health", get(health::deep_health_check))
138        // Kubernetes-style health endpoint aliases
139        .route("/healthz", get(health::deep_health_check))
140        .route("/readyz", get(health::readiness_probe))
141        .route("/livez", get(health::liveness_probe))
142        .route("/startupz", get(health::startup_probe));
143
144    // Analytics routes with Prometheus integration
145    let analytics_state = AnalyticsState::new(prometheus_url);
146
147    let analytics_router = Router::new()
148        .route("/__mockforge/analytics/summary", get(analytics::get_summary))
149        .route("/__mockforge/analytics/requests", get(analytics::get_requests))
150        .route("/__mockforge/analytics/endpoints", get(analytics::get_endpoints))
151        .route("/__mockforge/analytics/websocket", get(analytics::get_websocket))
152        .route("/__mockforge/analytics/smtp", get(analytics::get_smtp))
153        .route("/__mockforge/analytics/system", get(analytics::get_system))
154        .with_state(analytics_state);
155
156    router = router.merge(analytics_router);
157
158    // Add UI Builder router
159    // This provides a low-code visual interface for creating mock endpoints
160    {
161        use mockforge_http::{create_ui_builder_router, UIBuilderState};
162
163        // Load server config for UI Builder
164        // For now, create a default config. In production, this should be loaded from the actual config.
165        let server_config = mockforge_core::config::ServerConfig::default();
166        let ui_builder_state = UIBuilderState::new(server_config);
167        let ui_builder_router = create_ui_builder_router(ui_builder_state);
168
169        // Nest the UI builder router with its own state
170        router = router.nest_service("/__mockforge/ui-builder", ui_builder_router);
171        tracing::info!("UI Builder mounted at /__mockforge/ui-builder");
172    }
173
174    // SPA fallback: serve index.html for any unmatched routes to support client-side routing
175    // IMPORTANT: This must be AFTER all API routes
176    router = router.route("/{*path}", get(serve_admin_html));
177
178    router
179        .layer(CompressionLayer::new())
180        .layer(CorsLayer::permissive())
181        .with_state(state)
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187
188    #[tokio::test]
189    async fn test_create_admin_router() {
190        let http_addr: std::net::SocketAddr = "127.0.0.1:3000".parse().unwrap();
191        let router = create_admin_router(
192            Some(http_addr),
193            None,
194            None,
195            None,
196            true,
197            8080,
198            "http://localhost:9090".to_string(),
199        );
200
201        // Router should be created successfully
202        let _ = router;
203    }
204
205    #[tokio::test]
206    async fn test_create_admin_router_no_servers() {
207        let router = create_admin_router(
208            None,
209            None,
210            None,
211            None,
212            false,
213            8080,
214            "http://localhost:9090".to_string(),
215        );
216
217        // Router should still work without server addresses
218        let _ = router;
219    }
220}