modified: backend/src/controllers/databaseManagementController.ts

modified:   backend/src/controllers/dynamicApiController.ts
	modified:   backend/src/controllers/endpointController.ts
	new file:   backend/src/migrations/005_add_aql_support.sql
	new file:   backend/src/services/AqlExecutor.ts
	modified:   backend/src/types/index.ts
	modified:   frontend/src/components/EndpointModal.tsx
	modified:   frontend/src/pages/Databases.tsx
	modified:   frontend/src/types/index.ts
This commit is contained in:
GEgorov
2025-10-07 19:33:50 +03:00
parent 7d8fddfe4f
commit 713e9ba7f7
9 changed files with 793 additions and 147 deletions

View File

@@ -223,6 +223,11 @@ function DatabaseModal({
password: database?.password || '',
ssl: database?.ssl || false,
is_active: database?.is_active !== undefined ? database.is_active : true,
// AQL-specific fields
aql_base_url: database?.aql_base_url || '',
aql_auth_type: database?.aql_auth_type || 'basic',
aql_auth_value: database?.aql_auth_value || '',
aql_headers: database?.aql_headers || {},
});
const saveMutation = useMutation({
@@ -265,102 +270,188 @@ function DatabaseModal({
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Тип</label>
<select
value={formData.type}
onChange={(e) => setFormData({ ...formData, type: e.target.value })}
className="input w-full"
>
<option value="postgresql">PostgreSQL</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Порт</label>
<input
type="number"
required
value={formData.port}
onChange={(e) => setFormData({ ...formData, port: parseInt(e.target.value) })}
className="input w-full"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Хост</label>
<input
type="text"
required
value={formData.host}
onChange={(e) => setFormData({ ...formData, host: e.target.value })}
<label className="block text-sm font-medium text-gray-700 mb-1">Тип</label>
<select
value={formData.type}
onChange={(e) => setFormData({ ...formData, type: e.target.value })}
className="input w-full"
placeholder="localhost"
/>
>
<option value="postgresql">PostgreSQL</option>
<option value="aql">AQL (HTTP API)</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Имя базы данных</label>
<input
type="text"
required
value={formData.database_name}
onChange={(e) => setFormData({ ...formData, database_name: e.target.value })}
className="input w-full"
placeholder="my_database"
/>
</div>
{formData.type === 'aql' ? (
<>
{/* AQL Fields */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">AQL Base URL *</label>
<input
type="text"
required
value={formData.aql_base_url}
onChange={(e) => setFormData({ ...formData, aql_base_url: e.target.value })}
className="input w-full"
placeholder="http://api.ehrdb.ncms-i.ru/api/rest/v1"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Пользователь</label>
<input
type="text"
required
value={formData.username}
onChange={(e) => setFormData({ ...formData, username: e.target.value })}
className="input w-full"
placeholder="postgres"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Пароль</label>
<input
type="password"
required={!database}
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
className="input w-full"
placeholder={database ? '••••••••' : 'Введите пароль'}
/>
{database && (
<p className="text-xs text-gray-500 mt-1">Оставьте пустым, чтобы не менять пароль</p>
)}
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Тип аутентификации</label>
<select
value={formData.aql_auth_type}
onChange={(e) => setFormData({ ...formData, aql_auth_type: e.target.value })}
className="input w-full"
>
<option value="basic">Basic Auth</option>
<option value="bearer">Bearer Token</option>
<option value="custom">Custom Header</option>
</select>
</div>
<div className="flex items-center gap-4">
<label className="flex items-center gap-2">
<input
type="checkbox"
checked={formData.ssl}
onChange={(e) => setFormData({ ...formData, ssl: e.target.checked })}
className="rounded"
/>
<span className="text-sm text-gray-700">Использовать SSL</span>
</label>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
{formData.aql_auth_type === 'basic' && 'Basic Auth Value (Base64)'}
{formData.aql_auth_type === 'bearer' && 'Bearer Token'}
{formData.aql_auth_type === 'custom' && 'Custom Authorization Value'}
</label>
<input
type="password"
required={!database}
value={formData.aql_auth_value}
onChange={(e) => setFormData({ ...formData, aql_auth_value: e.target.value })}
className="input w-full"
placeholder={database ? '••••••••' : 'Введите значение'}
/>
{database && (
<p className="text-xs text-gray-500 mt-1">Оставьте пустым, чтобы не менять</p>
)}
</div>
<label className="flex items-center gap-2">
<input
type="checkbox"
checked={formData.is_active}
onChange={(e) => setFormData({ ...formData, is_active: e.target.checked })}
className="rounded"
/>
<span className="text-sm text-gray-700">Активна</span>
</label>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Дополнительные заголовки (JSON)
<span className="text-xs text-gray-500 ml-2">необязательно</span>
</label>
<textarea
value={typeof formData.aql_headers === 'string' ? formData.aql_headers : JSON.stringify(formData.aql_headers, null, 2)}
onChange={(e) => {
try {
const parsed = JSON.parse(e.target.value);
setFormData({ ...formData, aql_headers: parsed });
} catch {
setFormData({ ...formData, aql_headers: e.target.value });
}
}}
className="input w-full font-mono text-sm"
rows={4}
placeholder='{"x-dbrole": "KIS.EMIAS.XАПИД", "Hack-Time": "true"}'
/>
</div>
<div className="flex items-center gap-4">
<label className="flex items-center gap-2">
<input
type="checkbox"
checked={formData.is_active}
onChange={(e) => setFormData({ ...formData, is_active: e.target.checked })}
className="rounded"
/>
<span className="text-sm text-gray-700">Активна</span>
</label>
</div>
</>
) : (
<>
{/* PostgreSQL Fields */}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Хост</label>
<input
type="text"
required
value={formData.host}
onChange={(e) => setFormData({ ...formData, host: e.target.value })}
className="input w-full"
placeholder="localhost"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Порт</label>
<input
type="number"
required
value={formData.port}
onChange={(e) => setFormData({ ...formData, port: parseInt(e.target.value) })}
className="input w-full"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Имя базы данных</label>
<input
type="text"
required
value={formData.database_name}
onChange={(e) => setFormData({ ...formData, database_name: e.target.value })}
className="input w-full"
placeholder="my_database"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Пользователь</label>
<input
type="text"
required
value={formData.username}
onChange={(e) => setFormData({ ...formData, username: e.target.value })}
className="input w-full"
placeholder="postgres"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Пароль</label>
<input
type="password"
required={!database}
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
className="input w-full"
placeholder={database ? '••••••••' : 'Введите пароль'}
/>
{database && (
<p className="text-xs text-gray-500 mt-1">Оставьте пустым, чтобы не менять пароль</p>
)}
</div>
</div>
<div className="flex items-center gap-4">
<label className="flex items-center gap-2">
<input
type="checkbox"
checked={formData.ssl}
onChange={(e) => setFormData({ ...formData, ssl: e.target.checked })}
className="rounded"
/>
<span className="text-sm text-gray-700">Использовать SSL</span>
</label>
<label className="flex items-center gap-2">
<input
type="checkbox"
checked={formData.is_active}
onChange={(e) => setFormData({ ...formData, is_active: e.target.checked })}
className="rounded"
/>
<span className="text-sm text-gray-700">Активна</span>
</label>
</div>
</>
)}
<div className="flex gap-3 pt-4 border-t border-gray-200">
<button type="button" onClick={onClose} className="btn btn-secondary">