Fix backup status dots disappearing after page load

/api/backups returned plain filename strings while pages/backups.html's
server-rendered dots came from get_local_backups_with_status()/
get_vm_backups_with_status(). platform.js's DOMContentLoaded handler always
calls refreshBackupsList() (backups.html has #backup-history-list, which
triggers it unconditionally), which re-fetches /api/backups and rebuilds
#local-backup-list/#vm-backup-list via renderBackupList() — a version with
zero knowledge of status, wiping out the dots seconds after initial paint.

Switch /api/backups to the *_with_status() variants and have
renderBackupList() render the same status-dot markup as the Jinja template,
so the dot is sourced from persisted backup metadata on every render path,
not just the first one.
This commit is contained in:
root
2026-08-21 11:07:01 +02:00
parent d498c81d5b
commit 72dba7a3a4
2 changed files with 13 additions and 6 deletions

View File

@@ -377,7 +377,7 @@ def api_container_status(name):
@app.route('/api/backups')
@login_required
def api_backups():
return jsonify({'local': get_local_backups(), 'vm': get_vm_backups()})
return jsonify({'local': get_local_backups_with_status(), 'vm': get_vm_backups_with_status()})
@app.route('/api/backups/log')

View File

@@ -377,8 +377,8 @@ async function refreshBackupsList() {
renderBackupList(d.vm, 'vm-backup-list', 'vm');
if (el('stat-local-bk')) el('stat-local-bk').textContent = d.local.length;
if (el('stat-vm-bk')) el('stat-vm-bk').textContent = d.vm.length;
if (el('local-options')) el('local-options').innerHTML = d.local.length ? d.local.map((b) => `<option value="${b}" data-source="local">${b}</option>`).join('') : '<option disabled>No local backups</option>';
if (el('vm-options')) el('vm-options').innerHTML = d.vm.length ? d.vm.map((b) => `<option value="${b}" data-source="vm">${b}</option>`).join('') : '<option disabled>No VM backups</option>';
if (el('local-options')) el('local-options').innerHTML = d.local.length ? d.local.map((b) => `<option value="${b.name}" data-source="local">${b.name}</option>`).join('') : '<option disabled>No local backups</option>';
if (el('vm-options')) el('vm-options').innerHTML = d.vm.length ? d.vm.map((b) => `<option value="${b.name}" data-source="vm">${b.name}</option>`).join('') : '<option disabled>No VM backups</option>';
} catch (_) {}
}
@@ -394,7 +394,13 @@ function renderBackupList(items, id, source) {
return;
}
node.innerHTML = items.map((b) => {
const safe = jsStr(b);
const name = b.name;
const safe = jsStr(name);
const status = b.status || 'unknown';
const titleParts = [status];
if (b.apps && b.apps.length) titleParts.push(`apps: ${b.apps.join(', ')}`);
if (b.size_human) titleParts.push(b.size_human);
const title = escapeHtml(titleParts.join(' · '));
const uploadBtn = source === 'local' && typeof quickUploadR2 === 'function'
? `<button class="btn btn-ghost btn-sm" onclick="quickUploadR2('${safe}',this)" title="Upload to R2"><i class="fas fa-cloud-upload-alt"></i></button>`
: '';
@@ -402,9 +408,10 @@ function renderBackupList(items, id, source) {
? `<button class="btn btn-ghost btn-sm" style="color:var(--text3);" onclick="showBackupDetails('${source}','${safe}',this)" title="Quick details"><i class="fas fa-circle-info"></i></button>`
: '';
return `
<div class="backup-item" id="bk-item-${source}-${b.replace(/[^a-z0-9]/gi, '_')}">
<div class="backup-item" id="bk-item-${source}-${name.replace(/[^a-z0-9]/gi, '_')}">
<div class="backup-item-main">
<span class="backup-name">${escapeHtml(b)}</span>
<span class="status-dot status-dot-${status}" title="${title}"></span>
<span class="backup-name">${escapeHtml(name)}</span>
</div>
<div class="backup-actions">
${detailsBtn}