Add network interface collection and display
Some checks failed
Build and Release / build-and-release (push) Failing after 1m32s
Some checks failed
Build and Release / build-and-release (push) Failing after 1m32s
Extend NixOS collector to gather network interfaces using ip command JSON output. Display all interfaces with IPv4 and IPv6 addresses in Network section above CPU metrics. Filters out loopback and link-local addresses. Version bump to 0.1.161
This commit is contained in:
parent
3858309a5d
commit
b7ffeaced5
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -279,7 +279,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
|
||||
|
||||
[[package]]
|
||||
name = "cm-dashboard"
|
||||
version = "0.1.160"
|
||||
version = "0.1.161"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@ -301,7 +301,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cm-dashboard-agent"
|
||||
version = "0.1.160"
|
||||
version = "0.1.161"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -324,7 +324,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cm-dashboard-shared"
|
||||
version = "0.1.160"
|
||||
version = "0.1.161"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "cm-dashboard-agent"
|
||||
version = "0.1.160"
|
||||
version = "0.1.161"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use async_trait::async_trait;
|
||||
use cm_dashboard_shared::AgentData;
|
||||
use cm_dashboard_shared::{AgentData, NetworkInterfaceData};
|
||||
use std::fs;
|
||||
use std::process::Command;
|
||||
use tracing::debug;
|
||||
@ -32,6 +32,9 @@ impl NixOSCollector {
|
||||
// Set NixOS build/generation information
|
||||
agent_data.build_version = self.get_nixos_generation().await;
|
||||
|
||||
// Collect network interfaces
|
||||
agent_data.system.network.interfaces = self.get_network_interfaces().await;
|
||||
|
||||
// Set current timestamp
|
||||
agent_data.timestamp = chrono::Utc::now().timestamp() as u64;
|
||||
|
||||
@ -101,6 +104,72 @@ impl NixOSCollector {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get network interfaces and their IP addresses
|
||||
async fn get_network_interfaces(&self) -> Vec<NetworkInterfaceData> {
|
||||
let mut interfaces = Vec::new();
|
||||
|
||||
// Use ip command with JSON output for easier parsing
|
||||
match Command::new("ip").args(["-j", "addr"]).output() {
|
||||
Ok(output) if output.status.success() => {
|
||||
let json_str = String::from_utf8_lossy(&output.stdout);
|
||||
|
||||
// Parse JSON output
|
||||
if let Ok(json_data) = serde_json::from_str::<serde_json::Value>(&json_str) {
|
||||
if let Some(ifaces) = json_data.as_array() {
|
||||
for iface in ifaces {
|
||||
let name = iface["ifname"].as_str().unwrap_or("").to_string();
|
||||
|
||||
// Skip loopback and empty names
|
||||
if name.is_empty() || name == "lo" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut ipv4_addresses = Vec::new();
|
||||
let mut ipv6_addresses = Vec::new();
|
||||
|
||||
// Extract IP addresses
|
||||
if let Some(addr_info) = iface["addr_info"].as_array() {
|
||||
for addr in addr_info {
|
||||
if let Some(family) = addr["family"].as_str() {
|
||||
if let Some(local) = addr["local"].as_str() {
|
||||
match family {
|
||||
"inet" => ipv4_addresses.push(local.to_string()),
|
||||
"inet6" => {
|
||||
// Skip link-local IPv6 addresses (fe80::)
|
||||
if !local.starts_with("fe80:") {
|
||||
ipv6_addresses.push(local.to_string());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only add interfaces that have at least one IP address
|
||||
if !ipv4_addresses.is_empty() || !ipv6_addresses.is_empty() {
|
||||
interfaces.push(NetworkInterfaceData {
|
||||
name,
|
||||
ipv4_addresses,
|
||||
ipv6_addresses,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Failed to execute ip command: {}", e);
|
||||
}
|
||||
Ok(output) => {
|
||||
debug!("ip command failed with status: {}", output.status);
|
||||
}
|
||||
}
|
||||
|
||||
interfaces
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "cm-dashboard"
|
||||
version = "0.1.160"
|
||||
version = "0.1.161"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@ -8,13 +8,16 @@ use ratatui::{
|
||||
|
||||
use crate::ui::theme::{StatusIcons, Typography};
|
||||
|
||||
/// System widget displaying NixOS info, CPU, RAM, and Storage in unified layout
|
||||
/// System widget displaying NixOS info, Network, CPU, RAM, and Storage in unified layout
|
||||
#[derive(Clone)]
|
||||
pub struct SystemWidget {
|
||||
// NixOS information
|
||||
nixos_build: Option<String>,
|
||||
agent_hash: Option<String>,
|
||||
|
||||
|
||||
// Network interfaces
|
||||
network_interfaces: Vec<cm_dashboard_shared::NetworkInterfaceData>,
|
||||
|
||||
// CPU metrics
|
||||
cpu_load_1min: Option<f32>,
|
||||
cpu_load_5min: Option<f32>,
|
||||
@ -89,6 +92,7 @@ impl SystemWidget {
|
||||
Self {
|
||||
nixos_build: None,
|
||||
agent_hash: None,
|
||||
network_interfaces: Vec::new(),
|
||||
cpu_load_1min: None,
|
||||
cpu_load_5min: None,
|
||||
cpu_load_15min: None,
|
||||
@ -164,6 +168,9 @@ impl Widget for SystemWidget {
|
||||
// Extract build version
|
||||
self.nixos_build = agent_data.build_version.clone();
|
||||
|
||||
// Extract network interfaces
|
||||
self.network_interfaces = agent_data.system.network.interfaces.clone();
|
||||
|
||||
// Extract CPU data directly
|
||||
let cpu = &agent_data.system.cpu;
|
||||
self.cpu_load_1min = Some(cpu.load_1min);
|
||||
@ -573,8 +580,46 @@ impl SystemWidget {
|
||||
lines
|
||||
}
|
||||
|
||||
/// Render system widget
|
||||
pub fn render(&mut self, frame: &mut Frame, area: Rect, hostname: &str, config: Option<&crate::config::DashboardConfig>) {
|
||||
/// Render network section for display
|
||||
fn render_network(&self) -> Vec<Line<'_>> {
|
||||
let mut lines = Vec::new();
|
||||
|
||||
if self.network_interfaces.is_empty() {
|
||||
return lines;
|
||||
}
|
||||
|
||||
for (i, interface) in self.network_interfaces.iter().enumerate() {
|
||||
let is_last = i == self.network_interfaces.len() - 1;
|
||||
let tree_symbol = if is_last { " └─ " } else { " ├─ " };
|
||||
|
||||
// Show interface name
|
||||
let mut interface_text = format!("{}: ", interface.name);
|
||||
|
||||
// Add IPv4 addresses
|
||||
if !interface.ipv4_addresses.is_empty() {
|
||||
interface_text.push_str(&interface.ipv4_addresses.join(", "));
|
||||
}
|
||||
|
||||
// Add IPv6 addresses
|
||||
if !interface.ipv6_addresses.is_empty() {
|
||||
if !interface.ipv4_addresses.is_empty() {
|
||||
interface_text.push_str(", ");
|
||||
}
|
||||
interface_text.push_str(&interface.ipv6_addresses.join(", "));
|
||||
}
|
||||
|
||||
let mut spans = vec![
|
||||
Span::styled(tree_symbol, Typography::tree()),
|
||||
];
|
||||
spans.extend(StatusIcons::create_status_spans(Status::Ok, &interface_text));
|
||||
lines.push(Line::from(spans));
|
||||
}
|
||||
|
||||
lines
|
||||
}
|
||||
|
||||
/// Render system widget
|
||||
pub fn render(&mut self, frame: &mut Frame, area: Rect, hostname: &str, _config: Option<&crate::config::DashboardConfig>) {
|
||||
let mut lines = Vec::new();
|
||||
|
||||
// NixOS section
|
||||
@ -591,17 +636,16 @@ impl SystemWidget {
|
||||
lines.push(Line::from(vec![
|
||||
Span::styled(format!("Agent: {}", agent_version_text), Typography::secondary())
|
||||
]));
|
||||
|
||||
// Display detected connection IP
|
||||
if let Some(config) = config {
|
||||
if let Some(host_details) = config.hosts.get(hostname) {
|
||||
let detected_ip = host_details.get_connection_ip(hostname);
|
||||
lines.push(Line::from(vec![
|
||||
Span::styled(format!("IP: {}", detected_ip), Typography::secondary())
|
||||
]));
|
||||
}
|
||||
|
||||
// Network section
|
||||
if !self.network_interfaces.is_empty() {
|
||||
lines.push(Line::from(vec![
|
||||
Span::styled("Network:", Typography::widget_title())
|
||||
]));
|
||||
|
||||
let network_lines = self.render_network();
|
||||
lines.extend(network_lines);
|
||||
}
|
||||
|
||||
|
||||
// CPU section
|
||||
lines.push(Line::from(vec![
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "cm-dashboard-shared"
|
||||
version = "0.1.160"
|
||||
version = "0.1.161"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@ -16,11 +16,26 @@ pub struct AgentData {
|
||||
/// System-level monitoring data
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SystemData {
|
||||
pub network: NetworkData,
|
||||
pub cpu: CpuData,
|
||||
pub memory: MemoryData,
|
||||
pub storage: StorageData,
|
||||
}
|
||||
|
||||
/// Network interface monitoring data
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct NetworkData {
|
||||
pub interfaces: Vec<NetworkInterfaceData>,
|
||||
}
|
||||
|
||||
/// Individual network interface data
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct NetworkInterfaceData {
|
||||
pub name: String,
|
||||
pub ipv4_addresses: Vec<String>,
|
||||
pub ipv6_addresses: Vec<String>,
|
||||
}
|
||||
|
||||
/// CPU monitoring data
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CpuData {
|
||||
@ -171,6 +186,9 @@ impl AgentData {
|
||||
build_version: None,
|
||||
timestamp: chrono::Utc::now().timestamp() as u64,
|
||||
system: SystemData {
|
||||
network: NetworkData {
|
||||
interfaces: Vec::new(),
|
||||
},
|
||||
cpu: CpuData {
|
||||
load_1min: 0.0,
|
||||
load_5min: 0.0,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user