feat(NetworkConfigManager, SettingsModal): add global PTR zone settings management, allowing users to define DNS replacements in both global and interface-specific contexts for enhanced configuration flexibility
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m47s

This commit is contained in:
2026-01-25 21:35:33 +07:00
parent f4f79a8f01
commit ac34faaf48
2 changed files with 92 additions and 10 deletions
+55 -9
View File
@@ -232,13 +232,30 @@ function NetworkConfigManager() {
const [mikrotikCodeModalOpen, setMikrotikCodeModalOpen] = useState(false);
const [generatedMikrotikCode, setGeneratedMikrotikCode] = useState([]);
// === Глобальные настройки PTR зоны ===
const [globalPtrZoneReplaceFrom, setGlobalPtrZoneReplaceFrom] = useState('');
const [globalPtrZoneReplaceTo, setGlobalPtrZoneReplaceTo] = useState('');
// === Загрузка данных ===
useEffect(() => {
fetchConfig();
fetchServers();
fetchIpsecPasswords();
fetchUiSettings();
}, []);
const fetchUiSettings = async () => {
try {
const response = await api.get('/ui-settings');
const data = response.data || {};
setGlobalPtrZoneReplaceFrom(String(data?.ptrZoneReplaceFrom || ''));
setGlobalPtrZoneReplaceTo(String(data?.ptrZoneReplaceTo || ''));
} catch (error) {
// Мягко игнорируем ошибку, используем значения по умолчанию
console.warn('Не удалось загрузить глобальные настройки PTR зоны:', error);
}
};
const fetchConfig = async () => {
setLoading(true);
try {
@@ -1494,14 +1511,20 @@ function NetworkConfigManager() {
};
// Функция для генерации PTR зоны с учетом замены
// Приоритет: настройки интерфейса > глобальные настройки
const generatePtrZone = (serverDns, iface) => {
if (!serverDns) return '—';
// Если указана замена PTR зоны, применяем её
// Сначала проверяем настройки интерфейса
if (iface?.ptrZoneReplaceFrom && iface?.ptrZoneReplaceTo) {
return serverDns.replace(iface.ptrZoneReplaceFrom, iface.ptrZoneReplaceTo);
}
// Если настройки интерфейса не заданы, используем глобальные настройки
if (globalPtrZoneReplaceFrom && globalPtrZoneReplaceTo) {
return serverDns.replace(globalPtrZoneReplaceFrom, globalPtrZoneReplaceTo);
}
return serverDns;
};
@@ -1560,7 +1583,7 @@ function NetworkConfigManager() {
}
return a.type.localeCompare(b.type);
});
}, [config.tunnelInterfaces, servers]);
}, [config.tunnelInterfaces, servers, globalPtrZoneReplaceFrom, globalPtrZoneReplaceTo]);
// === Фильтрация реестра IP ===
const filteredIpRegistry = useMemo(() => {
@@ -4254,10 +4277,24 @@ function NetworkConfigManager() {
) : null;
})()}
{/* Поля для замены PTR зоны */}
{/* Поля для замены PTR зоны (опционально, переопределяют глобальные настройки) */}
<div className="col-12">
<hr />
<h6 className="mb-3">Настройка PTR зоны</h6>
<h6 className="mb-3">Настройка PTR зоны (опционально)</h6>
<div className="alert alert-info alert-dismissible mb-3" role="alert">
<div className="small">
{globalPtrZoneReplaceFrom && globalPtrZoneReplaceTo ? (
<>
<strong>Глобальные настройки:</strong> "{globalPtrZoneReplaceFrom}" "{globalPtrZoneReplaceTo}"<br />
Оставьте поля пустыми, чтобы использовать глобальные настройки, или укажите свои значения для переопределения.
</>
) : (
<>
Глобальные настройки PTR зоны не заданы. Укажите значения здесь или в разделе "Настройки интерфейса".
</>
)}
</div>
</div>
<div className="row g-3">
<div className="col-md-6">
<FormField
@@ -4265,10 +4302,10 @@ function NetworkConfigManager() {
name="ptrZoneReplaceFrom"
value={editingInterface.ptrZoneReplaceFrom || ''}
onChange={(val) => setEditingInterface({ ...editingInterface, ptrZoneReplaceFrom: val })}
placeholder="rt.shx"
placeholder={globalPtrZoneReplaceFrom || "rt.shx"}
/>
<div className="form-text">
Часть DNS домена, которую нужно заменить (например, "rt.shx")
Часть DNS домена, которую нужно заменить (например, "rt.shx"). Оставьте пустым для использования глобальных настроек.
</div>
</div>
<div className="col-md-6">
@@ -4277,13 +4314,13 @@ function NetworkConfigManager() {
name="ptrZoneReplaceTo"
value={editingInterface.ptrZoneReplaceTo || ''}
onChange={(val) => setEditingInterface({ ...editingInterface, ptrZoneReplaceTo: val })}
placeholder="shrt"
placeholder={globalPtrZoneReplaceTo || "shrt"}
/>
<div className="form-text">
На что заменить (например, "shrt"). Результат: DNS "selectel.msk.rt.shx.su" PTR "selectel.msk.shrt.su"
На что заменить (например, "shrt"). Оставьте пустым для использования глобальных настроек.
</div>
</div>
{editingInterface.serverId && editingInterface.ptrZoneReplaceFrom && editingInterface.ptrZoneReplaceTo && (() => {
{editingInterface.serverId && (editingInterface.ptrZoneReplaceFrom && editingInterface.ptrZoneReplaceTo || globalPtrZoneReplaceFrom && globalPtrZoneReplaceTo) && (() => {
const server = getServerInfo(editingInterface.serverId);
const originalDns = server?.dns || '';
const ptrZone = generatePtrZone(originalDns, editingInterface);
@@ -4298,6 +4335,15 @@ function NetworkConfigManager() {
<div>
<strong>PTR зона:</strong> <code className="text-info">{ptrZone}</code>
</div>
{editingInterface.ptrZoneReplaceFrom && editingInterface.ptrZoneReplaceTo ? (
<div className="text-muted mt-1" style={{ fontSize: '0.75rem' }}>
Используются настройки интерфейса
</div>
) : (
<div className="text-muted mt-1" style={{ fontSize: '0.75rem' }}>
Используются глобальные настройки
</div>
)}
</div>
</div>
</div>
+37 -1
View File
@@ -17,6 +17,8 @@ export default function SettingsModal({ open, onClose }) {
const [dohServer, setDohServer] = useState('');
const [wsUrl, setWsUrl] = useState('');
const [baseAS, setBaseAS] = useState('65001');
const [ptrZoneReplaceFrom, setPtrZoneReplaceFrom] = useState('');
const [ptrZoneReplaceTo, setPtrZoneReplaceTo] = useState('');
useEffect(() => {
if (!open) return;
@@ -31,6 +33,8 @@ export default function SettingsModal({ open, onClose }) {
setDohServer(String(data?.dohServer || ''));
setWsUrl(String(data?.wsUpdateUrl || ''));
setBaseAS(String(data?.baseAS || '65001'));
setPtrZoneReplaceFrom(String(data?.ptrZoneReplaceFrom || ''));
setPtrZoneReplaceTo(String(data?.ptrZoneReplaceTo || ''));
const e = res?.headers?.etag || res?.headers?.ETag || '';
setEtag(e ? String(e) : '');
} catch (e) {
@@ -90,7 +94,9 @@ export default function SettingsModal({ open, onClose }) {
settings: {
dohServer: String(dohServer || '').trim(),
wsUpdateUrl: String(wsUrl || '').trim(),
baseAS: String(baseAS || '65001').trim()
baseAS: String(baseAS || '65001').trim(),
ptrZoneReplaceFrom: String(ptrZoneReplaceFrom || '').trim(),
ptrZoneReplaceTo: String(ptrZoneReplaceTo || '').trim()
},
etag
};
@@ -186,6 +192,36 @@ export default function SettingsModal({ open, onClose }) {
helpText="AS по умолчанию для community (например, 65001). Используется при поиске и нормализации community."
disabled={loading || saving}
/>
<div className="mt-4 pt-3 border-top">
<h6 className="mb-3">Настройка PTR зоны</h6>
<div className="row g-3">
<div className="col-md-6">
<FormField
label="Заменить в DNS домене"
name="ptrZoneReplaceFrom"
type="text"
value={ptrZoneReplaceFrom}
onChange={setPtrZoneReplaceFrom}
placeholder="rt.shx"
helpText='Часть DNS домена, которую нужно заменить (например, "rt.shx")'
disabled={loading || saving}
/>
</div>
<div className="col-md-6">
<FormField
label="Заменить на"
name="ptrZoneReplaceTo"
type="text"
value={ptrZoneReplaceTo}
onChange={setPtrZoneReplaceTo}
placeholder="shrt"
helpText='На что заменить (например, "shrt"). Результат: DNS "selectel.msk.rt.shx.su" → PTR "selectel.msk.shrt.su"'
disabled={loading || saving}
/>
</div>
</div>
</div>
</div>
<div className="modal-footer">