use std::collections::VecDeque;
pub const HISTORY_SIZE: usize = 60;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum IpDisplayPreference {
#[default]
Ipv6Gua,
Ipv6Lla,
Ipv6Ula,
Ipv4,
}
impl IpDisplayPreference {
pub fn all() -> &'static [IpDisplayPreference] {
&[
IpDisplayPreference::Ipv6Gua,
IpDisplayPreference::Ipv6Lla,
IpDisplayPreference::Ipv6Ula,
IpDisplayPreference::Ipv4,
]
}
pub fn display_name(&self) -> &'static str {
match self {
IpDisplayPreference::Ipv6Gua => "IPv6 GUA",
IpDisplayPreference::Ipv6Lla => "IPv6 LLA",
IpDisplayPreference::Ipv6Ula => "IPv6 ULA",
IpDisplayPreference::Ipv4 => "IPv4",
}
}
}
impl std::fmt::Display for IpDisplayPreference {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
IpDisplayPreference::Ipv6Gua => write!(f, "ipv6-gua"),
IpDisplayPreference::Ipv6Lla => write!(f, "ipv6-lla"),
IpDisplayPreference::Ipv6Ula => write!(f, "ipv6-ula"),
IpDisplayPreference::Ipv4 => write!(f, "ipv4"),
}
}
}
impl std::str::FromStr for IpDisplayPreference {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"ipv6-gua" | "ipv6_gua" | "ipv6gua" | "gua" => Ok(IpDisplayPreference::Ipv6Gua),
"ipv6-lla" | "ipv6_lla" | "ipv6lla" | "lla" => Ok(IpDisplayPreference::Ipv6Lla),
"ipv6-ula" | "ipv6_ula" | "ipv6ula" | "ula" => Ok(IpDisplayPreference::Ipv6Ula),
"ipv4" | "v4" => Ok(IpDisplayPreference::Ipv4),
_ => Err(format!("Unknown IP display preference: {}", s)),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct SystemData {
pub hostname: String,
pub time: String,
pub hour: u8,
pub minute: u8,
pub day: u8,
pub month: u8,
pub year: u16,
pub day_of_week: u8,
pub uptime: String,
pub cpu_percent: f64,
pub cpu_temp: Option<f64>,
pub ram_percent: f64,
pub disk_read_rate: f64,
pub disk_write_rate: f64,
pub disk_history: VecDeque<f64>,
pub disk_read_history: VecDeque<f64>,
pub disk_write_history: VecDeque<f64>,
pub net_interface: String,
pub net_rx_rate: f64,
pub net_tx_rate: f64,
pub net_history: VecDeque<f64>,
pub net_rx_history: VecDeque<f64>,
pub net_tx_history: VecDeque<f64>,
pub display_ip: Option<String>,
}
impl SystemData {
pub fn format_time(&self, format: &str) -> String {
match format {
"digital-12h" => {
let (hour_12, am_pm) = if self.hour == 0 {
(12, "AM")
} else if self.hour < 12 {
(self.hour, "AM")
} else if self.hour == 12 {
(12, "PM")
} else {
(self.hour - 12, "PM")
};
format!("{:2}:{:02} {}", hour_12, self.minute, am_pm)
}
"analogue" => {
String::new()
}
_ => format!("{:02}:{:02}", self.hour, self.minute),
}
}
pub fn format_date(&self, format: &str) -> Option<String> {
let month_names = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
let month_abbrev = [
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
];
let weekday_abbrev = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
let month_idx = (self.month.saturating_sub(1) as usize).min(11);
let weekday_idx = (self.day_of_week as usize).min(6);
match format {
"hidden" => None,
"iso" => Some(format!(
"{:04}-{:02}-{:02}",
self.year, self.month, self.day
)),
"us" => Some(format!(
"{:02}/{:02}/{:04}",
self.month, self.day, self.year
)),
"eu" => Some(format!(
"{:02}/{:02}/{:04}",
self.day, self.month, self.year
)),
"short" => Some(format!("{} {}", month_abbrev[month_idx], self.day)),
"long" => Some(format!(
"{} {}, {}",
month_names[month_idx], self.day, self.year
)),
"weekday" => Some(format!(
"{}, {} {}",
weekday_abbrev[weekday_idx], month_abbrev[month_idx], self.day
)),
_ => None,
}
}
pub fn format_rate(bytes_per_sec: f64) -> String {
if bytes_per_sec >= 1_000_000_000.0 {
format!("{:.1} GB/s", bytes_per_sec / 1_000_000_000.0)
} else if bytes_per_sec >= 1_000_000.0 {
format!("{:.1} MB/s", bytes_per_sec / 1_000_000.0)
} else if bytes_per_sec >= 1_000.0 {
format!("{:.1} KB/s", bytes_per_sec / 1_000.0)
} else {
format!("{:.0} B/s", bytes_per_sec)
}
}
pub fn format_rate_compact(bytes_per_sec: f64) -> String {
if bytes_per_sec >= 1_000_000_000.0 {
format!("{:.1}G", bytes_per_sec / 1_000_000_000.0)
} else if bytes_per_sec >= 1_000_000.0 {
format!("{:.1}M", bytes_per_sec / 1_000_000.0)
} else if bytes_per_sec >= 1_000.0 {
format!("{:.1}K", bytes_per_sec / 1_000.0)
} else {
format!("{:.0}B", bytes_per_sec)
}
}
pub fn compute_graph_scale(history: &VecDeque<f64>) -> f64 {
const MIN_SCALE: f64 = 1_000_000.0;
let max_val = history.iter().copied().fold(0.0_f64, |a, b| a.max(b));
if max_val <= MIN_SCALE {
return MIN_SCALE;
}
let magnitude = 10_f64.powf(max_val.log10().floor());
let normalized = max_val / magnitude;
let multiplier = if normalized <= 1.0 {
1.0
} else if normalized <= 2.0 {
2.0
} else if normalized <= 5.0 {
5.0
} else {
10.0
};
(magnitude * multiplier).max(MIN_SCALE)
}
}