Handle empty dashboard metrics safely
This commit is contained in:
@@ -1 +0,0 @@
|
||||
11efad3fbbe8f71092177ef912353d0c0f733b66974af18aba83a0275c2c22f9 urbm-2026.07.13.r009-x86_64-1.txz
|
||||
BIN
Binary file not shown.
@@ -0,0 +1 @@
|
||||
3f655152c8e0875711c8b02036b05b1a163227a658fe6b2e4bf91397270a7f48 urbm-2026.07.13.r010-x86_64-1.txz
|
||||
Vendored
+6
-2
@@ -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.
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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
@@ -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
@@ -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 ?>">
|
||||
|
||||
@@ -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();
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user