M3: extended plugins
- folders, amps, ports, raid, wifi, ip (drop-in compatible stats) - differential tests pass for all 22 implemented plugins - TUI left sidebar extended
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
//! 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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user