Handle empty dashboard metrics safely

This commit is contained in:
Mikei386
2026-07-13 20:44:46 +02:00
parent b1fba3ed16
commit ea97d7dbc9
9 changed files with 30 additions and 8 deletions
-1
View File
@@ -1 +0,0 @@
11efad3fbbe8f71092177ef912353d0c0f733b66974af18aba83a0275c2c22f9 urbm-2026.07.13.r009-x86_64-1.txz
+1
View File
@@ -0,0 +1 @@
3f655152c8e0875711c8b02036b05b1a163227a658fe6b2e4bf91397270a7f48 urbm-2026.07.13.r010-x86_64-1.txz
+6 -2
View File
@@ -2,13 +2,17 @@
<!DOCTYPE PLUGIN [
<!ENTITY name "urbm">
<!ENTITY author "Michael Roll">
<!ENTITY version "2026.07.13.r009">
<!ENTITY version "2026.07.13.r010">
<!ENTITY pluginURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm.plg">
<!ENTITY packageURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm-&version;-x86_64-1.txz">
<!ENTITY packageSHA256 "11efad3fbbe8f71092177ef912353d0c0f733b66974af18aba83a0275c2c22f9">
<!ENTITY packageSHA256 "3f655152c8e0875711c8b02036b05b1a163227a658fe6b2e4bf91397270a7f48">
]>
<PLUGIN name="&name;" author="&author;" version="&version;" pluginURL="&pluginURL;" min="7.0.0" support="https://git.casaderoll.de/michael/URBM/issues" icon="urbm.png">
<CHANGES>
### 2026.07.13.r010
- Return an empty JSON array instead of `null` when no dashboard backup measurements exist yet.
- Make the WebGUI tolerate empty or invalid metric responses so the rest of the interface remains available.
### 2026.07.13.r009
- Store completed-backup chart measurements independently in `backup-metrics.json` so clearing activities no longer erases the dashboard history.
- Migrate existing successful backup runs into the new durable metric history automatically and expose them through a dedicated read-only API.
+12
View File
@@ -2,6 +2,7 @@ package service
import (
"context"
"encoding/json"
"os"
"path/filepath"
"testing"
@@ -11,6 +12,17 @@ import (
"git.casaderoll.de/michael/urbm/internal/store"
)
func TestEmptyBackupMetricsEncodeAsJSONArray(t *testing.T) {
s := &Service{}
encoded, err := json.Marshal(s.BackupMetrics())
if err != nil {
t.Fatal(err)
}
if string(encoded) != "[]" {
t.Fatalf("empty backup metrics JSON = %s", encoded)
}
}
func TestRepositoryLockHonorsContextCancellation(t *testing.T) {
s := &Service{repositoryLocks: map[string]chan struct{}{}}
repo := model.Repository{Type: model.RepositoryLocal, Location: "/repo"}
+3 -1
View File
@@ -134,7 +134,9 @@ func (s *Service) Runs() []model.Run { return s.queue.Snapshot() }
func (s *Service) BackupMetrics() []model.BackupMetric {
s.metricsMu.RLock()
defer s.metricsMu.RUnlock()
return append([]model.BackupMetric(nil), s.backupMetrics...)
metrics := make([]model.BackupMetric, len(s.backupMetrics))
copy(metrics, s.backupMetrics)
return metrics
}
func (s *Service) Logs() map[string]any {
return map[string]any{
+5 -1
View File
@@ -2,13 +2,17 @@
<!DOCTYPE PLUGIN [
<!ENTITY name "urbm">
<!ENTITY author "Michael Roll">
<!ENTITY version "2026.07.13.r009">
<!ENTITY version "2026.07.13.r010">
<!ENTITY pluginURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm.plg">
<!ENTITY packageURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm-&version;-x86_64-1.txz">
<!ENTITY packageSHA256 "REPLACE_DURING_RELEASE">
]>
<PLUGIN name="&name;" author="&author;" version="&version;" pluginURL="&pluginURL;" min="7.0.0" support="https://git.casaderoll.de/michael/URBM/issues" icon="urbm.png">
<CHANGES>
### 2026.07.13.r010
- Return an empty JSON array instead of `null` when no dashboard backup measurements exist yet.
- Make the WebGUI tolerate empty or invalid metric responses so the rest of the interface remains available.
### 2026.07.13.r009
- Store completed-backup chart measurements independently in `backup-metrics.json` so clearing activities no longer erases the dashboard history.
- Migrate existing successful backup runs into the new durable metric history automatically and expose them through a dedicated read-only API.
+1 -1
View File
@@ -8,7 +8,7 @@ Tag="URBM Unraid Restic Backup Manager backup snapshots restore"
---
<?php
$pluginRoot = '/plugins/urbm';
$urbmVersion = '2026.07.13.r009';
$urbmVersion = '2026.07.13.r010';
$urbmAssetVersion = preg_replace('/[^A-Za-z0-9]/', '', $urbmVersion);
?>
<link rel="stylesheet" href="<?= $pluginRoot ?>/assets/urbm.css?v=<?= $urbmAssetVersion ?>">
+2 -2
View File
@@ -402,7 +402,7 @@
async function load() {
try {
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;
state.config = config; state.runs = newestRuns(runs); state.backupMetrics = Array.isArray(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';
@@ -1105,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){ state.backupMetrics=await api('/v1/backup-metrics'); if(['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){ const backupMetrics=await api('/v1/backup-metrics'); state.backupMetrics=Array.isArray(backupMetrics) ? backupMetrics : []; if(['dashboard','activity'].includes(state.view))render(); } }catch(_){ } },2000);
load();
})();