Files
api_builder/frontend/src/pages/Endpoints.tsx
GEgorov 8943f5a070 new file: .claude/settings.local.json
new file:   .gitignore
	new file:   backend/.env.example
	new file:   backend/.gitignore
	new file:   backend/ecosystem.config.js
	new file:   backend/nodemon.json
	new file:   backend/package-lock.json
	new file:   backend/package.json
	new file:   backend/src/config/database.ts
	new file:   backend/src/config/dynamicSwagger.ts
	new file:   backend/src/config/environment.ts
	new file:   backend/src/config/swagger.ts
	new file:   backend/src/controllers/apiKeyController.ts
	new file:   backend/src/controllers/authController.ts
	new file:   backend/src/controllers/databaseController.ts
	new file:   backend/src/controllers/databaseManagementController.ts
	new file:   backend/src/controllers/dynamicApiController.ts
	new file:   backend/src/controllers/endpointController.ts
	new file:   backend/src/controllers/folderController.ts
	new file:   backend/src/controllers/logsController.ts
	new file:   backend/src/controllers/userController.ts
	new file:   backend/src/middleware/apiKey.ts
	new file:   backend/src/middleware/auth.ts
	new file:   backend/src/middleware/logging.ts
	new file:   backend/src/migrations/001_initial_schema.sql
	new file:   backend/src/migrations/002_add_logging.sql
	new file:   backend/src/migrations/003_add_scripting.sql
	new file:   backend/src/migrations/004_add_superadmin.sql
	new file:   backend/src/migrations/run.ts
	new file:   backend/src/migrations/seed.ts
	new file:   backend/src/routes/apiKeys.ts
	new file:   backend/src/routes/auth.ts
	new file:   backend/src/routes/databaseManagement.ts
	new file:   backend/src/routes/databases.ts
	new file:   backend/src/routes/dynamic.ts
	new file:   backend/src/routes/endpoints.ts
	new file:   backend/src/routes/folders.ts
	new file:   backend/src/routes/logs.ts
	new file:   backend/src/routes/users.ts
	new file:   backend/src/server.ts
	new file:   backend/src/services/DatabasePoolManager.ts
	new file:   backend/src/services/ScriptExecutor.ts
	new file:   backend/src/services/SqlExecutor.ts
	new file:   backend/src/types/index.ts
	new file:   backend/tsconfig.json
	new file:   frontend/.gitignore
	new file:   frontend/index.html
	new file:   frontend/nginx.conf
	new file:   frontend/package-lock.json
	new file:   frontend/package.json
	new file:   frontend/postcss.config.js
	new file:   frontend/src/App.tsx
	new file:   frontend/src/components/CodeEditor.tsx
2025-10-07 00:04:04 +03:00

187 lines
6.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { endpointsApi, databasesApi } from '@/services/api';
import { Endpoint } from '@/types';
import { Plus, Search, Edit2, Trash2 } from 'lucide-react';
import toast from 'react-hot-toast';
import EndpointModal from '@/components/EndpointModal';
import Dialog from '@/components/Dialog';
export default function Endpoints() {
const queryClient = useQueryClient();
const [search, setSearch] = useState('');
const [showModal, setShowModal] = useState(false);
const [editingEndpoint, setEditingEndpoint] = useState<Endpoint | null>(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 { data: databases } = useQuery({
queryKey: ['databases'],
queryFn: () => databasesApi.getAll().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 handleEdit = (endpoint: Endpoint) => {
setEditingEndpoint(endpoint);
setShowModal(true);
};
const handleCreate = () => {
setEditingEndpoint(null);
setShowModal(true);
};
return (
<div>
<div className="flex items-center justify-between mb-6">
<div>
<h1 className="text-3xl font-bold text-gray-900 mb-2">API Эндпоинты</h1>
<p className="text-gray-600">Управление динамическими API эндпоинтами</p>
</div>
<button onClick={handleCreate} className="btn btn-primary flex items-center gap-2">
<Plus size={20} />
Новый эндпоинт
</button>
</div>
<div className="card p-4 mb-6">
<div className="flex items-center gap-3">
<Search size={20} className="text-gray-400" />
<input
type="text"
placeholder="Поиск эндпоинтов по имени, пути или SQL запросу..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="flex-1 outline-none"
/>
</div>
</div>
{isLoading ? (
<div className="text-center py-12">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600 mx-auto"></div>
<p className="mt-4 text-gray-600">Загрузка эндпоинтов...</p>
</div>
) : (
<div className="space-y-4">
{endpoints?.map((endpoint) => (
<div key={endpoint.id} className="card p-6 hover:shadow-md transition-shadow">
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
<h3 className="text-xl font-semibold text-gray-900">{endpoint.name}</h3>
<span className={`px-3 py-1 text-xs font-semibold rounded ${
endpoint.method === 'GET' ? 'bg-green-100 text-green-700' :
endpoint.method === 'POST' ? 'bg-blue-100 text-blue-700' :
endpoint.method === 'PUT' ? 'bg-yellow-100 text-yellow-700' :
'bg-red-100 text-red-700'
}`}>
{endpoint.method}
</span>
{endpoint.is_public && (
<span className="px-3 py-1 text-xs font-semibold rounded bg-purple-100 text-purple-700">
Публичный
</span>
)}
</div>
<p className="text-gray-600 mb-2">{endpoint.description}</p>
<code className="text-sm bg-gray-100 px-3 py-1 rounded text-gray-800">
{endpoint.path}
</code>
{endpoint.folder_name && (
<span className="ml-2 text-sm text-gray-500">📁 {endpoint.folder_name}</span>
)}
{endpoint.parameters && endpoint.parameters.length > 0 && (
<div className="mt-2">
<span className="text-xs text-gray-500">Параметры: </span>
{endpoint.parameters.map((param: any, idx: number) => (
<span key={idx} className="inline-block text-xs bg-gray-200 text-gray-700 px-2 py-1 rounded mr-1 mb-1">
{param.name} ({param.type}){param.required && '*'}
</span>
))}
</div>
)}
</div>
<div className="flex gap-2">
<button
onClick={() => handleEdit(endpoint)}
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
title="Редактировать"
>
<Edit2 size={18} className="text-gray-600" />
</button>
<button
onClick={() => handleDelete(endpoint.id)}
className="p-2 hover:bg-red-50 rounded-lg transition-colors"
title="Удалить"
>
<Trash2 size={18} className="text-red-600" />
</button>
</div>
</div>
</div>
))}
{endpoints?.length === 0 && (
<div className="text-center py-12">
<p className="text-gray-500">Эндпоинты не найдены. Создайте первый эндпоинт!</p>
</div>
)}
</div>
)}
{showModal && (
<EndpointModal
endpoint={editingEndpoint}
databases={databases || []}
onClose={() => setShowModal(false)}
/>
)}
<Dialog
isOpen={dialog.isOpen}
onClose={() => setDialog({ ...dialog, isOpen: false })}
title={dialog.title}
message={dialog.message}
type={dialog.type}
onConfirm={dialog.onConfirm}
/>
</div>
);
}