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 */}
);
}
// ---------------------------------------------------------------------------
// 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 (
{keys.length === 0 ? (
No API keys generated yet.
) : (
{keys.map((key) => (
-
{key.label}
{key.prefix}... · Created{" "}
{new Date(key.created_at).toLocaleDateString()}
))}
)}
);
}
// ---------------------------------------------------------------------------
// 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 && (
)}
{webhookList.length === 0 && !showForm ? (
No webhooks configured.
) : (
{webhookList.map((wh) => (
-
))}
)}
);
}
// ---------------------------------------------------------------------------
// 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 && (
)}
{/* Content */}
{!loading && !error && (
)}
);
}