Gunnar

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
    }
}