feat(MikrotikConnection): add MikroTik connection testing functionality in ServerModal; update API routes and README for new encryption key and connection endpoint
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m13s

This commit is contained in:
2026-02-03 19:12:32 +07:00
parent 0ea0f376a6
commit 1a27217676
7 changed files with 350 additions and 52 deletions
+102 -1
View File
@@ -1,5 +1,6 @@
import { useState, useEffect, useRef } from 'react';
import { IconPlus, IconEdit, IconTrash, IconServer, IconWorld, IconMapPin } from '@tabler/icons-react';
import { IconPlus, IconEdit, IconTrash, IconServer, IconWorld, IconMapPin, IconRouter, IconPlug } from '@tabler/icons-react';
import api from '../../lib/api.js';
import {
makeGateway,
normalizeGateways,
@@ -76,6 +77,8 @@ function ServerModal({ show, mode = 'add', server, onSave, onClose, error: exter
const [localServer, setLocalServer] = useState(getEmptyServer());
const [customProvider, setCustomProvider] = useState('');
const [internalError, setInternalError] = useState('');
const [mikrotikTesting, setMikrotikTesting] = useState(false);
const [mikrotikTestResult, setMikrotikTestResult] = useState(null);
const isEditMode = mode === 'edit';
const error = externalError || internalError;
@@ -119,6 +122,7 @@ function ServerModal({ show, mode = 'add', server, onSave, onClose, error: exter
}
if (!show) {
initializedRef.current = false;
setMikrotikTestResult(null);
}
}, [show, server, isEditMode]);
@@ -560,6 +564,103 @@ function ServerModal({ show, mode = 'add', server, onSave, onClose, error: exter
</div>
</div>
)}
{/* MikroTik — только для jumphost */}
{localServer.type === 'jumphost' && (
<div className="mt-4 pt-3 border-top">
<label className="form-label d-flex align-items-center gap-2">
<IconRouter size={18} />
MikroTik RouterOS API
</label>
<div className="row g-3">
<div className="col-md-4">
<label className="form-label small">Хост</label>
<input
type="text"
className="form-control form-control-sm"
value={localServer.mikrotikHost || ''}
onChange={(e) => handleFieldChange('mikrotikHost', e.target.value)}
placeholder={localServer.ip || 'IP или DNS'}
/>
</div>
<div className="col-md-2">
<label className="form-label small">Порт</label>
<input
type="number"
className="form-control form-control-sm"
value={localServer.mikrotikPort || '8728'}
onChange={(e) => handleFieldChange('mikrotikPort', e.target.value)}
placeholder="8728"
/>
</div>
<div className="col-md-3">
<label className="form-label small">Пользователь</label>
<input
type="text"
className="form-control form-control-sm"
value={localServer.mikrotikUser || 'admin'}
onChange={(e) => handleFieldChange('mikrotikUser', e.target.value)}
placeholder="admin"
/>
</div>
<div className="col-md-3">
<label className="form-label small">Пароль</label>
<input
type="password"
className="form-control form-control-sm"
value={localServer.mikrotikPassword ?? ''}
onChange={(e) => handleFieldChange('mikrotikPassword', e.target.value)}
placeholder={localServer.hasMikrotikPassword ? '••••••' : 'Введите пароль'}
/>
</div>
</div>
<div className="mt-2 d-flex align-items-center gap-2">
<button
type="button"
className="btn btn-outline-primary btn-sm d-flex align-items-center gap-1"
disabled={mikrotikTesting || !(localServer.mikrotikHost || localServer.ip)}
onClick={async () => {
setMikrotikTestResult(null);
setMikrotikTesting(true);
try {
const hasPassword = localServer.mikrotikPassword && String(localServer.mikrotikPassword).trim() !== '';
const payload = hasPassword
? {
host: localServer.mikrotikHost || localServer.ip,
port: localServer.mikrotikPort || 8728,
user: localServer.mikrotikUser || 'admin',
password: localServer.mikrotikPassword,
}
: (localServer.hasMikrotikPassword && (localServer.id || localServer.dns || localServer.ip))
? { serverId: localServer.id || localServer.dns || localServer.ip }
: null;
if (!payload) {
setMikrotikTestResult({ ok: false, message: 'Введите пароль или сохраните сервер с паролем' });
return;
}
const { data } = await api.post('/mikrotik/test-connection', payload);
setMikrotikTestResult(data);
} catch (err) {
setMikrotikTestResult({
ok: false,
message: err.response?.data?.message || err.message || 'Ошибка соединения',
});
} finally {
setMikrotikTesting(false);
}
}}
>
<IconPlug size={14} />
{mikrotikTesting ? 'Проверка...' : 'Проверить соединение'}
</button>
{mikrotikTestResult && (
<span className={mikrotikTestResult.ok ? 'text-success' : 'text-danger'}>
{mikrotikTestResult.ok ? '✓ ' + (mikrotikTestResult.message || 'OK') : '✗ ' + mikrotikTestResult.message}
</span>
)}
</div>
</div>
)}
</div>
<div className="modal-footer">