From 9f8ce670842377c1723ec4cf6d22c69d989ea520 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sat, 18 Apr 2026 15:04:21 -0400 Subject: [PATCH] feat(ui): group repeated failures by subscription on Dashboard recent activity Consecutive failures on the same source no longer spam the feed as separate rows. Failed downloads are now bucketed per subscription and rendered as a single row with a failure count and the latest timestamp, while successful/running entries still render individually. Co-Authored-By: Claude Opus 4.7 --- frontend/src/views/Dashboard.vue | 108 ++++++++++++++++++++++--------- 1 file changed, 78 insertions(+), 30 deletions(-) diff --git a/frontend/src/views/Dashboard.vue b/frontend/src/views/Dashboard.vue index b610032..ab65890 100644 --- a/frontend/src/views/Dashboard.vue +++ b/frontend/src/views/Dashboard.vue @@ -53,6 +53,10 @@ :loading="checkingAll" @click="checkAllSources" > + Check All Sources - Retry All Failed ({{ failedCount }}) + + Retry All Failed + Reset Stuck ({{ stuckRunningCount }}) @@ -189,35 +201,48 @@ - - - - - {{ getDownloadLabel(download) }} - - - {{ formatRelativeTime(download.created_at) }} - - {{ download.file_count }} new file{{ download.file_count !== 1 ? 's' : '' }} - - - - + +
mdi-history @@ -354,6 +379,29 @@ let refreshInterval = null const stats = computed(() => downloadsStore.stats) const recentActivity = computed(() => downloadsStore.recentActivity) +const recentActivityGrouped = computed(() => { + const singles = [] + const groups = new Map() + for (const d of recentActivity.value) { + if (d.status === 'failed') { + const key = `${d.subscription_name || '?'}:${d.platform || '?'}` + const g = groups.get(key) || { kind: 'group', key, label: key, items: [], latest: d } + g.items.push(d) + if (new Date(d.created_at) > new Date(g.latest.created_at)) g.latest = d + groups.set(key, g) + } else { + singles.push({ kind: 'single', key: `d-${d.id}`, download: d }) + } + } + const out = [...singles, ...groups.values()] + out.sort((a, b) => { + const tA = new Date(a.kind === 'single' ? a.download.created_at : a.latest.created_at) + const tB = new Date(b.kind === 'single' ? b.download.created_at : b.latest.created_at) + return tB - tA + }) + return out +}) + // Read threshold from settings, default 5 if unset or invalid const failureThreshold = computed(() => { const n = Number(settingsStore.settings['dashboard.failure_threshold'])