Update network display format to match CLAUDE.md specification
All checks were successful
Build and Release / build-and-release (push) Successful in 1m38s

Nest IP addresses under physical interface names. Show physical interfaces with status icon on header line. Virtual interfaces show inline with compressed IPs.

Format:
● eno1:
  ├─ ip: 192.168.30.105
  └─ tailscale0: 100.125.108.16

Version bump to 0.1.166
This commit is contained in:
Christoffer Martinsson 2025-11-26 19:13:28 +01:00
parent fc247bd0ad
commit 9f0aa5f806
6 changed files with 62 additions and 51 deletions

View File

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

6
Cargo.lock generated
View File

@ -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",

View File

@ -1,6 +1,6 @@
[package]
name = "cm-dashboard-agent"
version = "0.1.165"
version = "0.1.166"
edition = "2021"
[dependencies]

View File

@ -1,6 +1,6 @@
[package]
name = "cm-dashboard"
version = "0.1.165"
version = "0.1.166"
edition = "2021"
[dependencies]

View File

@ -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()),

View File

@ -1,6 +1,6 @@
[package]
name = "cm-dashboard-shared"
version = "0.1.165"
version = "0.1.166"
edition = "2021"
[dependencies]