e039689eff
The Activity tab only knew Celery — the GPU agent (the majority of processing) and the download pipeline were invisible there. Two new self-polling panels: - GpuActivityPanel: queue depths + triage verdicts (defects / file-ok / unprobed, top reason buckets) with a jump to Maintenance -> Failed processing. The triage detail refetches only when the error count moves. - DownloadsActivityPanel: 24h stat chips + failing-source names with a jump into Subscriptions. Both panels join the Activity tab under Queues+workers AND double as the Overview health strip (side-by-side grid under the Celery summary) — one component set, so Overview answers 'is everything healthy?' across all systems. SystemStatsCards reviewed: content still accurate, left as-is. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
111 lines
3.5 KiB
Vue
111 lines
3.5 KiB
Vue
<template>
|
|
<v-card class="mb-4">
|
|
<CardHeading icon="mdi-expansion-card" title="GPU agent pipeline">
|
|
<v-spacer />
|
|
<v-btn
|
|
variant="text" size="small" rounded="pill"
|
|
@click="$emit('open-maintenance')"
|
|
>
|
|
Open maintenance
|
|
<v-icon end size="small">mdi-arrow-right</v-icon>
|
|
</v-btn>
|
|
</CardHeading>
|
|
<v-card-text>
|
|
<div class="fc-cells mb-2">
|
|
<div class="fc-cell">
|
|
<div class="fc-cell__n">{{ q.pending }}</div>
|
|
<div class="fc-cell__l">pending</div>
|
|
</div>
|
|
<div class="fc-cell">
|
|
<div class="fc-cell__n">{{ q.leased }}</div>
|
|
<div class="fc-cell__l">in flight</div>
|
|
</div>
|
|
<div class="fc-cell">
|
|
<div class="fc-cell__n fc-good">{{ q.done }}</div>
|
|
<div class="fc-cell__l">done</div>
|
|
</div>
|
|
<div class="fc-cell">
|
|
<div class="fc-cell__n" :class="q.error ? 'fc-bad' : ''">{{ q.error }}</div>
|
|
<div class="fc-cell__l">errored</div>
|
|
</div>
|
|
</div>
|
|
<p v-if="!q.error" class="fc-muted text-body-2 mb-0">
|
|
No failed jobs — the pipeline is clean. Work drains whenever the
|
|
desktop agent is running.
|
|
</p>
|
|
<p v-else class="text-body-2 mb-0">
|
|
Triage: <b>{{ triage.defect }}</b> defective file(s) ·
|
|
{{ triage.file_ok }} file-ok · {{ triage.unclassified }} unprobed
|
|
<span v-if="reasonSummary" class="fc-muted">— {{ reasonSummary }}</span>
|
|
<br>
|
|
<span class="fc-muted text-caption">
|
|
Recover defective files from Maintenance → Failed processing.
|
|
</span>
|
|
</p>
|
|
</v-card-text>
|
|
</v-card>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
|
|
|
import CardHeading from '../common/CardHeading.vue'
|
|
import { useGpuStore } from '../../stores/gpu.js'
|
|
|
|
defineEmits(['open-maintenance'])
|
|
|
|
const store = useGpuStore()
|
|
const q = ref({ pending: 0, leased: 0, done: 0, error: 0 })
|
|
const triage = ref({ defect: 0, file_ok: 0, unclassified: 0 })
|
|
const byClass = ref({})
|
|
let pollId = null
|
|
let lastErrorCount = -1
|
|
|
|
const reasonSummary = computed(() =>
|
|
Object.entries(byClass.value)
|
|
.sort((a, b) => b[1] - a[1])
|
|
.slice(0, 3)
|
|
.map(([k, n]) => `${k.replaceAll('_', ' ')} ${n}`)
|
|
.join(' · '))
|
|
|
|
async function poll() {
|
|
try {
|
|
q.value = await store.status()
|
|
// The triage detail is only worth a second call when the error count
|
|
// actually moved (it's a 500-row join server-side).
|
|
if (q.value.error !== lastErrorCount) {
|
|
lastErrorCount = q.value.error
|
|
if (q.value.error > 0) {
|
|
const body = await store.errors()
|
|
triage.value = body.triage
|
|
byClass.value = body.by_class
|
|
} else {
|
|
triage.value = { defect: 0, file_ok: 0, unclassified: 0 }
|
|
byClass.value = {}
|
|
}
|
|
}
|
|
} catch { /* non-fatal — panel just shows the last snapshot */ }
|
|
}
|
|
|
|
onMounted(() => {
|
|
poll()
|
|
pollId = setInterval(() => { if (!document.hidden) poll() }, 5000)
|
|
})
|
|
onUnmounted(() => { if (pollId) clearInterval(pollId) })
|
|
</script>
|
|
|
|
<style scoped>
|
|
.fc-muted { color: rgb(var(--v-theme-on-surface-variant)); }
|
|
.fc-cells { display: flex; gap: 28px; }
|
|
.fc-cell__n {
|
|
font-size: 20px; font-weight: 700; line-height: 1.1;
|
|
font-family: 'JetBrains Mono', monospace;
|
|
}
|
|
.fc-cell__l {
|
|
font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em;
|
|
color: rgb(var(--v-theme-on-surface-variant));
|
|
}
|
|
.fc-good { color: rgb(var(--v-theme-success)); }
|
|
.fc-bad { color: rgb(var(--v-theme-error)); }
|
|
</style>
|