herolib-mos 0.3.13

Mycelium Operating System (MOS) - Network and VM abstraction layer
Documentation
use crate::systemfacts::{
    os::OsInfo,
    hardware::{CPUInfo, DiskInfo, NetworkInterface, MemoryInfo, GPUInfo},
    capabilities::SystemCapabilities,
};
use crate::mui::widgets::{progress_bar};
// use comfy_table::{Cell, Color, Attribute}; - Removed unused

pub struct SystemSection;

impl SystemSection {
    pub fn render(os_info: &OsInfo) -> Vec<String> {
        let mut lines = Vec::new();
        lines.push(format!("Hostname: {}", os_info.hostname)); 
        lines.push(format!("OS:       {} ({})", os_info.distribution.name, os_info.distribution.version));
        lines.push(format!("Kernel:   {}", os_info.kernel_version));
        lines.push(format!("Timezone: {}", os_info.timezone));
        
        let uptime_secs = os_info.uptime.as_secs();
        let days = uptime_secs / 86400;
        let hours = (uptime_secs % 86400) / 3600;
        let minutes = (uptime_secs % 3600) / 60;
        lines.push(format!("Uptime:   {}d {}h {}m", days, hours, minutes));
        
        // Load Avg omitted as per plan
        lines
    }
}

pub struct ResourcesSection;

impl ResourcesSection {
    pub fn render(cpu: &CPUInfo, memory: &MemoryInfo, gpus: &[GPUInfo]) -> Vec<String> {
        let mut lines = Vec::new();
        lines.push("CPU:".to_string());
        
        let width = 30;
        let bar = progress_bar(cpu.usage, width);
        lines.push(format!("[{}]", bar));
        lines.push(format!("Usage: {:.0}%  Cores: {}", cpu.usage, cpu.logical_cores));
        
        lines.push("".to_string());
        lines.push("Memory:".to_string());
        let mem_used = memory.used_kib as f64;
        let mem_total = memory.total_kib as f64;
        let mem_usage_pct = if mem_total > 0.0 { (mem_used / mem_total) * 100.0 } else { 0.0 };
        let mem_bar = progress_bar(mem_usage_pct as f32, width);
        lines.push(format!("[{}]", mem_bar));
        
        let used_gb = mem_used / (1024.0 * 1024.0);
        let total_gb = mem_total / (1024.0 * 1024.0);
        lines.push(format!("Usage: {:.0}%  {:.1}GB / {:.1}GB", mem_usage_pct, used_gb, total_gb));
        
        if !gpus.is_empty() {
             lines.push("".to_string());
             lines.push("GPU:".to_string());
             for gpu in gpus {
                 lines.push(format!("- {}", gpu.name));
                 // Optionally add driver info if space permits
                 // lines.push(format!("  Driver: {}", gpu.driver)); 
             }
        }

        lines
    }
}

pub struct StorageSection;

impl StorageSection {
    pub fn render(disks: &[DiskInfo]) -> Vec<String> {
        let mut lines = Vec::new();
        
        if disks.is_empty() {
             lines.push("No disks found".to_string());
             return lines;
        }

        for disk in disks {
            // Show all disks for now, or filter if list is too long
            // Simplification: just show name and usage
            let total = disk.total_space as f64;
            let used = (disk.total_space.saturating_sub(disk.available_space)) as f64;
            let usage_pct = if total > 0.0 { (used / total) * 100.0 } else { 0.0 };
            
            lines.push(format!("{} ({}):", disk.name, disk.mount_point));
            
            let width = 20;
            let bar = progress_bar(usage_pct as f32, width);
            lines.push(format!("[{}]", bar));
            
            // Convert to GB
            let used_gb = used / 1_073_741_824.0;
            let total_gb = total / 1_073_741_824.0;
            lines.push(format!("{:.0}%  {:.1}GB / {:.1}GB", usage_pct, used_gb, total_gb));
            lines.push("".to_string());
        }
        
        // Remove last empty line if exists
        if let Some(last) = lines.last() {
            if last.is_empty() {
                lines.pop();
            }
        }
        
        lines
    }
}

pub struct NetworkSection;

impl NetworkSection {
    pub fn render(interfaces: &[NetworkInterface]) -> Vec<String> {
        let mut lines = Vec::new();
        
        for iface in interfaces {
            // Filter loopback if desired, but showing all for transparency is fine
            if iface.name == "lo" { continue; }

            // Format: eth0: 192.168.1.5
            let ips = if iface.ip_addresses.is_empty() {
                "No IP".to_string()
            } else {
                iface.ip_addresses.join(", ")
            };
            
            lines.push(format!("{}: {}", iface.name, ips));
        }
        
        // Mycelium check - strictly using available inspect if we knew the path, 
        // but without a specific seed file path or config exposed in systemfacts, 
        // we might just skip or show a static message for now as per "placeholder" agreement 
        // if we can't easily find it key.
        // However, we can use the `mycelium` module if we had the seed.
        // Since we don't have the seed here, we'll omit or show "Mycelium: Not configured"
        
        lines
    }
}

pub struct CapabilitiesSection;

impl CapabilitiesSection {
    pub fn render(caps: &SystemCapabilities) -> Vec<String> {
        let mut lines = Vec::new();
        
        fn bool_str(b: bool) -> &'static str {
            if b { "[Yes]" } else { "[No] " }
        }

        lines.push(format!("{} Virtualization", bool_str(caps.virtualization)));
        lines.push(format!("{} KVM Support", bool_str(caps.kvm_support)));
        lines.push(format!("{} Docker", bool_str(caps.docker_support)));
        lines.push(format!("{} Systemd", bool_str(caps.systemd_support)));
        lines.push(format!("CGroup Version: v{}", caps.cgroup_version));

        lines
    }
}