1use axum::{
4 routing::{delete, get, post},
5 Router,
6};
7use tower_http::{compression::CompressionLayer, cors::CorsLayer};
8
9use crate::audit::init_global_audit_store;
10use crate::auth::init_global_user_store;
11use crate::handlers::analytics::AnalyticsState;
12use crate::handlers::AdminState;
13use crate::handlers::*;
14use crate::rbac::rbac_middleware;
15use crate::time_travel_handlers;
16use axum::middleware::from_fn;
17use mockforge_core::{get_global_logger, init_global_logger};
18
19pub fn create_admin_router(
35 http_server_addr: Option<std::net::SocketAddr>,
36 ws_server_addr: Option<std::net::SocketAddr>,
37 grpc_server_addr: Option<std::net::SocketAddr>,
38 graphql_server_addr: Option<std::net::SocketAddr>,
39 api_enabled: bool,
40 admin_port: u16,
41 prometheus_url: String,
42 chaos_api_state: Option<std::sync::Arc<mockforge_chaos::api::ChaosApiState>>,
43 latency_injector: Option<
44 std::sync::Arc<tokio::sync::RwLock<mockforge_core::latency::LatencyInjector>>,
45 >,
46 mockai: Option<
47 std::sync::Arc<tokio::sync::RwLock<mockforge_core::intelligent_behavior::MockAI>>,
48 >,
49 continuum_config: Option<mockforge_core::ContinuumConfig>,
50 virtual_clock: Option<std::sync::Arc<mockforge_core::VirtualClock>>,
51) -> Router {
52 let _logger = get_global_logger().unwrap_or_else(|| init_global_logger(1000));
54
55 let _audit_store = init_global_audit_store(10000);
57
58 let _user_store = init_global_user_store();
60
61 let state = AdminState::new(
62 http_server_addr,
63 ws_server_addr,
64 grpc_server_addr,
65 graphql_server_addr,
66 api_enabled,
67 admin_port,
68 chaos_api_state,
69 latency_injector,
70 mockai,
71 continuum_config,
72 virtual_clock,
73 );
74
75 let state_clone = state.clone();
77 tokio::spawn(async move {
78 state_clone.start_system_monitoring().await;
79 });
80 let mut router = Router::new()
81 .route("/", get(serve_admin_html))
83 .route("/assets/index.css", get(serve_admin_css))
84 .route("/assets/index.js", get(serve_admin_js))
85 .route("/assets/{filename}", get(serve_vendor_asset))
86 .route("/api-docs", get(serve_api_docs))
87 .route("/mockforge-icon.png", get(serve_icon))
88 .route("/mockforge-icon-32.png", get(serve_icon_32))
89 .route("/mockforge-icon-48.png", get(serve_icon_48))
90 .route("/mockforge-logo.png", get(serve_logo))
91 .route("/mockforge-logo-40.png", get(serve_logo_40))
92 .route("/mockforge-logo-80.png", get(serve_logo_80))
93 .route("/manifest.json", get(serve_manifest))
94 .route("/sw.js", get(serve_service_worker))
95 .route("/__mockforge/auth/login", post(crate::auth::login))
97 .route("/__mockforge/auth/refresh", post(crate::auth::refresh_token))
98 .route("/__mockforge/auth/logout", post(crate::auth::logout))
99 .route("/__mockforge/health", get(get_health));
100
101 router = router
103 .route("/__mockforge/dashboard", get(get_dashboard))
104 .route("/_mf", get(get_dashboard)) .route("/admin/server-info", get(get_server_info))
106 .route("/__mockforge/server-info", get(get_server_info))
107 .route("/__mockforge/routes", get(get_routes))
108 .route("/__mockforge/logs", get(get_logs))
109 .route("/__mockforge/logs/sse", get(logs_sse))
110 .route("/__mockforge/metrics", get(get_metrics))
111 .route("/__mockforge/api/reality/trace/{request_id}", get(get_reality_trace))
112 .route("/__mockforge/api/reality/response-trace/{request_id}", get(get_response_trace))
113 .route("/__mockforge/config", get(get_config))
114 .route("/__mockforge/config/latency", post(update_latency))
115 .route("/__mockforge/config/faults", post(update_faults))
116 .route("/__mockforge/config/proxy", post(update_proxy))
117 .route("/__mockforge/config/traffic-shaping", post(update_traffic_shaping))
118 .route("/__mockforge/logs", delete(clear_logs))
119 .route("/__mockforge/restart", post(restart_servers))
120 .route("/__mockforge/restart/status", get(get_restart_status))
121 .route("/__mockforge/fixtures", get(get_fixtures))
122 .route("/__mockforge/fixtures/{id}", delete(delete_fixture))
123 .route("/__mockforge/fixtures/bulk", delete(delete_fixtures_bulk))
124 .route("/__mockforge/audit/logs", get(get_audit_logs))
125 .route("/__mockforge/audit/stats", get(get_audit_stats))
126 .route("/__mockforge/fixtures/{id}/download", get(download_fixture))
127 .route("/__mockforge/fixtures/{id}/rename", post(rename_fixture))
128 .route("/__mockforge/fixtures/{id}/move", post(move_fixture))
129 .route("/__mockforge/import/postman", post(import_postman))
131 .route("/__mockforge/import/insomnia", post(import_insomnia))
132 .route("/__mockforge/import/curl", post(import_curl))
133 .route("/__mockforge/import/preview", post(preview_import))
134 .route("/__mockforge/import/history", get(get_import_history))
135 .route("/__mockforge/import/history/clear", post(clear_import_history))
136 .route("/__mockforge/plugins", get(get_plugins))
138 .route("/__mockforge/plugins/status", get(get_plugin_status))
139 .route("/__mockforge/plugins/{id}", get(get_plugin_details))
140 .route("/__mockforge/plugins/{id}", delete(delete_plugin))
141 .route("/__mockforge/plugins/reload", post(reload_plugin))
142 .route("/__mockforge/workspaces/{workspace_id}/environments", get(get_environments))
146 .route("/__mockforge/workspaces/{workspace_id}/environments", post(create_environment))
147 .route("/__mockforge/workspaces/{workspace_id}/environments/order", axum::routing::put(update_environments_order))
148 .route("/__mockforge/workspaces/{workspace_id}/environments/{environment_id}", axum::routing::put(update_environment))
149 .route("/__mockforge/workspaces/{workspace_id}/environments/{environment_id}", delete(delete_environment))
150 .route("/__mockforge/workspaces/{workspace_id}/environments/{environment_id}/activate", post(set_active_environment))
151 .route("/__mockforge/workspaces/{workspace_id}/environments/{environment_id}/variables", get(get_environment_variables))
152 .route("/__mockforge/workspaces/{workspace_id}/environments/{environment_id}/variables", post(set_environment_variable))
153 .route("/__mockforge/workspaces/{workspace_id}/environments/{environment_id}/variables/{variable_name}", delete(remove_environment_variable))
154 .route("/__mockforge/chains", get(proxy_chains_list))
156 .route("/__mockforge/chains", post(proxy_chains_create))
157 .route("/__mockforge/chains/{id}", get(proxy_chain_get))
158 .route("/__mockforge/chains/{id}", axum::routing::put(proxy_chain_update))
159 .route("/__mockforge/chains/{id}", delete(proxy_chain_delete))
160 .route("/__mockforge/chains/{id}/execute", post(proxy_chain_execute))
161 .route("/__mockforge/chains/{id}/validate", post(proxy_chain_validate))
162 .route("/__mockforge/chains/{id}/history", get(proxy_chain_history))
163 .route("/__mockforge/graph", get(get_graph))
165 .route("/__mockforge/graph/sse", get(graph_sse))
166 .route("/__mockforge/validation", get(get_validation))
168 .route("/__mockforge/validation", post(update_validation))
169 .route("/__mockforge/migration/routes", get(migration::get_migration_routes))
171 .route("/__mockforge/migration/routes/{pattern}/toggle", post(migration::toggle_route_migration))
172 .route("/__mockforge/migration/routes/{pattern}", axum::routing::put(migration::set_route_migration_mode))
173 .route("/__mockforge/migration/groups/{group}/toggle", post(migration::toggle_group_migration))
174 .route("/__mockforge/migration/groups/{group}", axum::routing::put(migration::set_group_migration_mode))
175 .route("/__mockforge/migration/groups", get(migration::get_migration_groups))
176 .route("/__mockforge/migration/status", get(migration::get_migration_status))
177 .route("/__mockforge/env", get(get_env_vars))
179 .route("/__mockforge/env", post(update_env_var))
180 .route("/__mockforge/files/content", post(get_file_content))
182 .route("/__mockforge/files/save", post(save_file_content))
183 .route("/__mockforge/smoke", get(get_smoke_tests))
185 .route("/__mockforge/smoke/run", get(run_smoke_tests_endpoint))
186 .route("/__mockforge/time-travel/status", get(time_travel_handlers::get_time_travel_status))
188 .route("/__mockforge/time-travel/enable", post(time_travel_handlers::enable_time_travel))
189 .route("/__mockforge/time-travel/disable", post(time_travel_handlers::disable_time_travel))
190 .route("/__mockforge/time-travel/advance", post(time_travel_handlers::advance_time))
191 .route("/__mockforge/time-travel/set", post(time_travel_handlers::set_time))
192 .route("/__mockforge/time-travel/scale", post(time_travel_handlers::set_time_scale))
193 .route("/__mockforge/time-travel/reset", post(time_travel_handlers::reset_time_travel))
194 .route("/__mockforge/time-travel/schedule", post(time_travel_handlers::schedule_response))
195 .route("/__mockforge/time-travel/scheduled", get(time_travel_handlers::list_scheduled_responses))
196 .route("/__mockforge/time-travel/scheduled/{id}", delete(time_travel_handlers::cancel_scheduled_response))
197 .route("/__mockforge/time-travel/scheduled/clear", post(time_travel_handlers::clear_scheduled_responses))
198 .route("/__mockforge/time-travel/scenario/save", post(time_travel_handlers::save_scenario))
199 .route("/__mockforge/time-travel/scenario/load", post(time_travel_handlers::load_scenario))
200 .route("/__mockforge/time-travel/cron", get(time_travel_handlers::list_cron_jobs))
202 .route("/__mockforge/time-travel/cron", post(time_travel_handlers::create_cron_job))
203 .route("/__mockforge/time-travel/cron/{id}", get(time_travel_handlers::get_cron_job))
204 .route("/__mockforge/time-travel/cron/{id}", delete(time_travel_handlers::delete_cron_job))
205 .route("/__mockforge/time-travel/cron/{id}/enable", post(time_travel_handlers::set_cron_job_enabled))
206 .route("/__mockforge/time-travel/mutations", get(time_travel_handlers::list_mutation_rules))
208 .route("/__mockforge/time-travel/mutations", post(time_travel_handlers::create_mutation_rule))
209 .route("/__mockforge/time-travel/mutations/{id}", get(time_travel_handlers::get_mutation_rule))
210 .route("/__mockforge/time-travel/mutations/{id}", delete(time_travel_handlers::delete_mutation_rule))
211 .route("/__mockforge/time-travel/mutations/{id}/enable", post(time_travel_handlers::set_mutation_rule_enabled))
212 .route("/__mockforge/verification/verify", post(verification::verify))
214 .route("/__mockforge/verification/count", post(verification::count))
215 .route("/__mockforge/verification/sequence", post(verification::verify_sequence_handler))
216 .route("/__mockforge/verification/never", post(verification::verify_never_handler))
217 .route("/__mockforge/verification/at-least", post(verification::verify_at_least_handler))
218 .route("/__mockforge/reality/level", get(get_reality_level))
220 .route("/__mockforge/reality/level", axum::routing::put(set_reality_level))
221 .route("/__mockforge/reality/presets", get(list_reality_presets))
222 .route("/__mockforge/reality/presets/import", post(import_reality_preset))
223 .route("/__mockforge/reality/presets/export", post(export_reality_preset))
224 .route("/__mockforge/continuum/ratio", get(get_continuum_ratio))
226 .route("/__mockforge/continuum/ratio", axum::routing::put(set_continuum_ratio))
227 .route("/__mockforge/continuum/schedule", get(get_continuum_schedule))
228 .route("/__mockforge/continuum/schedule", axum::routing::put(set_continuum_schedule))
229 .route("/__mockforge/continuum/advance", post(advance_continuum_ratio))
230 .route("/__mockforge/continuum/enabled", axum::routing::put(set_continuum_enabled))
231 .route("/__mockforge/continuum/overrides", get(get_continuum_overrides))
232 .route("/__mockforge/continuum/overrides", axum::routing::delete(clear_continuum_overrides))
233 .route("/__mockforge/contract-diff/upload", post(contract_diff::upload_request))
235 .route("/__mockforge/contract-diff/submit", post(contract_diff::submit_request))
236 .route("/__mockforge/contract-diff/captures", get(contract_diff::get_captured_requests))
237 .route("/__mockforge/contract-diff/captures/{id}", get(contract_diff::get_captured_request))
238 .route("/__mockforge/contract-diff/captures/{id}/analyze", post(contract_diff::analyze_captured_request))
239 .route("/__mockforge/contract-diff/captures/{id}/patch", post(contract_diff::generate_patch_file))
240 .route("/__mockforge/contract-diff/statistics", get(contract_diff::get_capture_statistics))
241 .route("/__mockforge/playground/endpoints", get(playground::list_playground_endpoints))
243 .route("/__mockforge/playground/execute", post(playground::execute_rest_request))
244 .route("/__mockforge/playground/graphql", post(playground::execute_graphql_query))
245 .route("/__mockforge/playground/graphql/introspect", get(playground::graphql_introspect))
246 .route("/__mockforge/playground/history", get(playground::get_request_history))
247 .route("/__mockforge/playground/history/{id}/replay", post(playground::replay_request))
248 .route("/__mockforge/playground/snippets", post(playground::generate_code_snippet))
249 .route("/api/v2/voice/process", post(voice::process_voice_command))
251 .route("/__mockforge/voice/process", post(voice::process_voice_command))
252 .route("/api/v2/voice/transpile-hook", post(voice::transpile_hook))
253 .route("/__mockforge/voice/transpile-hook", post(voice::transpile_hook))
254 .route(
255 "/api/v2/voice/create-workspace-scenario",
256 post(voice::create_workspace_scenario),
257 )
258 .route(
259 "/__mockforge/voice/create-workspace-scenario",
260 post(voice::create_workspace_scenario),
261 )
262 .route(
263 "/api/v2/voice/create-workspace-preview",
264 post(voice::create_workspace_preview),
265 )
266 .route(
267 "/__mockforge/voice/create-workspace-preview",
268 post(voice::create_workspace_preview),
269 )
270 .route("/api/v1/ai-studio/chat", post(ai_studio::chat))
273 .route("/__mockforge/ai-studio/chat", post(ai_studio::chat))
274 .route("/api/v1/ai-studio/generate-mock", post(ai_studio::generate_mock))
275 .route("/__mockforge/ai-studio/generate-mock", post(ai_studio::generate_mock))
276 .route("/api/v1/ai-studio/debug-test", post(ai_studio::debug_test))
277 .route("/__mockforge/ai-studio/debug-test", post(ai_studio::debug_test))
278 .route("/api/v1/ai-studio/generate-persona", post(ai_studio::generate_persona))
279 .route("/__mockforge/ai-studio/generate-persona", post(ai_studio::generate_persona))
280 .route("/api/v1/ai-studio/freeze", post(ai_studio::freeze_artifact))
281 .route("/__mockforge/ai-studio/freeze", post(ai_studio::freeze_artifact))
282 .route("/api/v1/ai-studio/usage", get(ai_studio::get_usage))
283 .route("/__mockforge/ai-studio/usage", get(ai_studio::get_usage))
284 .route("/api/v2/failures/analyze", post(failure_analysis::analyze_failure))
286 .route("/api/v2/failures/{request_id}", get(failure_analysis::get_failure_analysis))
287 .route("/api/v2/failures/recent", get(failure_analysis::list_recent_failures))
288 .route("/__mockforge/failures/analyze", post(failure_analysis::analyze_failure))
289 .route("/__mockforge/failures/{request_id}", get(failure_analysis::get_failure_analysis))
290 .route("/__mockforge/failures/recent", get(failure_analysis::list_recent_failures))
291 .route("/__mockforge/community/showcase/projects", get(community::get_showcase_projects))
293 .route("/__mockforge/community/showcase/projects/{id}", get(community::get_showcase_project))
294 .route("/__mockforge/community/showcase/categories", get(community::get_showcase_categories))
295 .route("/__mockforge/community/showcase/stories", get(community::get_success_stories))
296 .route("/__mockforge/community/showcase/submit", post(community::submit_showcase_project))
297 .route("/__mockforge/community/learning/resources", get(community::get_learning_resources))
298 .route("/__mockforge/community/learning/resources/{id}", get(community::get_learning_resource))
299 .route("/__mockforge/community/learning/categories", get(community::get_learning_categories))
300 .route("/__mockforge/flows", get(behavioral_cloning::get_flows))
302 .route("/__mockforge/flows/{id}", get(behavioral_cloning::get_flow))
303 .route("/__mockforge/flows/{id}/tag", axum::routing::put(behavioral_cloning::tag_flow))
304 .route("/__mockforge/flows/{id}/compile", post(behavioral_cloning::compile_flow))
305 .route("/__mockforge/scenarios", get(behavioral_cloning::get_scenarios))
306 .route("/__mockforge/scenarios/{id}", get(behavioral_cloning::get_scenario))
307 .route("/__mockforge/scenarios/{id}/export", get(behavioral_cloning::export_scenario))
308 .route("/health/live", get(health::liveness_probe))
310 .route("/health/ready", get(health::readiness_probe))
311 .route("/health/startup", get(health::startup_probe))
312 .route("/health", get(health::deep_health_check))
313 .route("/healthz", get(health::deep_health_check))
315 .route("/readyz", get(health::readiness_probe))
316 .route("/livez", get(health::liveness_probe))
317 .route("/startupz", get(health::startup_probe));
318
319 let analytics_state = AnalyticsState::new(prometheus_url);
321
322 let analytics_router = Router::new()
323 .route("/__mockforge/analytics/summary", get(analytics::get_summary))
324 .route("/__mockforge/analytics/requests", get(analytics::get_requests))
325 .route("/__mockforge/analytics/endpoints", get(analytics::get_endpoints))
326 .route("/__mockforge/analytics/websocket", get(analytics::get_websocket))
327 .route("/__mockforge/analytics/smtp", get(analytics::get_smtp))
328 .route("/__mockforge/analytics/system", get(analytics::get_system))
329 .with_state(analytics_state);
330
331 router = router.merge(analytics_router);
332
333 {
335 use crate::handlers::workspaces::WorkspaceState;
336 use mockforge_core::multi_tenant::{MultiTenantConfig, MultiTenantWorkspaceRegistry};
337 use std::sync::Arc;
338
339 let mt_config = MultiTenantConfig {
341 enabled: true,
342 default_workspace: "default".to_string(),
343 ..Default::default()
344 };
345 let registry = MultiTenantWorkspaceRegistry::new(mt_config);
346 let workspace_state = WorkspaceState::new(Arc::new(tokio::sync::RwLock::new(registry)));
347
348 use crate::handlers::workspaces;
350 let workspace_router = Router::new()
351 .route("/__mockforge/workspaces", get(workspaces::list_workspaces))
352 .route("/__mockforge/workspaces", post(workspaces::create_workspace))
353 .route("/__mockforge/workspaces/{workspace_id}", get(workspaces::get_workspace))
354 .route(
355 "/__mockforge/workspaces/{workspace_id}",
356 axum::routing::put(workspaces::update_workspace),
357 )
358 .route("/__mockforge/workspaces/{workspace_id}", delete(workspaces::delete_workspace))
359 .route("/api/v2/voice/create-workspace-confirm", post(voice::create_workspace_confirm))
365 .route(
366 "/__mockforge/voice/create-workspace-confirm",
367 post(voice::create_workspace_confirm),
368 )
369 .with_state(workspace_state);
370
371 router = router.merge(workspace_router);
372 tracing::info!("Workspace router mounted with WorkspaceState");
373 }
374
375 {
378 use mockforge_http::{create_ui_builder_router, UIBuilderState};
379
380 let server_config = mockforge_core::ServerConfig::default();
384 let ui_builder_state = UIBuilderState::new(server_config);
385 let ui_builder_router = create_ui_builder_router(ui_builder_state);
386
387 router = router.nest_service("/__mockforge/ui-builder", ui_builder_router);
389 tracing::info!("UI Builder mounted at /__mockforge/ui-builder");
390 }
391
392 router = router.route("/{*path}", get(serve_admin_html));
395
396 router = router.layer(from_fn(rbac_middleware));
400
401 router
402 .layer(CompressionLayer::new())
403 .layer(CorsLayer::permissive())
404 .with_state(state)
405}
406
407#[cfg(test)]
408mod tests {
409 use super::*;
410
411 #[tokio::test]
412 async fn test_create_admin_router() {
413 let http_addr: std::net::SocketAddr = "127.0.0.1:3000".parse().unwrap();
414 let router = create_admin_router(
415 Some(http_addr),
416 None,
417 None,
418 None,
419 true,
420 8080,
421 "http://localhost:9090".to_string(),
422 None,
423 None,
424 None,
425 None,
426 None,
427 );
428
429 let _ = router;
431 }
432
433 #[tokio::test]
434 async fn test_create_admin_router_no_servers() {
435 let router = create_admin_router(
436 None,
437 None,
438 None,
439 None,
440 false,
441 8080,
442 "http://localhost:9090".to_string(),
443 None,
444 None,
445 None,
446 None,
447 None,
448 );
449
450 let _ = router;
452 }
453}