import { useState, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { endpointsApi } from '@/services/api'; import { ImportPreviewResponse } from '@/types'; import { Plus, Search, Edit2, Trash2, Download, Upload } from 'lucide-react'; import toast from 'react-hot-toast'; import ImportEndpointModal from '@/components/ImportEndpointModal'; import Dialog from '@/components/Dialog'; export default function Endpoints() { const queryClient = useQueryClient(); const navigate = useNavigate(); const [search, setSearch] = useState(''); const [showImportModal, setShowImportModal] = useState(false); const [importFile, setImportFile] = useState(null); const [importPreview, setImportPreview] = useState(null); const fileInputRef = useRef(null); const [dialog, setDialog] = useState<{ isOpen: boolean; title: string; message: string; type: 'alert' | 'confirm'; onConfirm?: () => void; }>({ isOpen: false, title: '', message: '', type: 'alert', }); const { data: endpoints, isLoading } = useQuery({ queryKey: ['endpoints', search], queryFn: () => endpointsApi.getAll(search).then(res => res.data), }); const deleteMutation = useMutation({ mutationFn: (id: string) => endpointsApi.delete(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['endpoints'] }); toast.success('Эндпоинт успешно удален'); }, onError: () => toast.error('Не удалось удалить эндпоинт'), }); const handleDelete = (id: string) => { setDialog({ isOpen: true, title: 'Подтверждение', message: 'Вы уверены, что хотите удалить этот эндпоинт?', type: 'confirm', onConfirm: () => { deleteMutation.mutate(id); }, }); }; const handleExport = async (endpointId: string, endpointName: string) => { try { const response = await endpointsApi.exportEndpoint(endpointId); const blob = new Blob([response.data]); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${endpointName.replace(/[^a-zA-Z0-9_\-а-яА-ЯёЁ]/g, '_')}.kabe`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); toast.success('Эндпоинт экспортирован'); } catch { toast.error('Ошибка экспорта эндпоинта'); } }; const handleImportFileSelect = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; if (!file.name.endsWith('.kabe')) { toast.error('Выберите файл с расширением .kabe'); return; } try { const response = await endpointsApi.importPreview(file); setImportFile(file); setImportPreview(response.data); setShowImportModal(true); } catch (error: any) { toast.error(error.response?.data?.error || 'Ошибка чтения файла'); } e.target.value = ''; }; return (

API Эндпоинты

Управление динамическими API эндпоинтами

setSearch(e.target.value)} className="flex-1 outline-none" />
{isLoading ? (

Загрузка эндпоинтов...

) : (
{endpoints?.map((endpoint) => (

{endpoint.name}

{endpoint.method} {endpoint.is_public && ( Публичный )}

{endpoint.description}

{endpoint.path} {endpoint.folder_name && ( 📁 {endpoint.folder_name} )} {endpoint.parameters && endpoint.parameters.length > 0 && (
Параметры: {endpoint.parameters.map((param: any, idx: number) => ( {param.name} ({param.type}){param.required && '*'} ))}
)}
))} {endpoints?.length === 0 && (

Эндпоинты не найдены. Создайте первый эндпоинт!

)}
)} {showImportModal && importPreview && importFile && ( { setShowImportModal(false); setImportFile(null); setImportPreview(null); }} /> )} setDialog({ ...dialog, isOpen: false })} title={dialog.title} message={dialog.message} type={dialog.type} onConfirm={dialog.onConfirm} />
); }