use eframe::egui;
use egui::{RichText, Ui};
use facett::graph::{DepEdge, DepGraphView, DepNode};
use crate::data::{DepGraphData, RepoRow};
pub(super) fn render_security_graph(graph: &DepGraphData, ui: &mut Ui) {
if graph.nodes.is_empty() {
ui.label("no components to graph.");
return;
}
if !graph.has_edges {
ui.label(
RichText::new(
"no resolved dependency graph for this archive — showing components coloured by verdict",
)
.weak(),
);
}
ui.label(format!(
"nodes {} · block {} · warn {} · pass {}",
graph.nodes.len(),
graph.block,
graph.warn,
graph.pass,
));
let nodes: Vec<DepNode> = graph
.nodes
.iter()
.map(|n| {
let color = match n.decision.as_str() {
"block" => egui::Color32::from_rgb(255, 80, 80),
"warn" => egui::Color32::from_rgb(240, 200, 90),
_ => egui::Color32::from_rgb(90, 220, 140),
};
let sublabel = if n.ids.is_empty() {
n.version.clone()
} else {
format!("{} · {}", n.version, n.ids.join(", "))
};
DepNode { label: n.name.clone(), sublabel: Some(sublabel), color }
})
.collect();
let edges: Vec<DepEdge> =
graph.edges.iter().map(|(f, t)| DepEdge { from: *f, to: *t, via: Vec::new() }).collect();
DepGraphView::new(nodes, edges)
.with_title("Móðguðr — dependency verdict graph")
.show(ui);
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(super) enum GraphNodeKind {
Server,
Repository,
Artifact,
Upstream,
ReadOnly,
Quarantined,
}
impl GraphNodeKind {
pub(super) fn color(self) -> egui::Color32 {
match self {
GraphNodeKind::Server => egui::Color32::from_rgb(120, 220, 255),
GraphNodeKind::Repository => egui::Color32::from_rgb(80, 230, 160),
GraphNodeKind::Artifact => egui::Color32::from_rgb(210, 230, 90),
GraphNodeKind::Upstream => egui::Color32::from_rgb(255, 160, 70),
GraphNodeKind::ReadOnly => egui::Color32::from_rgb(150, 110, 240),
GraphNodeKind::Quarantined => egui::Color32::from_rgb(255, 60, 130),
}
}
pub(super) fn label(self) -> &'static str {
match self {
GraphNodeKind::Server => "server",
GraphNodeKind::Repository => "repository",
GraphNodeKind::Artifact => "artifact",
GraphNodeKind::Upstream => "upstream",
GraphNodeKind::ReadOnly => "read-only",
GraphNodeKind::Quarantined => "quarantined",
}
}
}
pub(super) fn repo_node_kind(r: &RepoRow) -> GraphNodeKind {
let t = r.repo_type.to_ascii_lowercase();
let n = r.name.to_ascii_lowercase();
if n.contains("quarant") || t.contains("quarant") || t.contains("bad") || t.contains("drift") {
GraphNodeKind::Quarantined
} else if t.contains("proxy") || t.contains("upstream") || t.contains("remote") || n.contains("cache") || t.contains("cache") {
GraphNodeKind::Upstream
} else if !r.writable {
GraphNodeKind::ReadOnly
} else {
GraphNodeKind::Repository
}
}