feat(dashboards): scheduler health strip, failing-source rollup, 24h activity sparkline, credential staleness nudge
D1 scheduler visibility: AppSetting last-tick stamp on every Beat tick + GET /api/sources/schedule-status (last_tick_at/next_due_at/due_now/auto_sources) + SchedulerStatusBar on the Subscriptions tab (re-polled every 30s). D2 failing-source rollup: ?failing=true on the sources list + FailingSourcesCard on Downloads with per-source and bulk "retry" (re-runs the feed via /check). D3 activity sparkline: GET /api/downloads/activity hourly buckets + CSS bar chart by the stat chips (failures stacked in error color); refreshes on live poll. D4 credential staleness: surface last_verified age + "re-verify recommended" warning past 30d; also fixes the dead last_verified_at field-name mismatch so the verification row renders at all. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,8 @@ export const useDownloadsStore = defineStore('downloads', () => {
|
||||
const loading = ref(false)
|
||||
const error = ref(null)
|
||||
const stats = ref({ pending: 0, running: 0, ok: 0, error: 0, skipped: 0 })
|
||||
const activity = ref({ hours: 24, buckets: [] })
|
||||
const failing = ref([])
|
||||
|
||||
function _params(extra = {}) {
|
||||
const out = { limit: 50, ...extra }
|
||||
@@ -74,8 +76,20 @@ export const useDownloadsStore = defineStore('downloads', () => {
|
||||
return stats.value
|
||||
}
|
||||
|
||||
async function loadActivity(hours = 24) {
|
||||
activity.value = await api.get('/api/downloads/activity', { params: { hours } })
|
||||
return activity.value
|
||||
}
|
||||
|
||||
async function loadFailing() {
|
||||
failing.value = await api.get('/api/sources', { params: { failing: true } })
|
||||
return failing.value
|
||||
}
|
||||
|
||||
return {
|
||||
events, cursor, hasMore, filter, selected, loading, error, stats,
|
||||
activity, failing,
|
||||
loadFirst, loadMore, loadOne, applyFilter, closeDetail, loadStats,
|
||||
loadActivity, loadFailing,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -9,6 +9,7 @@ export const useSourcesStore = defineStore('sources', () => {
|
||||
const byArtist = ref(new Map())
|
||||
const loading = ref(false)
|
||||
const error = ref(null)
|
||||
const scheduleStatus = ref(null)
|
||||
|
||||
const allSources = computed(() => byArtist.value.get(null) ?? [])
|
||||
|
||||
@@ -70,6 +71,11 @@ export const useSourcesStore = defineStore('sources', () => {
|
||||
return await api.get('/api/artists/autocomplete', { params: { q: query, limit } })
|
||||
}
|
||||
|
||||
async function loadScheduleStatus() {
|
||||
scheduleStatus.value = await api.get('/api/sources/schedule-status')
|
||||
return scheduleStatus.value
|
||||
}
|
||||
|
||||
// FC-3c: trigger a download for one source. Returns {download_event_id,status}.
|
||||
const checkingIds = ref(new Set())
|
||||
|
||||
@@ -104,13 +110,14 @@ export const useSourcesStore = defineStore('sources', () => {
|
||||
}
|
||||
|
||||
return {
|
||||
byArtist, loading, error,
|
||||
byArtist, loading, error, scheduleStatus,
|
||||
allSources,
|
||||
checkingIds,
|
||||
loadAll, loadForArtist,
|
||||
create, update, remove,
|
||||
checkNow,
|
||||
findOrCreateArtist, autocompleteArtist,
|
||||
loadScheduleStatus,
|
||||
sourcesByArtistGrouped,
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user