a2d1ed935d
- Promote .fc-section-h to a global token (app.css). It was copied identically into 4 cards, and TranslationCard used the class with NO local def — so its section headers rendered unstyled. Now fixed everywhere. - Promote .fc-good / .fc-weak status colours to globals; delete the local copies in the GPU/heads cards. (.fc-ok stays local — divergent: on-surface in HeadsCard vs success in QueuesTable. .fc-bad stays — different name.) - Delete 10 identical local .fc-muted redefinitions that crept back after the 2026-06-09 sweep; the global utility already covers them. - DbMaintenanceCard: opacity:0.6 muted text → the .fc-muted token (the exact anti-pattern that token's comment forbids). - HeadsCard: collapse byte-identical ratePct() into pct(). CSS-only + one template class swap; no logic change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NsmJSQxnNxGgtM5Yz4GAqi
77 lines
2.3 KiB
Vue
77 lines
2.3 KiB
Vue
<template>
|
|
<v-card class="mb-4">
|
|
<CardHeading icon="mdi-download" title="Downloads (last 24h)">
|
|
<v-spacer />
|
|
<v-btn
|
|
variant="text" size="small" rounded="pill"
|
|
to="/subscriptions?tab=downloads"
|
|
>
|
|
Open subscriptions
|
|
<v-icon end size="small">mdi-arrow-right</v-icon>
|
|
</v-btn>
|
|
</CardHeading>
|
|
<v-card-text>
|
|
<div class="d-flex align-center flex-wrap mb-2" style="gap: 8px">
|
|
<v-chip size="small" variant="tonal" color="success">
|
|
{{ stats.ok }} ok
|
|
</v-chip>
|
|
<v-chip
|
|
size="small" variant="tonal"
|
|
:color="stats.error ? 'error' : undefined"
|
|
>
|
|
{{ stats.error }} failed
|
|
</v-chip>
|
|
<v-chip v-if="stats.running" size="small" variant="tonal" color="accent">
|
|
{{ stats.running }} running
|
|
</v-chip>
|
|
<v-chip v-if="stats.pending" size="small" variant="tonal">
|
|
{{ stats.pending }} pending
|
|
</v-chip>
|
|
<v-chip v-if="stats.skipped" size="small" variant="tonal">
|
|
{{ stats.skipped }} skipped
|
|
</v-chip>
|
|
</div>
|
|
<p v-if="!failing.length" class="fc-muted text-body-2 mb-0">
|
|
All subscription sources healthy.
|
|
</p>
|
|
<p v-else class="text-body-2 mb-0">
|
|
<b class="fc-bad">{{ failing.length }}</b> failing source(s):
|
|
<span class="fc-muted">{{ failingNames }}</span>
|
|
</p>
|
|
</v-card-text>
|
|
</v-card>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, onMounted, onUnmounted } from 'vue'
|
|
import { storeToRefs } from 'pinia'
|
|
|
|
import CardHeading from '../common/CardHeading.vue'
|
|
import { useDownloadsStore } from '../../stores/downloads.js'
|
|
|
|
const store = useDownloadsStore()
|
|
const { stats, failing } = storeToRefs(store)
|
|
let pollId = null
|
|
|
|
const failingNames = computed(() => {
|
|
const names = failing.value.map((s) => s.artist_name || s.url).slice(0, 3)
|
|
const extra = failing.value.length - names.length
|
|
return names.join(', ') + (extra > 0 ? ` +${extra} more` : '')
|
|
})
|
|
|
|
function poll() {
|
|
store.loadStats(24)
|
|
store.loadFailing()
|
|
}
|
|
|
|
onMounted(() => {
|
|
poll()
|
|
pollId = setInterval(() => { if (!document.hidden) poll() }, 30000)
|
|
})
|
|
onUnmounted(() => { if (pollId) clearInterval(pollId) })
|
|
</script>
|
|
|
|
<style scoped>
|
|
.fc-bad { color: rgb(var(--v-theme-error)); }
|
|
</style>
|