Server component fetches backup list via list_ops_backups agent command and parses filename/size output. Client BackupsPanel component shows a backup table and a Backup now button that triggers the backup_ops_db flow with streaming terminal output and audit log link. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
59 lines
1.8 KiB
TypeScript
59 lines
1.8 KiB
TypeScript
import Link from 'next/link'
|
|
import { redirect } from 'next/navigation'
|
|
import { getCurrentUser } from '@/lib/session'
|
|
import { execAgent } from '@/lib/agent-client'
|
|
import BackupsPanel from './_components/backups-panel'
|
|
|
|
export const dynamic = 'force-dynamic'
|
|
|
|
export interface BackupFile {
|
|
name: string
|
|
sizeBytes: number
|
|
label: string
|
|
}
|
|
|
|
function parseBackupList(output: string): BackupFile[] {
|
|
return output
|
|
.split('\n')
|
|
.map((line) => line.trim())
|
|
.filter(Boolean)
|
|
.map((line) => {
|
|
const [name, sizeStr] = line.split('\t')
|
|
const sizeBytes = parseInt(sizeStr ?? '0', 10) || 0
|
|
const m = name?.match(/ops_db_(\d{4})(\d{2})(\d{2})_(\d{2})(\d{2})\.dump/)
|
|
const label = m ? `${m[1]}-${m[2]}-${m[3]} ${m[4]}:${m[5]}` : (name ?? '')
|
|
return { name: name ?? '', sizeBytes, label }
|
|
})
|
|
.filter((b) => b.name)
|
|
}
|
|
|
|
export default async function BackupsPage() {
|
|
const user = await getCurrentUser()
|
|
if (!user) redirect('/login')
|
|
|
|
let backups: BackupFile[] = []
|
|
let listError: string | null = null
|
|
|
|
try {
|
|
const output = await execAgent('list_ops_backups')
|
|
backups = parseBackupList(output)
|
|
} catch (err) {
|
|
listError = err instanceof Error ? err.message : 'failed to list backups'
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-background p-6">
|
|
<div className="mx-auto max-w-4xl space-y-6">
|
|
<div className="flex items-center gap-3">
|
|
<Link href="/" className="text-sm text-muted-foreground hover:text-foreground">
|
|
← Home
|
|
</Link>
|
|
<span className="text-muted-foreground">/</span>
|
|
<h1 className="text-2xl font-semibold tracking-tight">Backups</h1>
|
|
</div>
|
|
|
|
<BackupsPanel backups={backups} listError={listError} />
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|