//! IP plugin: private and (optionally) public IP information. use crate::fields; use glances_core::plugin::{Field, Plugin, PluginModel, UpdateContext}; use glances_core::{Args, Config}; use serde_json::{Value, json}; use std::time::Instant; pub struct IpPlugin { pub model: PluginModel, public_api: Option, public_field: String, public_template: String, public_disabled: bool, public_username: Option, public_password: Option, public_interval: f64, public_timer: Instant, public_address: String, public_info: Option, } impl IpPlugin { pub fn new(config: &Config, args: &Args) -> Self { let fields = fields! { "address" => Field::new("Private IP address"), "mask" => Field::new("Private IP mask"), "mask_cidr" => Field::new("Private IP mask in CIDR format").unit("number"), "gateway" => Field::new("Private IP gateway"), "public_address" => Field::new("Public IP address"), "public_info_human" => Field::new("Public IP information"), }; let model = PluginModel::new( "ip", fields, Vec::new(), json!({}), config, args.time.unwrap_or(2.0), args.disable_history, args.disable_config_exec, ); let public_api = model.get_conf_value("public_api", "").first().cloned(); let public_disabled = model .get_conf_value("public_disabled", "") .first() .map(|v| v.to_lowercase() != "false") .unwrap_or(true) || public_api.is_none(); Self { model, public_api, public_field: "ip".to_string(), public_template: String::new(), public_disabled, public_username: None, public_password: None, public_interval: 300.0, public_timer: Instant::now() - std::time::Duration::from_secs(3600), public_address: String::new(), public_info: None, } } fn refresh_public(&mut self) { if self.public_disabled { return; } if self.public_timer.elapsed().as_secs_f64() < self.public_interval { return; } self.public_timer = Instant::now(); let Some(api) = &self.public_api else { return; }; let mut request = ureq::get(api) .config() .timeout_global(Some(std::time::Duration::from_secs(2))) .build(); if let (Some(username), Some(password)) = (&self.public_username, &self.public_password) { request = request.header("Authorization", &format!("Basic {}:{}", username, password)); } if let Ok(mut response) = request.call() { if let Ok(value) = response.body_mut().read_json::() { self.public_address = value.get(&self.public_field).and_then(Value::as_str).unwrap_or("").to_string(); self.public_info = Some(value); } } } fn public_info_human(&self) -> String { let Some(info) = &self.public_info else { return String::new(); }; if self.public_template.is_empty() { return String::new(); } let mut result = self.public_template.clone(); for (key, value) in info.as_object().into_iter().flatten() { let text = match value { Value::String(s) => s.clone(), other => glances_core::py_str(other), }; result = result.replace(&format!("{{{key}}}"), &text); } result } } impl Plugin for IpPlugin { fn name(&self) -> &str { "ip" } fn model(&self) -> &PluginModel { &self.model } fn model_mut(&mut self) -> &mut PluginModel { &mut self.model } fn collect(&mut self, ctx: &mut UpdateContext) -> anyhow::Result { let mut stats = serde_json::Map::new(); let (address, mask) = crate::collectors::get_ip_address(); if let Some(address) = &address { stats.insert("address".to_string(), json!(address)); } if let Some(mask) = &mask { stats.insert("mask".to_string(), json!(mask)); } stats.insert("mask_cidr".to_string(), json!(crate::collectors::ip_to_cidr(mask.as_deref()))); self.refresh_public(); if !self.public_address.is_empty() { let public = if ctx.args.hide_public_info { mask_public_ip(&self.public_address) } else { self.public_address.clone() }; stats.insert("public_address".to_string(), json!(public)); let human = self.public_info_human(); if !human.is_empty() { stats.insert("public_info_human".to_string(), json!(human)); } } Ok(Value::Object(stats)) } fn display_curse(&self) -> bool { true } fn render(&mut self, _ctx: &glances_core::RenderContext) -> Vec { let mut ret = Vec::new(); let s = &self.model.stats; let Some(address) = s.get("address").and_then(Value::as_str) else { return ret; }; let mut text = address.to_string(); if let Some(mask_cidr) = s.get("mask_cidr").and_then(Value::as_u64) { if mask_cidr > 0 { text += &format!("/{mask_cidr}"); } } ret.push(self.model.curse_add_line(text, glances_core::Decoration::Default, false, false, false)); if let Some(gateway) = s.get("gateway").and_then(Value::as_str) { ret.push(self.model.curse_add_line(format!(" gw {gateway}"), glances_core::Decoration::Default, true, false, false)); } ret } } fn mask_public_ip(address: &str) -> String { let parts: Vec<&str> = address.split('.').collect(); if parts.len() == 4 { format!("{}.{}.{}.xxx", parts[0], parts[1], parts[2]) } else { "xxx".to_string() } }