From 81d039ad94b812afcbf03d636b65827831d17197 Mon Sep 17 00:00:00 2001 From: Asthowen Date: Sat, 12 Jul 2025 15:23:57 +0200 Subject: [PATCH] feat: add networks support --- src/config/deserialize.rs | 29 ++++++++ src/config/mod.rs | 26 ++++++++ src/main.rs | 2 + src/system/disk.rs | 9 +-- src/system/disks.rs | 32 ++++++--- src/system/mod.rs | 48 ++++++++++++++ src/system/networks.rs | 129 ++++++++++++++++++++++++++++++++++++ src/translations/english.rs | 1 + src/translations/french.rs | 1 + 9 files changed, 262 insertions(+), 15 deletions(-) create mode 100644 src/system/networks.rs diff --git a/src/config/deserialize.rs b/src/config/deserialize.rs index d7f4aad..aaf4396 100644 --- a/src/config/deserialize.rs +++ b/src/config/deserialize.rs @@ -21,12 +21,30 @@ struct ConfigWrapper<'a> { struct InfoConfig<'a> { #[serde(default, borrow)] disks: Option>, + #[serde(default, borrow)] + networks: Option>, } #[derive(Debug, Deserialize)] struct DisksInfoConfig<'a> { #[serde(default, borrow)] exclude: Option>, + #[serde(default, borrow)] + include: Option>, +} + +#[derive(Debug, Deserialize)] +struct NetworksInfoConfig<'a> { + #[serde(default, borrow)] + exclude: Option>, + #[serde(default, borrow)] + include: Option>, + #[serde(default)] + private_only: Option, + #[serde(default)] + assigned_only: Option, + #[serde(default)] + ignore_loopback: Option, } #[derive(Debug, Deserialize)] @@ -212,6 +230,17 @@ impl<'de: 'static> serde::Deserialize<'de> for super::Config { .disks .map(|disks| super::DisksInfoConfig { exclude: disks.exclude.unwrap_or_default(), + include: disks.include, + }) + .unwrap_or_default(), + networks: info + .networks + .map(|networks| super::NetworksInfoConfig { + exclude: networks.exclude.unwrap_or_default(), + include: networks.include, + private_only: networks.private_only.unwrap_or(true), + assigned_only: networks.assigned_only.unwrap_or(true), + ignore_loopback: networks.ignore_loopback.unwrap_or(true), }) .unwrap_or_default(), }) diff --git a/src/config/mod.rs b/src/config/mod.rs index 6409761..3662fca 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -31,21 +31,47 @@ pub struct Config { #[derive(Debug, Default, Decode, Encode)] pub struct InfoConfig<'a> { pub disks: DisksInfoConfig<'a>, + pub networks: NetworksInfoConfig<'a>, } #[derive(Debug, Decode, Encode)] pub struct DisksInfoConfig<'a> { pub exclude: Vec<&'a str>, + pub include: Option>, +} + +#[derive(Debug, Decode, Encode)] +pub struct NetworksInfoConfig<'a> { + pub exclude: Vec<&'a str>, + pub include: Option>, + pub private_only: bool, + pub assigned_only: bool, + pub ignore_loopback: bool, } impl<'a> Default for DisksInfoConfig<'a> { fn default() -> Self { Self { + include: None, exclude: vec!["/boot", "/etc", "/snapd", "/docker"], } } } +impl<'a> Default for NetworksInfoConfig<'a> { + fn default() -> Self { + Self { + include: None, + exclude: vec![ + "br-", "docker", "veth", "tun", "tap", "wg", "virbr", "vmnet", + ], + private_only: true, + assigned_only: true, + ignore_loopback: true, + } + } +} + #[derive(Debug, Decode, Encode)] pub struct ColorOption { pub header: Option, diff --git a/src/main.rs b/src/main.rs index 287c02f..dbe47c5 100755 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ use afetch::system::kernel::get_kernel; use afetch::system::loadavg::get_loadavg; use afetch::system::memory::get_memory; use afetch::system::motherboard::get_motherboard; +use afetch::system::networks::get_networks; use afetch::system::product::get_product; use afetch::system::uptime::get_uptime; use afetch::system::{InfoGroup, InfoKind, InfoResult}; @@ -44,6 +45,7 @@ fn main() -> Result<(), FetchInfoError> { InfoKind::Loadavg => get_loadavg(language_func, fields, &config), InfoKind::Memory => get_memory(language_func, fields, &config), InfoKind::Motherboard => get_motherboard(language_func, fields, &config), + InfoKind::Networks => get_networks(language_func, fields, &config), InfoKind::Product => get_product(language_func, fields, &config), InfoKind::Uptime => get_uptime(language_func, fields, &config), }, diff --git a/src/system/disk.rs b/src/system/disk.rs index 0f6a8e6..8f9f242 100644 --- a/src/system/disk.rs +++ b/src/system/disk.rs @@ -1,6 +1,7 @@ use crate::config::Config; use crate::error::FetchInfoError; use crate::filtered_values; +use crate::system::disks::ignore_disk; use crate::system::{InfoField, InfoGroup, InfoResult, InfoValue}; use crate::util::ToOptionString; use crate::util::convert_to_readable_unity; @@ -16,13 +17,7 @@ pub fn get_disk( for disk in Disks::new_with_refreshed_list().list() { let mount_point = disk.mount_point().to_string_lossy().to_string(); - if config - .parameters - .disks - .exclude - .iter() - .any(|ignore| mount_point.starts_with(ignore)) - { + if ignore_disk(config, &mount_point) { continue; } diff --git a/src/system/disks.rs b/src/system/disks.rs index 01ce3df..6d02ace 100644 --- a/src/system/disks.rs +++ b/src/system/disks.rs @@ -14,25 +14,21 @@ pub fn get_disks( let mut available_space = 0; let mut total_space = 0; let mut count = 0; + let mut count_filtered = 0; for disk in Disks::new_with_refreshed_list_specifics(DiskRefreshKind::nothing().with_storage()).list() { let mount_point = disk.mount_point().to_string_lossy().to_string(); + count += 1; - if config - .parameters - .disks - .exclude - .iter() - .any(|ignore| mount_point.starts_with(ignore)) - { + if ignore_disk(config, &mount_point) { continue; } available_space += disk.available_space(); total_space += disk.total_space(); - count += 1; + count_filtered += 1; } Ok(InfoResult::Single(InfoGroup { @@ -40,6 +36,7 @@ pub fn get_disks( fields, [ (InfoField::DisksCount, count.to_string()), + (InfoField::DisksCountFiltered, count_filtered.to_string()), ( InfoField::DisksAvailableSpace, convert_to_readable_unity(available_space as f64) @@ -56,3 +53,22 @@ pub fn get_disks( ), })) } + +pub(crate) fn ignore_disk(config: &Config, mount_point: &str) -> bool { + config + .parameters + .disks + .exclude + .iter() + .any(|ignore| mount_point.starts_with(ignore)) + || config + .parameters + .disks + .include + .as_ref() + .is_some_and(|include| { + !include + .iter() + .any(|&include| mount_point.starts_with(include)) + }) +} diff --git a/src/system/mod.rs b/src/system/mod.rs index 9200fdf..09ae1f1 100755 --- a/src/system/mod.rs +++ b/src/system/mod.rs @@ -12,6 +12,7 @@ pub mod kernel; pub mod loadavg; pub mod memory; pub mod motherboard; +pub mod networks; pub mod product; pub mod uptime; @@ -30,6 +31,7 @@ pub enum InfoKind { Loadavg, Memory, Motherboard, + Networks, Product, Uptime, } @@ -77,6 +79,7 @@ impl InfoKind { ], Self::Disks => &[ InfoField::DisksCount, + InfoField::DisksCountFiltered, InfoField::DisksAvailableSpace, InfoField::DisksUsedSpace, InfoField::DisksTotalSpace, @@ -104,6 +107,21 @@ impl InfoKind { InfoField::MotherboardVendorName, InfoField::MotherboardVersion, ], + Self::Networks => &[ + InfoField::NetworkName, + InfoField::NetworkFirstIp, + InfoField::NetworkPreferFirstIpv4, + InfoField::NetworkPreferFirstIpv6, + InfoField::NetworkAllIp, + InfoField::NetworkMacAddress, + InfoField::NetworkMaximumTransferUnit, + InfoField::NetworkErrorsOnReceived, + InfoField::NetworkErrorsOnTransmitted, + InfoField::NetworkPacketsReceived, + InfoField::NetworkPacketsTransmitted, + InfoField::NetworkReceived, + InfoField::NetworkTransmitted, + ], Self::Product => &[ InfoField::ProductFamily, InfoField::ProductName, @@ -128,6 +146,7 @@ impl InfoKind { Self::Loadavg => "{loadavg_one}, {loadavg_five}, {loadavg_fifteen}", Self::Memory => "{memory_used} / {memory_total}", Self::Motherboard => "{motherboard_name} {motherboard_version}", + Self::Networks => "{network_prefer_first_ipv4}", Self::Product => "{product_name} {product_version}", Self::Uptime => "{uptime}", } @@ -144,6 +163,7 @@ impl InfoKind { Self::Loadavg => "loadavg", Self::Memory => "memory", Self::Motherboard => "motherboard", + Self::Networks => "networks", Self::Product => "host", Self::Uptime => "uptime", } @@ -185,6 +205,7 @@ pub enum InfoField { DiskWrittenSinceBoot, DiskReadSinceBoot, DisksCount, + DisksCountFiltered, DisksAvailableSpace, DisksUsedSpace, DisksTotalSpace, @@ -206,6 +227,19 @@ pub enum InfoField { MotherboardSerialNumber, MotherboardVendorName, MotherboardVersion, + NetworkName, + NetworkFirstIp, + NetworkPreferFirstIpv4, + NetworkPreferFirstIpv6, + NetworkAllIp, + NetworkMacAddress, + NetworkMaximumTransferUnit, + NetworkErrorsOnReceived, + NetworkErrorsOnTransmitted, + NetworkPacketsReceived, + NetworkPacketsTransmitted, + NetworkReceived, + NetworkTransmitted, ProductFamily, ProductName, ProductSerialNumber, @@ -253,6 +287,7 @@ impl InfoField { Self::DiskWrittenSinceBoot => "disk_written_since_boot", Self::DiskReadSinceBoot => "disk_read_since_boot", Self::DisksCount => "disks_count", + Self::DisksCountFiltered => "disks_count_filtered", Self::DisksAvailableSpace => "disks_available_space", Self::DisksUsedSpace => "disks_used_space", Self::DisksTotalSpace => "disks_total_space", @@ -274,6 +309,19 @@ impl InfoField { Self::MotherboardSerialNumber => "motherboard_serial_number", Self::MotherboardVendorName => "motherboard_vendor_name", Self::MotherboardVersion => "motherboard_version", + Self::NetworkName => "network_name", + Self::NetworkPreferFirstIpv4 => "network_prefer_first_ipv4", + Self::NetworkPreferFirstIpv6 => "network_prefer_first_ipv6", + Self::NetworkFirstIp => "network_first_ip", + Self::NetworkAllIp => "network_all_ip", + Self::NetworkMacAddress => "network_mac_address", + Self::NetworkMaximumTransferUnit => "network_maximum_transfer_unit", + Self::NetworkErrorsOnReceived => "network_errors_on_received", + Self::NetworkErrorsOnTransmitted => "network_errors_on_transmitted", + Self::NetworkPacketsReceived => "network_packets_received", + Self::NetworkPacketsTransmitted => "network_packets_transmitted", + Self::NetworkReceived => "network_received", + Self::NetworkTransmitted => "network_transmitted", Self::ProductFamily => "product_family", Self::ProductName => "product_name", Self::ProductSerialNumber => "product_serial_number", diff --git a/src/system/networks.rs b/src/system/networks.rs new file mode 100644 index 0000000..f10894c --- /dev/null +++ b/src/system/networks.rs @@ -0,0 +1,129 @@ +use crate::config::Config; +use crate::error::FetchInfoError; +use crate::filtered_values; +use crate::system::{InfoField, InfoGroup, InfoResult, InfoValue}; +use crate::util::ToOptionString; +use crate::util::convert_to_readable_unity; +use std::net::IpAddr; +use sysinfo::Networks; + +pub fn get_networks( + _languages_func: fn(&str) -> &str, + fields: &[InfoField], + config: &Config, +) -> Result { + let mut networks_info: Vec = Vec::new(); + + for (name, network) in Networks::new_with_refreshed_list().list() { + if config + .parameters + .networks + .exclude + .iter() + .any(|ignore| name.starts_with(ignore)) + || config + .parameters + .networks + .include + .as_ref() + .is_some_and(|include| !include.iter().any(|include| name.starts_with(include))) + { + continue; + } + + if config.parameters.networks.ignore_loopback + && network.ip_networks().iter().any(|ip| ip.addr.is_loopback()) + { + continue; + } + + let first_ip = match network.ip_networks().first() { + Some(ip) => Some(ip.to_string()), + None if config.parameters.networks.assigned_only => continue, + None => None, + }; + let first_ipv4 = network + .ip_networks() + .iter() + .find(|ip| ip.addr.is_ipv4()) + .map(|ip| ip.addr); + let first_ipv6 = network.ip_networks().iter().find(|ip| ip.addr.is_ipv6()); + + if config.parameters.networks.private_only { + if let Some(IpAddr::V4(ip)) = first_ipv4 { + if !ip.is_private() { + continue; + } + } + } + + networks_info.push(InfoGroup { + values: filtered_values!( + fields, + [ + (InfoField::NetworkName, name.clone()), + (InfoField::NetworkFirstIp, first_ip.clone()), + ( + InfoField::NetworkPreferFirstIpv4, + first_ipv4 + .map(|ip| ip.to_string()) + .or_else(|| first_ip.clone()) + ), + ( + InfoField::NetworkPreferFirstIpv6, + first_ipv6.map(|ip| ip.to_string()).or(first_ip) + ), + ( + InfoField::NetworkAllIp, + if network.ip_networks().is_empty() { + None + } else { + Some( + network + .ip_networks() + .iter() + .map(|ip| ip.to_string()) + .collect::>() + .join(" "), + ) + } + ), + ( + InfoField::NetworkMacAddress, + network.mac_address().to_string() + ), + ( + InfoField::NetworkMaximumTransferUnit, + network.mtu().to_string() + ), + ( + InfoField::NetworkErrorsOnReceived, + network.total_errors_on_received().to_string() + ), + ( + InfoField::NetworkErrorsOnTransmitted, + network.total_errors_on_transmitted().to_string() + ), + ( + InfoField::NetworkPacketsReceived, + network.total_packets_received().to_string() + ), + ( + InfoField::NetworkPacketsTransmitted, + network.total_packets_transmitted().to_string() + ), + ( + InfoField::NetworkReceived, + convert_to_readable_unity(network.total_received() as f64) + ), + ( + InfoField::NetworkTransmitted, + convert_to_readable_unity(network.total_transmitted() as f64) + ) + ] + ), + }); + } + + Ok(InfoResult::Several(networks_info)) +} diff --git a/src/translations/english.rs b/src/translations/english.rs index e25fca1..e738a37 100755 --- a/src/translations/english.rs +++ b/src/translations/english.rs @@ -20,6 +20,7 @@ pub fn english(key: &str) -> &'static str { "terminal-font" => "Font", "memory" => "Memory", "motherboard" => "Motherboard", + "networks" => "Local IP ({network_name})", "cpu" => "CPU", "gpu" => "GPU", "network" => "Network", diff --git a/src/translations/french.rs b/src/translations/french.rs index 987b98d..fe530c6 100755 --- a/src/translations/french.rs +++ b/src/translations/french.rs @@ -20,6 +20,7 @@ pub fn french(key: &str) -> &'static str { "terminal-font" => "Police", "memory" => "Mémoire", "motherboard" => "Carte mère", + "networks" => "IP locale ({network_name})", "cpu" => "CPU", "gpu" => "GPU", "network" => "Réseau",