- folders, amps, ports, raid, wifi, ip (drop-in compatible stats) - differential tests pass for all 22 implemented plugins - TUI left sidebar extended
182 lines
6.1 KiB
Rust
182 lines
6.1 KiB
Rust
//! 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<String>,
|
|
public_field: String,
|
|
public_template: String,
|
|
public_disabled: bool,
|
|
public_username: Option<String>,
|
|
public_password: Option<String>,
|
|
public_interval: f64,
|
|
public_timer: Instant,
|
|
public_address: String,
|
|
public_info: Option<Value>,
|
|
}
|
|
|
|
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::<Value>() {
|
|
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<Value> {
|
|
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<glances_core::StatLine> {
|
|
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()
|
|
}
|
|
}
|