Hide backup panel when no backup data is present

- Add has_data() method to BackupWidget to check if backup metrics exist
- Modify dashboard layout to conditionally show backup panel only when data exists
- When no backup data: system panel takes full left side height
- When backup data exists: system and backup panels share left side equally

Prevents empty backup panel from taking up screen space unnecessarily.
This commit is contained in:
Christoffer Martinsson 2025-10-20 13:01:42 +02:00
parent 8023da2c1e
commit d4531ef2e8
2 changed files with 134 additions and 86 deletions

View File

@ -275,21 +275,40 @@ impl TuiApp {
])
.split(main_chunks[1]);
// Left side: system on top, backup on bottom (equal height)
let left_chunks = ratatui::layout::Layout::default()
// Check if backup panel should be shown
let show_backup = if let Some(hostname) = self.current_host.clone() {
let host_widgets = self.get_or_create_host_widgets(&hostname);
host_widgets.backup_widget.has_data()
} else {
false
};
// Left side: dynamic layout based on backup data availability
let left_chunks = if show_backup {
// Show both system and backup panels
ratatui::layout::Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Percentage(ThemeLayout::SYSTEM_PANEL_HEIGHT), // System section
Constraint::Percentage(ThemeLayout::BACKUP_PANEL_HEIGHT), // Backup section
])
.split(content_chunks[0]);
.split(content_chunks[0])
} else {
// Show only system panel (full height)
ratatui::layout::Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Percentage(100)]) // System section takes full height
.split(content_chunks[0])
};
// Render title bar
self.render_btop_title(frame, main_chunks[0], metric_store);
// Render new panel layout
self.render_system_panel(frame, left_chunks[0], metric_store);
if show_backup && left_chunks.len() > 1 {
self.render_backup_panel(frame, left_chunks[1]);
}
// Render services widget for current host
if let Some(hostname) = self.current_host.clone() {

View File

@ -7,7 +7,7 @@ use ratatui::{
use tracing::debug;
use super::Widget;
use crate::ui::theme::{Typography, StatusIcons};
use crate::ui::theme::{StatusIcons, Typography};
/// Backup widget displaying backup status, services, and repository information
#[derive(Clone)]
@ -74,6 +74,12 @@ impl BackupWidget {
}
}
/// Check if the backup widget has any data to display
pub fn has_data(&self) -> bool {
self.has_data
}
/// Format duration for display
fn format_duration(&self) -> String {
match self.duration_seconds {
@ -107,7 +113,6 @@ impl BackupWidget {
}
}
/// Format disk usage in format "usedGB/totalGB"
fn format_repo_size(&self) -> String {
match (self.backup_disk_used_gb, self.backup_disk_total_gb) {
@ -147,8 +152,6 @@ impl BackupWidget {
}
}
/// Format product name display
fn format_product_name(&self) -> String {
if let Some(ref product_name) = self.backup_disk_product_name {
@ -196,22 +199,30 @@ impl Widget for BackupWidget {
fn update_from_metrics(&mut self, metrics: &[&Metric]) {
debug!("Backup widget updating with {} metrics", metrics.len());
for metric in metrics {
debug!("Backup metric: {} = {:?} (status: {:?})", metric.name, metric.value, metric.status);
debug!(
"Backup metric: {} = {:?} (status: {:?})",
metric.name, metric.value, metric.status
);
}
// Also debug the service_data after processing
debug!("Processing individual service metrics...");
// Log how many metrics are backup service metrics
let service_metric_count = metrics.iter()
let service_metric_count = metrics
.iter()
.filter(|m| m.name.starts_with("backup_service_"))
.count();
debug!("Found {} backup_service_ metrics out of {} total backup metrics",
service_metric_count, metrics.len());
debug!(
"Found {} backup_service_ metrics out of {} total backup metrics",
service_metric_count,
metrics.len()
);
// Reset service metrics
self.service_metrics.clear();
let mut service_data: std::collections::HashMap<String, ServiceMetricData> = std::collections::HashMap::new();
let mut service_data: std::collections::HashMap<String, ServiceMetricData> =
std::collections::HashMap::new();
for metric in metrics {
match metric.name.as_str() {
@ -263,13 +274,18 @@ impl Widget for BackupWidget {
_ => {
// Handle individual service metrics
if let Some(service_name) = Self::extract_service_name(&metric.name) {
debug!("Extracted service name '{}' from metric '{}'", service_name, metric.name);
let entry = service_data.entry(service_name.clone()).or_insert_with(|| ServiceMetricData {
debug!(
"Extracted service name '{}' from metric '{}'",
service_name, metric.name
);
let entry = service_data.entry(service_name.clone()).or_insert_with(|| {
ServiceMetricData {
name: service_name,
status: Status::Unknown,
exit_code: None,
archive_count: None,
repo_size_gb: None,
}
});
if metric.name.ends_with("_status") {
@ -279,13 +295,22 @@ impl Widget for BackupWidget {
entry.exit_code = metric.value.as_i64();
} else if metric.name.ends_with("_archive_count") {
entry.archive_count = metric.value.as_i64();
debug!("Set archive_count for {}: {:?}", entry.name, entry.archive_count);
debug!(
"Set archive_count for {}: {:?}",
entry.name, entry.archive_count
);
} else if metric.name.ends_with("_repo_size_gb") {
entry.repo_size_gb = metric.value.as_f32();
debug!("Set repo_size_gb for {}: {:?}", entry.name, entry.repo_size_gb);
debug!(
"Set repo_size_gb for {}: {:?}",
entry.name, entry.repo_size_gb
);
}
} else {
debug!("Could not extract service name from metric: {}", metric.name);
debug!(
"Could not extract service name from metric: {}",
metric.name
);
}
}
}
@ -298,13 +323,19 @@ impl Widget for BackupWidget {
self.has_data = !metrics.is_empty();
debug!("Backup widget updated: status={:?}, services={}, total_size={:?}GB",
self.overall_status, self.service_metrics.len(), self.total_repo_size_gb);
debug!(
"Backup widget updated: status={:?}, services={}, total_size={:?}GB",
self.overall_status,
self.service_metrics.len(),
self.total_repo_size_gb
);
// Debug individual service data
for service in &self.service_metrics {
debug!("Service {}: status={:?}, archives={:?}, size={:?}GB",
service.name, service.status, service.archive_count, service.repo_size_gb);
debug!(
"Service {}: status={:?}, archives={:?}, size={:?}GB",
service.name, service.status, service.archive_count, service.repo_size_gb
);
}
}
@ -343,12 +374,12 @@ impl BackupWidget {
.split(area);
// "Latest backup" header
let header_para = Paragraph::new("Latest backup:")
.style(Typography::widget_title());
let header_para = Paragraph::new("Latest backup:").style(Typography::widget_title());
frame.render_widget(header_para, content_chunks[0]);
// Status line
let status_text = format!("Status: {}",
let status_text = format!(
"Status: {}",
match self.overall_status {
Status::Ok => "OK",
Status::Warning => "Warning",
@ -361,30 +392,27 @@ impl BackupWidget {
frame.render_widget(status_para, content_chunks[1]);
// Duration and last run
let time_text = format!("Duration: {} • Last: {}",
let time_text = format!(
"Duration: {} • Last: {}",
self.format_duration(),
self.format_last_run()
);
let time_para = Paragraph::new(time_text)
.style(Typography::secondary());
let time_para = Paragraph::new(time_text).style(Typography::secondary());
frame.render_widget(time_para, content_chunks[2]);
// Repository size
let size_text = format!("Disk usage: {}", self.format_repo_size());
let size_para = Paragraph::new(size_text)
.style(Typography::secondary());
let size_para = Paragraph::new(size_text).style(Typography::secondary());
frame.render_widget(size_para, content_chunks[3]);
// Product name
let product_text = self.format_product_name();
let product_para = Paragraph::new(product_text)
.style(Typography::secondary());
let product_para = Paragraph::new(product_text).style(Typography::secondary());
frame.render_widget(product_para, content_chunks[4]);
// Serial number
let serial_text = self.format_serial_number();
let serial_para = Paragraph::new(serial_text)
.style(Typography::secondary());
let serial_para = Paragraph::new(serial_text).style(Typography::secondary());
frame.render_widget(serial_para, content_chunks[5]);
}
@ -410,7 +438,9 @@ impl BackupWidget {
height: 1,
};
let service_info = if let (Some(archives), Some(size_gb)) = (service.archive_count, service.repo_size_gb) {
let service_info = if let (Some(archives), Some(size_gb)) =
(service.archive_count, service.repo_size_gb)
{
let size_str = Self::format_size_with_proper_units(size_gb);
format!(" {}archives {}", archives, size_str)
} else {
@ -437,8 +467,7 @@ impl BackupWidget {
};
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);
}