67c7ca8603
The app uses a plain sticky TopNav (no v-main), and the nav's height was hardcoded as 64px in ~6 places: the Explore + Subscriptions full-height workspaces (height: calc(100vh - 64px)) and every sticky sub-header pinned beneath the nav (top: 64px — Gallery filter bar, Browse/Series/Settings tabs). Vuetify 4's MD3 sizing changed the real nav height, so 64px was wrong: the Explore workspace was sized taller than the space below the nav, overflowed the viewport, and its breadcrumb tucked under the (taller) nav on 1080p. TopNav now measures its own height via ResizeObserver and publishes it as --fc-nav-h on documentElement (default 64px in app.css). Every consumer uses var(--fc-nav-h) instead of the magic number, so the layout self-corrects to the nav's real height and stays correct as it reflows (per-view teleported actions, mobile breakpoint). Also tightens the new chrome-gradient seam — sub-headers now pin at the nav's exact bottom. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
113 lines
4.1 KiB
Vue
113 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. The .fc-chrome-continues fade
|
|
continues the nav's gradient across the strip (operator 2026-07-13). -->
|
|
<v-tabs
|
|
v-model="tab" color="accent" class="mb-4 fc-chrome-continues"
|
|
style="position: sticky; top: var(--fc-nav-h, 64px); z-index: 4;"
|
|
>
|
|
<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>
|