import { useState, useMemo } from 'react' import { Pencil, Plus, Search, Trash2 } from 'lucide-react' import { useTranslation } from 'react-i18next' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { safeJsonParseWithValidation } from '../utils/json-parser' import { isObjectRecord } from '../utils/json-validators' import { RateLimitDialog, type RateLimitEntryData } from './rate-limit-dialog' type RateLimitVisualEditorProps = { value: string onChange: (value: string) => void } type RateLimitEntry = RateLimitEntryData export function RateLimitVisualEditor({ value, onChange, }: RateLimitVisualEditorProps) { const { t } = useTranslation() const [searchText, setSearchText] = useState('') const [dialogOpen, setDialogOpen] = useState(false) const [editData, setEditData] = useState(null) const rateLimits = useMemo(() => { if (!value || value.trim() === '') return [] const parsed = safeJsonParseWithValidation>(value, { fallback: {}, validator: isObjectRecord, validatorMessage: 'Rate limits must be a JSON object', context: 'rate limits', }) return Object.entries(parsed) .map(([groupName, limits]) => { if ( Array.isArray(limits) && limits.length === 2 && typeof limits[0] === 'number' && typeof limits[1] === 'number' ) { return { groupName, maxRequests: limits[0], maxSuccess: limits[1], } } return null }) .filter((item): item is RateLimitEntry => item !== null) }, [value]) const filteredRateLimits = useMemo(() => { if (!searchText) return rateLimits const lowerSearch = searchText.toLowerCase() return rateLimits.filter((limit) => limit.groupName.toLowerCase().includes(lowerSearch) ) }, [rateLimits, searchText]) const handleSave = (data: RateLimitEntryData) => { const parsed = safeJsonParseWithValidation>(value, { fallback: {}, validator: isObjectRecord, silent: true, }) if (editData && editData.groupName !== data.groupName) { delete parsed[editData.groupName] } parsed[data.groupName] = [data.maxRequests, data.maxSuccess] onChange(JSON.stringify(parsed, null, 2)) } const handleDelete = (groupName: string) => { const parsed = safeJsonParseWithValidation>(value, { fallback: {}, validator: isObjectRecord, silent: true, }) delete parsed[groupName] onChange(JSON.stringify(parsed, null, 2)) } const handleEdit = (limit: RateLimitEntry) => { setEditData(limit) setDialogOpen(true) } const handleAdd = () => { setEditData(null) setDialogOpen(true) } return (
setSearchText(e.target.value)} className='pl-9' />
{filteredRateLimits.length === 0 ? (
{searchText ? t('No groups match your search') : t( 'No group-based rate limits configured. Click "Add group" to get started.' )}
) : (
{t('Group Name')} {t('Max Requests (incl. failures)')} {t('Max Success')} {t('Actions')} {filteredRateLimits.map((limit) => ( {limit.groupName} {limit.maxRequests === 0 ? t('Unlimited') : limit.maxRequests.toLocaleString()} {limit.maxSuccess.toLocaleString()}
))}
)}
) }