Files
FabledCurator/frontend/src/views/SettingsView.vue
T
bvandeusen e039689eff
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 32s
CI / integration (push) Successful in 3m35s
feat(ia): wave 2 — Activity becomes the whole-app pulse; Overview gets the health strip
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
2026-07-02 17:45:45 -04:00

115 lines
4.1 KiB
Vue

<template>
<!-- Centered, width-capped column so the tab strip + every panel sit in a
tidy centered measure rather than stretching full-width (operator
2026-06-18). v-container is margin-auto by default; the cap keeps long
maintenance/import panels readable. -->
<v-container class="pt-2 pb-6 fc-settings">
<!-- Sticky tabs: operator-flagged 2026-05-25 long Import / Maintenance
panels pushed the tab strip out of the viewport, forcing a scroll-
to-top just to change tab. AppShell's TopNav is 64px sticky, so the
tab strip lives directly under it. Background uses the theme surface
token so it visually merges with the page rather than the
translucent v-tabs default. -->
<v-tabs
v-model="tab" color="accent" class="mb-4"
style="position: sticky; top: 64px; z-index: 4;
background: rgb(var(--v-theme-surface));"
>
<v-tab value="overview">Overview</v-tab>
<v-tab value="activity">Activity</v-tab>
<v-tab value="cleanup">Cleanup</v-tab>
<v-tab value="maintenance">Maintenance</v-tab>
</v-tabs>
<v-window v-model="tab">
<v-window-item value="overview">
<SystemStatsCards :stats="system.stats" />
<SystemActivitySummary
class="mt-4"
@open-activity="tab = 'activity'"
/>
<!-- Health strip (2026-07-02): the same GPU + downloads panels the
Activity tab uses — Overview answers "is everything healthy?"
across ALL systems, not just the Celery queues above. -->
<div class="fc-health-row mt-4">
<GpuActivityPanel @open-maintenance="tab = 'maintenance'" />
<DownloadsActivityPanel />
</div>
<v-alert
v-if="system.stats && (system.stats.tasks.pending + system.stats.tasks.queued) > 0"
type="info" variant="tonal" class="mt-4" closable
>
{{ system.stats.tasks.pending + system.stats.tasks.queued }} import task(s) pending.
<v-btn variant="text" size="small" @click="tab = 'activity'">View in Activity</v-btn>
</v-alert>
</v-window-item>
<v-window-item value="activity">
<SystemActivityTab @open-maintenance="tab = 'maintenance'" />
</v-window-item>
<v-window-item value="cleanup">
<CleanupView />
</v-window-item>
<v-window-item value="maintenance">
<MaintenancePanel />
</v-window-item>
</v-window>
</v-container>
</template>
<script setup>
import { onMounted, onUnmounted, ref, watch } from 'vue'
import { useSystemStore } from '../stores/system.js'
import SystemStatsCards from '../components/settings/SystemStatsCards.vue'
import SystemActivitySummary from '../components/settings/SystemActivitySummary.vue'
import SystemActivityTab from '../components/settings/SystemActivityTab.vue'
import GpuActivityPanel from '../components/settings/GpuActivityPanel.vue'
import DownloadsActivityPanel from '../components/settings/DownloadsActivityPanel.vue'
import MaintenancePanel from '../components/settings/MaintenancePanel.vue'
import CleanupView from './CleanupView.vue'
import { useMLStore } from '../stores/ml.js'
const tab = ref('overview')
const system = useSystemStore()
const mlStore = useMLStore()
let pollId = null
function startPolling() {
if (pollId) return
system.refreshStats()
pollId = setInterval(() => {
if (!document.hidden) system.refreshStats()
}, 5000)
}
function stopPolling() {
if (pollId) { clearInterval(pollId); pollId = null }
}
onMounted(startPolling)
onUnmounted(stopPolling)
watch(tab, (t) => {
if (t === 'maintenance') mlStore.loadSettings()
})
</script>
<style scoped>
/* Centered, capped measure for the whole Settings surface (tabs + panels). */
.fc-settings {
max-width: 1140px;
}
/* Overview health strip: the two pulse panels side by side, stacking on
narrow screens. Cancel the panels' own bottom margin the row owns gaps. */
.fc-health-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
gap: 16px;
align-items: start;
}
.fc-health-row :deep(.v-card) { margin-bottom: 0 !important; }
</style>