Files
router-lists-ui/frontend/src/components/SettingsModal.jsx
T

191 lines
6.2 KiB
React

import FormField from './FormField';
import { useEffect, useState } from 'react';
import api from '../lib/api.js';
import { IconSettings, IconDeviceFloppy } from '@tabler/icons-react';
import ErrorAlert from './ErrorAlert';
/**
* SettingsModal - модальное окно настроек интерфейса
* Простой подход со встроенным backdrop (как в HistoryModal)
*/
export default function SettingsModal({ open, onClose }) {
const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false);
const [error, setError] = useState('');
const [success, setSuccess] = useState('');
const [etag, setEtag] = useState('');
const [dohServer, setDohServer] = useState('');
const [wsUrl, setWsUrl] = useState('');
useEffect(() => {
if (!open) return;
setError('');
setSuccess('');
setLoading(true);
(async () => {
try {
const res = await api.get('/ui-settings');
const data = res?.data || {};
setDohServer(String(data?.dohServer || ''));
setWsUrl(String(data?.wsUpdateUrl || ''));
const e = res?.headers?.etag || res?.headers?.ETag || '';
setEtag(e ? String(e) : '');
} catch (e) {
setError('Не удалось загрузить настройки');
} finally {
setLoading(false);
}
})();
}, [open]);
if (!open) return null;
const validateDoh = (value) => {
if (!value) return { valid: true, message: '' };
try {
const u = new URL(String(value));
return u.protocol === 'https:'
? { valid: true, message: 'Корректный HTTPS URL' }
: { valid: false, message: 'Используйте HTTPS' };
} catch {
return { valid: false, message: 'Некорректный URL' };
}
};
const validateWs = (value) => {
if (!value) return { valid: true, message: '' };
try {
const u = new URL(String(value));
return u.protocol === 'ws:' || u.protocol === 'wss:'
? { valid: true, message: 'Корректный WebSocket URL' }
: { valid: false, message: 'Используйте ws:// или wss://' };
} catch {
return { valid: false, message: 'Некорректный URL' };
}
};
const onSave = async () => {
setError('');
setSuccess('');
const dohValidation = validateDoh(dohServer);
const wsValidation = validateWs(wsUrl);
if (!dohValidation.valid) {
setError('Укажите корректный HTTPS URL для DoH');
return;
}
if (!wsValidation.valid) {
setError('Укажите корректный WebSocket URL (ws:// или wss://)');
return;
}
setSaving(true);
try {
const payload = {
settings: {
dohServer: String(dohServer || '').trim(),
wsUpdateUrl: String(wsUrl || '').trim()
},
etag
};
const res = await api.post('/ui-settings', payload);
const meta = res?.data || {};
setSuccess('Настройки успешно сохранены');
setEtag(String(meta?.etag || ''));
setTimeout(() => setSuccess(''), 3000);
} catch (e) {
setError(e?.response?.data?.message || 'Ошибка при сохранении настроек');
} finally {
setSaving(false);
}
};
return (
<div
className="modal show d-block"
role="dialog"
aria-modal="true"
style={{ backgroundColor: 'rgba(0,0,0,0.5)' }}
onKeyDown={(e) => { if (e.key === 'Escape') onClose?.(); }}
>
<div className="modal-dialog modal-dialog-centered" role="document">
<div className="modal-content" tabIndex={-1}>
<div className="modal-header">
<h5 className="modal-title d-flex align-items-center">
<IconSettings className="me-2" size={24} />
Настройки интерфейса
</h5>
<button type="button" className="btn-close" onClick={onClose} />
</div>
<div className="modal-body">
{error && <ErrorAlert message={error} onClose={() => setError('')} />}
{success && (
<div className="alert alert-success alert-dismissible" role="alert">
<div className="d-flex">
<div className="flex-grow-1">{success}</div>
</div>
<button
type="button"
className="btn-close"
onClick={() => setSuccess('')}
/>
</div>
)}
<FormField
label="WebSocket URL (BGP Live)"
name="wsUrl"
type="text"
value={wsUrl}
onChange={setWsUrl}
onValidate={validateWs}
placeholder="ws://host:port/ws/update_bgp?api_key=..."
helpText="URL для Live-обновления BGP (ws:// или wss://). Можно оставить пустым."
disabled={loading || saving}
autoFocus
/>
<FormField
label="DoH сервер"
name="dohServer"
type="text"
value={dohServer}
onChange={setDohServer}
onValidate={validateDoh}
placeholder="https://dns.google/dns-query"
helpText="HTTPS URL для DNS-over-HTTPS"
disabled={loading || saving}
/>
</div>
<div className="modal-footer">
<button
type="button"
className="btn btn-secondary"
onClick={onClose}
disabled={saving}
>
Закрыть
</button>
<button
type="button"
className="btn btn-primary"
onClick={onSave}
disabled={saving || loading}
>
{saving && <span className="spinner-border spinner-border-sm me-2" />}
<IconDeviceFloppy size={16} className="me-1" />
Сохранить
</button>
</div>
</div>
</div>
</div>
);
}