Переработано окно эндпоинта, добавлены элементы дебага, добавлена возможность сохранять и загружать конфигурацию эндпоинта, добавлено отображение ошибок при загрузке конфигурации. Исправлены мелкие баги.
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import { useState } from 'react';
|
||||
import { useState, useRef } 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 { Endpoint, ImportPreviewResponse } from '@/types';
|
||||
import { Plus, Search, Edit2, Trash2, Download, Upload } from 'lucide-react';
|
||||
import toast from 'react-hot-toast';
|
||||
import EndpointModal from '@/components/EndpointModal';
|
||||
import ImportEndpointModal from '@/components/ImportEndpointModal';
|
||||
import Dialog from '@/components/Dialog';
|
||||
|
||||
export default function Endpoints() {
|
||||
@@ -12,6 +13,10 @@ export default function Endpoints() {
|
||||
const [search, setSearch] = useState('');
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [editingEndpoint, setEditingEndpoint] = useState<Endpoint | null>(null);
|
||||
const [showImportModal, setShowImportModal] = useState(false);
|
||||
const [importFile, setImportFile] = useState<File | null>(null);
|
||||
const [importPreview, setImportPreview] = useState<ImportPreviewResponse | null>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [dialog, setDialog] = useState<{
|
||||
isOpen: boolean;
|
||||
title: string;
|
||||
@@ -66,6 +71,42 @@ export default function Endpoints() {
|
||||
setShowModal(true);
|
||||
};
|
||||
|
||||
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<HTMLInputElement>) => {
|
||||
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 (
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
@@ -73,10 +114,26 @@ export default function Endpoints() {
|
||||
<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 className="flex gap-2">
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept=".kabe"
|
||||
className="hidden"
|
||||
onChange={handleImportFileSelect}
|
||||
/>
|
||||
<button
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="btn btn-secondary flex items-center gap-2"
|
||||
>
|
||||
<Upload size={20} />
|
||||
Импорт
|
||||
</button>
|
||||
<button onClick={handleCreate} className="btn btn-primary flex items-center gap-2">
|
||||
<Plus size={20} />
|
||||
Новый эндпоинт
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card p-4 mb-6">
|
||||
@@ -138,6 +195,13 @@ export default function Endpoints() {
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => handleExport(endpoint.id, endpoint.name)}
|
||||
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
title="Экспорт"
|
||||
>
|
||||
<Download size={18} className="text-gray-600" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleEdit(endpoint)}
|
||||
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
@@ -173,6 +237,18 @@ export default function Endpoints() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{showImportModal && importPreview && importFile && (
|
||||
<ImportEndpointModal
|
||||
preview={importPreview}
|
||||
file={importFile}
|
||||
onClose={() => {
|
||||
setShowImportModal(false);
|
||||
setImportFile(null);
|
||||
setImportPreview(null);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Dialog
|
||||
isOpen={dialog.isOpen}
|
||||
onClose={() => setDialog({ ...dialog, isOpen: false })}
|
||||
|
||||
Reference in New Issue
Block a user