use anyhow::Result;
use dirs::home_dir;
use serde::{Deserialize, Serialize};
use std::fs;
use std::net::IpAddr;
use std::path::PathBuf;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "lowercase")]
pub enum StatsUnit {
Kbps,
#[default]
Mbps,
Gbps,
}
impl StatsUnit {
pub fn suffix(self) -> &'static str {
match self {
Self::Kbps => "Kbps",
Self::Mbps => "Mbps",
Self::Gbps => "Gbps",
}
}
pub fn scale_from_mbps(self, mbps: f64) -> f64 {
match self {
Self::Kbps => mbps * 1000.0,
Self::Mbps => mbps,
Self::Gbps => mbps / 1000.0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct TuiSettings {
#[serde(default)]
pub stats_unit: StatsUnit,
pub stats_interface: Option<String>,
pub display_interface: Option<String>,
pub display_ip: Option<String>,
}
fn settings_path() -> Option<PathBuf> {
Some(home_dir()?.join(".netscli").join("tui-settings.json"))
}
pub fn load_settings() -> TuiSettings {
let Some(path) = settings_path() else {
return TuiSettings::default();
};
let Ok(data) = fs::read_to_string(&path) else {
return TuiSettings::default();
};
match serde_json::from_str::<TuiSettings>(&data) {
Ok(s) => s,
Err(e) => {
eprintln!(
"netscli: warning: could not parse {} ({e}); using defaults. \
Fix the file or delete it to stop seeing this message.",
path.display()
);
TuiSettings::default()
}
}
}
pub fn save_settings(settings: &TuiSettings) -> Result<()> {
use anyhow::Context;
let path = settings_path().context("could not determine settings path (no HOME)")?;
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
let data = serde_json::to_string_pretty(settings)?;
let tmp = path.with_extension("json.tmp");
fs::write(&tmp, data).with_context(|| format!("writing {}", tmp.display()))?;
fs::rename(&tmp, &path)
.with_context(|| format!("renaming {} to {}", tmp.display(), path.display()))?;
Ok(())
}
pub fn resolve_context_address(settings: &TuiSettings) -> Option<String> {
if let Some(ip) = settings.display_ip.as_deref() {
if ip.trim().is_empty() {
return None;
}
if let Ok(parsed) = ip.trim().parse::<IpAddr>() {
return Some(parsed.to_string());
}
return None;
}
let iface = settings
.display_interface
.as_deref()
.or(settings.stats_interface.as_deref());
if let Some(iface) = iface {
for info in netscli_core::NetworkManager::get_interfaces() {
if info.name == iface {
for net in info.ips {
if let IpAddr::V4(v4) = net.addr() {
return Some(v4.to_string());
}
}
}
}
}
netscli_core::detect_default_ipv4_addr().map(|ip| ip.to_string())
}