mikronova/kyokkou
src/system_topology.rs
use crate::common::{Scene, ValidationStatus, render_button, show_validation};
pub struct Node {
pub id: String,
pub label: String,
}
pub struct Edge {
pub from: String,
pub to: String,
}
pub struct SystemTopology {
pub nodes: Vec<Node>,
pub edges: Vec<Edge>,
pub import_source: String,
}
impl Default for SystemTopology {
fn default() -> Self {
Self {
nodes: vec![
Node { id: "a".into(), label: "web-01".into() },
Node { id: "b".into(), label: "db-01".into() },
],
edges: vec![
Edge { from: "a".into(), to: "b".into() },
],
import_source: String::new(),
}
}
}
impl Scene for SystemTopology {
fn name(&self) -> &str {
"System topology"
}
fn validate(&self) -> Vec<ValidationStatus> {
let mut v = vec![];
if self.nodes.len() > 50 {
v.push(ValidationStatus::Warning(
"Node count > 50 — readability may suffer".into(),
));
}
let orphaned = self.nodes.iter().filter(|n| {
!self.edges.iter().any(|e| e.from == n.id || e.to == n.id)
}).count();
if orphaned > 0 {
v.push(ValidationStatus::Warning(format!("{orphaned} orphaned node(s)")));
}
if self.nodes.is_empty() {
v.push(ValidationStatus::Error("No nodes defined".into()));
}
if v.is_empty() {
v.push(ValidationStatus::Ok);
}
v
}
fn show(&mut self, ui: &mut egui::Ui) -> Option<String> {
ui.heading("System topology");
ui.separator();
ui.label(format!("{} nodes, {} edges", self.nodes.len(), self.edges.len()));
ui.horizontal(|ui| {
ui.label("Import (JSON/YAML/Terraform):");
ui.text_edit_singleline(&mut self.import_source);
if ui.button("Load").clicked() {
// TODO: parse import_source and populate nodes/edges
}
});
ui.separator();
egui::ScrollArea::vertical().max_height(200.0).show(ui, |ui| {
for node in &self.nodes {
ui.label(format!(" [{}] {}", node.id, node.label));
}
});
ui.separator();
let statuses = self.validate();
show_validation(ui, &statuses);
ui.separator();
if render_button(ui, self.is_renderable()) {
// TODO: to_yaml() for system topology
}
None
}
}