modified: frontend/src/pages/Databases.tsx
modified: frontend/src/pages/Settings.tsx
This commit is contained in:
@@ -274,12 +274,17 @@ function DatabaseModal({
|
|||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Тип</label>
|
<label className="block text-sm font-medium text-gray-700 mb-1">Тип</label>
|
||||||
<select
|
<select
|
||||||
value={formData.type}
|
value={formData.type}
|
||||||
onChange={(e) => setFormData({ ...formData, type: e.target.value })}
|
onChange={(e) => {
|
||||||
|
console.log('🔄 Database type changed to:', e.target.value);
|
||||||
|
setFormData({ ...formData, type: e.target.value });
|
||||||
|
}}
|
||||||
className="input w-full"
|
className="input w-full"
|
||||||
>
|
>
|
||||||
<option value="postgresql">PostgreSQL</option>
|
<option value="postgresql">PostgreSQL</option>
|
||||||
<option value="aql">AQL (HTTP API)</option>
|
<option value="aql">AQL (HTTP API)</option>
|
||||||
</select>
|
</select>
|
||||||
|
{/* DEBUG: AQL option should be visible above */}
|
||||||
|
<div className="text-xs text-gray-500 mt-1">Available types: PostgreSQL, AQL (HTTP API)</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{formData.type === 'aql' ? (
|
{formData.type === 'aql' ? (
|
||||||
|
|||||||
@@ -351,10 +351,20 @@ function DatabasesSubTab() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-x-4 gap-y-1 text-sm text-gray-600 ml-8">
|
<div className="grid grid-cols-2 gap-x-4 gap-y-1 text-sm text-gray-600 ml-8">
|
||||||
<div>Хост: <span className="font-medium text-gray-900">{db.host}:{db.port}</span></div>
|
{db.type === 'aql' ? (
|
||||||
<div>База: <span className="font-medium text-gray-900">{db.database_name}</span></div>
|
<>
|
||||||
<div>Пользователь: <span className="font-medium text-gray-900">{db.username}</span></div>
|
<div>Base URL: <span className="font-medium text-gray-900">{db.aql_base_url}</span></div>
|
||||||
<div>Пароль: <span className="font-medium text-gray-900">••••••••</span></div>
|
<div>Auth Type: <span className="font-medium text-gray-900">{db.aql_auth_type}</span></div>
|
||||||
|
<div>Auth: <span className="font-medium text-gray-900">••••••••</span></div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div>Хост: <span className="font-medium text-gray-900">{db.host}:{db.port}</span></div>
|
||||||
|
<div>База: <span className="font-medium text-gray-900">{db.database_name}</span></div>
|
||||||
|
<div>Пользователь: <span className="font-medium text-gray-900">{db.username}</span></div>
|
||||||
|
<div>Пароль: <span className="font-medium text-gray-900">••••••••</span></div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
@@ -430,6 +440,11 @@ function DatabaseModal({
|
|||||||
password: '',
|
password: '',
|
||||||
ssl: database?.ssl || false,
|
ssl: database?.ssl || false,
|
||||||
is_active: database?.is_active !== undefined ? database.is_active : true,
|
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({
|
const saveMutation = useMutation({
|
||||||
@@ -439,6 +454,10 @@ function DatabaseModal({
|
|||||||
if (database && !payload.password) {
|
if (database && !payload.password) {
|
||||||
delete payload.password;
|
delete payload.password;
|
||||||
}
|
}
|
||||||
|
// Для AQL: если редактируем и auth_value пустой, удаляем его
|
||||||
|
if (database && data.type === 'aql' && !payload.aql_auth_value) {
|
||||||
|
delete payload.aql_auth_value;
|
||||||
|
}
|
||||||
return database ? dbManagementApi.update(database.id, payload) : dbManagementApi.create(payload);
|
return database ? dbManagementApi.update(database.id, payload) : dbManagementApi.create(payload);
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@@ -478,111 +497,197 @@ function DatabaseModal({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Хост</label>
|
<label className="block text-sm font-medium text-gray-700 mb-1">Тип</label>
|
||||||
<input
|
<select
|
||||||
type="text"
|
value={formData.type}
|
||||||
required
|
onChange={(e) => setFormData({ ...formData, type: e.target.value })}
|
||||||
value={formData.host}
|
|
||||||
onChange={(e) => setFormData({ ...formData, host: e.target.value })}
|
|
||||||
className="input w-full"
|
className="input w-full"
|
||||||
placeholder="localhost"
|
>
|
||||||
/>
|
<option value="postgresql">PostgreSQL</option>
|
||||||
|
<option value="aql">AQL (HTTP API)</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
{formData.type === 'aql' ? (
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Имя базы данных</label>
|
<>
|
||||||
<input
|
{/* AQL Fields */}
|
||||||
type="text"
|
<div>
|
||||||
required
|
<label className="block text-sm font-medium text-gray-700 mb-1">AQL Base URL *</label>
|
||||||
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>
|
|
||||||
<div className="relative">
|
|
||||||
<input
|
<input
|
||||||
type={showPassword ? 'text' : 'password'}
|
type="text"
|
||||||
required={!database}
|
required
|
||||||
value={formData.password}
|
value={formData.aql_base_url}
|
||||||
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, aql_base_url: e.target.value })}
|
||||||
className="input w-full pr-10"
|
className="input w-full"
|
||||||
placeholder={database ? 'Оставьте пустым, чтобы не менять' : 'Введите пароль'}
|
placeholder="http://api.ehrdb.ncms-i.ru/api/rest/v1"
|
||||||
/>
|
/>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
|
||||||
className="absolute right-2 top-1/2 -translate-y-1/2 p-1 hover:bg-gray-100 rounded"
|
|
||||||
>
|
|
||||||
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
{database && (
|
|
||||||
<p className="text-xs text-gray-500 mt-1">Оставьте пустым, чтобы не менять пароль</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<div>
|
||||||
<label className="flex items-center gap-2">
|
<label className="block text-sm font-medium text-gray-700 mb-1">Тип аутентификации</label>
|
||||||
<input
|
<select
|
||||||
type="checkbox"
|
value={formData.aql_auth_type}
|
||||||
checked={formData.ssl}
|
onChange={(e) => setFormData({ ...formData, aql_auth_type: e.target.value })}
|
||||||
onChange={(e) => setFormData({ ...formData, ssl: e.target.checked })}
|
className="input w-full"
|
||||||
className="rounded"
|
>
|
||||||
/>
|
<option value="basic">Basic Auth</option>
|
||||||
<span className="text-sm text-gray-700">Использовать SSL</span>
|
<option value="bearer">Bearer Token</option>
|
||||||
</label>
|
<option value="custom">Custom Header</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<label className="flex items-center gap-2">
|
<div>
|
||||||
<input
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
type="checkbox"
|
{formData.aql_auth_type === 'basic' && 'Basic Auth Value (Base64)'}
|
||||||
checked={formData.is_active}
|
{formData.aql_auth_type === 'bearer' && 'Bearer Token'}
|
||||||
onChange={(e) => setFormData({ ...formData, is_active: e.target.checked })}
|
{formData.aql_auth_type === 'custom' && 'Custom Authorization Value'}
|
||||||
className="rounded"
|
</label>
|
||||||
/>
|
<input
|
||||||
<span className="text-sm text-gray-700">Активна</span>
|
type="password"
|
||||||
</label>
|
required={!database}
|
||||||
</div>
|
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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
type={showPassword ? 'text' : 'password'}
|
||||||
|
required={!database}
|
||||||
|
value={formData.password}
|
||||||
|
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
|
||||||
|
className="input w-full pr-10"
|
||||||
|
placeholder={database ? 'Оставьте пустым, чтобы не менять' : 'Введите пароль'}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
|
className="absolute right-2 top-1/2 -translate-y-1/2 p-1 hover:bg-gray-100 rounded"
|
||||||
|
>
|
||||||
|
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{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">
|
<div className="flex gap-3 pt-4 border-t border-gray-200">
|
||||||
<button type="button" onClick={onClose} className="btn btn-secondary">
|
<button type="button" onClick={onClose} className="btn btn-secondary">
|
||||||
|
|||||||
Reference in New Issue
Block a user