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:
parent
11be496a26
commit
2ccfc4256a
@ -739,9 +739,8 @@ impl SystemdCollector {
|
|||||||
while i < lines.len() {
|
while i < lines.len() {
|
||||||
let line = lines[i].trim();
|
let line = lines[i].trim();
|
||||||
if line.starts_with("server") && line.contains("{") {
|
if line.starts_with("server") && line.contains("{") {
|
||||||
if let Some(proxy_url) = self.parse_server_block(&lines, &mut i) {
|
if let Some((server_name, proxy_url)) = self.parse_server_block(&lines, &mut i) {
|
||||||
let site_name = proxy_url.replace("http://", "").replace("https://", "");
|
sites.push((server_name, proxy_url));
|
||||||
sites.push((site_name, proxy_url));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
@ -752,7 +751,7 @@ impl SystemdCollector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a server block to extract the primary server_name
|
/// 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;
|
use tracing::debug;
|
||||||
let mut server_names = Vec::new();
|
let mut server_names = Vec::new();
|
||||||
let mut proxy_pass_url = None;
|
let mut proxy_pass_url = None;
|
||||||
@ -806,9 +805,15 @@ impl SystemdCollector {
|
|||||||
|
|
||||||
*start_index = i - 1;
|
*start_index = i - 1;
|
||||||
|
|
||||||
if let Some(proxy_url) = proxy_pass_url {
|
if !server_names.is_empty() && !has_redirect {
|
||||||
if !has_redirect {
|
if let Some(proxy_url) = proxy_pass_url {
|
||||||
return Some(proxy_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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ use std::collections::HashMap;
|
|||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use super::Widget;
|
use super::Widget;
|
||||||
use crate::ui::theme::{Theme, Typography, Components, StatusIcons};
|
use crate::ui::theme::{Components, StatusIcons, Theme, Typography};
|
||||||
use ratatui::style::Style;
|
use ratatui::style::Style;
|
||||||
|
|
||||||
/// Services widget displaying hierarchical systemd service statuses
|
/// Services widget displaying hierarchical systemd service statuses
|
||||||
@ -42,16 +42,18 @@ impl ServicesWidget {
|
|||||||
has_data: false,
|
has_data: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract service name and determine if it's a parent or sub-service
|
/// Extract service name and determine if it's a parent or sub-service
|
||||||
fn extract_service_info(metric_name: &str) -> Option<(String, Option<String>)> {
|
fn extract_service_info(metric_name: &str) -> Option<(String, Option<String>)> {
|
||||||
if metric_name.starts_with("service_") {
|
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("_memory_mb"))
|
||||||
.or_else(|| metric_name.rfind("_disk_gb"))
|
.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
|
let service_part = &metric_name[8..end_pos]; // Remove "service_" prefix
|
||||||
|
|
||||||
// Check for sub-services patterns
|
// Check for sub-services patterns
|
||||||
if service_part.starts_with("nginx_") {
|
if service_part.starts_with("nginx_") {
|
||||||
// nginx sub-services: service_nginx_gitea_latency_ms -> ("nginx", "gitea")
|
// nginx sub-services: service_nginx_gitea_latency_ms -> ("nginx", "gitea")
|
||||||
@ -69,11 +71,11 @@ impl ServicesWidget {
|
|||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format disk size with appropriate units (kB/MB/GB)
|
/// Format disk size with appropriate units (kB/MB/GB)
|
||||||
fn format_disk_size(size_gb: f32) -> String {
|
fn format_disk_size(size_gb: f32) -> String {
|
||||||
let size_mb = size_gb * 1024.0; // Convert GB to MB
|
let size_mb = size_gb * 1024.0; // Convert GB to MB
|
||||||
|
|
||||||
if size_mb >= 1024.0 {
|
if size_mb >= 1024.0 {
|
||||||
// Show as GB
|
// Show as GB
|
||||||
format!("{:.1}GB", size_gb)
|
format!("{:.1}GB", size_gb)
|
||||||
@ -93,41 +95,47 @@ impl ServicesWidget {
|
|||||||
|
|
||||||
/// Format parent service line - returns text without icon for span formatting
|
/// Format parent service line - returns text without icon for span formatting
|
||||||
fn format_parent_service_line(&self, name: &str, info: &ServiceInfo) -> String {
|
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 memory_str = info
|
||||||
let disk_str = info.disk_gb.map_or("0".to_string(), |d| Self::format_disk_size(d));
|
.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)
|
// Truncate long service names to fit layout (account for icon space)
|
||||||
let short_name = if name.len() > 22 {
|
let short_name = if name.len() > 22 {
|
||||||
format!("{}...", &name[..19])
|
format!("{}...", &name[..19])
|
||||||
} else {
|
} else {
|
||||||
name.to_string()
|
name.to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parent services always show active/inactive status
|
// Parent services always show active/inactive status
|
||||||
let status_str = match info.widget_status {
|
let status_str = match info.widget_status {
|
||||||
Status::Ok => "active".to_string(),
|
Status::Ok => "active".to_string(),
|
||||||
Status::Warning => "inactive".to_string(),
|
Status::Warning => "inactive".to_string(),
|
||||||
Status::Critical => "failed".to_string(),
|
Status::Critical => "failed".to_string(),
|
||||||
Status::Unknown => "unknown".to_string(),
|
Status::Unknown => "unknown".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
format!("{:<24} {:<10} {:<8} {:<8}",
|
format!(
|
||||||
short_name,
|
"{:<23} {:<10} {:<8} {:<8}",
|
||||||
status_str,
|
short_name, status_str, memory_str, disk_str
|
||||||
memory_str,
|
)
|
||||||
disk_str)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Create spans for sub-service with icon next to name
|
/// 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)
|
// Truncate long sub-service names to fit layout (accounting for indentation)
|
||||||
let short_name = if name.len() > 18 {
|
let short_name = if name.len() > 18 {
|
||||||
format!("{}...", &name[..15])
|
format!("{}...", &name[..15])
|
||||||
} else {
|
} else {
|
||||||
name.to_string()
|
name.to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sub-services show latency if available, otherwise status
|
// Sub-services show latency if available, otherwise status
|
||||||
let status_str = if let Some(latency) = info.latency_ms {
|
let status_str = if let Some(latency) = info.latency_ms {
|
||||||
if latency < 0.0 {
|
if latency < 0.0 {
|
||||||
@ -138,41 +146,47 @@ impl ServicesWidget {
|
|||||||
} else {
|
} else {
|
||||||
match info.widget_status {
|
match info.widget_status {
|
||||||
Status::Ok => "active".to_string(),
|
Status::Ok => "active".to_string(),
|
||||||
Status::Warning => "inactive".to_string(),
|
Status::Warning => "inactive".to_string(),
|
||||||
Status::Critical => "failed".to_string(),
|
Status::Critical => "failed".to_string(),
|
||||||
Status::Unknown => "unknown".to_string(),
|
Status::Unknown => "unknown".to_string(),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let status_color = match info.widget_status {
|
let status_color = match info.widget_status {
|
||||||
Status::Ok => Theme::success(),
|
Status::Ok => Theme::success(),
|
||||||
Status::Warning => Theme::warning(),
|
Status::Warning => Theme::warning(),
|
||||||
Status::Critical => Theme::error(),
|
Status::Critical => Theme::error(),
|
||||||
Status::Unknown => Theme::muted_text(),
|
Status::Unknown => Theme::muted_text(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let icon = StatusIcons::get_icon(info.widget_status);
|
let icon = StatusIcons::get_icon(info.widget_status);
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
// Indentation and tree prefix
|
// Indentation and tree prefix
|
||||||
ratatui::text::Span::styled(
|
ratatui::text::Span::styled(
|
||||||
" ├─ ".to_string(),
|
" ├─ ".to_string(),
|
||||||
Style::default().fg(Theme::secondary_text()).bg(Theme::background())
|
Style::default()
|
||||||
|
.fg(Theme::secondary_text())
|
||||||
|
.bg(Theme::background()),
|
||||||
),
|
),
|
||||||
// Status icon
|
// Status icon
|
||||||
ratatui::text::Span::styled(
|
ratatui::text::Span::styled(
|
||||||
format!("{} ", icon),
|
format!("{} ", icon),
|
||||||
Style::default().fg(status_color).bg(Theme::background())
|
Style::default().fg(status_color).bg(Theme::background()),
|
||||||
),
|
),
|
||||||
// Service name
|
// Service name
|
||||||
ratatui::text::Span::styled(
|
ratatui::text::Span::styled(
|
||||||
format!("{:<18} ", short_name),
|
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
|
// Status/latency text
|
||||||
ratatui::text::Span::styled(
|
ratatui::text::Span::styled(
|
||||||
status_str,
|
status_str,
|
||||||
Style::default().fg(Theme::secondary_text()).bg(Theme::background())
|
Style::default()
|
||||||
|
.fg(Theme::secondary_text())
|
||||||
|
.bg(Theme::background()),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -181,23 +195,26 @@ impl ServicesWidget {
|
|||||||
impl Widget for ServicesWidget {
|
impl Widget for ServicesWidget {
|
||||||
fn update_from_metrics(&mut self, metrics: &[&Metric]) {
|
fn update_from_metrics(&mut self, metrics: &[&Metric]) {
|
||||||
debug!("Services widget updating with {} metrics", metrics.len());
|
debug!("Services widget updating with {} metrics", metrics.len());
|
||||||
|
|
||||||
// Don't clear existing services - preserve data between metric batches
|
// Don't clear existing services - preserve data between metric batches
|
||||||
|
|
||||||
// Process individual service metrics
|
// Process individual service metrics
|
||||||
for metric in metrics {
|
for metric in metrics {
|
||||||
if let Some((parent_service, sub_service)) = Self::extract_service_info(&metric.name) {
|
if let Some((parent_service, sub_service)) = Self::extract_service_info(&metric.name) {
|
||||||
match sub_service {
|
match sub_service {
|
||||||
None => {
|
None => {
|
||||||
// Parent service metric
|
// Parent service metric
|
||||||
let service_info = self.parent_services.entry(parent_service).or_insert(ServiceInfo {
|
let service_info =
|
||||||
status: "unknown".to_string(),
|
self.parent_services
|
||||||
memory_mb: None,
|
.entry(parent_service)
|
||||||
disk_gb: None,
|
.or_insert(ServiceInfo {
|
||||||
latency_ms: None,
|
status: "unknown".to_string(),
|
||||||
widget_status: Status::Unknown,
|
memory_mb: None,
|
||||||
});
|
disk_gb: None,
|
||||||
|
latency_ms: None,
|
||||||
|
widget_status: Status::Unknown,
|
||||||
|
});
|
||||||
|
|
||||||
if metric.name.ends_with("_status") {
|
if metric.name.ends_with("_status") {
|
||||||
service_info.status = metric.value.as_string();
|
service_info.status = metric.value.as_string();
|
||||||
service_info.widget_status = metric.status;
|
service_info.widget_status = metric.status;
|
||||||
@ -213,22 +230,31 @@ impl Widget for ServicesWidget {
|
|||||||
}
|
}
|
||||||
Some(sub_name) => {
|
Some(sub_name) => {
|
||||||
// Sub-service metric
|
// 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
|
// 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
|
&mut sub_service_list[pos].1
|
||||||
} else {
|
} else {
|
||||||
sub_service_list.push((sub_name.clone(), ServiceInfo {
|
sub_service_list.push((
|
||||||
status: "unknown".to_string(),
|
sub_name.clone(),
|
||||||
memory_mb: None,
|
ServiceInfo {
|
||||||
disk_gb: None,
|
status: "unknown".to_string(),
|
||||||
latency_ms: None,
|
memory_mb: None,
|
||||||
widget_status: Status::Unknown,
|
disk_gb: None,
|
||||||
}));
|
latency_ms: None,
|
||||||
|
widget_status: Status::Unknown,
|
||||||
|
},
|
||||||
|
));
|
||||||
&mut sub_service_list.last_mut().unwrap().1
|
&mut sub_service_list.last_mut().unwrap().1
|
||||||
};
|
};
|
||||||
|
|
||||||
if metric.name.ends_with("_status") {
|
if metric.name.ends_with("_status") {
|
||||||
sub_service_info.status = metric.value.as_string();
|
sub_service_info.status = metric.value.as_string();
|
||||||
sub_service_info.widget_status = metric.status;
|
sub_service_info.widget_status = metric.status;
|
||||||
@ -250,88 +276,102 @@ impl Widget for ServicesWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aggregate status from all parent and sub-services
|
// Aggregate status from all parent and sub-services
|
||||||
let mut all_statuses = Vec::new();
|
let mut all_statuses = Vec::new();
|
||||||
|
|
||||||
// Add parent service statuses
|
// Add parent service statuses
|
||||||
all_statuses.extend(self.parent_services.values().map(|info| info.widget_status));
|
all_statuses.extend(self.parent_services.values().map(|info| info.widget_status));
|
||||||
|
|
||||||
// Add sub-service statuses
|
// Add sub-service statuses
|
||||||
for sub_list in self.sub_services.values() {
|
for sub_list in self.sub_services.values() {
|
||||||
all_statuses.extend(sub_list.iter().map(|(_, info)| info.widget_status));
|
all_statuses.extend(sub_list.iter().map(|(_, info)| info.widget_status));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.status = if all_statuses.is_empty() {
|
self.status = if all_statuses.is_empty() {
|
||||||
Status::Unknown
|
Status::Unknown
|
||||||
} else {
|
} else {
|
||||||
Status::aggregate(&all_statuses)
|
Status::aggregate(&all_statuses)
|
||||||
};
|
};
|
||||||
|
|
||||||
self.has_data = !self.parent_services.is_empty() || !self.sub_services.is_empty();
|
self.has_data = !self.parent_services.is_empty() || !self.sub_services.is_empty();
|
||||||
|
|
||||||
debug!("Services widget updated: {} parent services, {} sub-service groups, status={:?}",
|
debug!(
|
||||||
self.parent_services.len(), self.sub_services.len(), self.status);
|
"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) {
|
fn render(&mut self, frame: &mut Frame, area: Rect) {
|
||||||
let services_block = Components::widget_block("services");
|
let services_block = Components::widget_block("services");
|
||||||
let inner_area = services_block.inner(area);
|
let inner_area = services_block.inner(area);
|
||||||
frame.render_widget(services_block, area);
|
frame.render_widget(services_block, area);
|
||||||
|
|
||||||
let content_chunks = Layout::default()
|
let content_chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints([Constraint::Length(1), Constraint::Min(0)])
|
.constraints([Constraint::Length(1), Constraint::Min(0)])
|
||||||
.split(inner_area);
|
.split(inner_area);
|
||||||
|
|
||||||
// Header
|
// 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());
|
let header_para = Paragraph::new(header).style(Typography::muted());
|
||||||
frame.render_widget(header_para, content_chunks[0]);
|
frame.render_widget(header_para, content_chunks[0]);
|
||||||
|
|
||||||
// Check if we have any services to display
|
// Check if we have any services to display
|
||||||
if self.parent_services.is_empty() && self.sub_services.is_empty() {
|
if self.parent_services.is_empty() && self.sub_services.is_empty() {
|
||||||
let empty_text = Paragraph::new("No process data").style(Typography::muted());
|
let empty_text = Paragraph::new("No process data").style(Typography::muted());
|
||||||
frame.render_widget(empty_text, content_chunks[1]);
|
frame.render_widget(empty_text, content_chunks[1]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build hierarchical service list for display
|
// Build hierarchical service list for display
|
||||||
let mut display_lines = Vec::new();
|
let mut display_lines = Vec::new();
|
||||||
|
|
||||||
// Sort parent services alphabetically for consistent order
|
// Sort parent services alphabetically for consistent order
|
||||||
let mut parent_services: Vec<_> = self.parent_services.iter().collect();
|
let mut parent_services: Vec<_> = self.parent_services.iter().collect();
|
||||||
parent_services.sort_by(|(a, _), (b, _)| a.cmp(b));
|
parent_services.sort_by(|(a, _), (b, _)| a.cmp(b));
|
||||||
|
|
||||||
for (parent_name, parent_info) in parent_services {
|
for (parent_name, parent_info) in parent_services {
|
||||||
// Add parent service line
|
// Add parent service line
|
||||||
let parent_line = self.format_parent_service_line(parent_name, parent_info);
|
let parent_line = self.format_parent_service_line(parent_name, parent_info);
|
||||||
display_lines.push((parent_line, parent_info.widget_status, false, None)); // false = not sub-service
|
display_lines.push((parent_line, parent_info.widget_status, false, None)); // false = not sub-service
|
||||||
|
|
||||||
// Add sub-services for this parent (if any)
|
// Add sub-services for this parent (if any)
|
||||||
if let Some(sub_list) = self.sub_services.get(parent_name) {
|
if let Some(sub_list) = self.sub_services.get(parent_name) {
|
||||||
// Sort sub-services by name for consistent display
|
// Sort sub-services by name for consistent display
|
||||||
let mut sorted_subs = sub_list.clone();
|
let mut sorted_subs = sub_list.clone();
|
||||||
sorted_subs.sort_by(|(a, _), (b, _)| a.cmp(b));
|
sorted_subs.sort_by(|(a, _), (b, _)| a.cmp(b));
|
||||||
|
|
||||||
for (sub_name, sub_info) in sorted_subs {
|
for (sub_name, sub_info) in sorted_subs {
|
||||||
// Store sub-service info for custom span rendering
|
// 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render all lines within available space
|
// Render all lines within available space
|
||||||
let available_lines = content_chunks[1].height as usize;
|
let available_lines = content_chunks[1].height as usize;
|
||||||
let lines_to_show = available_lines.min(display_lines.len());
|
let lines_to_show = available_lines.min(display_lines.len());
|
||||||
|
|
||||||
if lines_to_show > 0 {
|
if lines_to_show > 0 {
|
||||||
let service_chunks = Layout::default()
|
let service_chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints(vec![Constraint::Length(1); lines_to_show])
|
.constraints(vec![Constraint::Length(1); lines_to_show])
|
||||||
.split(content_chunks[1]);
|
.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() {
|
let spans = if *is_sub && sub_info.is_some() {
|
||||||
// Use custom sub-service span creation
|
// Use custom sub-service span creation
|
||||||
self.create_sub_service_spans(line_text, sub_info.as_ref().unwrap())
|
self.create_sub_service_spans(line_text, sub_info.as_ref().unwrap())
|
||||||
@ -343,7 +383,7 @@ impl Widget for ServicesWidget {
|
|||||||
frame.render_widget(service_para, service_chunks[i]);
|
frame.render_widget(service_para, service_chunks[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show indicator if there are more services than we can display
|
// Show indicator if there are more services than we can display
|
||||||
if display_lines.len() > available_lines {
|
if display_lines.len() > available_lines {
|
||||||
let more_count = display_lines.len() - available_lines;
|
let more_count = display_lines.len() - available_lines;
|
||||||
@ -354,7 +394,7 @@ impl Widget for ServicesWidget {
|
|||||||
width: content_chunks[1].width,
|
width: content_chunks[1].width,
|
||||||
height: 1,
|
height: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
let more_text = format!("... and {} more services", more_count);
|
let more_text = format!("... and {} more services", more_count);
|
||||||
let more_para = Paragraph::new(more_text).style(Typography::muted());
|
let more_para = Paragraph::new(more_text).style(Typography::muted());
|
||||||
frame.render_widget(more_para, last_line_area);
|
frame.render_widget(more_para, last_line_area);
|
||||||
@ -367,4 +407,4 @@ impl Default for ServicesWidget {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user