Testing
This commit is contained in:
parent
2239badc8a
commit
656d410a7a
@ -85,6 +85,9 @@ impl ServiceCollector {
|
|||||||
|
|
||||||
// Get disk usage for this service
|
// Get disk usage for this service
|
||||||
let disk_used_gb = self.get_service_disk_usage(service).await.unwrap_or(0.0);
|
let disk_used_gb = self.get_service_disk_usage(service).await.unwrap_or(0.0);
|
||||||
|
|
||||||
|
// Get service-specific description
|
||||||
|
let description = self.get_service_description(service).await;
|
||||||
|
|
||||||
Ok(ServiceData {
|
Ok(ServiceData {
|
||||||
name: service.to_string(),
|
name: service.to_string(),
|
||||||
@ -94,6 +97,7 @@ impl ServiceCollector {
|
|||||||
cpu_percent,
|
cpu_percent,
|
||||||
sandbox_limit: None, // TODO: Implement sandbox limit detection
|
sandbox_limit: None, // TODO: Implement sandbox limit detection
|
||||||
disk_used_gb,
|
disk_used_gb,
|
||||||
|
description,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -448,6 +452,153 @@ impl ServiceCollector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_service_description(&self, service: &str) -> Option<String> {
|
||||||
|
match service {
|
||||||
|
"sshd" | "ssh" => self.get_ssh_active_users().await,
|
||||||
|
"nginx" | "apache2" | "httpd" => self.get_web_server_connections().await,
|
||||||
|
"docker" => self.get_docker_containers().await,
|
||||||
|
"postgresql" | "postgres" => self.get_postgres_connections().await,
|
||||||
|
"mysql" | "mariadb" => self.get_mysql_connections().await,
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_ssh_active_users(&self) -> Option<String> {
|
||||||
|
let output = Command::new("who")
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
let mut ssh_users = Vec::new();
|
||||||
|
|
||||||
|
for line in stdout.lines() {
|
||||||
|
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||||
|
if parts.len() >= 2 {
|
||||||
|
let user = parts[0];
|
||||||
|
let terminal = parts[1];
|
||||||
|
// SSH sessions typically show pts/X terminals
|
||||||
|
if terminal.starts_with("pts/") {
|
||||||
|
ssh_users.push(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ssh_users.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let unique_users: std::collections::HashSet<&str> = ssh_users.into_iter().collect();
|
||||||
|
let count = unique_users.len();
|
||||||
|
let users: Vec<&str> = unique_users.into_iter().collect();
|
||||||
|
|
||||||
|
if count == 1 {
|
||||||
|
Some(format!("1 active user: {}", users[0]))
|
||||||
|
} else {
|
||||||
|
Some(format!("{} active users: {}", count, users.join(", ")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_web_server_connections(&self) -> Option<String> {
|
||||||
|
let output = Command::new("ss")
|
||||||
|
.args(["-tn", "state", "established", "sport", ":80", "or", "sport", ":443"])
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
let connection_count = stdout.lines().count().saturating_sub(1); // Subtract header line
|
||||||
|
|
||||||
|
if connection_count > 0 {
|
||||||
|
Some(format!("{} active connections", connection_count))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_docker_containers(&self) -> Option<String> {
|
||||||
|
let output = Command::new("docker")
|
||||||
|
.args(["ps", "--format", "table {{.Names}}"])
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
let container_count = stdout.lines().count().saturating_sub(1); // Subtract header line
|
||||||
|
|
||||||
|
if container_count > 0 {
|
||||||
|
Some(format!("{} running containers", container_count))
|
||||||
|
} else {
|
||||||
|
Some("no containers running".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_postgres_connections(&self) -> Option<String> {
|
||||||
|
let output = Command::new("sudo")
|
||||||
|
.args(["-u", "postgres", "psql", "-t", "-c", "SELECT count(*) FROM pg_stat_activity WHERE state = 'active';"])
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
if let Some(line) = stdout.lines().next() {
|
||||||
|
if let Ok(count) = line.trim().parse::<i32>() {
|
||||||
|
if count > 0 {
|
||||||
|
return Some(format!("{} active connections", count));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_mysql_connections(&self) -> Option<String> {
|
||||||
|
let output = Command::new("mysql")
|
||||||
|
.args(["-e", "SHOW PROCESSLIST;"])
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
let connection_count = stdout.lines().count().saturating_sub(1); // Subtract header line
|
||||||
|
|
||||||
|
if connection_count > 0 {
|
||||||
|
Some(format!("{} active connections", connection_count))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@ -510,6 +661,7 @@ impl Collector for ServiceCollector {
|
|||||||
cpu_percent: 0.0,
|
cpu_percent: 0.0,
|
||||||
sandbox_limit: None,
|
sandbox_limit: None,
|
||||||
disk_used_gb: 0.0,
|
disk_used_gb: 0.0,
|
||||||
|
description: None,
|
||||||
});
|
});
|
||||||
tracing::warn!("Failed to collect metrics for service {}: {}", service, e);
|
tracing::warn!("Failed to collect metrics for service {}: {}", service, e);
|
||||||
}
|
}
|
||||||
@ -581,6 +733,8 @@ struct ServiceData {
|
|||||||
cpu_percent: f32,
|
cpu_percent: f32,
|
||||||
sandbox_limit: Option<f32>,
|
sandbox_limit: Option<f32>,
|
||||||
disk_used_gb: f32,
|
disk_used_gb: f32,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
description: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
|||||||
@ -48,7 +48,7 @@ fn render_metrics(
|
|||||||
let mut data = WidgetData::new(
|
let mut data = WidgetData::new(
|
||||||
title,
|
title,
|
||||||
Some(WidgetStatus::new(widget_status)),
|
Some(WidgetStatus::new(widget_status)),
|
||||||
vec!["Service".to_string(), "Memory".to_string(), "Disk".to_string(), "Description".to_string()]
|
vec!["Service".to_string(), "Memory".to_string(), "Disk".to_string()]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
@ -60,7 +60,6 @@ fn render_metrics(
|
|||||||
WidgetValue::new("No services reported"),
|
WidgetValue::new("No services reported"),
|
||||||
WidgetValue::new(""),
|
WidgetValue::new(""),
|
||||||
WidgetValue::new(""),
|
WidgetValue::new(""),
|
||||||
WidgetValue::new(""),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
render_widget_data(frame, area, data);
|
render_widget_data(frame, area, data);
|
||||||
@ -82,6 +81,7 @@ fn render_metrics(
|
|||||||
ServiceStatus::Stopped => StatusLevel::Error,
|
ServiceStatus::Stopped => StatusLevel::Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Main service row
|
||||||
data.add_row(
|
data.add_row(
|
||||||
Some(WidgetStatus::new(status_level)),
|
Some(WidgetStatus::new(status_level)),
|
||||||
"",
|
"",
|
||||||
@ -89,9 +89,23 @@ fn render_metrics(
|
|||||||
WidgetValue::new(svc.name.clone()),
|
WidgetValue::new(svc.name.clone()),
|
||||||
WidgetValue::new(format_memory_value(svc.memory_used_mb, svc.memory_quota_mb)),
|
WidgetValue::new(format_memory_value(svc.memory_used_mb, svc.memory_quota_mb)),
|
||||||
WidgetValue::new(format_disk_value(svc.disk_used_gb)),
|
WidgetValue::new(format_disk_value(svc.disk_used_gb)),
|
||||||
WidgetValue::new(svc.description.as_deref().unwrap_or("—")),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Description row (indented) if description exists
|
||||||
|
if let Some(description) = &svc.description {
|
||||||
|
if !description.trim().is_empty() {
|
||||||
|
data.add_row(
|
||||||
|
None,
|
||||||
|
"",
|
||||||
|
vec![
|
||||||
|
WidgetValue::new(format!(" {}", description)),
|
||||||
|
WidgetValue::new(""),
|
||||||
|
WidgetValue::new(""),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render_widget_data(frame, area, data);
|
render_widget_data(frame, area, data);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user