Persist dashboard backup metrics separately

This commit is contained in:
Mikei386
2026-07-13 20:39:31 +02:00
parent 2ecdcdf602
commit b1fba3ed16
15 changed files with 183 additions and 14 deletions
+8 -7
View File
@@ -10,7 +10,7 @@
const dialog = document.getElementById('bu-dialog');
const sourceRoots = ['/mnt/user','/mnt/disks','/mnt/remotes','/boot'].map(path=>({name:path,path}));
const repositoryRoots = ['/mnt/user','/mnt/disks','/mnt/remotes'].map(path=>({name:path,path}));
const state = { config: null, runs: [], logs: null, logsLoading: false, logsError: '', dashboardJobId: '', view: 'dashboard', snapshots: [], selectedSnapshot: null, snapshotRepo: null, files: [], snapshotTree: null, snapshotTreeMeta: null, snapshotTreeChildren: {}, snapshotTreeLoading: false, snapshotTreeNodeLoading: {}, snapshotTreeErrors: {}, snapshotTreeError: '', snapshotTreeExpanded: {}, restoreIncludes: [], backupSelections: [], workloadSelections: [], workloads: [], workloadAvailable: true, workloadError: '', sourceTreeRoots: sourceRoots, sourceTreeChildren: {}, sourceTreeExpanded: {}, sourceTreeLoading: {}, sourceTreeErrors: {}, directoryTrees: {}, liveLogOpen: {} };
const state = { config: null, runs: [], backupMetrics: [], logs: null, logsLoading: false, logsError: '', dashboardJobId: '', view: 'dashboard', snapshots: [], selectedSnapshot: null, snapshotRepo: null, files: [], snapshotTree: null, snapshotTreeMeta: null, snapshotTreeChildren: {}, snapshotTreeLoading: false, snapshotTreeNodeLoading: {}, snapshotTreeErrors: {}, snapshotTreeError: '', snapshotTreeExpanded: {}, restoreIncludes: [], backupSelections: [], workloadSelections: [], workloads: [], workloadAvailable: true, workloadError: '', sourceTreeRoots: sourceRoots, sourceTreeChildren: {}, sourceTreeExpanded: {}, sourceTreeLoading: {}, sourceTreeErrors: {}, directoryTrees: {}, liveLogOpen: {} };
const fieldHelp = {
name: 'Frei wählbarer Anzeigename. Beispiel: Appdata täglich oder VM Home Assistant.',
@@ -401,8 +401,8 @@
async function load() {
try {
const [config, runs, daemon] = await Promise.all([api('/v1/config'), api('/v1/runs'), api('/v1/health')]);
state.config = config; state.runs = newestRuns(runs);
const [config, runs, backupMetrics, daemon] = await Promise.all([api('/v1/config'), api('/v1/runs'), api('/v1/backup-metrics'), api('/v1/health')]);
state.config = config; state.runs = newestRuns(runs); state.backupMetrics = backupMetrics;
health.textContent = `Daemon online (${daemon.version || 'unbekannt'})`; health.className = 'bu-health ok'; render();
} catch (error) {
health.textContent = 'Daemon nicht erreichbar'; health.className = 'bu-health bad';
@@ -459,9 +459,10 @@
const lastSuccess=orderedRuns.find(r=>r.status==='success');
const last = orderedRuns.slice(0, 6);
const backupJobs=state.config.jobs.filter(job=>job.type!=='rsync');
if(!state.dashboardJobId) state.dashboardJobId=orderedRuns.find(run=>run.taskType==='backup'&&backupJobs.some(job=>job.id===run.jobId))?.jobId||backupJobs[0]?.id||'all';
const orderedMetrics=[...state.backupMetrics].sort((a,b)=>new Date(b.finishedAt)-new Date(a.finishedAt));
if(!state.dashboardJobId) state.dashboardJobId=orderedMetrics.find(metric=>backupJobs.some(job=>job.id===metric.jobId))?.jobId||backupJobs[0]?.id||'all';
if(state.dashboardJobId!=='all'&&!backupJobs.some(job=>job.id===state.dashboardJobId)) state.dashboardJobId='all';
const backups=orderedRuns.filter(run=>run.taskType==='backup'&&['success','warning'].includes(run.status)&&(state.dashboardJobId==='all'||run.jobId===state.dashboardJobId));
const backups=orderedMetrics.filter(metric=>state.dashboardJobId==='all'||metric.jobId===state.dashboardJobId);
const chartOptions=`<option value="all" ${state.dashboardJobId==='all'?'selected':''}>Alle Backup-Jobs</option>${backupJobs.map(job=>`<option value="${esc(job.id)}" ${state.dashboardJobId===job.id?'selected':''}>${esc(job.name)}</option>`).join('')}`;
const needsSetup=!state.config.jobs.length||!state.config.repositories.length;
const latestFailed=orderedRuns[0]?.status==='failed';
@@ -869,7 +870,7 @@
if (name === 'pause-run') { await api(`/v1/runs/${a}/pause`,{method:'POST'}); notify('Vorgang pausiert'); return load(); }
if (name === 'resume-run') { await api(`/v1/runs/${a}/resume`,{method:'POST'}); notify('Vorgang wird fortgesetzt'); return load(); }
if (name === 'go-view') { state.view=a; document.querySelectorAll('.bu-tabs button').forEach(x=>x.classList.toggle('active',x.dataset.view===a)); render(); if(a==='logs'&&!state.logs&&!state.logsLoading) loadLogs(); return; }
if (name === 'clear-activity') { if(await askConfirm({title:'Aktivitätsverlauf löschen?',message:'Alle abgeschlossenen Aktivitäten und ihre gespeicherten Laufprotokolle werden entfernt. Laufende und wartende Aufgaben bleiben erhalten.',accept:'Verlauf löschen'})) { const result=await api('/v1/runs',{method:'DELETE'}); notify(result.cleared?`${result.cleared} abgeschlossene Aktivitäten gelöscht`:'Keine abgeschlossenen Aktivitäten vorhanden'); return load(); } return; }
if (name === 'clear-activity') { if(await askConfirm({title:'Aktivitätsverlauf löschen?',message:'Alle abgeschlossenen Aktivitäten und ihre gespeicherten Laufprotokolle werden entfernt. Laufende und wartende Aufgaben sowie die Messwerte der Dashboard-Diagramme bleiben erhalten.',accept:'Verlauf löschen'})) { const result=await api('/v1/runs',{method:'DELETE'}); notify(result.cleared?`${result.cleared} abgeschlossene Aktivitäten gelöscht`:'Keine abgeschlossenen Aktivitäten vorhanden'); return load(); } return; }
if (name === 'refresh-logs') { state.logs=null; return loadLogs(); }
if (name === 'test-repo') { await api(`/v1/repositories/${a}/test`,{method:'POST'}); return notify('Repository-Verbindung erfolgreich getestet'); }
if (name === 'init-repo') { if(await askConfirm({title:'Neues Repository initialisieren?',message:'Am konfigurierten Ziel wird ein neues, leeres Restic-Repository angelegt. Verwende diese Aktion nicht für ein bereits vorhandenes Repository.',accept:'Initialisieren',danger:false})) { await api(`/v1/repositories/${a}/init`,{method:'POST'}); notify('Repository erfolgreich initialisiert'); } return; }
@@ -1104,6 +1105,6 @@
window.addEventListener('scroll', hideTooltip, true);
window.addEventListener('resize', hideTooltip);
enhanceTooltips(root);
setInterval(async()=>{ if(!state.config)return; try { const runs=newestRuns(await api('/v1/runs')); const changed=JSON.stringify(runs)!==JSON.stringify(state.runs); state.runs=runs; if(changed&&['dashboard','activity'].includes(state.view))render(); }catch(_){ } },2000);
setInterval(async()=>{ if(!state.config)return; try { const runs=newestRuns(await api('/v1/runs')); const changed=JSON.stringify(runs)!==JSON.stringify(state.runs); state.runs=runs; if(changed){ state.backupMetrics=await api('/v1/backup-metrics'); if(['dashboard','activity'].includes(state.view))render(); } }catch(_){ } },2000);
load();
})();