- 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
88 lines
2.7 KiB
Vue
88 lines
2.7 KiB
Vue
<template>
|
|
<MaintenanceTile
|
|
icon="mdi-database-cog"
|
|
title="Database maintenance"
|
|
blurb="VACUUM ANALYZE + per-table bloat stats."
|
|
:open="busy"
|
|
>
|
|
<p class="text-body-2 mb-3">
|
|
VACUUM (ANALYZE) reclaims dead-tuple bloat — which slows the random
|
|
showcase, since it samples physical blocks — and refreshes the query
|
|
planner's statistics. Runs automatically each week; trigger a pass
|
|
here after a large import or cleanup.
|
|
</p>
|
|
<v-btn color="primary" rounded="pill" :loading="busy" @click="run">
|
|
<v-icon start>mdi-database-cog</v-icon> Run VACUUM ANALYZE now
|
|
</v-btn>
|
|
<span v-if="queued" class="ml-3 text-caption text-success">Queued ✓</span>
|
|
<QueueStatusBar queue="maintenance" queue-label="Maintenance" />
|
|
|
|
<v-table
|
|
v-if="store.tables.length" density="compact" class="mt-4 fc-dbm__table"
|
|
>
|
|
<thead>
|
|
<tr>
|
|
<th>Table</th>
|
|
<th class="text-right">Live rows</th>
|
|
<th class="text-right">Dead</th>
|
|
<th class="text-right">Dead %</th>
|
|
<th>Last vacuum</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="t in store.tables" :key="t.table">
|
|
<td>{{ t.table }}</td>
|
|
<td class="text-right">{{ t.live.toLocaleString() }}</td>
|
|
<td class="text-right">{{ t.dead.toLocaleString() }}</td>
|
|
<td
|
|
class="text-right"
|
|
:class="{ 'text-warning': t.dead_pct >= 20 }"
|
|
>{{ t.dead_pct }}%</td>
|
|
<td class="text-caption">{{ fmt(t.last_vacuum || t.last_autovacuum) }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</v-table>
|
|
<p v-else class="text-caption mt-3 fc-muted">
|
|
No table statistics yet.
|
|
</p>
|
|
</MaintenanceTile>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { onMounted, ref } from 'vue'
|
|
|
|
import { toast } from '../../utils/toast.js'
|
|
import { useDbMaintenanceStore } from '../../stores/dbMaintenance.js'
|
|
import MaintenanceTile from '../common/MaintenanceTile.vue'
|
|
import QueueStatusBar from './QueueStatusBar.vue'
|
|
|
|
const store = useDbMaintenanceStore()
|
|
const busy = ref(false)
|
|
const queued = ref(false)
|
|
|
|
onMounted(() => store.loadStats())
|
|
|
|
async function run () {
|
|
busy.value = true
|
|
queued.value = false
|
|
try {
|
|
await store.runVacuum()
|
|
queued.value = true
|
|
// pg_stat updates after the vacuum lands — refresh shortly after.
|
|
setTimeout(() => store.loadStats(), 5000)
|
|
} catch (e) {
|
|
toast({ text: e.message, type: 'error' })
|
|
} finally {
|
|
busy.value = false
|
|
}
|
|
}
|
|
|
|
function fmt (iso) {
|
|
return iso ? new Date(iso).toLocaleString() : '—'
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.fc-dbm__table { background: transparent; }
|
|
</style>
|