1use axum::{
7 extract::{Path, Query, State},
8 http::StatusCode,
9 response::Json,
10};
11use mockforge_core::security::compliance_dashboard::{
12 AlertType, ComplianceDashboardEngine, ComplianceStandard, GapSeverity,
13};
14use serde::Deserialize;
15use std::collections::HashMap;
16use std::sync::Arc;
17use tokio::sync::RwLock;
18use tracing::{error, info};
19
20#[derive(Clone)]
22pub struct ComplianceDashboardState {
23 pub engine: Arc<RwLock<ComplianceDashboardEngine>>,
25}
26
27#[derive(Debug, Deserialize)]
29pub struct AddGapRequest {
30 pub description: String,
32 pub severity: GapSeverity,
34 pub standard: ComplianceStandard,
36 pub control_id: Option<String>,
38 pub target_remediation_date: Option<chrono::DateTime<chrono::Utc>>,
40}
41
42#[derive(Debug, Deserialize)]
44pub struct AddAlertRequest {
45 pub alert_type: AlertType,
47 pub severity: GapSeverity,
49 pub message: String,
51 pub standard: Option<ComplianceStandard>,
53 pub control_id: Option<String>,
55}
56
57#[derive(Debug, Deserialize)]
59pub struct UpdateGapStatusRequest {
60 pub status: mockforge_core::security::compliance_dashboard::GapStatus,
62}
63
64pub async fn get_dashboard(
68 State(state): State<ComplianceDashboardState>,
69) -> Result<Json<serde_json::Value>, StatusCode> {
70 let engine = state.engine.read().await;
71 let dashboard = engine.get_dashboard_data().await.map_err(|e| {
72 error!("Failed to get dashboard data: {}", e);
73 StatusCode::INTERNAL_SERVER_ERROR
74 })?;
75
76 Ok(Json(serde_json::to_value(&dashboard).unwrap()))
77}
78
79pub async fn get_gaps(
83 State(state): State<ComplianceDashboardState>,
84 Query(params): Query<HashMap<String, String>>,
85) -> Result<Json<serde_json::Value>, StatusCode> {
86 let engine = state.engine.read().await;
87
88 let gaps = if let Some(severity_str) = params.get("severity") {
89 let severity = match severity_str.as_str() {
90 "critical" => GapSeverity::Critical,
91 "high" => GapSeverity::High,
92 "medium" => GapSeverity::Medium,
93 "low" => GapSeverity::Low,
94 _ => return Err(StatusCode::BAD_REQUEST),
95 };
96 engine.get_gaps_by_severity(severity).await.map_err(|e| {
97 error!("Failed to get gaps by severity: {}", e);
98 StatusCode::INTERNAL_SERVER_ERROR
99 })?
100 } else {
101 engine.get_all_gaps().await.map_err(|e| {
102 error!("Failed to get all gaps: {}", e);
103 StatusCode::INTERNAL_SERVER_ERROR
104 })?
105 };
106
107 Ok(Json(serde_json::to_value(&gaps).unwrap()))
108}
109
110pub async fn add_gap(
114 State(state): State<ComplianceDashboardState>,
115 Json(request): Json<AddGapRequest>,
116) -> Result<Json<serde_json::Value>, StatusCode> {
117 let gap_id = format!("GAP-{}", uuid::Uuid::new_v4().to_string().split('-').next().unwrap());
118
119 let engine = state.engine.write().await;
120 engine
121 .add_gap(
122 gap_id.clone(),
123 request.description,
124 request.severity,
125 request.standard,
126 request.control_id,
127 request.target_remediation_date,
128 )
129 .await
130 .map_err(|e| {
131 error!("Failed to add gap: {}", e);
132 StatusCode::INTERNAL_SERVER_ERROR
133 })?;
134
135 info!("Compliance gap added: {}", gap_id);
136
137 Ok(Json(serde_json::json!({
138 "gap_id": gap_id,
139 "status": "created"
140 })))
141}
142
143pub async fn update_gap_status(
147 State(state): State<ComplianceDashboardState>,
148 Path(gap_id): Path<String>,
149 Json(request): Json<UpdateGapStatusRequest>,
150) -> Result<Json<serde_json::Value>, StatusCode> {
151 let engine = state.engine.write().await;
152 engine.update_gap_status(&gap_id, request.status).await.map_err(|e| {
153 error!("Failed to update gap status: {}", e);
154 StatusCode::BAD_REQUEST
155 })?;
156
157 info!("Gap status updated: {}", gap_id);
158
159 Ok(Json(serde_json::json!({
160 "gap_id": gap_id,
161 "status": "updated"
162 })))
163}
164
165pub async fn get_alerts(
169 State(state): State<ComplianceDashboardState>,
170 Query(params): Query<HashMap<String, String>>,
171) -> Result<Json<serde_json::Value>, StatusCode> {
172 let engine = state.engine.read().await;
173
174 let alerts = if let Some(severity_str) = params.get("severity") {
175 let severity = match severity_str.as_str() {
176 "critical" => GapSeverity::Critical,
177 "high" => GapSeverity::High,
178 "medium" => GapSeverity::Medium,
179 "low" => GapSeverity::Low,
180 _ => return Err(StatusCode::BAD_REQUEST),
181 };
182 engine.get_alerts_by_severity(severity).await.map_err(|e| {
183 error!("Failed to get alerts by severity: {}", e);
184 StatusCode::INTERNAL_SERVER_ERROR
185 })?
186 } else {
187 engine.get_all_alerts().await.map_err(|e| {
188 error!("Failed to get all alerts: {}", e);
189 StatusCode::INTERNAL_SERVER_ERROR
190 })?
191 };
192
193 Ok(Json(serde_json::to_value(&alerts).unwrap()))
194}
195
196pub async fn add_alert(
200 State(state): State<ComplianceDashboardState>,
201 Json(request): Json<AddAlertRequest>,
202) -> Result<Json<serde_json::Value>, StatusCode> {
203 let alert_id = format!("ALERT-{}", uuid::Uuid::new_v4().to_string().split('-').next().unwrap());
204
205 let engine = state.engine.write().await;
206 engine
207 .add_alert(
208 alert_id.clone(),
209 request.alert_type,
210 request.severity,
211 request.message,
212 request.standard,
213 request.control_id,
214 )
215 .await
216 .map_err(|e| {
217 error!("Failed to add alert: {}", e);
218 StatusCode::INTERNAL_SERVER_ERROR
219 })?;
220
221 info!("Compliance alert added: {}", alert_id);
222
223 Ok(Json(serde_json::json!({
224 "alert_id": alert_id,
225 "status": "created"
226 })))
227}
228
229pub async fn get_compliance_status(
233 State(state): State<ComplianceDashboardState>,
234) -> Result<Json<serde_json::Value>, StatusCode> {
235 let engine = state.engine.read().await;
236 let dashboard = engine.get_dashboard_data().await.map_err(|e| {
237 error!("Failed to get dashboard data: {}", e);
238 StatusCode::INTERNAL_SERVER_ERROR
239 })?;
240
241 let mut by_area = serde_json::Map::new();
243 for (category, effectiveness) in &dashboard.control_effectiveness {
244 let category_name = match category {
245 mockforge_core::security::compliance_dashboard::ControlCategory::AccessControl => {
246 "access_control"
247 }
248 mockforge_core::security::compliance_dashboard::ControlCategory::Encryption => {
249 "encryption"
250 }
251 mockforge_core::security::compliance_dashboard::ControlCategory::Monitoring => {
252 "monitoring"
253 }
254 mockforge_core::security::compliance_dashboard::ControlCategory::ChangeManagement => {
255 "change_management"
256 }
257 mockforge_core::security::compliance_dashboard::ControlCategory::IncidentResponse => {
258 "incident_response"
259 }
260 };
261 by_area.insert(
262 category_name.to_string(),
263 serde_json::Value::Number(effectiveness.effectiveness.into()),
264 );
265 }
266
267 Ok(Json(serde_json::json!({
268 "overall_compliance": dashboard.overall_compliance,
269 "soc2_compliance": dashboard.soc2_compliance,
270 "iso27001_compliance": dashboard.iso27001_compliance,
271 "by_area": by_area,
272 "gaps": dashboard.gaps.total,
273 "remediation_in_progress": dashboard.remediation.in_progress
274 })))
275}
276
277pub async fn get_compliance_report(
281 State(state): State<ComplianceDashboardState>,
282 Path(period): Path<String>,
283 Query(params): Query<HashMap<String, String>>,
284) -> Result<Json<serde_json::Value>, StatusCode> {
285 let engine = state.engine.read().await;
286 let dashboard = engine.get_dashboard_data().await.map_err(|e| {
287 error!("Failed to get dashboard data: {}", e);
288 StatusCode::INTERNAL_SERVER_ERROR
289 })?;
290
291 let report_period = params
293 .get("month")
294 .or_else(|| params.get("period"))
295 .cloned()
296 .unwrap_or_else(|| {
297 let now = chrono::Utc::now();
299 now.format("%Y-%m").to_string()
300 });
301
302 let all_gaps = engine.get_all_gaps().await.map_err(|e| {
304 error!("Failed to get gaps: {}", e);
305 StatusCode::INTERNAL_SERVER_ERROR
306 })?;
307
308 let mut recommendations = Vec::new();
310 for gap in &all_gaps {
311 match gap.severity {
312 mockforge_core::security::compliance_dashboard::GapSeverity::Critical => {
313 recommendations.push(format!("Urgent: {}", gap.description));
314 }
315 mockforge_core::security::compliance_dashboard::GapSeverity::High => {
316 recommendations.push(format!("High priority: {}", gap.description));
317 }
318 _ => {}
319 }
320 }
321
322 if recommendations.is_empty() {
324 if dashboard
325 .control_effectiveness
326 .get(&mockforge_core::security::compliance_dashboard::ControlCategory::ChangeManagement)
327 .map(|e| e.effectiveness < 95)
328 .unwrap_or(false)
329 {
330 recommendations.push("Enhance change management procedures".to_string());
331 }
332 if dashboard
333 .control_effectiveness
334 .get(&mockforge_core::security::compliance_dashboard::ControlCategory::IncidentResponse)
335 .map(|e| e.effectiveness < 95)
336 .unwrap_or(false)
337 {
338 recommendations.push("Improve incident response time".to_string());
339 }
340 }
341
342 let gaps_summary: Vec<serde_json::Value> = all_gaps
344 .iter()
345 .take(10)
346 .map(|gap| {
347 serde_json::json!({
348 "id": gap.gap_id,
349 "severity": format!("{:?}", gap.severity).to_lowercase(),
350 "remediation_status": format!("{:?}", gap.status).to_lowercase()
351 })
352 })
353 .collect();
354
355 let mut control_effectiveness = serde_json::Map::new();
357 for (category, effectiveness) in &dashboard.control_effectiveness {
358 let category_name = match category {
359 mockforge_core::security::compliance_dashboard::ControlCategory::AccessControl => {
360 "access_control"
361 }
362 mockforge_core::security::compliance_dashboard::ControlCategory::Encryption => {
363 "encryption"
364 }
365 mockforge_core::security::compliance_dashboard::ControlCategory::Monitoring => {
366 "monitoring"
367 }
368 mockforge_core::security::compliance_dashboard::ControlCategory::ChangeManagement => {
369 "change_management"
370 }
371 mockforge_core::security::compliance_dashboard::ControlCategory::IncidentResponse => {
372 "incident_response"
373 }
374 };
375 control_effectiveness.insert(
376 category_name.to_string(),
377 serde_json::Value::Number(effectiveness.effectiveness.into()),
378 );
379 }
380
381 Ok(Json(serde_json::json!({
382 "report_period": report_period,
383 "overall_compliance": dashboard.overall_compliance,
384 "control_effectiveness": control_effectiveness,
385 "gaps": gaps_summary,
386 "recommendations": recommendations
387 })))
388}
389
390pub fn compliance_dashboard_router(state: ComplianceDashboardState) -> axum::Router {
392 use axum::routing::{get, patch, post};
393
394 axum::Router::new()
395 .route("/dashboard", get(get_dashboard))
396 .route("/status", get(get_compliance_status))
397 .route("/reports/:period", get(get_compliance_report))
398 .route("/gaps", get(get_gaps))
399 .route("/gaps", post(add_gap))
400 .route("/gaps/{gap_id}/status", patch(update_gap_status))
401 .route("/alerts", get(get_alerts))
402 .route("/alerts", post(add_alert))
403 .with_state(state)
404}