2 Commits

Author SHA1 Message Date
30cf93df27 Merge pull request 'Remove legacy-facing UI labels' (#29)
Reviewed-on: #29
2026-05-21 11:19:48 +02:00
01c4ccb316 (ui) Remove legacy-facing labels from operator pages
Replace refactor-era wording such as Source, Source root, SQL records,
database, runtime, and Django generation labels with operator-facing copy
around backup source, tracking records, changelog files, active config, and
pobsync-managed SSH keys.

Add view assertions so the old source/SQL labels do not quietly return.

Refs #24
2026-05-21 11:13:10 +02:00
9 changed files with 21 additions and 13 deletions

View File

@@ -192,7 +192,7 @@ class SshCredentialForm(forms.ModelForm):
if not raw_private_key.strip(): if not raw_private_key.strip():
if self.instance and self.instance.pk and self.instance.key_path: if self.instance and self.instance.pk and self.instance.key_path:
return self.instance.private_key return self.instance.private_key
raise forms.ValidationError("Paste a private key, upload a private key file, or generate a key from Django.") raise forms.ValidationError("Paste a private key, upload a private key file, or generate a key in pobsync.")
private_key = normalize_private_key(raw_private_key) private_key = normalize_private_key(raw_private_key)
public_key = validate_ssh_private_key(private_key) public_key = validate_ssh_private_key(private_key)

View File

@@ -266,13 +266,13 @@ def _config_checks() -> list[SelfCheck]:
message = "Default global config exists." message = "Default global config exists."
if global_config.backup_root != settings.POBSYNC_BACKUP_ROOT: if global_config.backup_root != settings.POBSYNC_BACKUP_ROOT:
status = "warning" status = "warning"
message = "Global config backup root differs from the runtime backup root." message = "Saved backup root differs from the active backup root."
return [ return [
SelfCheck( SelfCheck(
"Global config", "Global config",
status, status,
message, message,
f"database={global_config.backup_root} runtime={settings.POBSYNC_BACKUP_ROOT}", f"saved={global_config.backup_root} active={settings.POBSYNC_BACKUP_ROOT}",
) )
] ]

View File

@@ -12,7 +12,7 @@
<section class="panel"> <section class="panel">
<div class="stack spaced"> <div class="stack spaced">
<div><strong>Installed version:</strong> {{ app_version }}</div> <div><strong>Installed version:</strong> {{ app_version }}</div>
<div class="muted">Source: {{ changelog_path }}</div> <div class="muted">Changelog file: {{ changelog_path }}</div>
{% if missing %} {% if missing %}
<div class="status warning">missing</div> <div class="status warning">missing</div>
{% endif %} {% endif %}

View File

@@ -13,7 +13,7 @@
<h2>{% if global_config %}Edit Global Config{% else %}Create Global Config{% endif %}</h2> <h2>{% if global_config %}Edit Global Config{% else %}Create Global Config{% endif %}</h2>
<div class="stack spaced"> <div class="stack spaced">
<div><strong>Backup root:</strong> {{ backup_root }}</div> <div><strong>Backup root:</strong> {{ backup_root }}</div>
<div class="muted">This path comes from the runtime environment and is written back when the config is saved.</div> <div class="muted">This path is managed by the service environment and is saved with the config.</div>
</div> </div>
<form method="post" class="form-grid"> <form method="post" class="form-grid">
{% csrf_token %} {% csrf_token %}

View File

@@ -44,7 +44,7 @@
<div><strong>Enabled:</strong> {{ host.enabled|yesno:"yes,no" }}</div> <div><strong>Enabled:</strong> {{ host.enabled|yesno:"yes,no" }}</div>
<div><strong>SSH key:</strong> {{ host.ssh_credential|default:"global default" }}</div> <div><strong>SSH key:</strong> {{ host.ssh_credential|default:"global default" }}</div>
<div><strong>SSH:</strong> {{ host.ssh_user|default:"global" }}{% if host.ssh_port %}:{{ host.ssh_port }}{% endif %}</div> <div><strong>SSH:</strong> {{ host.ssh_user|default:"global" }}{% if host.ssh_port %}:{{ host.ssh_port }}{% endif %}</div>
<div><strong>Source:</strong> {{ host.source_root|default:"global default" }}</div> <div><strong>Backup source:</strong> {{ host.source_root|default:"global default" }}</div>
<div><strong>Retention:</strong> daily {{ host.retention_daily }}, weekly {{ host.retention_weekly }}, monthly {{ host.retention_monthly }}, yearly {{ host.retention_yearly }}</div> <div><strong>Retention:</strong> daily {{ host.retention_daily }}, weekly {{ host.retention_weekly }}, monthly {{ host.retention_monthly }}, yearly {{ host.retention_yearly }}</div>
</div> </div>
</section> </section>
@@ -101,7 +101,7 @@
<h2>Effective Config</h2> <h2>Effective Config</h2>
<div class="two-col"> <div class="two-col">
<div class="stack"> <div class="stack">
<div><strong>Source root:</strong> {{ effective_config.source_root }}</div> <div><strong>Backup source:</strong> {{ effective_config.source_root }}</div>
<div><strong>Destination subdir:</strong> {{ effective_config.destination_subdir|default:"none" }}</div> <div><strong>Destination subdir:</strong> {{ effective_config.destination_subdir|default:"none" }}</div>
<div><strong>SSH:</strong> {{ effective_config.ssh.user }}@{{ host.address }}:{{ effective_config.ssh.port }}</div> <div><strong>SSH:</strong> {{ effective_config.ssh.user }}@{{ host.address }}:{{ effective_config.ssh.port }}</div>
<div><strong>SSH key:</strong> {{ effective_config.ssh.credential|default:"none selected" }}</div> <div><strong>SSH key:</strong> {{ effective_config.ssh.credential|default:"none selected" }}</div>
@@ -237,7 +237,7 @@
<div class="stack spaced"> <div class="stack spaced">
<div><strong>Status:</strong> <span class="status {% if last_preflight.ok %}ok{% else %}failed{% endif %}">{% if last_preflight.ok %}ok{% else %}failed{% endif %}</span></div> <div><strong>Status:</strong> <span class="status {% if last_preflight.ok %}ok{% else %}failed{% endif %}">{% if last_preflight.ok %}ok{% else %}failed{% endif %}</span></div>
<div><strong>Target:</strong> {{ last_preflight.target }}</div> <div><strong>Target:</strong> {{ last_preflight.target }}</div>
<div><strong>Source root:</strong> {{ last_preflight.source_root }}</div> <div><strong>Backup source:</strong> {{ last_preflight.source_root }}</div>
<div><strong>Remote rsync:</strong> {{ last_preflight.rsync_binary }}</div> <div><strong>Remote rsync:</strong> {{ last_preflight.rsync_binary }}</div>
</div> </div>
<table> <table>

View File

@@ -14,7 +14,6 @@
</section> </section>
<section class="grid" aria-label="Retention plan summary"> <section class="grid" aria-label="Retention plan summary">
<div class="metric"><div class="label">Source</div><div class="value">{{ plan.source }}</div></div>
<div class="metric"><div class="label">Kind</div><div class="value">{{ plan.kind }}</div></div> <div class="metric"><div class="label">Kind</div><div class="value">{{ plan.kind }}</div></div>
<div class="metric"><div class="label">Keep</div><div class="value">{{ plan.keep|length }}</div></div> <div class="metric"><div class="label">Keep</div><div class="value">{{ plan.keep|length }}</div></div>
<div class="metric"><div class="label">Would Delete</div><div class="value">{{ plan.delete|length }}</div></div> <div class="metric"><div class="label">Would Delete</div><div class="value">{{ plan.delete|length }}</div></div>
@@ -42,7 +41,7 @@
</p> </p>
<p> <p>
After inspection, use the dedicated cleanup form below to delete only incomplete snapshot directories and their After inspection, use the dedicated cleanup form below to delete only incomplete snapshot directories and their
SQL records. Successful scheduled and manual snapshots are not touched by this cleanup. tracking records. Successful scheduled and manual snapshots are not touched by this cleanup.
</p> </p>
</section> </section>
{% endif %} {% endif %}

View File

@@ -193,7 +193,6 @@
<h2>Retention</h2> <h2>Retention</h2>
<div class="stack"> <div class="stack">
<div><strong>Status:</strong> {% if prune_result.ok %}ok{% else %}warning{% endif %}</div> <div><strong>Status:</strong> {% if prune_result.ok %}ok{% else %}warning{% endif %}</div>
{% if prune_result.source %}<div><strong>Source:</strong> {{ prune_result.source }}</div>{% endif %}
{% if prune_result.kind %}<div><strong>Kind:</strong> {{ prune_result.kind }}</div>{% endif %} {% if prune_result.kind %}<div><strong>Kind:</strong> {{ prune_result.kind }}</div>{% endif %}
{% if prune_result.planned_delete_count is not None %}<div><strong>Planned deletions:</strong> {{ prune_result.planned_delete_count }}</div>{% endif %} {% if prune_result.planned_delete_count is not None %}<div><strong>Planned deletions:</strong> {{ prune_result.planned_delete_count }}</div>{% endif %}
{% if prune_result.deleted %}<div><strong>Deleted:</strong> {{ prune_result.deleted|length }}</div>{% endif %} {% if prune_result.deleted %}<div><strong>Deleted:</strong> {{ prune_result.deleted|length }}</div>{% endif %}

View File

@@ -63,7 +63,7 @@
<section class="panel"> <section class="panel">
<h2>Restore Guidance</h2> <h2>Restore Guidance</h2>
<div class="stack spaced"> <div class="stack spaced">
<div><strong>Snapshot data source:</strong> {{ restore.source_path }}</div> <div><strong>Snapshot data path:</strong> {{ restore.source_path }}</div>
<div><strong>Example staging destination:</strong> {{ restore.destination_path }}</div> <div><strong>Example staging destination:</strong> {{ restore.destination_path }}</div>
<div class="muted"> <div class="muted">
Restore from the snapshot's <code>data/</code> directory. Start with a dry run, restore to a staging path first, Restore from the snapshot's <code>data/</code> directory. Start with a dry run, restore to a staging path first,
@@ -93,7 +93,7 @@
<div class="muted">Replace <code>{{ restore.example_file_relative_path }}</code> with the file you want to restore.</div> <div class="muted">Replace <code>{{ restore.example_file_relative_path }}</code> with the file you want to restore.</div>
</div> </div>
<div class="stack spaced"> <div class="stack spaced">
<div><strong>Dry-run restore back to the source host:</strong></div> <div><strong>Dry-run restore back to the original host:</strong></div>
<pre>{{ restore.remote_dry_run_command }}</pre> <pre>{{ restore.remote_dry_run_command }}</pre>
</div> </div>
<p class="muted"> <p class="muted">

View File

@@ -59,6 +59,8 @@ class ViewTests(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertContains(response, "Installed version:") self.assertContains(response, "Installed version:")
self.assertContains(response, "Changelog file:")
self.assertNotContains(response, "Source:")
self.assertContains(response, "1.0.0 - 2026-05-21") self.assertContains(response, "1.0.0 - 2026-05-21")
self.assertContains(response, "Django control panel") self.assertContains(response, "Django control panel")
self.assertContains(response, "Native systemd installer") self.assertContains(response, "Native systemd installer")
@@ -868,6 +870,8 @@ class ViewTests(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertContains(response, "Effective Config") self.assertContains(response, "Effective Config")
self.assertContains(response, "Backup source:")
self.assertNotContains(response, "Source root:")
self.assertContains(response, "root@web-01.example.test:2222") self.assertContains(response, "root@web-01.example.test:2222")
self.assertContains(response, "default-key") self.assertContains(response, "default-key")
self.assertContains(response, "-oBatchMode=yes") self.assertContains(response, "-oBatchMode=yes")
@@ -1426,6 +1430,7 @@ class ViewTests(TestCase):
self.assertContains(response, "Check network connectivity.") self.assertContains(response, "Check network connectivity.")
self.assertContains(response, "Retention") self.assertContains(response, "Retention")
self.assertContains(response, "Planned deletions") self.assertContains(response, "Planned deletions")
self.assertNotContains(response, "Source:</strong> sql")
self.assertContains(response, "Max delete") self.assertContains(response, "Max delete")
self.assertContains(response, "Protect bases") self.assertContains(response, "Protect bases")
self.assertContains(response, "Incomplete ignored") self.assertContains(response, "Incomplete ignored")
@@ -1616,6 +1621,10 @@ class ViewTests(TestCase):
self.assertContains(response, "Files seen:</strong> 100") self.assertContains(response, "Files seen:</strong> 100")
self.assertContains(response, "Hardlinked files:</strong> 9") self.assertContains(response, "Hardlinked files:</strong> 9")
self.assertContains(response, "Restore Guidance") self.assertContains(response, "Restore Guidance")
self.assertContains(response, "Snapshot data path:")
self.assertNotContains(response, "Snapshot data source:")
self.assertContains(response, "Dry-run restore back to the original host:")
self.assertNotContains(response, "Dry-run restore back to the source host:")
self.assertContains(response, f"{base.path}/data") self.assertContains(response, f"{base.path}/data")
self.assertContains(response, f"/restore/{host.host}") self.assertContains(response, f"/restore/{host.host}")
self.assertContains(response, "rsync -aHAX --numeric-ids --info=progress2 --dry-run") self.assertContains(response, "rsync -aHAX --numeric-ids --info=progress2 --dry-run")
@@ -1694,6 +1703,7 @@ class ViewTests(TestCase):
self.assertContains(response, "newest") self.assertContains(response, "newest")
self.assertContains(response, "Would Delete") self.assertContains(response, "Would Delete")
self.assertContains(response, "outside retention policy") self.assertContains(response, "outside retention policy")
self.assertNotContains(response, "<div class=\"label\">Source</div>", html=True)
self.assertContains(response, "Confirm delete count") self.assertContains(response, "Confirm delete count")
self.assertContains(response, "Type 1 to confirm the current number of planned deletions.") self.assertContains(response, "Type 1 to confirm the current number of planned deletions.")