import { useEffect, useState, useCallback } from "react"; import { admin, endpoints, webhooks, ApiError, } from "../api/client"; import type { EndpointResponse, WebhookResponse, WebhookCreate, } from "../api/client"; // --------------------------------------------------------------------------- // Types for admin settings / stats // --------------------------------------------------------------------------- interface AdminSettings { guest_access_enabled: boolean; default_endpoint_id: string | null; token_budget_daily: number | null; token_budget_monthly: number | null; api_keys: ApiKeyEntry[]; } interface ApiKeyEntry { id: string; label: string; prefix: string; created_at: string; } interface SystemStats { total_runs: number; total_experiments: number; total_projects: number; cache_entries: number; cache_hit_rate: number; storage_bytes: number; tokens_spent: number; } function defaultSettings(): AdminSettings { return { guest_access_enabled: false, default_endpoint_id: null, token_budget_daily: null, token_budget_monthly: null, api_keys: [], }; } function defaultStats(): SystemStats { return { total_runs: 0, total_experiments: 0, total_projects: 0, cache_entries: 0, cache_hit_rate: 0, storage_bytes: 0, tokens_spent: 0, }; } // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- function formatBytes(bytes: number): string { if (bytes === 0) return "0 B"; const units = ["B", "KB", "MB", "GB", "TB"]; const i = Math.floor(Math.log(bytes) / Math.log(1024)); const val = bytes / Math.pow(1024, i); return `${val.toFixed(i === 0 ? 0 : 1)} ${units[i]}`; } function formatNumber(n: number): string { return n.toLocaleString(); } // --------------------------------------------------------------------------- // Section Card wrapper // --------------------------------------------------------------------------- function SectionCard({ title, children, }: { title: string; children: React.ReactNode; }) { return (

{title}

{children}
); } // --------------------------------------------------------------------------- // Settings Section // --------------------------------------------------------------------------- function SettingsSection({ settings, endpointList, onUpdate, saving, }: { settings: AdminSettings; endpointList: EndpointResponse[]; onUpdate: (patch: Partial) => void; saving: boolean; }) { return (
{/* Guest access toggle */}

Guest Access

Allow unauthenticated users to view experiments.

{/* Default endpoint */}
{/* Token budgets */}
onUpdate({ token_budget_daily: e.target.value ? Number(e.target.value) : null, }) } placeholder="Unlimited" className="w-full rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-700 px-3 py-2 text-sm text-slate-900 dark:text-white placeholder-slate-400 focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/20 transition" />
onUpdate({ token_budget_monthly: e.target.value ? Number(e.target.value) : null, }) } placeholder="Unlimited" className="w-full rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-700 px-3 py-2 text-sm text-slate-900 dark:text-white placeholder-slate-400 focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/20 transition" />
); } // --------------------------------------------------------------------------- // API Keys Section // --------------------------------------------------------------------------- function ApiKeysSection({ keys, onGenerate, onRevoke, generating, }: { keys: ApiKeyEntry[]; onGenerate: (label: string) => void; onRevoke: (id: string) => void; generating: boolean; }) { const [newLabel, setNewLabel] = useState(""); function handleGenerate(e: React.FormEvent) { e.preventDefault(); if (!newLabel.trim()) return; onGenerate(newLabel.trim()); setNewLabel(""); } return (
setNewLabel(e.target.value)} placeholder="Key label (e.g. CI pipeline)" data-testid="api-key-label" disabled={generating} className="flex-1 rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-700 px-3 py-2 text-sm text-slate-900 dark:text-white placeholder-slate-400 focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/20 transition" />
{keys.length === 0 ? (

No API keys generated yet.

) : ( )}
); } // --------------------------------------------------------------------------- // System Stats Section // --------------------------------------------------------------------------- function StatsSection({ stats }: { stats: SystemStats }) { const statItems = [ { label: "Total Runs", value: formatNumber(stats.total_runs) }, { label: "Total Experiments", value: formatNumber(stats.total_experiments), }, { label: "Total Projects", value: formatNumber(stats.total_projects) }, { label: "Cache Entries", value: formatNumber(stats.cache_entries) }, { label: "Cache Hit Rate", value: `${(stats.cache_hit_rate * 100).toFixed(1)}%`, }, { label: "Storage Usage", value: formatBytes(stats.storage_bytes) }, { label: "Tokens Spent", value: formatNumber(stats.tokens_spent) }, ]; return (
{statItems.map((item) => (

{item.label}

{item.value}

))}
); } // --------------------------------------------------------------------------- // Webhooks Section // --------------------------------------------------------------------------- const EVENT_TYPES = [ "run.started", "run.completed", "run.failed", "sweep.started", "sweep.completed", "new_best_found", ]; function WebhooksSection({ webhookList, onDelete, onCreate, creating, }: { webhookList: WebhookResponse[]; onDelete: (id: string) => void; onCreate: (data: WebhookCreate) => void; creating: boolean; }) { const [showForm, setShowForm] = useState(false); const [eventType, setEventType] = useState(EVENT_TYPES[0]); const [url, setUrl] = useState(""); function handleCreate(e: React.FormEvent) { e.preventDefault(); if (!url.trim()) return; onCreate({ event_type: eventType, url: url.trim() }); setUrl(""); setShowForm(false); } return (

{webhookList.length} webhook{webhookList.length !== 1 ? "s" : ""}{" "} configured

{showForm && (
setUrl(e.target.value)} placeholder="https://example.com/webhook" data-testid="webhook-url-input" disabled={creating} className="w-full rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-700 px-3 py-2 text-sm text-slate-900 dark:text-white placeholder-slate-400 focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/20 transition" />
)} {webhookList.length === 0 && !showForm ? (

No webhooks configured.

) : ( )}
); } // --------------------------------------------------------------------------- // Admin Page // --------------------------------------------------------------------------- export default function AdminPage() { const [settings, setSettings] = useState(defaultSettings()); const [stats, setStats] = useState(defaultStats()); const [endpointList, setEndpointList] = useState([]); const [webhookList, setWebhookList] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [saving, setSaving] = useState(false); const [generating, setGenerating] = useState(false); const [creatingWebhook, setCreatingWebhook] = useState(false); const [saveMessage, setSaveMessage] = useState(null); const loadData = useCallback(async () => { setLoading(true); setError(null); try { const [settingsResp, statsResp, endpointsResp, webhooksResp] = await Promise.all([ admin.getSettings(), admin.getStats(), endpoints.list(), webhooks.list(), ]); setSettings({ ...defaultSettings(), ...(settingsResp as Partial), }); setStats({ ...defaultStats(), ...(statsResp as Partial) }); setEndpointList(endpointsResp.items); setWebhookList(webhooksResp.items); } catch (err: unknown) { if (err instanceof ApiError) { setError(`Failed to load admin data (${err.status}).`); } else { setError("Network error. Is the server running?"); } } finally { setLoading(false); } }, []); useEffect(() => { loadData(); }, [loadData]); async function handleUpdateSettings(patch: Partial) { const updated = { ...settings, ...patch }; setSettings(updated); setSaving(true); setSaveMessage(null); try { await admin.updateSettings( updated as unknown as Record, ); setSaveMessage("Settings saved."); setTimeout(() => setSaveMessage(null), 2000); } catch { setSaveMessage("Failed to save settings."); } finally { setSaving(false); } } async function handleGenerateKey(label: string) { setGenerating(true); try { const resp = await admin.updateSettings({ action: "generate_api_key", label, }); const newKey = resp as unknown as { api_key?: ApiKeyEntry; api_keys?: ApiKeyEntry[]; }; if (newKey.api_keys) { setSettings((prev) => ({ ...prev, api_keys: newKey.api_keys! })); } else if (newKey.api_key) { setSettings((prev) => ({ ...prev, api_keys: [...prev.api_keys, newKey.api_key!], })); } } catch { setSaveMessage("Failed to generate API key."); setTimeout(() => setSaveMessage(null), 2000); } finally { setGenerating(false); } } async function handleRevokeKey(keyId: string) { try { await admin.updateSettings({ action: "revoke_api_key", key_id: keyId, }); setSettings((prev) => ({ ...prev, api_keys: prev.api_keys.filter((k) => k.id !== keyId), })); } catch { setSaveMessage("Failed to revoke API key."); setTimeout(() => setSaveMessage(null), 2000); } } async function handleCreateWebhook(data: WebhookCreate) { setCreatingWebhook(true); try { const created = await webhooks.create(data); setWebhookList((prev) => [...prev, created]); } catch { setSaveMessage("Failed to create webhook."); setTimeout(() => setSaveMessage(null), 2000); } finally { setCreatingWebhook(false); } } async function handleDeleteWebhook(id: string) { try { await webhooks.delete(id); setWebhookList((prev) => prev.filter((w) => w.id !== id)); } catch { setSaveMessage("Failed to delete webhook."); setTimeout(() => setSaveMessage(null), 2000); } } return (
{/* Header */}

Admin

System administration and settings.

{saveMessage && (

{saveMessage}

)}
{/* Loading */} {loading && (

Loading admin data...

)} {/* Error */} {!loading && error && (

{error}

)} {/* Content */} {!loading && !error && (
)}
); }