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 %}
-
+
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 %}
-
+
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 %}
-
+
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 %}
-
-
+
+
+
{% 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 %}
-
+
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)