From af548f11c4a4c961c7219ad798f238f31e068dee Mon Sep 17 00:00:00 2001 From: Peter van Arkel Date: Thu, 21 May 2026 14:13:05 +0200 Subject: [PATCH] (ui) Standardize primary form actions Add shared form action styling and consistent Cancel links across config, schedule, and SSH key forms so create/edit flows behave predictably. Refs #25 --- .../templates/pobsync_backend/base.html | 22 +++++++++++++++++++ .../pobsync_backend/global_form.html | 3 ++- .../templates/pobsync_backend/host_form.html | 7 +++++- .../pobsync_backend/schedule_form.html | 3 ++- .../pobsync_backend/ssh_credential_form.html | 7 ++++-- .../ssh_credential_generate.html | 3 ++- src/pobsync_backend/tests/test_views.py | 19 ++++++++++++++++ 7 files changed, 58 insertions(+), 6 deletions(-) diff --git a/src/pobsync_backend/templates/pobsync_backend/base.html b/src/pobsync_backend/templates/pobsync_backend/base.html index f2453d6..3d3cd22 100644 --- a/src/pobsync_backend/templates/pobsync_backend/base.html +++ b/src/pobsync_backend/templates/pobsync_backend/base.html @@ -280,6 +280,17 @@ } button.secondary:hover, .button-link.secondary:hover { background: #eef3f8; } + button.danger, + .button-link.danger { + background: var(--failed); + border-color: var(--failed); + color: #fff; + } + button.danger:hover, + .button-link.danger:hover { + background: #842828; + border-color: #842828; + } button.compact, .button-link.compact { font-size: 12px; @@ -679,6 +690,16 @@ .message.error { border-color: #e8b4b4; background: #fff0f0; color: var(--failed); } .message.warning { border-color: #e7cf8a; background: #fff8df; color: var(--running); } .form-grid { display: grid; gap: 15px; max-width: 720px; } + .form-actions { + align-items: center; + border-top: 1px solid var(--border); + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 4px; + padding-top: 15px; + } + .form-actions .button-link.secondary { margin-left: auto; } .field { display: grid; gap: 6px; } .field label { font-weight: 700; } .field input[type="text"], .field input[type="number"], .field select, .field textarea { @@ -736,6 +757,7 @@ .host-control-grid { grid-template-columns: 1fr; } .schedule-row { grid-template-columns: 1fr; } .schedule-time { justify-items: start; text-align: left; } + .form-actions .button-link.secondary { margin-left: 0; } .host-card-header { display: grid; } .host-card-status { justify-content: flex-start; max-width: none; } .host-card-layout { grid-template-columns: 1fr; } diff --git a/src/pobsync_backend/templates/pobsync_backend/global_form.html b/src/pobsync_backend/templates/pobsync_backend/global_form.html index 98fe535..41c05bc 100644 --- a/src/pobsync_backend/templates/pobsync_backend/global_form.html +++ b/src/pobsync_backend/templates/pobsync_backend/global_form.html @@ -33,8 +33,9 @@ {% endfor %} -
+
+ Cancel
diff --git a/src/pobsync_backend/templates/pobsync_backend/host_form.html b/src/pobsync_backend/templates/pobsync_backend/host_form.html index 6847561..06a8abd 100644 --- a/src/pobsync_backend/templates/pobsync_backend/host_form.html +++ b/src/pobsync_backend/templates/pobsync_backend/host_form.html @@ -33,8 +33,13 @@
{% endfor %} -
+
+ {% if host %} + Cancel + {% else %} + Cancel + {% endif %}
diff --git a/src/pobsync_backend/templates/pobsync_backend/schedule_form.html b/src/pobsync_backend/templates/pobsync_backend/schedule_form.html index 9aa7581..d9cee47 100644 --- a/src/pobsync_backend/templates/pobsync_backend/schedule_form.html +++ b/src/pobsync_backend/templates/pobsync_backend/schedule_form.html @@ -30,8 +30,9 @@
{% endfor %} -
+
+ Cancel
diff --git a/src/pobsync_backend/templates/pobsync_backend/ssh_credential_form.html b/src/pobsync_backend/templates/pobsync_backend/ssh_credential_form.html index 092480f..0cdf441 100644 --- a/src/pobsync_backend/templates/pobsync_backend/ssh_credential_form.html +++ b/src/pobsync_backend/templates/pobsync_backend/ssh_credential_form.html @@ -41,8 +41,9 @@
{% endfor %} -
+
+ Cancel
@@ -64,7 +65,9 @@
- +
+ +
{% endif %} diff --git a/src/pobsync_backend/templates/pobsync_backend/ssh_credential_generate.html b/src/pobsync_backend/templates/pobsync_backend/ssh_credential_generate.html index 1b5933c..acb10ba 100644 --- a/src/pobsync_backend/templates/pobsync_backend/ssh_credential_generate.html +++ b/src/pobsync_backend/templates/pobsync_backend/ssh_credential_generate.html @@ -29,8 +29,9 @@ {% endfor %} -
+
+ Cancel
diff --git a/src/pobsync_backend/tests/test_views.py b/src/pobsync_backend/tests/test_views.py index 1f63df6..76ebdc6 100644 --- a/src/pobsync_backend/tests/test_views.py +++ b/src/pobsync_backend/tests/test_views.py @@ -542,6 +542,19 @@ class ViewTests(TestCase): self.assertEqual(credential.private_key, "UPLOADED PRIVATE KEY\n") self.assertEqual(credential.public_key, "DERIVED PUBLIC KEY") + def test_ssh_credential_forms_render_cancel_actions(self) -> None: + self.client.force_login(self.staff_user) + credential = SshCredential.objects.create(name="backup-key") + + create_response = self.client.get(reverse("create_ssh_credential")) + edit_response = self.client.get(reverse("edit_ssh_credential", args=[credential.id])) + generate_response = self.client.get(reverse("generate_ssh_credential")) + + for response in (create_response, edit_response, generate_response): + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Cancel") + self.assertContains(response, reverse("ssh_credentials")) + def test_ssh_credentials_view_generates_filesystem_key(self) -> None: self.client.force_login(self.staff_user) with TemporaryDirectory() as tmp, override_settings(POBSYNC_HOME=str(Path(tmp) / "home")): @@ -746,6 +759,8 @@ class ViewTests(TestCase): self.assertContains(response, f'value="{credential.id}" selected') self.assertContains(response, "--archive") self.assertContains(response, "/proc/***") + self.assertContains(response, "Cancel") + self.assertContains(response, reverse("dashboard")) def test_global_config_form_renders_static_container_backup_root_on_edit(self) -> None: self.client.force_login(self.staff_user) @@ -2142,6 +2157,8 @@ class ViewTests(TestCase): self.assertContains(response, "evaluated by the pobsync scheduler service") self.assertContains(response, "15 2 * * *") self.assertContains(response, "Save schedule") + self.assertContains(response, "Cancel") + self.assertContains(response, reverse("host_detail", args=[host.host])) def test_schedule_form_creates_schedule(self) -> None: self.client.force_login(self.staff_user) @@ -2243,6 +2260,8 @@ class ViewTests(TestCase): self.assertContains(response, "/srv") self.assertContains(response, "*.tmp") self.assertContains(response, "--numeric-ids") + self.assertContains(response, "Cancel") + self.assertContains(response, reverse("host_detail", args=[host.host])) def test_host_config_form_renders_effective_config_check(self) -> None: self.client.force_login(self.staff_user)