import { useEffect, useMemo, useState } from 'react'; import { AlertTriangle, CalendarClock, ClipboardList, Database, DatabaseBackup, LogOut, Moon, Network, RefreshCw, Settings as SettingsIcon, Shield, } from 'lucide-react'; import { api, errorMessage } from './api.js'; import { Dashboard } from './components/Dashboard.jsx'; import { Login } from './components/Login.jsx'; import { Nodes } from './components/Nodes.jsx'; import { Operations } from './components/Operations.jsx'; import { Repositories } from './components/Repositories.jsx'; import { Scheduler } from './components/Scheduler.jsx'; import { Settings } from './components/Settings.jsx'; import { VMDetail } from './components/VMDetail.jsx'; export default function App() { const [health, setHealth] = useState(null); const [vms, setVms] = useState([]); const [jobs, setJobs] = useState([]); const [schedules, setSchedules] = useState([]); const [nodes, setNodes] = useState([]); const [selectedVm, setSelectedVm] = useState(null); const [session, setSession] = useState(null); const [sessionLoading, setSessionLoading] = useState(true); const [view, setView] = useState('dashboard'); const [error, setError] = useState(''); const [loading, setLoading] = useState(true); async function refresh() { try { const [healthResult, vmsResult, jobsResult, schedulesResult, nodesResult] = await Promise.allSettled([ api.get('/health'), api.get('/vms'), api.get('/jobs'), api.get('/schedules'), api.get('/nodes'), ]); if (healthResult.status === 'fulfilled') { setStableState(setHealth, healthResult.value.data); } else { setStableState(setHealth, healthResult.reason.response?.data || { ok: false, checks: {} }); } if (vmsResult.status === 'fulfilled') setStableState(setVms, vmsResult.value.data); if (jobsResult.status === 'fulfilled') setStableState(setJobs, jobsResult.value.data); if (schedulesResult.status === 'fulfilled') setStableState(setSchedules, schedulesResult.value.data); if (nodesResult.status === 'fulfilled') setStableState(setNodes, nodesResult.value.data); const firstFailure = [healthResult, vmsResult, jobsResult, schedulesResult, nodesResult].find((result) => result.status === 'rejected'); setStableState(setError, firstFailure ? errorMessage(firstFailure.reason) : ''); } catch (requestError) { setStableState(setError, errorMessage(requestError)); } finally { setLoading(false); } } useEffect(() => { loadSession(); }, []); useEffect(() => { if (!session?.authenticated) return undefined; refresh(); const timer = window.setInterval(refresh, 5000); return () => window.clearInterval(timer); }, [session?.authenticated]); const currentVm = useMemo( () => vms.find((vm) => vm.id === selectedVm) || null, [selectedVm, vms], ); async function loadSession() { try { const result = await api.get('/auth/session'); setSession(result.data); } catch { setSession({ authenticated: false, user: null }); } finally { setSessionLoading(false); } } async function logout() { await api.post('/auth/logout'); setSession({ authenticated: false, user: null }); setVms([]); setJobs([]); setSchedules([]); setNodes([]); } if (sessionLoading) { return
; } if (!session?.authenticated) { return ; } const pageTitle = currentVm ? currentVm.name : { dashboard: 'Backups', nodes: 'Nodes', repositories: 'Repositories', operations: 'Operations', scheduler: 'Scheduler', settings: 'Settings', }[view] || 'Backups'; return (

{pageTitle}

{session.user?.username || 'admin'}
{error ? (
{error}
) : null}
{view === 'nodes' ? ( ) : view === 'repositories' ? ( ) : view === 'operations' ? ( ) : view === 'settings' ? ( ) : view === 'scheduler' ? ( ) : currentVm ? ( setSelectedVm(null)} onChanged={refresh} /> ) : ( switchView('scheduler')} onManage={(vmName) => { setSelectedVm(vmName); setView('dashboard'); }} schedules={schedules} vms={vms} /> )}
); function switchView(nextView) { setSelectedVm(null); setView(nextView); } } function setStableState(setter, nextValue) { setter((currentValue) => ( stableStringify(currentValue) === stableStringify(nextValue) ? currentValue : nextValue )); } function stableStringify(value) { return JSON.stringify(value); } function NavButton({ active, icon: Icon, label, onClick }) { return ( ); }