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
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m58s
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user