From f29de3616cf76af0dd8756a83335559e3e59272b Mon Sep 17 00:00:00 2001
From: Matthias Nott <mnott@mnsoft.org>
Date: Sun, 22 Feb 2026 00:30:09 +0100
Subject: [PATCH] feat: add project/env filters to backups page
---
static/js/app.js | 78 ++++++++++++++++++++++++++++++++++-----
1 files changed, 68 insertions(+), 10 deletions(-)
diff --git a/static/js/app.js b/static/js/app.js
index 0415eb7..69566b8 100644
--- a/static/js/app.js
+++ b/static/js/app.js
@@ -18,6 +18,10 @@
let refreshTimer = null;
const REFRESH_INTERVAL = 30000;
+// Backup filter state
+let backupFilterProject = null; // null = all
+let backupFilterEnv = null; // null = all
+
// Log modal state
let logCtx = { project: null, env: null, service: null };
@@ -459,6 +463,15 @@
// ---------------------------------------------------------------------------
// Backups
// ---------------------------------------------------------------------------
+function fmtBackupDate(raw) {
+ if (!raw) return '\u2014';
+ // YYYYMMDD_HHMMSS -> YYYY-MM-DD HH:MM
+ const m = String(raw).match(/^(\d{4})(\d{2})(\d{2})[_T](\d{2})(\d{2})/);
+ if (m) return `${m[1]}-${m[2]}-${m[3]} ${m[4]}:${m[5]}`;
+ // YYYY-MM-DD passthrough
+ return raw;
+}
+
async function renderBackups() {
updateBreadcrumbs();
const c = document.getElementById('page-content');
@@ -467,6 +480,18 @@
api('/api/backups/'),
api('/api/backups/offsite').catch(() => []),
]);
+
+ // Apply filters
+ const filteredLocal = local.filter(b => {
+ if (backupFilterProject && b.project !== backupFilterProject) return false;
+ if (backupFilterEnv && (b.env || b.environment || '') !== backupFilterEnv) return false;
+ return true;
+ });
+ const filteredOffsite = offsite.filter(b => {
+ if (backupFilterProject && b.project !== backupFilterProject) return false;
+ if (backupFilterEnv && (b.env || b.environment || '') !== backupFilterEnv) return false;
+ return true;
+ });
let h = '<div class="page-enter">';
@@ -481,26 +506,53 @@
}
h += '</div></div>';
+ // Filter bar
+ const activeStyle = 'background:rgba(59,130,246,0.2);color:#60a5fa;';
+ h += '<div style="display:flex;flex-wrap:wrap;gap:0.5rem;align-items:center;margin-bottom:1.5rem;padding:0.75rem 1rem;background:#1f2937;border-radius:0.5rem;">';
+ h += '<span style="color:#9ca3af;font-size:0.875rem;margin-right:0.25rem;">Project:</span>';
+ h += `<button class="btn btn-ghost btn-xs" style="${backupFilterProject === null ? activeStyle : ''}" onclick="setBackupFilter('project',null)">All</button>`;
+ h += `<button class="btn btn-ghost btn-xs" style="${backupFilterProject === 'mdf' ? activeStyle : ''}" onclick="setBackupFilter('project','mdf')">mdf</button>`;
+ h += `<button class="btn btn-ghost btn-xs" style="${backupFilterProject === 'seriousletter' ? activeStyle : ''}" onclick="setBackupFilter('project','seriousletter')">seriousletter</button>`;
+ h += '<span style="color:#374151;margin:0 0.25rem;">|</span>';
+ h += '<span style="color:#9ca3af;font-size:0.875rem;margin-right:0.25rem;">Env:</span>';
+ h += `<button class="btn btn-ghost btn-xs" style="${backupFilterEnv === null ? activeStyle : ''}" onclick="setBackupFilter('env',null)">All</button>`;
+ h += `<button class="btn btn-ghost btn-xs" style="${backupFilterEnv === 'dev' ? activeStyle : ''}" onclick="setBackupFilter('env','dev')">dev</button>`;
+ h += `<button class="btn btn-ghost btn-xs" style="${backupFilterEnv === 'int' ? activeStyle : ''}" onclick="setBackupFilter('env','int')">int</button>`;
+ h += `<button class="btn btn-ghost btn-xs" style="${backupFilterEnv === 'prod' ? activeStyle : ''}" onclick="setBackupFilter('env','prod')">prod</button>`;
+ h += '</div>';
+
// Local
h += '<h2 style="font-size:1.125rem;font-weight:600;color:#f3f4f6;margin-bottom:0.75rem;">Local Backups</h2>';
- if (local.length === 0) {
- h += '<div class="card" style="color:#6b7280;">No local backups found.</div>';
+ if (filteredLocal.length === 0) {
+ h += '<div class="card" style="color:#6b7280;">No local backups match the current filter.</div>';
} else {
- h += '<div class="table-wrapper"><table class="ops-table"><thead><tr><th>Project</th><th>Env</th><th>Date</th><th>Size</th><th>Files</th></tr></thead><tbody>';
- for (const b of local) {
- h += `<tr><td>${esc(b.project||'')}</td><td><span class="badge badge-blue">${esc(b.env||b.environment||'')}</span></td><td>${esc(b.date||b.timestamp||'')}</td><td>${esc(b.size||'')}</td><td class="mono" style="font-size:0.75rem;">${esc(b.file||b.files||'')}</td></tr>`;
+ h += '<div class="table-wrapper"><table class="ops-table"><thead><tr><th>Project</th><th>Env</th><th>File</th><th>Date</th><th>Size</th></tr></thead><tbody>';
+ for (const b of filteredLocal) {
+ h += `<tr>
+ <td>${esc(b.project||'')}</td>
+ <td><span class="badge badge-blue">${esc(b.env||b.environment||'')}</span></td>
+ <td class="mono" style="font-size:0.8125rem;">${esc(b.name||b.file||'')}</td>
+ <td>${esc(fmtBackupDate(b.date||b.timestamp||''))}</td>
+ <td>${esc(b.size_human||b.size||'')}</td>
+ </tr>`;
}
h += '</tbody></table></div>';
}
// Offsite
h += '<h2 style="font-size:1.125rem;font-weight:600;color:#f3f4f6;margin:1.5rem 0 0.75rem;">Offsite Backups</h2>';
- if (offsite.length === 0) {
- h += '<div class="card" style="color:#6b7280;">No offsite backups found.</div>';
+ if (filteredOffsite.length === 0) {
+ h += '<div class="card" style="color:#6b7280;">No offsite backups match the current filter.</div>';
} else {
- h += '<div class="table-wrapper"><table class="ops-table"><thead><tr><th>Project</th><th>Env</th><th>Date</th><th>Size</th></tr></thead><tbody>';
- for (const b of offsite) {
- h += `<tr><td>${esc(b.project||'')}</td><td><span class="badge badge-blue">${esc(b.env||b.environment||'')}</span></td><td>${esc(b.date||b.timestamp||'')}</td><td>${esc(b.size||'')}</td></tr>`;
+ h += '<div class="table-wrapper"><table class="ops-table"><thead><tr><th>Project</th><th>Env</th><th>File</th><th>Date</th><th>Size</th></tr></thead><tbody>';
+ for (const b of filteredOffsite) {
+ h += `<tr>
+ <td>${esc(b.project||'')}</td>
+ <td><span class="badge badge-blue">${esc(b.env||b.environment||'')}</span></td>
+ <td class="mono" style="font-size:0.8125rem;">${esc(b.name||'')}</td>
+ <td>${esc(fmtBackupDate(b.date||''))}</td>
+ <td>${esc(b.size||'')}</td>
+ </tr>`;
}
h += '</tbody></table></div>';
}
@@ -704,6 +756,12 @@
logCtx = { project: null, env: null, service: null };
}
+function setBackupFilter(type, value) {
+ if (type === 'project') backupFilterProject = value;
+ if (type === 'env') backupFilterEnv = value;
+ renderBackups();
+}
+
async function createBackup(project, env) {
if (!confirm(`Create backup for ${project}/${env}?`)) return;
toast('Creating backup...', 'info');
--
Gitblit v1.3.1