'use client'; import { useEffect, useState } from 'react'; import { Server, AlertTriangle, Clock, Globe } from 'lucide-react'; import type { AuditEntry } from '@/lib/admin/types'; interface AdminStatus { enabled: boolean; authenticated: boolean; lastLogin: string | null; passwordChangedAt: string | null; } interface ConfigData { appName?: string; jmapServerUrl?: string; settingsSyncEnabled?: boolean; stalwartFeaturesEnabled?: boolean; oauthEnabled?: boolean; devMode?: boolean; } export default function AdminDashboardPage() { const [status, setStatus] = useState(null); const [recentActivity, setRecentActivity] = useState([]); const [config, setConfig] = useState(null); const [, setConfigSources] = useState | null>(null); const [warnings, setWarnings] = useState([]); useEffect(() => { fetchDashboardData(); }, []); async function fetchDashboardData() { const [statusRes, auditRes, configRes, adminConfigRes] = await Promise.all([ fetch('/api/admin/auth'), fetch('/api/admin/audit?limit=10'), fetch('/api/config'), fetch('/api/admin/config'), ]); if (statusRes.ok) setStatus(await statusRes.json()); if (auditRes.ok) { const data = await auditRes.json(); setRecentActivity(data.entries || []); } if (configRes.ok) setConfig(await configRes.json()); // Build warnings const w: string[] = []; if (adminConfigRes.ok) { const sources = await adminConfigRes.json(); setConfigSources(sources); const sessionSecret = sources?.sessionSecret; if (!sessionSecret?.value || sessionSecret.value === 'your-secret-key-here') { w.push('SESSION_SECRET is not set or using a default value. Sessions are insecure.'); } } setWarnings(w); } const jmapUrl = config?.jmapServerUrl || '—'; return (

Dashboard

Server overview and recent activity

{/* Status cards */}
} label="Application" value={config?.appName || '—'} /> } label="JMAP Server" value={jmapUrl ? new URL(jmapUrl).hostname : '—'} detail={jmapUrl} /> } label="Last Login" value={status?.lastLogin ? new Date(status.lastLogin).toLocaleString() : 'Never'} />
{/* Feature status row */}
{/* Warnings */} {warnings.map((msg, i) => (

{msg}

))} {status && !status.lastLogin && (

First login detected

Remember to remove ADMIN_PASSWORD from your .env file now that the hash is stored securely.

)} {/* Recent activity */}

Recent Activity

{recentActivity.length === 0 ? (
No activity recorded yet
) : ( recentActivity.map((entry, i) => (
{entry.action} {formatDetail(entry.detail)}
{entry.ip} {new Date(entry.ts).toLocaleString()}
)) )}
); } function StatusCard({ icon, label, value, detail }: { icon: React.ReactNode; label: string; value: string; detail?: string }) { return (
{icon} {label}
{value}
{detail &&

{detail}

}
); } function FeaturePill({ label, active }: { label: string; active: boolean }) { return (
{label} {active ? 'On' : 'Off'}
); } function formatDetail(detail: Record): string { if (!detail || Object.keys(detail).length === 0) return ''; if (detail.key) return `${detail.key}: ${detail.old} → ${detail.new}`; if (detail.reason) return String(detail.reason); if (detail.changes && Array.isArray(detail.changes)) return `${detail.changes.length} setting(s) changed`; return JSON.stringify(detail).slice(0, 80); }