Improve SQLite batching and diagnostics visibility

This commit is contained in:
2026-03-03 15:03:23 +13:00
parent e582ff4ef7
commit dda17a20a5
10 changed files with 667 additions and 188 deletions

View File

@@ -56,6 +56,21 @@ type AdminDiagnosticsPanelProps = {
embedded?: boolean
}
type DatabaseDiagnosticDetail = {
integrity_check?: string
database_path?: string
database_size_bytes?: number
wal_size_bytes?: number
shm_size_bytes?: number
page_size_bytes?: number
page_count?: number
freelist_pages?: number
allocated_bytes?: number
free_bytes?: number
row_counts?: Record<string, number>
timings_ms?: Record<string, number>
}
const REFRESH_INTERVAL_MS = 30000
const STATUS_LABELS: Record<string, string> = {
@@ -85,6 +100,54 @@ function statusLabel(status: string) {
return STATUS_LABELS[status] ?? status
}
function formatBytes(value?: number) {
if (typeof value !== 'number' || Number.isNaN(value) || value < 0) {
return '0 B'
}
if (value >= 1024 * 1024 * 1024) {
return `${(value / (1024 * 1024 * 1024)).toFixed(2)} GB`
}
if (value >= 1024 * 1024) {
return `${(value / (1024 * 1024)).toFixed(2)} MB`
}
if (value >= 1024) {
return `${(value / 1024).toFixed(1)} KB`
}
return `${value} B`
}
function formatDetailLabel(value: string) {
return value
.replace(/_/g, ' ')
.replace(/\b\w/g, (character) => character.toUpperCase())
}
function asDatabaseDiagnosticDetail(detail: unknown): DatabaseDiagnosticDetail | null {
if (!detail || typeof detail !== 'object' || Array.isArray(detail)) {
return null
}
return detail as DatabaseDiagnosticDetail
}
function renderDatabaseMetricGroup(title: string, values: Array<[string, string]>) {
if (values.length === 0) {
return null
}
return (
<div className="diagnostic-detail-group">
<h4>{title}</h4>
<div className="diagnostic-detail-grid">
{values.map(([label, value]) => (
<div key={`${title}-${label}`} className="diagnostic-detail-item">
<span>{label}</span>
<strong>{value}</strong>
</div>
))}
</div>
</div>
)
}
export default function AdminDiagnosticsPanel({ embedded = false }: AdminDiagnosticsPanelProps) {
const router = useRouter()
const [loading, setLoading] = useState(true)
@@ -405,6 +468,43 @@ export default function AdminDiagnosticsPanel({ embedded = false }: AdminDiagnos
<span className="system-dot" />
<span>{isRunning ? 'Running diagnostic...' : check.message}</span>
</div>
{check.key === 'database'
? (() => {
const detail = asDatabaseDiagnosticDetail(check.detail)
if (!detail) {
return null
}
return (
<div className="diagnostic-detail-panel">
{renderDatabaseMetricGroup('Storage', [
['Database file', formatBytes(detail.database_size_bytes)],
['WAL file', formatBytes(detail.wal_size_bytes)],
['Shared memory', formatBytes(detail.shm_size_bytes)],
['Allocated bytes', formatBytes(detail.allocated_bytes)],
['Free bytes', formatBytes(detail.free_bytes)],
['Page size', formatBytes(detail.page_size_bytes)],
['Page count', `${detail.page_count?.toLocaleString() ?? 0}`],
['Freelist pages', `${detail.freelist_pages?.toLocaleString() ?? 0}`],
])}
{renderDatabaseMetricGroup(
'Tables',
Object.entries(detail.row_counts ?? {}).map(([key, value]) => [
formatDetailLabel(key),
value.toLocaleString(),
]),
)}
{renderDatabaseMetricGroup(
'Timings',
Object.entries(detail.timings_ms ?? {}).map(([key, value]) => [
formatDetailLabel(key),
`${value.toFixed(1)} ms`,
]),
)}
</div>
)
})()
: null}
</article>
)
})}