Fix nginx monitoring and services panel alignment

- Add support for both proxied and static nginx sites
- Proxied sites show 'P' prefix and check backend URLs
- Static sites check external HTTPS URLs
- Fix services panel column alignment for main services
- Keep 10-second timeout for all site checks
This commit is contained in:
Christoffer Martinsson 2025-10-20 14:56:26 +02:00
parent 11be496a26
commit 2ccfc4256a
2 changed files with 129 additions and 84 deletions

View File

@ -739,9 +739,8 @@ impl SystemdCollector {
while i < lines.len() {
let line = lines[i].trim();
if line.starts_with("server") && line.contains("{") {
if let Some(proxy_url) = self.parse_server_block(&lines, &mut i) {
let site_name = proxy_url.replace("http://", "").replace("https://", "");
sites.push((site_name, proxy_url));
if let Some((server_name, proxy_url)) = self.parse_server_block(&lines, &mut i) {
sites.push((server_name, proxy_url));
}
}
i += 1;
@ -752,7 +751,7 @@ impl SystemdCollector {
}
/// Parse a server block to extract the primary server_name
fn parse_server_block(&self, lines: &[&str], start_index: &mut usize) -> Option<String> {
fn parse_server_block(&self, lines: &[&str], start_index: &mut usize) -> Option<(String, String)> {
use tracing::debug;
let mut server_names = Vec::new();
let mut proxy_pass_url = None;
@ -806,9 +805,15 @@ impl SystemdCollector {
*start_index = i - 1;
if let Some(proxy_url) = proxy_pass_url {
if !has_redirect {
return Some(proxy_url);
if !server_names.is_empty() && !has_redirect {
if let Some(proxy_url) = proxy_pass_url {
// Site with proxy_pass: check backend, show "P" prefix
let proxied_name = format!("P {}", server_names[0]);
return Some((proxied_name, proxy_url));
} else {
// Site without proxy_pass: check external HTTPS
let external_url = format!("https://{}", server_names[0]);
return Some((server_names[0].clone(), external_url));
}
}

View File

@ -8,7 +8,7 @@ use std::collections::HashMap;
use tracing::debug;
use super::Widget;
use crate::ui::theme::{Theme, Typography, Components, StatusIcons};
use crate::ui::theme::{Components, StatusIcons, Theme, Typography};
use ratatui::style::Style;
/// Services widget displaying hierarchical systemd service statuses
@ -46,10 +46,12 @@ impl ServicesWidget {
/// Extract service name and determine if it's a parent or sub-service
fn extract_service_info(metric_name: &str) -> Option<(String, Option<String>)> {
if metric_name.starts_with("service_") {
if let Some(end_pos) = metric_name.rfind("_status")
if let Some(end_pos) = metric_name
.rfind("_status")
.or_else(|| metric_name.rfind("_memory_mb"))
.or_else(|| metric_name.rfind("_disk_gb"))
.or_else(|| metric_name.rfind("_latency_ms")) {
.or_else(|| metric_name.rfind("_latency_ms"))
{
let service_part = &metric_name[8..end_pos]; // Remove "service_" prefix
// Check for sub-services patterns
@ -93,8 +95,12 @@ impl ServicesWidget {
/// Format parent service line - returns text without icon for span formatting
fn format_parent_service_line(&self, name: &str, info: &ServiceInfo) -> String {
let memory_str = info.memory_mb.map_or("0M".to_string(), |m| format!("{:.0}M", m));
let disk_str = info.disk_gb.map_or("0".to_string(), |d| Self::format_disk_size(d));
let memory_str = info
.memory_mb
.map_or("0M".to_string(), |m| format!("{:.0}M", m));
let disk_str = info
.disk_gb
.map_or("0".to_string(), |d| Self::format_disk_size(d));
// Truncate long service names to fit layout (account for icon space)
let short_name = if name.len() > 22 {
@ -111,16 +117,18 @@ impl ServicesWidget {
Status::Unknown => "unknown".to_string(),
};
format!("{:<24} {:<10} {:<8} {:<8}",
short_name,
status_str,
memory_str,
disk_str)
format!(
"{:<23} {:<10} {:<8} {:<8}",
short_name, status_str, memory_str, disk_str
)
}
/// Create spans for sub-service with icon next to name
fn create_sub_service_spans(&self, name: &str, info: &ServiceInfo) -> Vec<ratatui::text::Span<'static>> {
fn create_sub_service_spans(
&self,
name: &str,
info: &ServiceInfo,
) -> Vec<ratatui::text::Span<'static>> {
// Truncate long sub-service names to fit layout (accounting for indentation)
let short_name = if name.len() > 18 {
format!("{}...", &name[..15])
@ -157,22 +165,28 @@ impl ServicesWidget {
// Indentation and tree prefix
ratatui::text::Span::styled(
" ├─ ".to_string(),
Style::default().fg(Theme::secondary_text()).bg(Theme::background())
Style::default()
.fg(Theme::secondary_text())
.bg(Theme::background()),
),
// Status icon
ratatui::text::Span::styled(
format!("{} ", icon),
Style::default().fg(status_color).bg(Theme::background())
Style::default().fg(status_color).bg(Theme::background()),
),
// Service name
ratatui::text::Span::styled(
format!("{:<18} ", short_name),
Style::default().fg(Theme::secondary_text()).bg(Theme::background())
Style::default()
.fg(Theme::secondary_text())
.bg(Theme::background()),
),
// Status/latency text
ratatui::text::Span::styled(
status_str,
Style::default().fg(Theme::secondary_text()).bg(Theme::background())
Style::default()
.fg(Theme::secondary_text())
.bg(Theme::background()),
),
]
}
@ -190,13 +204,16 @@ impl Widget for ServicesWidget {
match sub_service {
None => {
// Parent service metric
let service_info = self.parent_services.entry(parent_service).or_insert(ServiceInfo {
status: "unknown".to_string(),
memory_mb: None,
disk_gb: None,
latency_ms: None,
widget_status: Status::Unknown,
});
let service_info =
self.parent_services
.entry(parent_service)
.or_insert(ServiceInfo {
status: "unknown".to_string(),
memory_mb: None,
disk_gb: None,
latency_ms: None,
widget_status: Status::Unknown,
});
if metric.name.ends_with("_status") {
service_info.status = metric.value.as_string();
@ -213,19 +230,28 @@ impl Widget for ServicesWidget {
}
Some(sub_name) => {
// Sub-service metric
let sub_service_list = self.sub_services.entry(parent_service).or_insert_with(Vec::new);
let sub_service_list = self
.sub_services
.entry(parent_service)
.or_insert_with(Vec::new);
// Find existing sub-service or create new one
let sub_service_info = if let Some(pos) = sub_service_list.iter().position(|(name, _)| name == &sub_name) {
let sub_service_info = if let Some(pos) = sub_service_list
.iter()
.position(|(name, _)| name == &sub_name)
{
&mut sub_service_list[pos].1
} else {
sub_service_list.push((sub_name.clone(), ServiceInfo {
status: "unknown".to_string(),
memory_mb: None,
disk_gb: None,
latency_ms: None,
widget_status: Status::Unknown,
}));
sub_service_list.push((
sub_name.clone(),
ServiceInfo {
status: "unknown".to_string(),
memory_mb: None,
disk_gb: None,
latency_ms: None,
widget_status: Status::Unknown,
},
));
&mut sub_service_list.last_mut().unwrap().1
};
@ -270,8 +296,12 @@ impl Widget for ServicesWidget {
self.has_data = !self.parent_services.is_empty() || !self.sub_services.is_empty();
debug!("Services widget updated: {} parent services, {} sub-service groups, status={:?}",
self.parent_services.len(), self.sub_services.len(), self.status);
debug!(
"Services widget updated: {} parent services, {} sub-service groups, status={:?}",
self.parent_services.len(),
self.sub_services.len(),
self.status
);
}
fn render(&mut self, frame: &mut Frame, area: Rect) {
@ -285,7 +315,10 @@ impl Widget for ServicesWidget {
.split(inner_area);
// Header
let header = format!("{:<25} {:<10} {:<8} {:<8}", "Service:", "Status:", "RAM:", "Disk:");
let header = format!(
"{:<25} {:<10} {:<8} {:<8}",
"Service:", "Status:", "RAM:", "Disk:"
);
let header_para = Paragraph::new(header).style(Typography::muted());
frame.render_widget(header_para, content_chunks[0]);
@ -316,7 +349,12 @@ impl Widget for ServicesWidget {
for (sub_name, sub_info) in sorted_subs {
// Store sub-service info for custom span rendering
display_lines.push((sub_name.clone(), sub_info.widget_status, true, Some(sub_info.clone()))); // true = sub-service
display_lines.push((
sub_name.clone(),
sub_info.widget_status,
true,
Some(sub_info.clone()),
)); // true = sub-service
}
}
}
@ -331,7 +369,9 @@ impl Widget for ServicesWidget {
.constraints(vec![Constraint::Length(1); lines_to_show])
.split(content_chunks[1]);
for (i, (line_text, line_status, is_sub, sub_info)) in display_lines.iter().take(lines_to_show).enumerate() {
for (i, (line_text, line_status, is_sub, sub_info)) in
display_lines.iter().take(lines_to_show).enumerate()
{
let spans = if *is_sub && sub_info.is_some() {
// Use custom sub-service span creation
self.create_sub_service_spans(line_text, sub_info.as_ref().unwrap())