use dioxus::prelude::*; use dioxus_free_icons::icons::bs_icons::*; use dioxus_free_icons::Icon; use crate::app::Route; use crate::components::page_header::PageHeader; use crate::components::pentest_wizard::PentestWizard; use crate::infrastructure::pentest::{ fetch_pentest_sessions, fetch_pentest_stats, pause_pentest_session, resume_pentest_session, stop_pentest_session, }; #[component] pub fn PentestDashboardPage() -> Element { let mut sessions = use_resource(|| async { fetch_pentest_sessions().await.ok() }); let stats = use_resource(|| async { fetch_pentest_stats().await.ok() }); let mut show_wizard = use_signal(|| false); // Extract stats values let running_sessions = { let s = stats.read(); match &*s { Some(Some(data)) => data .data .get("running_sessions") .and_then(|v| v.as_u64()) .unwrap_or(0), _ => 0, } }; let total_vulns = { let s = stats.read(); match &*s { Some(Some(data)) => data .data .get("total_vulnerabilities") .and_then(|v| v.as_u64()) .unwrap_or(0), _ => 0, } }; let tool_invocations = { let s = stats.read(); match &*s { Some(Some(data)) => data .data .get("total_tool_invocations") .and_then(|v| v.as_u64()) .unwrap_or(0), _ => 0, } }; let success_rate = { let s = stats.read(); match &*s { Some(Some(data)) => data .data .get("tool_success_rate") .and_then(|v| v.as_f64()) .unwrap_or(0.0), _ => 0.0, } }; // Severity counts from stats (nested under severity_distribution) let sev_dist = { let s = stats.read(); match &*s { Some(Some(data)) => data .data .get("severity_distribution") .cloned() .unwrap_or(serde_json::Value::Null), _ => serde_json::Value::Null, } }; let severity_critical = sev_dist .get("critical") .and_then(|v| v.as_u64()) .unwrap_or(0); let severity_high = sev_dist.get("high").and_then(|v| v.as_u64()).unwrap_or(0); let severity_medium = sev_dist.get("medium").and_then(|v| v.as_u64()).unwrap_or(0); let severity_low = sev_dist.get("low").and_then(|v| v.as_u64()).unwrap_or(0); rsx! { PageHeader { title: "Pentest Dashboard", description: "AI-powered penetration testing sessions — autonomous security assessment", } // Stat cards div { class: "stat-cards", style: "margin-bottom: 24px;", div { class: "stat-card-item", div { class: "stat-card-value", "{running_sessions}" } div { class: "stat-card-label", Icon { icon: BsPlayCircle, width: 14, height: 14 } " Running Sessions" } } div { class: "stat-card-item", div { class: "stat-card-value", "{total_vulns}" } div { class: "stat-card-label", Icon { icon: BsShieldExclamation, width: 14, height: 14 } " Total Vulnerabilities" } } div { class: "stat-card-item", div { class: "stat-card-value", "{tool_invocations}" } div { class: "stat-card-label", Icon { icon: BsWrench, width: 14, height: 14 } " Tool Invocations" } } div { class: "stat-card-item", div { class: "stat-card-value", "{success_rate:.0}%" } div { class: "stat-card-label", Icon { icon: BsCheckCircle, width: 14, height: 14 } " Success Rate" } } } // Severity distribution div { class: "card", style: "margin-bottom: 24px; padding: 16px;", div { style: "display: flex; align-items: center; gap: 16px; flex-wrap: wrap;", span { style: "font-weight: 600; color: var(--text-secondary); font-size: 0.85rem;", "Severity Distribution" } span { class: "badge", style: "background: #dc2626; color: #fff;", "Critical: {severity_critical}" } span { class: "badge", style: "background: #ea580c; color: #fff;", "High: {severity_high}" } span { class: "badge", style: "background: #d97706; color: #fff;", "Medium: {severity_medium}" } span { class: "badge", style: "background: #2563eb; color: #fff;", "Low: {severity_low}" } } } // Actions row div { style: "display: flex; gap: 12px; margin-bottom: 24px;", button { class: "btn btn-primary", onclick: move |_| show_wizard.set(true), Icon { icon: BsPlusCircle, width: 14, height: 14 } " New Pentest" } } // Sessions list div { class: "card", div { class: "card-header", "Recent Pentest Sessions" } match &*sessions.read() { Some(Some(data)) => { let sess_list = &data.data; if sess_list.is_empty() { rsx! { div { style: "padding: 32px; text-align: center; color: var(--text-secondary);", p { "No pentest sessions yet. Start one to begin autonomous security testing." } } } } else { rsx! { div { style: "display: grid; gap: 12px; padding: 16px;", for session in sess_list { { let id = session.get("_id") .and_then(|v| v.get("$oid")) .and_then(|v| v.as_str()) .unwrap_or("-").to_string(); let target_name = session.get("target_name").and_then(|v| v.as_str()).unwrap_or("Unknown Target").to_string(); let status = session.get("status").and_then(|v| v.as_str()).unwrap_or("unknown").to_string(); let strategy = session.get("strategy").and_then(|v| v.as_str()).unwrap_or("-").to_string(); let findings_count = session.get("findings_count").and_then(|v| v.as_u64()).unwrap_or(0); let tool_count = session.get("tool_invocations").and_then(|v| v.as_u64()).unwrap_or(0); let created_at = session.get("created_at").and_then(|v| v.as_str()).unwrap_or("-").to_string(); let status_style = match status.as_str() { "running" => "background: #16a34a; color: #fff;", "completed" => "background: #2563eb; color: #fff;", "failed" => "background: #dc2626; color: #fff;", "paused" => "background: #d97706; color: #fff;", _ => "background: var(--bg-tertiary); color: var(--text-secondary);", }; { let is_session_running = status == "running"; let is_session_paused = status == "paused"; let stop_id = id.clone(); let pause_id = id.clone(); let resume_id = id.clone(); rsx! { div { class: "card", style: "padding: 16px; transition: border-color 0.15s;", Link { to: Route::PentestSessionPage { session_id: id.clone() }, style: "text-decoration: none; cursor: pointer; display: block;", div { style: "display: flex; justify-content: space-between; align-items: flex-start;", div { div { style: "font-weight: 600; font-size: 1rem; margin-bottom: 4px; color: var(--text-primary);", "{target_name}" } div { style: "display: flex; gap: 8px; align-items: center; flex-wrap: wrap;", span { class: "badge", style: "{status_style}", "{status}" } span { class: "badge", style: "background: var(--bg-tertiary); color: var(--text-secondary);", "{strategy}" } } } div { style: "text-align: right; font-size: 0.85rem; color: var(--text-secondary);", div { style: "margin-bottom: 4px;", Icon { icon: BsShieldExclamation, width: 12, height: 12 } " {findings_count} findings" } div { style: "margin-bottom: 4px;", Icon { icon: BsWrench, width: 12, height: 12 } " {tool_count} tools" } div { "{created_at}" } } } } if is_session_running || is_session_paused { div { style: "margin-top: 8px; display: flex; justify-content: flex-end; gap: 6px;", if is_session_running { button { class: "btn btn-ghost", style: "font-size: 0.8rem; padding: 4px 12px; color: #d97706; border-color: #d97706;", onclick: move |e| { e.stop_propagation(); e.prevent_default(); let sid = pause_id.clone(); spawn(async move { let _ = pause_pentest_session(sid).await; sessions.restart(); }); }, Icon { icon: BsPauseCircle, width: 12, height: 12 } " Pause" } } if is_session_paused { button { class: "btn btn-ghost", style: "font-size: 0.8rem; padding: 4px 12px; color: #16a34a; border-color: #16a34a;", onclick: move |e| { e.stop_propagation(); e.prevent_default(); let sid = resume_id.clone(); spawn(async move { let _ = resume_pentest_session(sid).await; sessions.restart(); }); }, Icon { icon: BsPlayCircle, width: 12, height: 12 } " Resume" } } button { class: "btn btn-ghost", style: "font-size: 0.8rem; padding: 4px 12px; color: #dc2626; border-color: #dc2626;", onclick: move |e| { e.stop_propagation(); e.prevent_default(); let sid = stop_id.clone(); spawn(async move { let _ = stop_pentest_session(sid).await; sessions.restart(); }); }, Icon { icon: BsStopCircle, width: 12, height: 12 } " Stop" } } } } } } } } } } } }, Some(None) => rsx! { p { style: "padding: 16px;", "Failed to load sessions." } }, None => rsx! { p { style: "padding: 16px;", "Loading..." } }, } } // Pentest Wizard if *show_wizard.read() { PentestWizard { show: show_wizard } } } }