'use client'; import { useState, useEffect, useCallback } from 'react'; import type { DoctorFinding, DoctorLevel } from '@/lib/admin/doctor'; import * as Icons from './Icons'; import { useScrollLock } from './useScrollLock'; interface Props { isOpen: boolean; onClose: () => void; } const LEVEL_LABEL: Record = { ok: 'OK', warn: 'Check', error: 'Problem', }; /** * The config doctor's report (#491). * * Most "it does not work for me" reports are one of a handful of * misconfigurations that fail silently — a wrong proxy hop count, a deleted * album ID, a plaintext password. Each of those already logs a warning to the * server log, where nobody looks. This puts them where the operator is. */ export default function DoctorModal({ isOpen, onClose }: Props) { const [findings, setFindings] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [copied, setCopied] = useState(false); const run = useCallback(async () => { setLoading(true); setError(null); try { const res = await fetch('/api/admin/doctor'); if (!res.ok) throw new Error(`Diagnostics failed (${res.status})`); const data = await res.json(); setFindings(data.findings || []); } catch (err) { setError(err instanceof Error ? err.message : 'Diagnostics failed'); } finally { setLoading(false); } }, []); useEffect(() => { if (isOpen) { setCopied(false); run(); } }, [isOpen, run]); useScrollLock(isOpen); if (!isOpen) return null; /** Markdown, so it can be pasted straight into a GitHub issue. */ async function copyReport() { const body = findings .map((f) => `- **${LEVEL_LABEL[f.level]}** — ${f.title}\n ${f.detail}`) .join('\n'); try { await navigator.clipboard.writeText(`### Immich Folio diagnostics\n\n${body}\n`); setCopied(true); } catch { setError('Could not copy — your browser refused clipboard access.'); } } const problems = findings.filter((f) => f.level !== 'ok').length; return (
e.stopPropagation()}>

Diagnostics

{loading ? 'Checking…' : problems === 0 ? 'Nothing needs your attention.' : `${problems} of ${findings.length} checks want a look.`}

{error &&
{error}
} {loading && !findings.length ? (
) : (
    {findings.map((f) => (
  • ))}
)}
); }