'use client'; import { useState, useEffect, type ReactNode } from 'react'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; import BackupManagerModal from './BackupManagerModal'; import * as Icons from './Icons'; interface Props { onLogout: () => void; /** The active route's panel — Pages, Journal Studio, Settings or Analytics. */ children: ReactNode; } const TABS = [ { label: 'Pages', href: '/admin/pages', match: /^\/admin(\/pages)?$/ }, { label: 'Journal', href: '/admin/journal', match: /^\/admin\/journal/ }, { label: 'Settings', href: '/admin/settings', match: /^\/admin\/settings/ }, { label: 'Analytics', href: '/admin/analytics', match: /^\/admin\/analytics/ }, ]; export default function AdminDashboard({ onLogout, children }: Props) { const pathname = usePathname(); const [saving, setSaving] = useState(false); // Diagnostics & Backup state const [showStatus, setShowStatus] = useState(false); const [showBackupModal, setShowBackupModal] = useState(false); const [status, setStatus] = useState(null); const [statusLoading, setStatusLoading] = useState(false); // Distinct from `status === null`: the check itself did not run. Without this // an expired session or a 500 rendered exactly like a real outage — every // indicator flipped to its alarming value at once (#341). const [statusError, setStatusError] = useState(false); async function handleLogout() { await fetch('/api/admin/auth', { method: 'DELETE' }); onLogout(); } async function handleReload() { setSaving(true); try { await fetch('/api/admin/reload', { method: 'POST' }); // Refresh status after reload await fetchStatus(); } finally { setSaving(false); } } const fetchStatus = async () => { setStatusLoading(true); try { const res = await fetch('/api/admin/status'); if (res.ok) { const data = await res.json(); setStatus(data); setStatusError(false); } else { setStatus(null); setStatusError(true); } } catch { setStatus(null); setStatusError(true); } finally { setStatusLoading(false); } }; /** * Render one diagnostic as ok / bad / unknown. "Unknown" covers both the * in-flight check and a check that failed to run — neither is evidence that * the thing being checked is broken. */ const indicator = (healthy: boolean, okLabel: string, badLabel: string) => { if (statusLoading) return { className: 'unknown', label: 'Checking…' }; if (statusError || !status) return { className: 'unknown', label: 'Unknown' }; return healthy ? { className: 'ok', label: okLabel } : { className: 'error', label: badLabel }; }; useEffect(() => { fetchStatus(); }, []); const immichIndicator = indicator( status?.immich?.status === 'connected', 'Connected', 'Disconnected', ); const configIndicator = indicator(status?.config?.status === 'valid', 'Valid', 'Degraded'); return (

Immich Folio

{/* Diagnostics Badge */}
{showStatus && ( <>
setShowStatus(false)} />

System Diagnostics

Immich Connection {immichIndicator.label}
Config Integrity {configIndicator.label}
Config Backups {status?.backups?.count ?? 0} Backups
Latest Backup {status?.backups?.lastBackup ? new Date(status.backups.lastBackup).toLocaleDateString() : 'None'}
In-Memory Cache {status?.cache?.size ?? 0} items
)}
{children}
setShowBackupModal(false)} onRestoreSuccess={() => { fetchStatus(); }} />
); }