1use 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
15pub 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 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 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)) .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 .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 .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 .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 .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 .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 .route("/__mockforge/graph", get(get_graph))
147 .route("/__mockforge/graph/sse", get(graph_sse))
148 .route("/__mockforge/validation", get(get_validation))
150 .route("/__mockforge/validation", post(update_validation))
151 .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 .route("/__mockforge/env", get(get_env_vars))
161 .route("/__mockforge/env", post(update_env_var))
162 .route("/__mockforge/files/content", post(get_file_content))
164 .route("/__mockforge/files/save", post(save_file_content))
165 .route("/__mockforge/smoke", get(get_smoke_tests))
167 .route("/__mockforge/smoke/run", get(run_smoke_tests_endpoint))
168 .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 .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 .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 .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 .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 .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 .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 .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 .route("/api/v2/voice/process", post(voice::process_voice_command))
232 .route("/__mockforge/voice/process", post(voice::process_voice_command))
233 .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 .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 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 {
261 use mockforge_http::{create_ui_builder_router, UIBuilderState};
262
263 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 router = router.nest_service("/__mockforge/ui-builder", ui_builder_router);
272 tracing::info!("UI Builder mounted at /__mockforge/ui-builder");
273 }
274
275 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 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 let _ = router;
326 }
327}