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
16///
17/// # Arguments
18/// * `http_server_addr` - HTTP server address
19/// * `ws_server_addr` - WebSocket server address
20/// * `grpc_server_addr` - gRPC server address
21/// * `graphql_server_addr` - GraphQL server address
22/// * `api_enabled` - Whether API endpoints are enabled
23/// * `admin_port` - Admin server port
24/// * `prometheus_url` - Prometheus metrics URL
25/// * `chaos_api_state` - Optional chaos API state for hot-reload support
26/// * `latency_injector` - Optional latency injector for hot-reload support
27/// * `mockai` - Optional MockAI instance for hot-reload support
28/// * `continuum_config` - Optional Reality Continuum configuration
29/// * `virtual_clock` - Optional virtual clock for time-based progression
30pub fn create_admin_router(
31    http_server_addr: Option<std::net::SocketAddr>,
32    ws_server_addr: Option<std::net::SocketAddr>,
33    grpc_server_addr: Option<std::net::SocketAddr>,
34    graphql_server_addr: Option<std::net::SocketAddr>,
35    api_enabled: bool,
36    admin_port: u16,
37    prometheus_url: String,
38    chaos_api_state: Option<std::sync::Arc<mockforge_chaos::api::ChaosApiState>>,
39    latency_injector: Option<
40        std::sync::Arc<tokio::sync::RwLock<mockforge_core::latency::LatencyInjector>>,
41    >,
42    mockai: Option<
43        std::sync::Arc<tokio::sync::RwLock<mockforge_core::intelligent_behavior::MockAI>>,
44    >,
45    continuum_config: Option<mockforge_core::ContinuumConfig>,
46    virtual_clock: Option<std::sync::Arc<mockforge_core::VirtualClock>>,
47) -> Router {
48    // Initialize global logger if not already initialized
49    let _logger = get_global_logger().unwrap_or_else(|| init_global_logger(1000));
50
51    let state = AdminState::new(
52        http_server_addr,
53        ws_server_addr,
54        grpc_server_addr,
55        graphql_server_addr,
56        api_enabled,
57        admin_port,
58        chaos_api_state,
59        latency_injector,
60        mockai,
61        continuum_config,
62        virtual_clock,
63    );
64
65    // Start system monitoring background task to poll CPU, memory, and thread metrics
66    let state_clone = state.clone();
67    tokio::spawn(async move {
68        state_clone.start_system_monitoring().await;
69    });
70    let mut router = Router::new()
71        .route("/", get(serve_admin_html))
72        .route("/assets/index.css", get(serve_admin_css))
73        .route("/assets/index.js", get(serve_admin_js))
74        .route("/assets/{filename}", get(serve_vendor_asset))
75        .route("/api-docs", get(serve_api_docs))
76        .route("/mockforge-icon.png", get(serve_icon))
77        .route("/mockforge-icon-32.png", get(serve_icon_32))
78        .route("/mockforge-icon-48.png", get(serve_icon_48))
79        .route("/mockforge-logo.png", get(serve_logo))
80        .route("/mockforge-logo-40.png", get(serve_logo_40))
81        .route("/mockforge-logo-80.png", get(serve_logo_80));
82
83    router = router
84        .route("/__mockforge/dashboard", get(get_dashboard))
85        .route("/_mf", get(get_dashboard))  // Short alias for dashboard
86        .route("/__mockforge/health", get(get_health))
87        .route("/admin/server-info", get(get_server_info))
88        .route("/__mockforge/server-info", get(get_server_info))
89        .route("/__mockforge/routes", get(get_routes))
90        .route("/__mockforge/logs", get(get_logs))
91        .route("/__mockforge/logs/sse", get(logs_sse))
92        .route("/__mockforge/metrics", get(get_metrics))
93        .route("/__mockforge/config", get(get_config))
94        .route("/__mockforge/config/latency", post(update_latency))
95        .route("/__mockforge/config/faults", post(update_faults))
96        .route("/__mockforge/config/proxy", post(update_proxy))
97        .route("/__mockforge/config/traffic-shaping", post(update_traffic_shaping))
98        .route("/__mockforge/logs", delete(clear_logs))
99        .route("/__mockforge/restart", post(restart_servers))
100        .route("/__mockforge/restart/status", get(get_restart_status))
101        .route("/__mockforge/fixtures", get(get_fixtures))
102        .route("/__mockforge/fixtures/{id}", delete(delete_fixture))
103        .route("/__mockforge/fixtures/bulk", delete(delete_fixtures_bulk))
104        .route("/__mockforge/fixtures/{id}/download", get(download_fixture))
105        .route("/__mockforge/fixtures/{id}/rename", post(rename_fixture))
106        .route("/__mockforge/fixtures/{id}/move", post(move_fixture))
107        // Import routes
108        .route("/__mockforge/import/postman", post(import_postman))
109        .route("/__mockforge/import/insomnia", post(import_insomnia))
110        .route("/__mockforge/import/curl", post(import_curl))
111        .route("/__mockforge/import/preview", post(preview_import))
112        .route("/__mockforge/import/history", get(get_import_history))
113        .route("/__mockforge/import/history/clear", post(clear_import_history))
114        // Plugin management routes
115        .route("/__mockforge/plugins", get(get_plugins))
116        .route("/__mockforge/plugins/status", get(get_plugin_status))
117        .route("/__mockforge/plugins/{id}", get(get_plugin_details))
118        .route("/__mockforge/plugins/{id}", delete(delete_plugin))
119        .route("/__mockforge/plugins/reload", post(reload_plugin))
120        // Workspace management routes
121        .route("/__mockforge/workspaces", get(get_workspaces))
122        .route("/__mockforge/workspaces", post(create_workspace))
123        .route("/__mockforge/workspaces/{workspace_id}", get(get_workspace))
124        .route("/__mockforge/workspaces/{workspace_id}", delete(delete_workspace))
125        .route("/__mockforge/workspaces/{workspace_id}/activate", post(set_active_workspace))
126        // Environment management routes
127        .route("/__mockforge/workspaces/{workspace_id}/environments", get(get_environments))
128        .route("/__mockforge/workspaces/{workspace_id}/environments", post(create_environment))
129        .route("/__mockforge/workspaces/{workspace_id}/environments/order", axum::routing::put(update_environments_order))
130        .route("/__mockforge/workspaces/{workspace_id}/environments/{environment_id}", axum::routing::put(update_environment))
131        .route("/__mockforge/workspaces/{workspace_id}/environments/{environment_id}", delete(delete_environment))
132        .route("/__mockforge/workspaces/{workspace_id}/environments/{environment_id}/activate", post(set_active_environment))
133        .route("/__mockforge/workspaces/{workspace_id}/environments/{environment_id}/variables", get(get_environment_variables))
134        .route("/__mockforge/workspaces/{workspace_id}/environments/{environment_id}/variables", post(set_environment_variable))
135        .route("/__mockforge/workspaces/{workspace_id}/environments/{environment_id}/variables/{variable_name}", delete(remove_environment_variable))
136        // Chain management routes - proxy to main HTTP server
137        .route("/__mockforge/chains", get(proxy_chains_list))
138        .route("/__mockforge/chains", post(proxy_chains_create))
139        .route("/__mockforge/chains/{id}", get(proxy_chain_get))
140        .route("/__mockforge/chains/{id}", axum::routing::put(proxy_chain_update))
141        .route("/__mockforge/chains/{id}", delete(proxy_chain_delete))
142        .route("/__mockforge/chains/{id}/execute", post(proxy_chain_execute))
143        .route("/__mockforge/chains/{id}/validate", post(proxy_chain_validate))
144        .route("/__mockforge/chains/{id}/history", get(proxy_chain_history))
145        // Graph visualization routes
146        .route("/__mockforge/graph", get(get_graph))
147        .route("/__mockforge/graph/sse", get(graph_sse))
148        // Validation configuration routes
149        .route("/__mockforge/validation", get(get_validation))
150        .route("/__mockforge/validation", post(update_validation))
151        // Migration pipeline routes
152        .route("/__mockforge/migration/routes", get(migration::get_migration_routes))
153        .route("/__mockforge/migration/routes/{pattern}/toggle", post(migration::toggle_route_migration))
154        .route("/__mockforge/migration/routes/{pattern}", axum::routing::put(migration::set_route_migration_mode))
155        .route("/__mockforge/migration/groups/{group}/toggle", post(migration::toggle_group_migration))
156        .route("/__mockforge/migration/groups/{group}", axum::routing::put(migration::set_group_migration_mode))
157        .route("/__mockforge/migration/groups", get(migration::get_migration_groups))
158        .route("/__mockforge/migration/status", get(migration::get_migration_status))
159        // Environment variables routes
160        .route("/__mockforge/env", get(get_env_vars))
161        .route("/__mockforge/env", post(update_env_var))
162        // File management routes
163        .route("/__mockforge/files/content", post(get_file_content))
164        .route("/__mockforge/files/save", post(save_file_content))
165        // Smoke test routes
166        .route("/__mockforge/smoke", get(get_smoke_tests))
167        .route("/__mockforge/smoke/run", get(run_smoke_tests_endpoint))
168        // Time travel / temporal testing routes
169        .route("/__mockforge/time-travel/status", get(time_travel_handlers::get_time_travel_status))
170        .route("/__mockforge/time-travel/enable", post(time_travel_handlers::enable_time_travel))
171        .route("/__mockforge/time-travel/disable", post(time_travel_handlers::disable_time_travel))
172        .route("/__mockforge/time-travel/advance", post(time_travel_handlers::advance_time))
173        .route("/__mockforge/time-travel/scale", post(time_travel_handlers::set_time_scale))
174        .route("/__mockforge/time-travel/reset", post(time_travel_handlers::reset_time_travel))
175        .route("/__mockforge/time-travel/schedule", post(time_travel_handlers::schedule_response))
176        .route("/__mockforge/time-travel/scheduled", get(time_travel_handlers::list_scheduled_responses))
177        .route("/__mockforge/time-travel/scheduled/{id}", delete(time_travel_handlers::cancel_scheduled_response))
178        .route("/__mockforge/time-travel/scheduled/clear", post(time_travel_handlers::clear_scheduled_responses))
179        .route("/__mockforge/time-travel/scenario/save", post(time_travel_handlers::save_scenario))
180        .route("/__mockforge/time-travel/scenario/load", post(time_travel_handlers::load_scenario))
181        // Cron job management routes
182        .route("/__mockforge/time-travel/cron", get(time_travel_handlers::list_cron_jobs))
183        .route("/__mockforge/time-travel/cron", post(time_travel_handlers::create_cron_job))
184        .route("/__mockforge/time-travel/cron/{id}", get(time_travel_handlers::get_cron_job))
185        .route("/__mockforge/time-travel/cron/{id}", delete(time_travel_handlers::delete_cron_job))
186        .route("/__mockforge/time-travel/cron/{id}/enable", post(time_travel_handlers::set_cron_job_enabled))
187        // Mutation rule management routes
188        .route("/__mockforge/time-travel/mutations", get(time_travel_handlers::list_mutation_rules))
189        .route("/__mockforge/time-travel/mutations", post(time_travel_handlers::create_mutation_rule))
190        .route("/__mockforge/time-travel/mutations/{id}", get(time_travel_handlers::get_mutation_rule))
191        .route("/__mockforge/time-travel/mutations/{id}", delete(time_travel_handlers::delete_mutation_rule))
192        .route("/__mockforge/time-travel/mutations/{id}/enable", post(time_travel_handlers::set_mutation_rule_enabled))
193        // Verification routes
194        .route("/__mockforge/verification/verify", post(verification::verify))
195        .route("/__mockforge/verification/count", post(verification::count))
196        .route("/__mockforge/verification/sequence", post(verification::verify_sequence_handler))
197        .route("/__mockforge/verification/never", post(verification::verify_never_handler))
198        .route("/__mockforge/verification/at-least", post(verification::verify_at_least_handler))
199        // Reality Slider routes
200        .route("/__mockforge/reality/level", get(get_reality_level))
201        .route("/__mockforge/reality/level", axum::routing::put(set_reality_level))
202        .route("/__mockforge/reality/presets", get(list_reality_presets))
203        .route("/__mockforge/reality/presets/import", post(import_reality_preset))
204        .route("/__mockforge/reality/presets/export", post(export_reality_preset))
205        // Reality Continuum routes
206        .route("/__mockforge/continuum/ratio", get(get_continuum_ratio))
207        .route("/__mockforge/continuum/ratio", axum::routing::put(set_continuum_ratio))
208        .route("/__mockforge/continuum/schedule", get(get_continuum_schedule))
209        .route("/__mockforge/continuum/schedule", axum::routing::put(set_continuum_schedule))
210        .route("/__mockforge/continuum/advance", post(advance_continuum_ratio))
211        .route("/__mockforge/continuum/enabled", axum::routing::put(set_continuum_enabled))
212        .route("/__mockforge/continuum/overrides", get(get_continuum_overrides))
213        .route("/__mockforge/continuum/overrides", axum::routing::delete(clear_continuum_overrides))
214        // Contract diff routes
215        .route("/__mockforge/contract-diff/upload", post(contract_diff::upload_request))
216        .route("/__mockforge/contract-diff/submit", post(contract_diff::submit_request))
217        .route("/__mockforge/contract-diff/captures", get(contract_diff::get_captured_requests))
218        .route("/__mockforge/contract-diff/captures/{id}", get(contract_diff::get_captured_request))
219        .route("/__mockforge/contract-diff/captures/{id}/analyze", post(contract_diff::analyze_captured_request))
220        .route("/__mockforge/contract-diff/captures/{id}/patch", post(contract_diff::generate_patch_file))
221        .route("/__mockforge/contract-diff/statistics", get(contract_diff::get_capture_statistics))
222        // Playground routes
223        .route("/__mockforge/playground/endpoints", get(playground::list_playground_endpoints))
224        .route("/__mockforge/playground/execute", post(playground::execute_rest_request))
225        .route("/__mockforge/playground/graphql", post(playground::execute_graphql_query))
226        .route("/__mockforge/playground/graphql/introspect", get(playground::graphql_introspect))
227        .route("/__mockforge/playground/history", get(playground::get_request_history))
228        .route("/__mockforge/playground/history/{id}/replay", post(playground::replay_request))
229        .route("/__mockforge/playground/snippets", post(playground::generate_code_snippet))
230        // Voice + LLM Interface routes
231        .route("/api/v2/voice/process", post(voice::process_voice_command))
232        .route("/__mockforge/voice/process", post(voice::process_voice_command))
233        // Health check endpoints for Kubernetes probes
234        .route("/health/live", get(health::liveness_probe))
235        .route("/health/ready", get(health::readiness_probe))
236        .route("/health/startup", get(health::startup_probe))
237        .route("/health", get(health::deep_health_check))
238        // Kubernetes-style health endpoint aliases
239        .route("/healthz", get(health::deep_health_check))
240        .route("/readyz", get(health::readiness_probe))
241        .route("/livez", get(health::liveness_probe))
242        .route("/startupz", get(health::startup_probe));
243
244    // Analytics routes with Prometheus integration
245    let analytics_state = AnalyticsState::new(prometheus_url);
246
247    let analytics_router = Router::new()
248        .route("/__mockforge/analytics/summary", get(analytics::get_summary))
249        .route("/__mockforge/analytics/requests", get(analytics::get_requests))
250        .route("/__mockforge/analytics/endpoints", get(analytics::get_endpoints))
251        .route("/__mockforge/analytics/websocket", get(analytics::get_websocket))
252        .route("/__mockforge/analytics/smtp", get(analytics::get_smtp))
253        .route("/__mockforge/analytics/system", get(analytics::get_system))
254        .with_state(analytics_state);
255
256    router = router.merge(analytics_router);
257
258    // Add UI Builder router
259    // This provides a low-code visual interface for creating mock endpoints
260    {
261        use mockforge_http::{create_ui_builder_router, UIBuilderState};
262
263        // Load server config for UI Builder
264        // For now, create a default config. In production, this should be loaded from the actual config.
265        // Use the re-exported ServerConfig from mockforge_core root to match UIBuilderState's import
266        let server_config = mockforge_core::ServerConfig::default();
267        let ui_builder_state = UIBuilderState::new(server_config);
268        let ui_builder_router = create_ui_builder_router(ui_builder_state);
269
270        // Nest the UI builder router with its own state
271        router = router.nest_service("/__mockforge/ui-builder", ui_builder_router);
272        tracing::info!("UI Builder mounted at /__mockforge/ui-builder");
273    }
274
275    // SPA fallback: serve index.html for any unmatched routes to support client-side routing
276    // IMPORTANT: This must be AFTER all API routes
277    router = router.route("/{*path}", get(serve_admin_html));
278
279    router
280        .layer(CompressionLayer::new())
281        .layer(CorsLayer::permissive())
282        .with_state(state)
283}
284
285#[cfg(test)]
286mod tests {
287    use super::*;
288
289    #[tokio::test]
290    async fn test_create_admin_router() {
291        let http_addr: std::net::SocketAddr = "127.0.0.1:3000".parse().unwrap();
292        let router = create_admin_router(
293            Some(http_addr),
294            None,
295            None,
296            None,
297            true,
298            8080,
299            "http://localhost:9090".to_string(),
300            None,
301            None,
302            None,
303        );
304
305        // Router should be created successfully
306        let _ = router;
307    }
308
309    #[tokio::test]
310    async fn test_create_admin_router_no_servers() {
311        let router = create_admin_router(
312            None,
313            None,
314            None,
315            None,
316            false,
317            8080,
318            "http://localhost:9090".to_string(),
319            None,
320            None,
321            None,
322        );
323
324        // Router should still work without server addresses
325        let _ = router;
326    }
327}