feat(SettingsModal): enhance server selection for ping services with autocomplete input and load server list from API
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m58s

This commit is contained in:
2026-02-16 23:40:33 +07:00
parent 838ca6b220
commit ee357ed59a
+32 -16
View File
@@ -1,8 +1,9 @@
import FormField from './FormField'; import FormField from './FormField';
import { useEffect, useState } from 'react'; import { useEffect, useState, useMemo } from 'react';
import api from '../lib/api.js'; import api from '../lib/api.js';
import { IconSettings, IconDeviceFloppy } from '@tabler/icons-react'; import { IconSettings, IconDeviceFloppy } from '@tabler/icons-react';
import ErrorAlert from './ErrorAlert'; import ErrorAlert from './ErrorAlert';
import ServerAutocompleteInput from './ServerAutocompleteInput.jsx';
/** /**
* SettingsModal - модальное окно настроек интерфейса * SettingsModal - модальное окно настроек интерфейса
@@ -25,6 +26,13 @@ export default function SettingsModal({ open, onClose }) {
const [pingServicesSource, setPingServicesSource] = useState('web'); const [pingServicesSource, setPingServicesSource] = useState('web');
const [pingServicesServerId, setPingServicesServerId] = useState(''); const [pingServicesServerId, setPingServicesServerId] = useState('');
const [pingServicesGatewayIp, setPingServicesGatewayIp] = useState(''); const [pingServicesGatewayIp, setPingServicesGatewayIp] = useState('');
const [serversList, setServersList] = useState([]);
const routerServersForPing = useMemo(() => {
return (serversList || []).filter(
(s) => s && (String(s.type || '').toLowerCase() === 'jumphost' || String(s.type || '').toLowerCase() === 'home')
);
}, [serversList]);
useEffect(() => { useEffect(() => {
if (!open) return; if (!open) return;
@@ -34,8 +42,11 @@ export default function SettingsModal({ open, onClose }) {
(async () => { (async () => {
try { try {
const res = await api.get('/ui-settings'); const [settingsRes, serversRes] = await Promise.all([
const data = res?.data || {}; api.get('/ui-settings'),
api.get('/servers').catch(() => ({ data: [] })),
]);
const data = settingsRes?.data || {};
setRawSettings(data); setRawSettings(data);
setDohServer(String(data?.dohServer || '')); setDohServer(String(data?.dohServer || ''));
setWsUrl(String(data?.wsUpdateUrl || '')); setWsUrl(String(data?.wsUpdateUrl || ''));
@@ -47,8 +58,9 @@ export default function SettingsModal({ open, onClose }) {
setPingServicesSource(String(data?.pingServicesSource || 'web').toLowerCase() === 'router' ? 'router' : 'web'); setPingServicesSource(String(data?.pingServicesSource || 'web').toLowerCase() === 'router' ? 'router' : 'web');
setPingServicesServerId(String(data?.pingServicesServerId || '').trim()); setPingServicesServerId(String(data?.pingServicesServerId || '').trim());
setPingServicesGatewayIp(String(data?.pingServicesGatewayIp || '').trim()); setPingServicesGatewayIp(String(data?.pingServicesGatewayIp || '').trim());
const e = res?.headers?.etag || res?.headers?.ETag || ''; const e = settingsRes?.headers?.etag || settingsRes?.headers?.ETag || '';
setEtag(e ? String(e) : ''); setEtag(e ? String(e) : '');
setServersList(Array.isArray(serversRes?.data) ? serversRes.data : []);
} catch (e) { } catch (e) {
setError('Не удалось загрузить настройки'); setError('Не удалось загрузить настройки');
} finally { } finally {
@@ -256,26 +268,30 @@ export default function SettingsModal({ open, onClose }) {
{pingServicesSource === 'router' && ( {pingServicesSource === 'router' && (
<div className="row g-2"> <div className="row g-2">
<div className="col-md-6"> <div className="col-md-6">
<FormField <label className="form-label small">Домашний роутер</label>
label="Роутер (jumphost)" <div className={loading || saving ? 'opacity-75 pe-none' : ''}>
name="pingServicesServerId" <ServerAutocompleteInput
type="text" value={pingServicesServerId}
value={pingServicesServerId} onChange={(v) => setPingServicesServerId(String(v || '').trim())}
onChange={setPingServicesServerId} servers={routerServersForPing}
placeholder="ID, DNS или IP jumphost" placeholder="Выберите роутер (jumphost или входной)"
helpText="Оставьте пустым — будет использован первый jumphost из списка серверов." className="form-control form-control-sm"
disabled={loading || saving} maxSuggestions={10}
/> />
</div>
<div className="form-text small">
Выбор роутера с MikroTik API для пинга с главной. Пусто — первый jumphost из списка.
</div>
</div> </div>
<div className="col-md-6"> <div className="col-md-6">
<FormField <FormField
label="IP шлюза (опционально)" label="IP шлюза (не обязательно)"
name="pingServicesGatewayIp" name="pingServicesGatewayIp"
type="text" type="text"
value={pingServicesGatewayIp} value={pingServicesGatewayIp}
onChange={setPingServicesGatewayIp} onChange={setPingServicesGatewayIp}
placeholder="IP шлюза" placeholder="IP шлюза"
helpText="Оставьте пустым — берётся первый шлюз роутера." helpText="Не обязательно. Пусто — используется первый шлюз выбранного роутера."
disabled={loading || saving} disabled={loading || saving}
/> />
</div> </div>