Add DAST, graph modules, toast notifications, and dashboard enhancements
Add DAST scanning and code knowledge graph features across the stack: - compliance-dast and compliance-graph workspace crates - Agent API handlers and routes for DAST targets/scans and graph builds - Core models and traits for DAST and graph domains - Dashboard pages for DAST targets/findings/overview and graph explorer/impact - Toast notification system with auto-dismiss for async action feedback - Button click animations and disabled states for better UX Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
03ee69834d
commit
cea8f59e10
@@ -0,0 +1,105 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::components::page_header::PageHeader;
|
||||
use crate::components::toast::{ToastType, Toasts};
|
||||
use crate::infrastructure::graph::{fetch_graph, trigger_graph_build};
|
||||
|
||||
#[component]
|
||||
pub fn GraphExplorerPage(repo_id: String) -> Element {
|
||||
let repo_id_clone = repo_id.clone();
|
||||
let mut graph_data = use_resource(move || {
|
||||
let rid = repo_id_clone.clone();
|
||||
async move {
|
||||
if rid.is_empty() {
|
||||
return None;
|
||||
}
|
||||
fetch_graph(rid).await.ok()
|
||||
}
|
||||
});
|
||||
|
||||
let mut building = use_signal(|| false);
|
||||
let mut toasts = use_context::<Toasts>();
|
||||
|
||||
rsx! {
|
||||
PageHeader {
|
||||
title: "Code Knowledge Graph",
|
||||
description: "Interactive visualization of code structure and relationships",
|
||||
}
|
||||
|
||||
if repo_id.is_empty() {
|
||||
div { class: "card",
|
||||
p { "Select a repository to view its code graph." }
|
||||
p { "You can trigger a graph build from the Repositories page." }
|
||||
}
|
||||
} else {
|
||||
div { style: "margin-bottom: 16px;",
|
||||
button {
|
||||
class: "btn btn-primary",
|
||||
disabled: building(),
|
||||
onclick: {
|
||||
let rid = repo_id.clone();
|
||||
move |_| {
|
||||
let rid = rid.clone();
|
||||
building.set(true);
|
||||
spawn(async move {
|
||||
match trigger_graph_build(rid).await {
|
||||
Ok(_) => toasts.push(ToastType::Success, "Graph build triggered"),
|
||||
Err(e) => toasts.push(ToastType::Error, e.to_string()),
|
||||
}
|
||||
building.set(false);
|
||||
graph_data.restart();
|
||||
});
|
||||
}
|
||||
},
|
||||
if building() { "Building..." } else { "Build Graph" }
|
||||
}
|
||||
}
|
||||
|
||||
div { class: "card",
|
||||
h3 { "Graph Explorer \u{2014} {repo_id}" }
|
||||
|
||||
match &*graph_data.read() {
|
||||
Some(Some(data)) => {
|
||||
let build = data.data.build.clone().unwrap_or_default();
|
||||
let node_count = build.get("node_count").and_then(|n| n.as_u64()).unwrap_or(0);
|
||||
let edge_count = build.get("edge_count").and_then(|n| n.as_u64()).unwrap_or(0);
|
||||
let community_count = build.get("community_count").and_then(|n| n.as_u64()).unwrap_or(0);
|
||||
rsx! {
|
||||
div { class: "grid grid-cols-3 gap-4 mb-4",
|
||||
div { class: "stat-card",
|
||||
div { class: "stat-value", "{node_count}" }
|
||||
div { class: "stat-label", "Nodes" }
|
||||
}
|
||||
div { class: "stat-card",
|
||||
div { class: "stat-value", "{edge_count}" }
|
||||
div { class: "stat-label", "Edges" }
|
||||
}
|
||||
div { class: "stat-card",
|
||||
div { class: "stat-value", "{community_count}" }
|
||||
div { class: "stat-label", "Communities" }
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
id: "graph-container",
|
||||
style: "width: 100%; height: 600px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-secondary);",
|
||||
}
|
||||
|
||||
script {
|
||||
r#"
|
||||
console.log('Graph explorer loaded');
|
||||
"#
|
||||
}
|
||||
}
|
||||
},
|
||||
Some(None) => rsx! {
|
||||
p { "No graph data available. Build the graph first." }
|
||||
},
|
||||
None => rsx! {
|
||||
p { "Loading graph data..." }
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user