Restructure backup display to show per-repository metrics
All checks were successful
Build and Release / build-and-release (push) Successful in 1m15s
All checks were successful
Build and Release / build-and-release (push) Successful in 1m15s
Remove disk-based backup display and implement repository-centric view with per-repo archive counts and sizes. Backup now uses NFS storage instead of direct disk monitoring. Changes: - Remove BackupDiskData, add BackupRepositoryData structure - Display format: "Repo <timestamp>" with per-repo details - Show archive count and size (MB/GB) for each repository - Agent aggregates repo data from backup status TOML files - Dashboard renders repo list with individual status indicators
This commit is contained in:
@@ -44,9 +44,9 @@ pub struct SystemWidget {
|
||||
storage_pools: Vec<StoragePool>,
|
||||
|
||||
// Backup metrics
|
||||
backup_repositories: Vec<String>,
|
||||
backup_repository_status: Status,
|
||||
backup_disks: Vec<cm_dashboard_shared::BackupDiskData>,
|
||||
backup_last_time: Option<String>,
|
||||
backup_status: Status,
|
||||
backup_repositories: Vec<cm_dashboard_shared::BackupRepositoryData>,
|
||||
|
||||
// Overall status
|
||||
has_data: bool,
|
||||
@@ -112,9 +112,9 @@ impl SystemWidget {
|
||||
tmp_status: Status::Unknown,
|
||||
tmpfs_mounts: Vec::new(),
|
||||
storage_pools: Vec::new(),
|
||||
backup_last_time: None,
|
||||
backup_status: Status::Unknown,
|
||||
backup_repositories: Vec::new(),
|
||||
backup_repository_status: Status::Unknown,
|
||||
backup_disks: Vec::new(),
|
||||
has_data: false,
|
||||
scroll_offset: 0,
|
||||
last_viewport_height: 0,
|
||||
@@ -221,9 +221,9 @@ impl Widget for SystemWidget {
|
||||
|
||||
// Extract backup data
|
||||
let backup = &agent_data.backup;
|
||||
self.backup_last_time = backup.last_backup_time.clone();
|
||||
self.backup_status = backup.backup_status;
|
||||
self.backup_repositories = backup.repositories.clone();
|
||||
self.backup_repository_status = backup.repository_status;
|
||||
self.backup_disks = backup.disks.clone();
|
||||
|
||||
// Clamp scroll offset to valid range after update
|
||||
// This prevents scroll issues when switching between hosts
|
||||
@@ -533,79 +533,41 @@ impl SystemWidget {
|
||||
fn render_backup(&self) -> Vec<Line<'_>> {
|
||||
let mut lines = Vec::new();
|
||||
|
||||
// First section: Repository status and list
|
||||
if !self.backup_repositories.is_empty() {
|
||||
let repo_text = format!("Repo: {}", self.backup_repositories.len());
|
||||
let repo_spans = StatusIcons::create_status_spans(self.backup_repository_status, &repo_text);
|
||||
lines.push(Line::from(repo_spans));
|
||||
|
||||
// List all repositories (sorted for consistent display)
|
||||
let mut sorted_repos = self.backup_repositories.clone();
|
||||
sorted_repos.sort();
|
||||
let repo_count = sorted_repos.len();
|
||||
for (idx, repo) in sorted_repos.iter().enumerate() {
|
||||
let tree_char = if idx == repo_count - 1 { "└─" } else { "├─" };
|
||||
lines.push(Line::from(vec![
|
||||
Span::styled(format!(" {} ", tree_char), Typography::tree()),
|
||||
Span::styled(repo.clone(), Typography::secondary()),
|
||||
]));
|
||||
}
|
||||
if self.backup_repositories.is_empty() {
|
||||
return lines;
|
||||
}
|
||||
|
||||
// Second section: Per-disk backup information (sorted by serial for consistent display)
|
||||
let mut sorted_disks = self.backup_disks.clone();
|
||||
sorted_disks.sort_by(|a, b| a.serial.cmp(&b.serial));
|
||||
for disk in &sorted_disks {
|
||||
let truncated_serial = truncate_serial(&disk.serial);
|
||||
let mut details = Vec::new();
|
||||
// Format backup time (use complete timestamp)
|
||||
let time_display = if let Some(ref time_str) = self.backup_last_time {
|
||||
time_str.clone()
|
||||
} else {
|
||||
"unknown".to_string()
|
||||
};
|
||||
|
||||
if let Some(temp) = disk.temperature_celsius {
|
||||
details.push(format!("T: {}°C", temp as i32));
|
||||
}
|
||||
if let Some(wear) = disk.wear_percent {
|
||||
details.push(format!("W: {}%", wear as i32));
|
||||
}
|
||||
// Header: "Repo <complete timestamp>"
|
||||
let repo_text = format!("Repo {}", time_display);
|
||||
let repo_spans = StatusIcons::create_status_spans(self.backup_status, &repo_text);
|
||||
lines.push(Line::from(repo_spans));
|
||||
|
||||
let disk_text = if !details.is_empty() {
|
||||
format!("{} {}", truncated_serial, details.join(" "))
|
||||
// List all repositories with archive count and size
|
||||
let repo_count = self.backup_repositories.len();
|
||||
for (idx, repo) in self.backup_repositories.iter().enumerate() {
|
||||
let tree_char = if idx == repo_count - 1 { "└─" } else { "├─" };
|
||||
|
||||
// Format size: use MB for < 1GB, otherwise GB
|
||||
let size_display = if repo.repo_size_gb < 1.0 {
|
||||
format!("{:.0}MB", repo.repo_size_gb * 1024.0)
|
||||
} else {
|
||||
truncated_serial
|
||||
format!("{:.1}GB", repo.repo_size_gb)
|
||||
};
|
||||
|
||||
// Overall disk status (worst of backup and usage)
|
||||
let disk_status = disk.backup_status.max(disk.usage_status);
|
||||
let disk_spans = StatusIcons::create_status_spans(disk_status, &disk_text);
|
||||
lines.push(Line::from(disk_spans));
|
||||
let repo_text = format!("{} ({}) {}", repo.name, repo.archive_count, size_display);
|
||||
|
||||
// Show backup time with status
|
||||
if let Some(backup_time) = &disk.last_backup_time {
|
||||
let time_text = format!("Backup: {}", backup_time);
|
||||
let mut time_spans = vec![
|
||||
Span::styled(" ├─ ", Typography::tree()),
|
||||
];
|
||||
time_spans.extend(StatusIcons::create_status_spans(disk.backup_status, &time_text));
|
||||
lines.push(Line::from(time_spans));
|
||||
}
|
||||
|
||||
// Show usage with status and archive count
|
||||
let archive_display = if disk.archives_min == disk.archives_max {
|
||||
format!("{}", disk.archives_min)
|
||||
} else {
|
||||
format!("{}-{}", disk.archives_min, disk.archives_max)
|
||||
};
|
||||
|
||||
let usage_text = format!(
|
||||
"Usage: ({}) {:.0}% {:.0}GB/{:.0}GB",
|
||||
archive_display,
|
||||
disk.disk_usage_percent,
|
||||
disk.disk_used_gb,
|
||||
disk.disk_total_gb
|
||||
);
|
||||
let mut usage_spans = vec![
|
||||
Span::styled(" └─ ", Typography::tree()),
|
||||
let mut repo_spans = vec![
|
||||
Span::styled(format!(" {} ", tree_char), Typography::tree()),
|
||||
];
|
||||
usage_spans.extend(StatusIcons::create_status_spans(disk.usage_status, &usage_text));
|
||||
lines.push(Line::from(usage_spans));
|
||||
repo_spans.extend(StatusIcons::create_status_spans(repo.status, &repo_text));
|
||||
lines.push(Line::from(repo_spans));
|
||||
}
|
||||
|
||||
lines
|
||||
@@ -876,13 +838,10 @@ impl SystemWidget {
|
||||
}
|
||||
|
||||
// Backup section
|
||||
if !self.backup_repositories.is_empty() || !self.backup_disks.is_empty() {
|
||||
count += 1; // Header
|
||||
if !self.backup_repositories.is_empty() {
|
||||
count += 1; // Repo header
|
||||
count += self.backup_repositories.len();
|
||||
}
|
||||
count += self.backup_disks.len() * 3; // Each disk has 3 lines
|
||||
if !self.backup_repositories.is_empty() {
|
||||
count += 1; // Header: "Backup:"
|
||||
count += 1; // Repo count and timestamp header
|
||||
count += self.backup_repositories.len(); // Individual repos
|
||||
}
|
||||
|
||||
count
|
||||
@@ -988,7 +947,7 @@ impl SystemWidget {
|
||||
lines.extend(storage_lines);
|
||||
|
||||
// Backup section (if available)
|
||||
if !self.backup_repositories.is_empty() || !self.backup_disks.is_empty() {
|
||||
if !self.backup_repositories.is_empty() {
|
||||
lines.push(Line::from(vec![
|
||||
Span::styled("Backup:", Typography::widget_title())
|
||||
]));
|
||||
|
||||
Reference in New Issue
Block a user