import { useState } from 'react'; import { useAuthStore } from '../../auth/auth-store'; import { StatusBadge } from '../../components/admin/StatusBadge'; import { RefreshableCard } from '../../components/admin/RefreshableCard'; import { ConfirmDeleteDialog } from '../../components/admin/ConfirmDeleteDialog'; import { useDatabaseStatus, useDatabasePool, useDatabaseTables, useDatabaseQueries, useKillQuery, } from '../../api/queries/admin/database'; import { useThresholds, useSaveThresholds, type Thresholds } from '../../api/queries/admin/thresholds'; import styles from './DatabaseAdminPage.module.css'; export function DatabaseAdminPage() { const roles = useAuthStore((s) => s.roles); if (!roles.includes('ADMIN')) { return (
Access Denied — this page requires the ADMIN role.
); } return ; } function DatabaseAdminContent() { const status = useDatabaseStatus(); const pool = useDatabasePool(); const tables = useDatabaseTables(); const queries = useDatabaseQueries(); const thresholds = useThresholds(); if (status.isLoading) { return (

Database Administration

Loading...
); } const db = status.data; return (

Database Administration

{db?.version && {db.version}} {db?.host && {db.host}} {db?.schema && Schema: {db.schema}}
); } function PoolSection({ pool, warningPct, criticalPct, }: { pool: ReturnType; warningPct?: number; criticalPct?: number; }) { const data = pool.data; if (!data) return null; const usagePct = data.maxConnections > 0 ? Math.round((data.activeConnections / data.maxConnections) * 100) : 0; const barColor = criticalPct && usagePct >= criticalPct ? '#ef4444' : warningPct && usagePct >= warningPct ? '#eab308' : '#22c55e'; return ( pool.refetch()} isRefreshing={pool.isFetching} autoRefresh >
{data.activeConnections} / {data.maxConnections} connections {usagePct}%
{data.activeConnections} Active
{data.idleConnections} Idle
{data.pendingConnections} Pending
{data.maxWaitMillis}ms Max Wait
); } function TablesSection({ tables }: { tables: ReturnType }) { const data = tables.data; return ( tables.refetch()} isRefreshing={tables.isFetching} > {!data ? (
Loading...
) : (
{data.map((t) => ( ))}
Table Rows Data Size Index Size
{t.tableName} {t.rowEstimate.toLocaleString()} {t.dataSize} {t.indexSize}
)}
); } function QueriesSection({ queries, warningSeconds, }: { queries: ReturnType; warningSeconds?: number; }) { const [killTarget, setKillTarget] = useState(null); const killMutation = useKillQuery(); const data = queries.data; const warningMs = (warningSeconds ?? 30) * 1000; return ( queries.refetch()} isRefreshing={queries.isFetching} autoRefresh > {!data || data.length === 0 ? (
No active queries
) : (
{data.map((q) => ( warningMs ? styles.rowWarning : undefined} > ))}
PID Duration State Query
{q.pid} {formatDuration(q.durationMs)} {q.state} {q.query.length > 100 ? `${q.query.slice(0, 100)}...` : q.query}
)} setKillTarget(null)} onConfirm={() => { if (killTarget !== null) { killMutation.mutate(killTarget); setKillTarget(null); } }} resourceName={String(killTarget ?? '')} resourceType="query (PID)" />
); } function MaintenanceSection() { return (
); } function ThresholdsSection({ thresholds }: { thresholds?: Thresholds }) { const [form, setForm] = useState(null); const saveMutation = useSaveThresholds(); const [status, setStatus] = useState<{ type: 'success' | 'error'; msg: string } | null>(null); const current = form ?? thresholds; if (!current) return null; function update(key: keyof Thresholds, value: number) { setForm((prev) => ({ ...(prev ?? thresholds!), [key]: value })); } async function handleSave() { if (!form && !thresholds) return; const data = form ?? thresholds!; try { await saveMutation.mutateAsync(data); setStatus({ type: 'success', msg: 'Thresholds saved.' }); setTimeout(() => setStatus(null), 3000); } catch { setStatus({ type: 'error', msg: 'Failed to save thresholds.' }); } } return (
update('poolWarningPercent', Number(e.target.value))} />
update('poolCriticalPercent', Number(e.target.value))} />
update('queryDurationWarningSeconds', Number(e.target.value))} />
update('queryDurationCriticalSeconds', Number(e.target.value))} />
{status && ( {status.msg} )}
); } function formatDuration(ms: number): string { if (ms < 1000) return `${ms}ms`; const s = Math.floor(ms / 1000); if (s < 60) return `${s}s`; const m = Math.floor(s / 60); return `${m}m ${s % 60}s`; }