(ui) Standardize list filter actions

Give run, snapshot, schedule, purged snapshot, and log filters the same
responsive form layout with consistent Apply/Clear actions.

Refs #25
This commit is contained in:
2026-05-21 14:22:11 +02:00
parent 1604f0f6f4
commit 0f0de5dc30
7 changed files with 40 additions and 10 deletions

View File

@@ -690,6 +690,13 @@
.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; }
.filter-form {
align-items: end;
display: grid;
gap: 15px;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
max-width: none;
}
.form-actions {
align-items: center;
border-top: 1px solid var(--border);
@@ -700,6 +707,13 @@
padding-top: 15px;
}
.form-actions .button-link.secondary { margin-left: auto; }
.filter-form .form-actions {
border-top: 0;
justify-content: flex-end;
margin-top: 0;
padding-top: 0;
}
.filter-form .form-actions .button-link.secondary { margin-left: 0; }
.field { display: grid; gap: 6px; }
.field label { font-weight: 700; }
.field input[type="text"], .field input[type="number"], .field select, .field textarea {

View File

@@ -16,7 +16,7 @@
<section class="panel">
<h2>Filter</h2>
<form method="get" class="form-grid">
<form method="get" class="filter-form">
<div class="field">
<label for="unit">Unit</label>
<select id="unit" name="unit">
@@ -54,8 +54,9 @@
<label for="q">Message contains</label>
<input id="q" name="q" value="{{ query }}">
</div>
<div class="actions">
<div class="form-actions">
<button type="submit">Filter logs</button>
<a class="button-link secondary" href="{% url 'logs' %}">Clear</a>
</div>
</form>
</section>

View File

@@ -16,7 +16,7 @@
<section class="panel">
<h2>Filters</h2>
<form method="get" class="form-grid">
<form method="get" class="filter-form">
<div class="field">
<label for="host">Host</label>
<select id="host" name="host">
@@ -35,7 +35,7 @@
{% endfor %}
</select>
</div>
<div class="actions">
<div class="form-actions">
<button type="submit">Apply filters</button>
<a class="button-link secondary" href="{% url 'purged_snapshots' %}">Clear</a>
</div>

View File

@@ -16,7 +16,7 @@
<section class="panel">
<h2>Filters</h2>
<form method="get" class="form-grid">
<form method="get" class="filter-form">
<div class="field">
<label for="status">Status</label>
<select id="status" name="status">
@@ -52,7 +52,7 @@
<option value="reviewed" {% if selected_review == "reviewed" %}selected{% endif %}>Reviewed</option>
</select>
</div>
<div class="actions">
<div class="form-actions">
<button type="submit">Apply filters</button>
<a class="button-link secondary" href="{% url 'runs_list' %}">Clear</a>
</div>

View File

@@ -16,7 +16,7 @@
<section class="panel">
<h2>Filters</h2>
<form method="get" class="form-grid">
<form method="get" class="filter-form">
<div class="field">
<label for="host">Host</label>
<select id="host" name="host">
@@ -42,7 +42,7 @@
<option value="no" {% if selected_prune == "no" %}selected{% endif %}>Prune disabled</option>
</select>
</div>
<div class="actions">
<div class="form-actions">
<button type="submit">Apply filters</button>
<a class="button-link secondary" href="{% url 'schedules_list' %}">Clear</a>
</div>

View File

@@ -16,7 +16,7 @@
<section class="panel">
<h2>Filters</h2>
<form method="get" class="form-grid">
<form method="get" class="filter-form">
<div class="field">
<label for="host">Host</label>
<select id="host" name="host">
@@ -44,7 +44,7 @@
{% endfor %}
</select>
</div>
<div class="actions">
<div class="form-actions">
<button type="submit">Apply filters</button>
<a class="button-link secondary" href="{% url 'snapshots_list' %}">Clear</a>
</div>

View File

@@ -233,6 +233,9 @@ class ViewTests(TestCase):
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Runs")
self.assertContains(response, "Review queued, running, completed")
self.assertContains(response, "Apply filters")
self.assertContains(response, reverse("runs_list"))
self.assertContains(response, "Clear")
self.assertContains(response, f"Run {failed.id}")
self.assertContains(response, "web-01")
self.assertContains(response, "needed")
@@ -275,6 +278,9 @@ class ViewTests(TestCase):
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Snapshots")
self.assertContains(response, "Browse discovered scheduled, manual, and incomplete snapshots")
self.assertContains(response, "Apply filters")
self.assertContains(response, reverse("snapshots_list"))
self.assertContains(response, "Clear")
self.assertContains(response, manual.dirname)
self.assertContains(response, "web-01")
self.assertNotContains(response, scheduled.dirname)
@@ -291,6 +297,9 @@ class ViewTests(TestCase):
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Schedules")
self.assertContains(response, "Review configured backup schedules")
self.assertContains(response, "Apply filters")
self.assertContains(response, reverse("schedules_list"))
self.assertContains(response, "Clear")
self.assertContains(response, "web-01")
self.assertContains(response, "15 2 * * *")
self.assertContains(response, "success")
@@ -428,6 +437,9 @@ class ViewTests(TestCase):
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Logs")
self.assertContains(response, "Filter pobsync service logs")
self.assertContains(response, "Filter logs")
self.assertContains(response, reverse("logs"))
self.assertContains(response, "Clear")
self.assertContains(response, "web-01 failed backup run 12")
self.assertNotContains(response, "web-02 failed backup run 12")
self.assertNotContains(response, "started")
@@ -458,6 +470,9 @@ class ViewTests(TestCase):
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Purged Snapshots")
self.assertContains(response, "Audit trail for snapshots removed")
self.assertContains(response, "Apply filters")
self.assertContains(response, reverse("purged_snapshots"))
self.assertContains(response, "Clear")
self.assertContains(response, "20260518-021500Z__OLDSNAP")
self.assertContains(response, "outside retention policy")
self.assertContains(response, "Scheduled")