diff --git a/CLAUDE.md b/CLAUDE.md index a1eafe6..f2619a8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -304,6 +304,12 @@ exclude_fs_types = ["tmpfs", "devtmpfs", "sysfs", "proc"] ### Display Format ``` +Network: +● eno1: + ├─ ip: 192.168.30.105 + └─ tailscale0: 100.125.108.16 +● eno2: + └─ ip: 192.168.32.105 CPU: ● Load: 0.23 0.21 0.13 └─ Freq: 1048 MHz diff --git a/Cargo.lock b/Cargo.lock index 61b4208..7c0d183 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -279,7 +279,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "cm-dashboard" -version = "0.1.165" +version = "0.1.166" dependencies = [ "anyhow", "chrono", @@ -301,7 +301,7 @@ dependencies = [ [[package]] name = "cm-dashboard-agent" -version = "0.1.165" +version = "0.1.166" dependencies = [ "anyhow", "async-trait", @@ -324,7 +324,7 @@ dependencies = [ [[package]] name = "cm-dashboard-shared" -version = "0.1.165" +version = "0.1.166" dependencies = [ "chrono", "serde", diff --git a/agent/Cargo.toml b/agent/Cargo.toml index 6c20f08..30135f7 100644 --- a/agent/Cargo.toml +++ b/agent/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-dashboard-agent" -version = "0.1.165" +version = "0.1.166" edition = "2021" [dependencies] diff --git a/dashboard/Cargo.toml b/dashboard/Cargo.toml index 0e1b5cd..57a764c 100644 --- a/dashboard/Cargo.toml +++ b/dashboard/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-dashboard" -version = "0.1.165" +version = "0.1.166" edition = "2021" [dependencies] diff --git a/dashboard/src/ui/widgets/system.rs b/dashboard/src/ui/widgets/system.rs index 66f00fa..3f4eb15 100644 --- a/dashboard/src/ui/widgets/system.rs +++ b/dashboard/src/ui/widgets/system.rs @@ -628,60 +628,65 @@ impl SystemWidget { let physical: Vec<_> = self.network_interfaces.iter().filter(|i| i.is_physical).collect(); let virtual_interfaces: Vec<_> = self.network_interfaces.iter().filter(|i| !i.is_physical).collect(); - // Render physical interfaces first - for (i, interface) in physical.iter().enumerate() { - let is_last = i == physical.len() - 1 && virtual_interfaces.is_empty(); - let tree_symbol = if is_last { " └─ " } else { " ├─ " }; + // Render physical interfaces + for (phy_idx, interface) in physical.iter().enumerate() { + let is_last_physical = phy_idx == physical.len() - 1 && virtual_interfaces.is_empty(); - // Show interface name with IPs - let mut interface_text = format!("{}: ", interface.name); - - // Add compressed IPv4 addresses - if !interface.ipv4_addresses.is_empty() { - interface_text.push_str(&Self::compress_ipv4_addresses(&interface.ipv4_addresses)); - } - - // Add IPv6 addresses (no compression for now) - if !interface.ipv6_addresses.is_empty() { - if !interface.ipv4_addresses.is_empty() { - interface_text.push_str(", "); - } - interface_text.push_str(&interface.ipv6_addresses.join(", ")); - } - - // Physical interfaces show status icon - let mut spans = vec![ - Span::styled(tree_symbol, Typography::tree()), - ]; - spans.extend(StatusIcons::create_status_spans( + // Physical interface header with status icon + let mut header_spans = vec![]; + header_spans.extend(StatusIcons::create_status_spans( interface.link_status.clone(), - &interface_text + &format!("{}:", interface.name) )); - lines.push(Line::from(spans)); + lines.push(Line::from(header_spans)); + + // Show IPs nested under the interface + let ip_count = interface.ipv4_addresses.len() + interface.ipv6_addresses.len(); + let mut ip_index = 0; + + // IPv4 addresses + for ipv4 in &interface.ipv4_addresses { + ip_index += 1; + let is_last_ip = ip_index == ip_count && is_last_physical; + let tree_symbol = if is_last_ip { " └─ " } else { " ├─ " }; + lines.push(Line::from(vec![ + Span::styled(tree_symbol, Typography::tree()), + Span::styled(format!("ip: {}", ipv4), Typography::secondary()), + ])); + } + + // IPv6 addresses + for ipv6 in &interface.ipv6_addresses { + ip_index += 1; + let is_last_ip = ip_index == ip_count && is_last_physical; + let tree_symbol = if is_last_ip { " └─ " } else { " ├─ " }; + lines.push(Line::from(vec![ + Span::styled(tree_symbol, Typography::tree()), + Span::styled(format!("ip: {}", ipv6), Typography::secondary()), + ])); + } } - // Render virtual interfaces - for (i, interface) in virtual_interfaces.iter().enumerate() { - let is_last = i == virtual_interfaces.len() - 1; + // Render standalone virtual interfaces (those without a parent) + for (virt_idx, interface) in virtual_interfaces.iter().enumerate() { + let is_last = virt_idx == virtual_interfaces.len() - 1; let tree_symbol = if is_last { " └─ " } else { " ├─ " }; - // Show interface name with IPs - let mut interface_text = format!("{}: ", interface.name); + // Virtual interface with IPs + let ip_text = if !interface.ipv4_addresses.is_empty() { + Self::compress_ipv4_addresses(&interface.ipv4_addresses) + } else if !interface.ipv6_addresses.is_empty() { + interface.ipv6_addresses.join(", ") + } else { + String::new() + }; - // Add compressed IPv4 addresses - if !interface.ipv4_addresses.is_empty() { - interface_text.push_str(&Self::compress_ipv4_addresses(&interface.ipv4_addresses)); - } + let interface_text = if !ip_text.is_empty() { + format!("{}: {}", interface.name, ip_text) + } else { + format!("{}:", interface.name) + }; - // Add IPv6 addresses (no compression for now) - if !interface.ipv6_addresses.is_empty() { - if !interface.ipv4_addresses.is_empty() { - interface_text.push_str(", "); - } - interface_text.push_str(&interface.ipv6_addresses.join(", ")); - } - - // Virtual interfaces don't show status icon lines.push(Line::from(vec![ Span::styled(tree_symbol, Typography::tree()), Span::styled(interface_text, Typography::secondary()), diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 5c396d8..26b05e3 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-dashboard-shared" -version = "0.1.165" +version = "0.1.166" edition = "2021" [dependencies]