diff --git a/src/pobsync_backend/templates/pobsync_backend/base.html b/src/pobsync_backend/templates/pobsync_backend/base.html index c9b4ea1..beef593 100644 --- a/src/pobsync_backend/templates/pobsync_backend/base.html +++ b/src/pobsync_backend/templates/pobsync_backend/base.html @@ -288,7 +288,7 @@ .dashboard-priority-grid { display: grid; gap: 14px; - grid-template-columns: minmax(280px, 1.3fr) repeat(2, minmax(240px, 1fr)); + grid-template-columns: minmax(280px, 1.25fr) repeat(3, minmax(220px, 1fr)); margin-bottom: 20px; } .priority-panel { @@ -342,6 +342,34 @@ .schedule-time { justify-items: end; text-align: right; + } + .storage-priority { + display: grid; + gap: 12px; + } + .storage-priority .label { + color: var(--muted); + font-size: 12px; + font-weight: 650; + text-transform: uppercase; + } + .storage-priority .value { + font-size: 27px; + font-weight: 760; + line-height: 1.15; + margin-top: 4px; + } + .storage-priority-facts { + display: grid; + gap: 8px; + } + .storage-priority-facts > div { + align-items: baseline; + border-top: 1px solid var(--border); + display: flex; + gap: 10px; + justify-content: space-between; + padding-top: 8px; } .status-summary { align-items: center; diff --git a/src/pobsync_backend/templates/pobsync_backend/dashboard.html b/src/pobsync_backend/templates/pobsync_backend/dashboard.html index 2c44ff3..97324f8 100644 --- a/src/pobsync_backend/templates/pobsync_backend/dashboard.html +++ b/src/pobsync_backend/templates/pobsync_backend/dashboard.html @@ -114,6 +114,53 @@

No backup runs recorded yet.

{% endif %} + +
+

Storage Pressure

+ {% if stats_summary.runs_sampled %} +
+
+
Backup root used
+
+ {% if stats_summary.capacity.used_percent is not None %} + {{ stats_summary.capacity.used_percent|floatformat:1 }}% + {% else %} + unknown + {% endif %} +
+ {% if stats_summary.capacity.used_percent is not None %} +
+ +
+ {% endif %} +
+
+
+ Runway + + {% if stats_summary.estimated_days_until_full %} + {{ stats_summary.estimated_days_until_full }} days + {% elif stats_summary.estimated_runs_until_full %} + {{ stats_summary.estimated_runs_until_full }} runs + {% else %} + unknown + {% endif %} + +
+
+ New data + {{ stats_summary.avg_daily_literal_data_bytes|filesizeformat }}/day +
+
+ Available + {{ stats_summary.capacity.available_bytes|filesizeformat }} +
+
+
+ {% else %} +

Storage pressure appears after the first completed backup with stats.

+ {% endif %} +
@@ -129,24 +176,6 @@

Backup Trends

{% if stats_summary.runs_sampled %}
-
-
Storage Used
-
- {% if stats_summary.capacity.used_percent is not None %} - {{ stats_summary.capacity.used_percent|floatformat:1 }}% - {% else %} - unknown - {% endif %} -
- {% if stats_summary.capacity.used_percent is not None %} -
- -
- {% endif %} -
- {{ stats_summary.capacity.available_bytes|filesizeformat }} available from the backup root. -
-
Runway
diff --git a/src/pobsync_backend/tests/test_views.py b/src/pobsync_backend/tests/test_views.py index 118fcf7..e026558 100644 --- a/src/pobsync_backend/tests/test_views.py +++ b/src/pobsync_backend/tests/test_views.py @@ -177,14 +177,14 @@ class ViewTests(TestCase): self.assertEqual(response.status_code, 200) self.assertContains(response, "Backup Trends") - self.assertContains(response, "Storage Used") + self.assertContains(response, "Storage Pressure") + self.assertContains(response, "Backup root used") self.assertContains(response, "Runway") self.assertContains(response, "New Data") self.assertContains(response, "Link-Dest Savings") self.assertContains(response, "80.0%") self.assertContains(response, "10 days") self.assertContains(response, "Warnings") - self.assertContains(response, "Queued") self.assertContains(response, "Next Run") self.assertContains(response, "UTC") self.assertContains(response, "10") diff --git a/src/pobsync_backend/views.py b/src/pobsync_backend/views.py index ae0d91c..d6187cc 100644 --- a/src/pobsync_backend/views.py +++ b/src/pobsync_backend/views.py @@ -135,7 +135,7 @@ def _dashboard_action_items(hosts: list[HostConfig]) -> list[dict[str, object]]: "url": _runs_list_url(host=host_config.host, status="warning", review="needed"), } ) - if host_config.retention_warning.has_warning: + if host_config.retention_warning.get("has_warning"): action_items.append( { "host": host_config, @@ -168,15 +168,15 @@ def _dashboard_next_schedule_rows() -> list[dict[str, object]]: def _retention_warning_summary(retention_warning) -> str: parts = [] - if retention_warning.prune_exceeded: + if retention_warning.get("prune_exceeded"): parts.append( - f"Scheduled prune would delete {retention_warning.delete_count} snapshot(s), " - f"above max {retention_warning.max_delete}." + f"Scheduled prune would delete {retention_warning.get('delete_count')} snapshot(s), " + f"above max {retention_warning.get('max_delete')}." ) - if retention_warning.incomplete_count: - parts.append(f"{retention_warning.incomplete_count} incomplete snapshot(s) need review.") - if retention_warning.error: - parts.append(str(retention_warning.error)) + if retention_warning.get("incomplete_count"): + parts.append(f"{retention_warning.get('incomplete_count')} incomplete snapshot(s) need review.") + if retention_warning.get("error"): + parts.append(str(retention_warning.get("error"))) return " ".join(parts)