import { type Component, createSignal, createResource, createEffect, Show, For, } from 'solid-js'; import { Gauge, Save } from 'lucide-solid'; import { adminRateLimitsApi } from '~/api/admin'; import { useI18n } from '~/i18n'; import { useToast } from '~/components/ui/Toast'; import { isApiError } from '~/types'; import type { AdminRateLimit } from '~/types'; import LoadingSpinner from '~/components/ui/LoadingSpinner'; import Button from '~/components/ui/Button'; /** Local mutable copy of a rate-limit row for editing without modifying the resource. */ interface LocalRateLimit { id: string; provider_name: string; max_requests: number; time_window_seconds: number; } /** * Admin page for viewing and updating per-provider API rate-limit configuration. * * Fetched rate limits are copied into local signals so form inputs can mutate * freely. Saving PUTs the updated values to the backend and refetches. */ const RateLimits: Component = () => { const { t } = useI18n(); const { addToast } = useToast(); const [rateLimits, { refetch }] = createResource(() => adminRateLimitsApi.list(), ); const [localLimits, setLocalLimits] = createSignal([]); const [savingId, setSavingId] = createSignal(null); // Sync fetched data to local state createEffect(() => { const data = rateLimits(); if (data) { setLocalLimits( data.map((rl) => ({ id: rl.id, provider_name: rl.provider_name, max_requests: rl.max_requests, time_window_seconds: rl.time_window_seconds, })), ); } }); const updateLocal = ( id: string, field: 'max_requests' | 'time_window_seconds', value: number, ) => { setLocalLimits((prev) => prev.map((rl) => (rl.id === id ? { ...rl, [field]: value } : rl)), ); }; const handleSave = async (limit: LocalRateLimit) => { setSavingId(limit.id); try { await adminRateLimitsApi.update(limit.id, { max_requests: limit.max_requests, time_window_seconds: limit.time_window_seconds, }); addToast({ type: 'success', message: t('admin.rateLimits.saved'), duration: 3000, }); refetch(); } catch (err) { const message = isApiError(err) ? err.message : t('admin.rateLimits.saveError'); addToast({ type: 'error', message, duration: 5000 }); } finally { setSavingId(null); } }; return (

{t('admin.rateLimits.title')}

{t('admin.rateLimits.subtitle')}

{/* Loading state */} {/* Error state */}
{t('admin.rateLimits.loadError')}
{/* Empty state */}

{t('admin.rateLimits.empty')}

{t('admin.rateLimits.emptyHint')}

{/* Rate limit cards */}
{(limit) => (

{limit.provider_name}

updateLocal( limit.id, 'max_requests', parseInt(e.currentTarget.value) || 1, ) } />
updateLocal( limit.id, 'time_window_seconds', parseInt(e.currentTarget.value) || 1, ) } />

{t('admin.rateLimits.effectiveRate', { count: limit.max_requests, seconds: limit.time_window_seconds, })}

)}
); }; export default RateLimits;