import { useEffect, useState } from 'react' import { apiRequest, listBackupPlans, createBackupPlan, updateBackupPlan, deleteBackupPlan, runScheduledBackups, } from '../api/client' import { Plus, Trash2, Play, Pencil } from 'lucide-react' interface BackupPlanRecord { id: number name: string plan_type: string target_id: number schedule: string enabled: boolean } interface SiteRecord { id: number name: string } interface DbRecord { id: number name: string db_type: string } export function BackupPlansPage() { const [plans, setPlans] = useState([]) const [sites, setSites] = useState([]) const [databases, setDatabases] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState('') const [showCreate, setShowCreate] = useState(false) const [creating, setCreating] = useState(false) const [editPlan, setEditPlan] = useState(null) const [editPlanType, setEditPlanType] = useState<'site' | 'database'>('site') const [runLoading, setRunLoading] = useState(false) const [runResults, setRunResults] = useState<{ plan: string; status: string; msg?: string }[] | null>(null) const [createPlanType, setCreatePlanType] = useState<'site' | 'database'>('site') const loadPlans = () => { listBackupPlans() .then(setPlans) .catch((err) => setError(err.message)) } useEffect(() => { setLoading(true) Promise.all([ listBackupPlans(), apiRequest('/site/list'), apiRequest('/database/list'), ]) .then(([p, s, d]) => { setPlans(p) setSites(s) setDatabases(d.filter((x) => ['MySQL', 'PostgreSQL', 'MongoDB'].includes(x.db_type))) }) .catch((err) => setError(err.message)) .finally(() => setLoading(false)) }, []) const handleCreate = (e: React.FormEvent) => { e.preventDefault() const form = e.currentTarget const name = (form.elements.namedItem('name') as HTMLInputElement).value.trim() const plan_type = (form.elements.namedItem('plan_type') as HTMLSelectElement).value as 'site' | 'database' const target_id = Number((form.elements.namedItem('target_id') as HTMLSelectElement).value) const schedule = (form.elements.namedItem('schedule') as HTMLInputElement).value.trim() const enabled = (form.elements.namedItem('enabled') as HTMLInputElement).checked if (!name || !schedule || !target_id) { setError('Name, target and schedule are required') return } setCreating(true) createBackupPlan({ name, plan_type, target_id, schedule, enabled }) .then(() => { setShowCreate(false) form.reset() loadPlans() }) .catch((err) => setError(err.message)) .finally(() => setCreating(false)) } const handleDelete = (id: number, name: string) => { if (!confirm(`Delete backup plan "${name}"?`)) return deleteBackupPlan(id) .then(loadPlans) .catch((err) => setError(err.message)) } const handleEdit = (plan: BackupPlanRecord) => { setEditPlan(plan) setEditPlanType(plan.plan_type as 'site' | 'database') } const handleUpdate = (e: React.FormEvent) => { e.preventDefault() if (!editPlan) return const form = e.currentTarget const name = (form.elements.namedItem('edit_name') as HTMLInputElement).value.trim() const target_id = Number((form.elements.namedItem('edit_target_id') as HTMLSelectElement).value) const schedule = (form.elements.namedItem('edit_schedule') as HTMLInputElement).value.trim() const enabled = (form.elements.namedItem('edit_enabled') as HTMLInputElement).checked if (!name || !schedule || !target_id) return updateBackupPlan(editPlan.id, { name, plan_type: editPlanType, target_id, schedule, enabled }) .then(() => { setEditPlan(null) loadPlans() }) .catch((err) => setError(err.message)) } const handleRunScheduled = () => { setRunLoading(true) setRunResults(null) runScheduledBackups() .then((r) => setRunResults(r.results)) .catch((err) => setError(err.message)) .finally(() => setRunLoading(false)) } const getTargetName = (plan: BackupPlanRecord) => { if (plan.plan_type === 'site') { const s = sites.find((x) => x.id === plan.target_id) return s ? s.name : `#${plan.target_id}` } const d = databases.find((x) => x.id === plan.target_id) return d ? d.name : `#${plan.target_id}` } if (loading) return
Loading...
if (error) return
{error}
return (

Backup Plans

{runResults && runResults.length > 0 && (

Last run results

    {runResults.map((r, i) => (
  • {r.plan}: {r.status === 'ok' ? '✓' : r.status === 'skipped' ? '⊘' : '✗'} {r.msg || ''}
  • ))}
)}

Schedule automated backups. Add a cron entry (e.g. 0 * * * * hourly) to call{' '} POST /api/v1/backup/run-scheduled with your auth token.

{plans.length === 0 ? ( ) : ( plans.map((p) => ( )) )}
Name Type Target Schedule Enabled Actions
No backup plans. Click "Add Plan" to create one.
{p.name} {p.plan_type} {getTargetName(p)} {p.schedule} {p.enabled ? 'Yes' : 'No'}
{editPlan && (

Edit Backup Plan

)} {showCreate && (

Add Backup Plan

e.g. 0 2 * * * = daily at 2am, 0 */6 * * * = every 6 hours

)}
) }