From 9e75273fc585ef13797a323cd4243469cf56a3f2 Mon Sep 17 00:00:00 2001 From: Peter van Arkel Date: Thu, 21 May 2026 13:40:37 +0200 Subject: [PATCH 1/4] (ui) Promote host detail operator controls Add a first-screen host control workspace with status, backup actions, schedule state, and current activity so the host detail page behaves as the primary operator page instead of starting with raw configuration blocks. Refs #26 --- .../templates/pobsync_backend/base.html | 38 ++ .../pobsync_backend/host_detail.html | 338 +++++++++++------- src/pobsync_backend/tests/test_views.py | 7 +- 3 files changed, 251 insertions(+), 132 deletions(-) diff --git a/src/pobsync_backend/templates/pobsync_backend/base.html b/src/pobsync_backend/templates/pobsync_backend/base.html index beef593..31b3ecd 100644 --- a/src/pobsync_backend/templates/pobsync_backend/base.html +++ b/src/pobsync_backend/templates/pobsync_backend/base.html @@ -370,6 +370,43 @@ gap: 10px; justify-content: space-between; padding-top: 8px; + } + .host-control-grid { + display: grid; + gap: 14px; + grid-template-columns: minmax(280px, 1.25fr) repeat(3, minmax(220px, 1fr)); + margin-bottom: 20px; + } + .host-control-panel { + display: grid; + gap: 12px; + margin-bottom: 0; + } + .host-control-panel > h2:first-child { margin-bottom: 0; } + .host-control-primary { + display: grid; + gap: 8px; + } + .host-control-meta { + display: grid; + gap: 6px; + } + .host-control-meta > div { + align-items: baseline; + border-top: 1px solid var(--border); + display: flex; + gap: 10px; + justify-content: space-between; + padding-top: 7px; + } + .host-control-meta .label { + color: var(--muted); + font-size: 12px; + font-weight: 650; + text-transform: uppercase; + } + .host-control-meta strong { + text-align: right; } .status-summary { align-items: center; @@ -641,6 +678,7 @@ .page-header .actions { justify-content: flex-start; } .two-col { grid-template-columns: 1fr; } .dashboard-priority-grid { grid-template-columns: 1fr; } + .host-control-grid { grid-template-columns: 1fr; } .schedule-row { grid-template-columns: 1fr; } .schedule-time { justify-items: start; text-align: left; } .host-card-header { display: grid; } diff --git a/src/pobsync_backend/templates/pobsync_backend/host_detail.html b/src/pobsync_backend/templates/pobsync_backend/host_detail.html index 60bbae8..899762b 100644 --- a/src/pobsync_backend/templates/pobsync_backend/host_detail.html +++ b/src/pobsync_backend/templates/pobsync_backend/host_detail.html @@ -32,47 +32,6 @@ -
-
Snapshots
{{ counts.snapshots }}
-
Runs
{{ counts.runs }}
-
Queued
{{ counts.queued_runs }}
-
Running
{{ counts.running_runs }}
-
Failed Runs
{{ counts.failed_runs }}
-
Incomplete
{{ counts.incomplete_snapshots }}
-
- -
-
-

Config

-
-
Address: {{ host.address }}
-
Enabled: {{ host.enabled|yesno:"yes,no" }}
-
SSH key: {{ host.ssh_credential|default:"global default" }}
-
SSH: {{ host.ssh_user|default:"global" }}{% if host.ssh_port %}:{{ host.ssh_port }}{% endif %}
-
Backup source: {{ host.source_root|default:"global default" }}
-
Retention: daily {{ host.retention_daily }}, weekly {{ host.retention_weekly }}, monthly {{ host.retention_monthly }}, yearly {{ host.retention_yearly }}
-
-
- -
-

Schedule

- {% if schedule %} -
-
Schedule expression: {{ schedule.cron_expr }}
-
Evaluated by the pobsync scheduler service.
-
Enabled: {{ schedule.enabled|yesno:"yes,no" }}
-
Next run: {% if next_run_at %}{{ next_run_at|date:"Y-m-d H:i T" }} {{ scheduler_timezone }}{% endif %}
-
Prune: {{ schedule.prune|yesno:"yes,no" }}
-
Last status: {{ schedule.last_status|default:"" }}
-
Last started: {{ schedule.last_started_at|default:"" }}
-
Last finished: {{ schedule.last_finished_at|default:"" }}
-
- {% else %} -

No schedule configured.

- {% endif %} -
-
- {% if retention_warning.has_warning %}

Retention Warnings

@@ -101,60 +60,137 @@
{% endif %} - {% if effective_config %} -
-

Effective Config

-
-
-
Backup source: {{ effective_config.source_root }}
-
Destination subdir: {{ effective_config.destination_subdir|default:"none" }}
-
SSH: {{ effective_config.ssh.user }}@{{ host.address }}:{{ effective_config.ssh.port }}
-
SSH key: {{ effective_config.ssh.credential|default:"none selected" }}
-
SSH options: {{ effective_config.ssh.options|join:" " }}
-
Rsync binary: {{ effective_config.rsync.binary }}
-
Rsync args: {{ effective_config.rsync.args|join:" " }}
-
Timeout: {{ effective_config.rsync.timeout_seconds }}s
-
Bandwidth limit: {{ effective_config.rsync.bwlimit_kbps }} KB/s
-
- Retention: - d{{ effective_config.retention.daily }} - w{{ effective_config.retention.weekly }} - m{{ effective_config.retention.monthly }} - y{{ effective_config.retention.yearly }} -
-
-
-
Includes: {{ effective_config.includes|length }}
- {% if effective_config.includes %} -
{{ effective_config.includes|join:"
" }}
+
+
+

Host Status

+
+
+ {% if host.enabled %} + enabled {% else %} -
No include rules configured.
- {% endif %} -
Excludes: {{ effective_config.excludes|length }}
- {% if effective_config.excludes %} -
{{ effective_config.excludes|join:"
" }}
- {% else %} -
No exclude rules configured.
+ disabled {% endif %} + {{ host.address }}
+ {% if active_run %} + + {{ active_run.status }} + Run {{ active_run.id }} is active. + + {% elif counts.failed_runs %} + + failed + {{ counts.failed_runs }} failed run{{ counts.failed_runs|pluralize }} need review. + + {% elif retention_warning.has_warning %} + + warning + Retention needs attention. + + {% else %} + + ok + No active blockers for this host. + + {% endif %}
-
- {% endif %} +
+
Snapshots{{ counts.snapshots }}
+
Runs{{ counts.runs }}
+
Incomplete{{ counts.incomplete_snapshots }}
+
+ -
-

Snapshot Discovery

-
-
Backup root: {{ discovery.backup_root|default:"" }}
-
Host root: {{ discovery.host_root|default:"" }}
-
Status: {{ discovery.message }}
- {% if discovery.kind_counts %} -
On disk: - scheduled {{ discovery.kind_counts.scheduled|default:0 }}, - manual {{ discovery.kind_counts.manual|default:0 }}, - incomplete {{ discovery.kind_counts.incomplete|default:0 }} +
+

Backup Control

+
+ {% if active_run %} + {{ active_run.status }} + Run {{ active_run.id }} + {% elif has_global_config and host.enabled %} + {{ backup_gate.state }} + {{ backup_gate.message }} + {% elif not host.enabled %} + disabled + {% elif not has_global_config %} + missing global config + {% endif %} +
+
+
+ {% csrf_token %} + + + + +
+
+ {% csrf_token %} + + +
+
+ {% if active_run %} +

Wait for the active run to finish, or cancel it from the run detail page.

+ {% elif not can_queue_dry_run or not can_queue_real_backup %} + {% if not has_global_config %} +

Create the default global config before queueing backups.

+ {% elif not host.enabled %} +

Enable this host before queueing backups.

+ {% elif backup_gate.real_blockers %} +

Real backups are blocked by failed preflight checks. Dry-runs may still be available when storage-only checks fail.

+ {% endif %} + {% endif %} +
+ +
+

Schedule Edit

+ {% if schedule %} +
+
Schedule expression{{ schedule.cron_expr }}
+
Next run{% if next_run_at %}{{ next_run_at|date:"Y-m-d H:i T" }}{% else %}none{% endif %}
+
Timezone{{ scheduler_timezone }}
+
Prune{{ schedule.prune|yesno:"yes,no" }}
+
Last status{{ schedule.last_status|default:"none" }}
+
+

Evaluated by the pobsync scheduler service.

+ {% else %} +

No schedule configured.

+ Add schedule + {% endif %} +
+ +
+ +
+ +
+
Snapshots
{{ counts.snapshots }}
+
Runs
{{ counts.runs }}
+
Queued
{{ counts.queued_runs }}
+
Running
{{ counts.running_runs }}
+
Failed Runs
{{ counts.failed_runs }}
+
Incomplete
{{ counts.incomplete_snapshots }}
{% if stats_summary.runs %} @@ -268,50 +304,94 @@
{% endif %} -
-

Backup Control

-
- {% if active_run %} - {{ active_run.status }} - Run {{ active_run.id }} - {% elif has_global_config and host.enabled %} - {{ backup_gate.state }} - {{ backup_gate.message }} - {% elif not host.enabled %} - disabled - {% elif not has_global_config %} - missing global config - {% endif %} -
- -
-
- {% csrf_token %} - - - - -
-
- {% csrf_token %} - - -
+
+
+

Configuration

+
+
Address{{ host.address }}
+
SSH key{{ host.ssh_credential|default:"global default" }}
+
SSH{{ host.ssh_user|default:"global" }}{% if host.ssh_port %}:{{ host.ssh_port }}{% endif %}
+
Backup source{{ host.source_root|default:"global default" }}
+
Retentiond{{ host.retention_daily }} w{{ host.retention_weekly }} m{{ host.retention_monthly }} y{{ host.retention_yearly }}
+
+
- {% if active_run %} -

Wait for the active run to finish, or cancel it from the run detail page.

- {% elif not can_queue_dry_run or not can_queue_real_backup %} - {% if not has_global_config %} -

Create the default global config before queueing backups.

- {% elif not host.enabled %} -

Enable this host before queueing backups.

- {% elif backup_gate.real_blockers %} -

Real backups are blocked by failed preflight checks. Dry-runs may still be available when storage-only checks fail.

- {% endif %} - {% endif %} +
+

Snapshot Storage

+
+
Backup root{{ discovery.backup_root|default:"" }}
+
Host root{{ discovery.host_root|default:"" }}
+
Status{{ discovery.message }}
+ {% if discovery.kind_counts %} +
+ On disk + + scheduled {{ discovery.kind_counts.scheduled|default:0 }}, + manual {{ discovery.kind_counts.manual|default:0 }}, + incomplete {{ discovery.kind_counts.incomplete|default:0 }} + +
+ {% endif %} +
+
+
+ {% csrf_token %} + +
+
+ {% csrf_token %} + +
+
+
+
-

Advanced Options

+ {% if effective_config %} +
+

Effective Config

+
+
+
Backup source: {{ effective_config.source_root }}
+
Destination subdir: {{ effective_config.destination_subdir|default:"none" }}
+
SSH: {{ effective_config.ssh.user }}@{{ host.address }}:{{ effective_config.ssh.port }}
+
SSH key: {{ effective_config.ssh.credential|default:"none selected" }}
+
SSH options: {{ effective_config.ssh.options|join:" " }}
+
Rsync binary: {{ effective_config.rsync.binary }}
+
Rsync args: {{ effective_config.rsync.args|join:" " }}
+
Timeout: {{ effective_config.rsync.timeout_seconds }}s
+
Bandwidth limit: {{ effective_config.rsync.bwlimit_kbps }} KB/s
+
+ Retention: + d{{ effective_config.retention.daily }} + w{{ effective_config.retention.weekly }} + m{{ effective_config.retention.monthly }} + y{{ effective_config.retention.yearly }} +
+
+
+
Includes: {{ effective_config.includes|length }}
+ {% if effective_config.includes %} +
{{ effective_config.includes|join:"
" }}
+ {% else %} +
No include rules configured.
+ {% endif %} +
Excludes: {{ effective_config.excludes|length }}
+ {% if effective_config.excludes %} +
{{ effective_config.excludes|join:"
" }}
+ {% else %} +
No exclude rules configured.
+ {% endif %} +
+
+
+ {% endif %} + +
+

Backup Options

+

Use this when the quick actions above need a custom label, include/exclude override, or prune limit.

{% csrf_token %} {{ manual_backup_form.non_field_errors }} diff --git a/src/pobsync_backend/tests/test_views.py b/src/pobsync_backend/tests/test_views.py index e026558..c5d73ae 100644 --- a/src/pobsync_backend/tests/test_views.py +++ b/src/pobsync_backend/tests/test_views.py @@ -923,7 +923,7 @@ class ViewTests(TestCase): self.assertContains(response, "15 2 * * *") self.assertContains(response, "Schedule expression") self.assertContains(response, "Evaluated by the pobsync scheduler service.") - self.assertContains(response, "Next run:") + self.assertContains(response, "Next run") self.assertContains(response, "UTC") self.assertContains(response, "20260519-021500Z__ABCDEFGH") self.assertContains(response, "Discover snapshots") @@ -936,7 +936,7 @@ class ViewTests(TestCase): self.assertContains(response, "Host Check") self.assertContains(response, reverse("prepare_host_directories", args=[host.host])) self.assertContains(response, "warning") - self.assertContains(response, "Snapshot Discovery") + self.assertContains(response, "Snapshot Storage") self.assertContains(response, reverse("queue_manual_backup", args=[host.host])) self.assertContains(response, reverse("run_detail", args=[BackupRun.objects.get().id])) self.assertContains(response, reverse("snapshot_detail", args=[snapshot.id])) @@ -1221,7 +1221,8 @@ class ViewTests(TestCase): response = self.client.get(reverse("host_detail", args=[host.host])) self.assertEqual(response.status_code, 200) - self.assertContains(response, f"Host root: {backup_root / host.host}") + self.assertContains(response, "Host root") + self.assertContains(response, str(backup_root / host.host)) self.assertContains(response, "Found 2 snapshot directories") self.assertContains(response, "scheduled 1") self.assertContains(response, "incomplete 1") From 192919628779156f9f8d25d9852dd0f38d1d77f2 Mon Sep 17 00:00:00 2001 From: Peter van Arkel Date: Thu, 21 May 2026 13:53:10 +0200 Subject: [PATCH 2/4] (ui) Group host detail actions by context Move host-level actions out of the page header and into the panels where operators expect them: configuration, connection preflight, and snapshot storage. This keeps the host control page calmer while preserving the same actions. Refs #26 --- .../templates/pobsync_backend/base.html | 10 +- .../pobsync_backend/host_detail.html | 104 +++++++++--------- 2 files changed, 58 insertions(+), 56 deletions(-) diff --git a/src/pobsync_backend/templates/pobsync_backend/base.html b/src/pobsync_backend/templates/pobsync_backend/base.html index 31b3ecd..8f70d1b 100644 --- a/src/pobsync_backend/templates/pobsync_backend/base.html +++ b/src/pobsync_backend/templates/pobsync_backend/base.html @@ -241,6 +241,13 @@ .stack { display: grid; gap: 5px; } .stack.spaced { margin-bottom: 14px; } .two-col { display: grid; gap: 18px; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } + .panel-grid { + display: grid; + gap: 18px; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + margin-bottom: 18px; + } + .panel-grid .panel { margin-bottom: 0; } .actions { align-items: center; display: flex; @@ -676,7 +683,8 @@ display: grid; } .page-header .actions { justify-content: flex-start; } - .two-col { grid-template-columns: 1fr; } + .two-col, + .panel-grid { grid-template-columns: 1fr; } .dashboard-priority-grid { grid-template-columns: 1fr; } .host-control-grid { grid-template-columns: 1fr; } .schedule-row { grid-template-columns: 1fr; } diff --git a/src/pobsync_backend/templates/pobsync_backend/host_detail.html b/src/pobsync_backend/templates/pobsync_backend/host_detail.html index 899762b..1ffd285 100644 --- a/src/pobsync_backend/templates/pobsync_backend/host_detail.html +++ b/src/pobsync_backend/templates/pobsync_backend/host_detail.html @@ -9,27 +9,6 @@

{{ host.host }}

{{ host.address }} · {{ host.enabled|yesno:"enabled,disabled" }}
-
- Edit config - - {% csrf_token %} - - - Plan retention - Edit schedule -
- {% csrf_token %} - -
-
- {% csrf_token %} - -
-
- {% csrf_token %} - -
-
{% if retention_warning.has_warning %} @@ -144,7 +123,7 @@
-

Schedule Edit

+

Schedule Edit schedule

{% if schedule %}
Schedule expression{{ schedule.cron_expr }}
@@ -272,39 +251,7 @@
- {% if last_preflight %} -
-

Connection Preflight

-
-
Status: {% if last_preflight.ok %}ok{% else %}failed{% endif %}
-
Target: {{ last_preflight.target }}
-
Backup source: {{ last_preflight.source_root }}
-
Remote rsync: {{ last_preflight.rsync_binary }}
-
- - - - - - - - - - - {% for check in last_preflight.checks %} - - - - - - - {% endfor %} - -
StatusCheckMessageDetail
{% if check.ok %}ok{% else %}failed{% endif %}{{ check.name }}{{ check.message }}{{ check.detail }}
-
- {% endif %} - -
+

Configuration

@@ -316,9 +263,56 @@
+
+

Connection Preflight & SSH

+ {% if last_preflight %} +
+
+ Preflight + + + {% if last_preflight.ok %}ok{% else %}failed{% endif %} + + +
+
Target{{ last_preflight.target }}
+
Backup source{{ last_preflight.source_root }}
+
Remote rsync{{ last_preflight.rsync_binary }}
+
+ {% else %} +

No connection preflight recorded yet.

+ {% endif %} +
+
+ {% csrf_token %} + +
+
+ {% csrf_token %} + +
+
+ {% if last_preflight.checks %} +
+ {% for check in last_preflight.checks %} +
+ + {% if check.ok %}ok{% else %}failed{% endif %} + + + {{ check.name }} + {{ check.message }}{% if check.detail %} · {{ check.detail }}{% endif %} + +
+ {% endfor %} +
+ {% endif %} +
+

Snapshot Storage

From ab5291b8d34a2e18769f9c30da118bb81d032db7 Mon Sep 17 00:00:00 2001 From: Peter van Arkel Date: Thu, 21 May 2026 13:59:16 +0200 Subject: [PATCH 3/4] (ui) Show host runs and snapshots as record cards Replace the database-style Latest Runs and Snapshots tables on the host detail page with scannable record cards and host-filtered View all links. Refs #26 --- .../templates/pobsync_backend/base.html | 47 +++++++ .../pobsync_backend/host_detail.html | 130 +++++++++++------- src/pobsync_backend/tests/test_views.py | 2 + 3 files changed, 129 insertions(+), 50 deletions(-) diff --git a/src/pobsync_backend/templates/pobsync_backend/base.html b/src/pobsync_backend/templates/pobsync_backend/base.html index 8f70d1b..f2453d6 100644 --- a/src/pobsync_backend/templates/pobsync_backend/base.html +++ b/src/pobsync_backend/templates/pobsync_backend/base.html @@ -310,6 +310,53 @@ display: grid; gap: 8px; } + .record-list { + display: grid; + gap: 10px; + } + .record-card { + border: 1px solid var(--border); + border-radius: var(--radius); + display: grid; + gap: 10px; + padding: 12px; + } + .record-card-header { + align-items: start; + display: flex; + gap: 12px; + justify-content: space-between; + } + .record-title { + display: grid; + gap: 3px; + min-width: 0; + } + .record-title a { + font-weight: 750; + overflow-wrap: anywhere; + } + .record-facts { + display: grid; + gap: 8px 16px; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + } + .record-fact { + display: grid; + gap: 2px; + min-width: 0; + } + .record-fact .label { + color: var(--muted); + font-size: 11px; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; + } + .record-fact strong, + .record-fact span { + overflow-wrap: anywhere; + } .action-row, .activity-row, .schedule-row { diff --git a/src/pobsync_backend/templates/pobsync_backend/host_detail.html b/src/pobsync_backend/templates/pobsync_backend/host_detail.html index 1ffd285..10eb2f4 100644 --- a/src/pobsync_backend/templates/pobsync_backend/host_detail.html +++ b/src/pobsync_backend/templates/pobsync_backend/host_detail.html @@ -406,58 +406,88 @@
-

Latest Runs

- - - - - - - - - - - - {% for run in latest_runs %} - - - - - - - - {% empty %} - - {% endfor %} - -
StatusStartedEndedSnapshotBase
{{ run.status }}{{ run.started_at|default:"" }}{{ run.ended_at|default:"" }}{% if run.snapshot %}{{ run.snapshot.dirname }}{% else %}{{ run.snapshot_path }}{% endif %}{{ run.base_path|default:"" }}
No backup runs recorded for this host.
+

Latest Runs View all

+
+ {% for run in latest_runs %} +
+
+
+ Run {{ run.id }} + {{ run.run_type }}{% if run.result.duration_seconds %} · {{ run.result.duration_seconds }}s{% endif %} +
+ {{ run.status }} +
+
+
+ Started + {{ run.started_at|default:run.created_at }} +
+
+ Ended + {{ run.ended_at|default:"running or queued" }} +
+
+ Snapshot + {% if run.snapshot %} + {{ run.snapshot.dirname }} + {% elif run.snapshot_path %} + {{ run.snapshot_path }} + {% else %} + none + {% endif %} +
+
+ Base + {{ run.base_path|default:"none" }} +
+
+
+ {% empty %} +

No backup runs recorded for this host.

+ {% endfor %} +
-

Snapshots

- - - - - - - - - - - - {% for snapshot in snapshots %} - - - - - - - - {% empty %} - - {% endfor %} - -
KindStatusStartedDirnameBase
{{ snapshot.kind }}{{ snapshot.status }}{{ snapshot.started_at|default:"" }}{{ snapshot.dirname }}{% if snapshot.base %}{{ snapshot.base.dirname }}{% else %}{{ snapshot.base_dirname }}{% endif %}
No snapshots discovered for this host.
+

Snapshots View all

+
+ {% for snapshot in snapshots %} +
+
+
+ {{ snapshot.dirname }} + {{ snapshot.kind }} +
+ {{ snapshot.status }} +
+
+
+ Started + {{ snapshot.started_at|default:"unknown" }} +
+
+ Ended + {{ snapshot.ended_at|default:"unknown" }} +
+
+ Base + {% if snapshot.base %} + {{ snapshot.base.dirname }} + {% elif snapshot.base_dirname %} + {{ snapshot.base_dirname }} + {% else %} + none + {% endif %} +
+
+ Path + {{ snapshot.path|default:"not recorded" }} +
+
+
+ {% empty %} +

No snapshots discovered for this host.

+ {% endfor %} +
{% endblock %} diff --git a/src/pobsync_backend/tests/test_views.py b/src/pobsync_backend/tests/test_views.py index c5d73ae..1f63df6 100644 --- a/src/pobsync_backend/tests/test_views.py +++ b/src/pobsync_backend/tests/test_views.py @@ -940,6 +940,8 @@ class ViewTests(TestCase): self.assertContains(response, reverse("queue_manual_backup", args=[host.host])) self.assertContains(response, reverse("run_detail", args=[BackupRun.objects.get().id])) self.assertContains(response, reverse("snapshot_detail", args=[snapshot.id])) + self.assertContains(response, f'{reverse("runs_list")}?host={host.host}', html=False) + self.assertContains(response, f'{reverse("snapshots_list")}?host={host.host}', html=False) def test_host_detail_renders_effective_config_preview(self) -> None: self.client.force_login(self.staff_user) From 212813e06682298e9b3657e1832056b8f55fe586 Mon Sep 17 00:00:00 2001 From: Peter van Arkel Date: Thu, 21 May 2026 14:03:43 +0200 Subject: [PATCH 4/4] (ui) Make host diagnostics easier to scan Replace the raw host check table with diagnostic cards and group effective runtime config into operator-focused sections for backup target, connection, and selection/retention details. Refs #26 --- .../pobsync_backend/host_detail.html | 138 +++++++++++------- 1 file changed, 86 insertions(+), 52 deletions(-) diff --git a/src/pobsync_backend/templates/pobsync_backend/host_detail.html b/src/pobsync_backend/templates/pobsync_backend/host_detail.html index 10eb2f4..316fde3 100644 --- a/src/pobsync_backend/templates/pobsync_backend/host_detail.html +++ b/src/pobsync_backend/templates/pobsync_backend/host_detail.html @@ -229,26 +229,25 @@
Failed
{{ host_check_summary.failed }}
Skipped
{{ host_check_summary.skipped }}
- - - - - - - - - - - {% for check in host_checks %} - - - - - - - {% endfor %} - -
StatusCheckMessageDetail
{{ check.status }}{{ check.name }}{{ check.message }}{{ check.detail }}
+
+ {% for check in host_checks %} +
+
+
+ {{ check.name }} + {{ check.message }} +
+ {{ check.status }} +
+ {% if check.detail %} +
+ Detail + {{ check.detail }} +
+ {% endif %} +
+ {% endfor %} +
@@ -346,39 +345,74 @@ {% if effective_config %}

Effective Config

-
-
-
Backup source: {{ effective_config.source_root }}
-
Destination subdir: {{ effective_config.destination_subdir|default:"none" }}
-
SSH: {{ effective_config.ssh.user }}@{{ host.address }}:{{ effective_config.ssh.port }}
-
SSH key: {{ effective_config.ssh.credential|default:"none selected" }}
-
SSH options: {{ effective_config.ssh.options|join:" " }}
-
Rsync binary: {{ effective_config.rsync.binary }}
-
Rsync args: {{ effective_config.rsync.args|join:" " }}
-
Timeout: {{ effective_config.rsync.timeout_seconds }}s
-
Bandwidth limit: {{ effective_config.rsync.bwlimit_kbps }} KB/s
-
- Retention: - d{{ effective_config.retention.daily }} - w{{ effective_config.retention.weekly }} - m{{ effective_config.retention.monthly }} - y{{ effective_config.retention.yearly }} +

Runtime settings after global defaults and host overrides are combined.

+
+
+
+
+ Backup target + Source and destination used by rsync. +
-
-
-
Includes: {{ effective_config.includes|length }}
- {% if effective_config.includes %} -
{{ effective_config.includes|join:"
" }}
- {% else %} -
No include rules configured.
- {% endif %} -
Excludes: {{ effective_config.excludes|length }}
- {% if effective_config.excludes %} -
{{ effective_config.excludes|join:"
" }}
- {% else %} -
No exclude rules configured.
- {% endif %} -
+
+
Backup source:{{ effective_config.source_root }}
+
Destination subdir:{{ effective_config.destination_subdir|default:"none" }}
+
+ +
+
+
+ Connection + SSH and rsync execution settings. +
+
+
+
SSH:{{ effective_config.ssh.user }}@{{ host.address }}:{{ effective_config.ssh.port }}
+
SSH key:{{ effective_config.ssh.credential|default:"none selected" }}
+
SSH options:{{ effective_config.ssh.options|join:" " }}
+
Rsync binary:{{ effective_config.rsync.binary }}
+
Rsync args:{{ effective_config.rsync.args|join:" " }}
+
Timeout:{{ effective_config.rsync.timeout_seconds }}s
+
Bandwidth limit:{{ effective_config.rsync.bwlimit_kbps }} KB/s
+
+
+
+
+
+ Selection & retention + Include/exclude rules and retention counts. +
+
+
+
+ Retention: + + d{{ effective_config.retention.daily }} + w{{ effective_config.retention.weekly }} + m{{ effective_config.retention.monthly }} + y{{ effective_config.retention.yearly }} + +
+
Includes:{{ effective_config.includes|length }}
+
Excludes:{{ effective_config.excludes|length }}
+
+
+
+ {% if effective_config.includes %} +
{{ effective_config.includes|join:"
" }}
+ {% else %} +
No include rules configured.
+ {% endif %} +
+
+ {% if effective_config.excludes %} +
{{ effective_config.excludes|join:"
" }}
+ {% else %} +
No exclude rules configured.
+ {% endif %} +
+
+
{% endif %}