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:
@@ -1,5 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<SchedulerStatusBar :status="store.scheduleStatus" class="fc-subs__sched" />
|
||||
|
||||
<div class="fc-subs__bar">
|
||||
<v-btn color="accent" prepend-icon="mdi-plus" @click="openAddSource(null)">
|
||||
Add subscription
|
||||
@@ -197,7 +199,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useSourcesStore } from '../../stores/sources.js'
|
||||
import { usePlatformsStore } from '../../stores/platforms.js'
|
||||
@@ -207,6 +209,7 @@ import SourceHealthDot from './SourceHealthDot.vue'
|
||||
import SourceFormDialog from './SourceFormDialog.vue'
|
||||
import ArtistCreateDialog from './ArtistCreateDialog.vue'
|
||||
import PlatformChip from './PlatformChip.vue'
|
||||
import SchedulerStatusBar from './SchedulerStatusBar.vue'
|
||||
|
||||
const ITEMS_PER_PAGE_OPTIONS = [
|
||||
{ value: 25, title: '25' },
|
||||
@@ -250,14 +253,24 @@ const failureThreshold = computed(() =>
|
||||
async function refresh() {
|
||||
await store.loadAll()
|
||||
await platformsStore.loadAll()
|
||||
store.loadScheduleStatus()
|
||||
if (!importStore.settings) await importStore.loadSettings()
|
||||
}
|
||||
|
||||
// Re-poll scheduler status every 30s so the "last ran" reading + health
|
||||
// dot stay honest (the Beat tick fires every 60s).
|
||||
let schedId = null
|
||||
onMounted(() => {
|
||||
refresh()
|
||||
if (artistFilter.value != null) {
|
||||
expanded.value = [`artist-${artistFilter.value}`]
|
||||
}
|
||||
schedId = setInterval(() => {
|
||||
if (!document.hidden) store.loadScheduleStatus()
|
||||
}, 30000)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
if (schedId) { clearInterval(schedId); schedId = null }
|
||||
})
|
||||
watch(() => route.query.artist_id, refresh)
|
||||
|
||||
@@ -500,6 +513,7 @@ async function bulkDelete() {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.fc-subs__sched { margin-bottom: 10px; }
|
||||
.fc-subs__bar {
|
||||
display: flex; gap: 0.75rem; align-items: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
Reference in New Issue
Block a user