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:
186
compliance-core/src/models/graph.rs
Normal file
186
compliance-core/src/models/graph.rs
Normal file
@@ -0,0 +1,186 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Type of code node in the knowledge graph
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CodeNodeKind {
|
||||
Function,
|
||||
Method,
|
||||
Class,
|
||||
Struct,
|
||||
Enum,
|
||||
Interface,
|
||||
Trait,
|
||||
Module,
|
||||
File,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CodeNodeKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Function => write!(f, "function"),
|
||||
Self::Method => write!(f, "method"),
|
||||
Self::Class => write!(f, "class"),
|
||||
Self::Struct => write!(f, "struct"),
|
||||
Self::Enum => write!(f, "enum"),
|
||||
Self::Interface => write!(f, "interface"),
|
||||
Self::Trait => write!(f, "trait"),
|
||||
Self::Module => write!(f, "module"),
|
||||
Self::File => write!(f, "file"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A node in the code knowledge graph
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CodeNode {
|
||||
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<bson::oid::ObjectId>,
|
||||
pub repo_id: String,
|
||||
pub graph_build_id: String,
|
||||
/// Unique identifier within the graph (e.g., "src/main.rs::main")
|
||||
pub qualified_name: String,
|
||||
pub name: String,
|
||||
pub kind: CodeNodeKind,
|
||||
pub file_path: String,
|
||||
pub start_line: u32,
|
||||
pub end_line: u32,
|
||||
/// Language of the source file
|
||||
pub language: String,
|
||||
/// Community ID from Louvain clustering
|
||||
pub community_id: Option<u32>,
|
||||
/// Whether this is a public entry point (main, exported fn, HTTP handler, etc.)
|
||||
pub is_entry_point: bool,
|
||||
/// Internal petgraph node index for fast lookups
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub graph_index: Option<u32>,
|
||||
}
|
||||
|
||||
/// Type of relationship between code nodes
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CodeEdgeKind {
|
||||
Calls,
|
||||
Imports,
|
||||
Inherits,
|
||||
Implements,
|
||||
Contains,
|
||||
/// A type reference (e.g., function parameter type, return type)
|
||||
TypeRef,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CodeEdgeKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Calls => write!(f, "calls"),
|
||||
Self::Imports => write!(f, "imports"),
|
||||
Self::Inherits => write!(f, "inherits"),
|
||||
Self::Implements => write!(f, "implements"),
|
||||
Self::Contains => write!(f, "contains"),
|
||||
Self::TypeRef => write!(f, "type_ref"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An edge in the code knowledge graph
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CodeEdge {
|
||||
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<bson::oid::ObjectId>,
|
||||
pub repo_id: String,
|
||||
pub graph_build_id: String,
|
||||
/// Qualified name of source node
|
||||
pub source: String,
|
||||
/// Qualified name of target node
|
||||
pub target: String,
|
||||
pub kind: CodeEdgeKind,
|
||||
/// File where this relationship was found
|
||||
pub file_path: String,
|
||||
pub line_number: Option<u32>,
|
||||
}
|
||||
|
||||
/// Status of a graph build operation
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GraphBuildStatus {
|
||||
Running,
|
||||
Completed,
|
||||
Failed,
|
||||
}
|
||||
|
||||
/// Tracks a graph build operation for a repo/commit
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GraphBuildRun {
|
||||
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<bson::oid::ObjectId>,
|
||||
pub repo_id: String,
|
||||
pub commit_sha: Option<String>,
|
||||
pub status: GraphBuildStatus,
|
||||
pub node_count: u32,
|
||||
pub edge_count: u32,
|
||||
pub community_count: u32,
|
||||
pub languages_parsed: Vec<String>,
|
||||
pub error_message: Option<String>,
|
||||
pub started_at: DateTime<Utc>,
|
||||
pub completed_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl GraphBuildRun {
|
||||
pub fn new(repo_id: String) -> Self {
|
||||
Self {
|
||||
id: None,
|
||||
repo_id,
|
||||
commit_sha: None,
|
||||
status: GraphBuildStatus::Running,
|
||||
node_count: 0,
|
||||
edge_count: 0,
|
||||
community_count: 0,
|
||||
languages_parsed: Vec::new(),
|
||||
error_message: None,
|
||||
started_at: Utc::now(),
|
||||
completed_at: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Impact analysis result for a finding
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ImpactAnalysis {
|
||||
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<bson::oid::ObjectId>,
|
||||
pub repo_id: String,
|
||||
pub finding_id: String,
|
||||
pub graph_build_id: String,
|
||||
/// Number of nodes reachable from the finding location
|
||||
pub blast_radius: u32,
|
||||
/// Entry points affected by this finding (via reverse call chain)
|
||||
pub affected_entry_points: Vec<String>,
|
||||
/// Call chains from entry points to the finding location
|
||||
pub call_chains: Vec<Vec<String>>,
|
||||
/// Community IDs affected
|
||||
pub affected_communities: Vec<u32>,
|
||||
/// Direct callers of the affected function
|
||||
pub direct_callers: Vec<String>,
|
||||
/// Direct callees of the affected function
|
||||
pub direct_callees: Vec<String>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl ImpactAnalysis {
|
||||
pub fn new(repo_id: String, finding_id: String, graph_build_id: String) -> Self {
|
||||
Self {
|
||||
id: None,
|
||||
repo_id,
|
||||
finding_id,
|
||||
graph_build_id,
|
||||
blast_radius: 0,
|
||||
affected_entry_points: Vec::new(),
|
||||
call_chains: Vec::new(),
|
||||
affected_communities: Vec::new(),
|
||||
direct_callers: Vec::new(),
|
||||
direct_callees: Vec::new(),
|
||||
created_at: Utc::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user