311fe0ee9c
Goal (operator 2026-06-18): the overview of a Settings tab fits one unscrolled viewport; expanding a tile to read into it is the only reason to scroll. - Every Maintenance card converted to the collapsible MaintenanceTile (collapsed by default = icon + short title + one-line blurb). Task cards (ML backfill, centroids, thumbnails, archive re-extract, missing-file repair, DB maintenance) sit in a responsive grid; running tasks auto-expand. Tagging config (suggestion thresholds, allowlist, aliases) grouped in one Tagging section as collapsible tiles; Backup is its own collapsible tile. - Three labeled sections mirror the Cleanup tab: Backfills and reprocessing / Tagging / Storage. - Center the whole Settings surface: SettingsView is now a centered, width-capped (1140px) column so the tab strip and every panel sit in a tidy centered measure (was full-width). CleanupView drops its own left-aligned max-width to fill it. All card logic unchanged - only the chrome. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
139 lines
4.9 KiB
Vue
139 lines
4.9 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="import">Import</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'"
|
|
/>
|
|
<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 = 'import'">Go to Import tab</v-btn>
|
|
</v-alert>
|
|
<!-- Browser-extension install/download lives on Overview (moved
|
|
from Maintenance 2026-05-25). Overview is the discovery
|
|
surface for "things to set up"; Maintenance is for
|
|
housekeeping of already-set-up systems. -->
|
|
<BrowserExtensionCard class="mt-6" />
|
|
</v-window-item>
|
|
|
|
<v-window-item value="activity">
|
|
<SystemActivityTab />
|
|
</v-window-item>
|
|
|
|
<v-window-item value="import">
|
|
<!-- Order: filters → trigger → recent tasks. Filters hoisted above the
|
|
trigger (operator-flagged 2026-06-04); the task list stays
|
|
directly below the trigger so hit/miss feedback is adjacent to the
|
|
button that produced it (operator-flagged 2026-05-25). -->
|
|
<ImportFiltersForm />
|
|
<v-divider class="my-6" />
|
|
<ImportTriggerPanel />
|
|
<v-divider class="my-6" />
|
|
<ImportTaskList />
|
|
</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 { useImportStore } from '../stores/import.js'
|
|
import SystemStatsCards from '../components/settings/SystemStatsCards.vue'
|
|
import SystemActivitySummary from '../components/settings/SystemActivitySummary.vue'
|
|
import SystemActivityTab from '../components/settings/SystemActivityTab.vue'
|
|
import BrowserExtensionCard from '../components/settings/BrowserExtensionCard.vue'
|
|
import ImportTriggerPanel from '../components/settings/ImportTriggerPanel.vue'
|
|
import ImportFiltersForm from '../components/settings/ImportFiltersForm.vue'
|
|
import ImportTaskList from '../components/settings/ImportTaskList.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 importStore = useImportStore()
|
|
const mlStore = useMLStore()
|
|
|
|
let pollId = null
|
|
|
|
function startPolling() {
|
|
if (pollId) return
|
|
system.refreshStats()
|
|
pollId = setInterval(() => {
|
|
if (!document.hidden) {
|
|
system.refreshStats()
|
|
if (tab.value === 'import') {
|
|
importStore.refreshStatus()
|
|
// Refresh the task list while a batch is in flight so the UI
|
|
// doesn't sit stale next to a ticking imported/skipped counter.
|
|
if (importStore.activeBatch) importStore.loadTasks(true)
|
|
}
|
|
}
|
|
}, 5000)
|
|
}
|
|
|
|
function stopPolling() {
|
|
if (pollId) { clearInterval(pollId); pollId = null }
|
|
}
|
|
|
|
onMounted(() => {
|
|
startPolling()
|
|
importStore.loadSettings()
|
|
importStore.refreshStatus()
|
|
importStore.loadTasks(true)
|
|
})
|
|
onUnmounted(stopPolling)
|
|
|
|
watch(tab, (t) => {
|
|
if (t === 'import') {
|
|
importStore.refreshStatus()
|
|
importStore.loadTasks(true)
|
|
} else if (t === 'maintenance') {
|
|
mlStore.loadSettings()
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
/* Centered, capped measure for the whole Settings surface (tabs + panels). */
|
|
.fc-settings {
|
|
max-width: 1140px;
|
|
}
|
|
</style>
|