feat(downloads): front-and-center "active now" panel with live elapsed timers

New ActiveDownloadsPanel pinned at the top of the Downloads tab shows what's
running (pulsing dot + live mm:ss timer counting from started_at) and what's
queued, so the operator can see activity at a glance without the running
filter. Backed by store.loadActive() which fetches running + pending
independent of the feed filter; refreshed every 4s by the existing live poll.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-28 19:36:49 -04:00
parent c87e8e0932
commit 9e74c80e2f
3 changed files with 156 additions and 5 deletions
+15 -2
View File
@@ -18,6 +18,10 @@ export const useDownloadsStore = defineStore('downloads', () => {
const stats = ref({ pending: 0, running: 0, ok: 0, error: 0, skipped: 0 })
const activity = ref({ hours: 24, buckets: [] })
const failing = ref([])
// Running + queued events, fetched independent of the feed's filter so
// the "active now" panel always reflects what's happening regardless of
// how the operator has filtered the historical list below.
const activeEvents = ref([])
function _params(extra = {}) {
const out = { limit: 50, ...extra }
@@ -75,10 +79,19 @@ export const useDownloadsStore = defineStore('downloads', () => {
return failing.value
}
async function loadActive() {
const [running, pending] = await Promise.all([
api.get('/api/downloads', { params: { status: 'running', limit: 50 } }),
api.get('/api/downloads', { params: { status: 'pending', limit: 50 } }),
])
activeEvents.value = [...running, ...pending]
return activeEvents.value
}
return {
events, cursor, hasMore, filter, selected, loading, error, stats,
activity, failing,
activity, failing, activeEvents,
loadFirst, loadMore, loadOne, applyFilter, closeDetail, loadStats,
loadActivity, loadFailing,
loadActivity, loadFailing, loadActive,
}
})