feat(NetworkConfigManager): extend IPSec password handling for GRE tunnels; update modal behavior and enhance MikroTik code generation for GRE with optional IPSec support
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m38s

This commit is contained in:
2026-01-22 19:22:34 +07:00
parent a77ba76ff8
commit 76a8b867f8
+82 -18
View File
@@ -551,7 +551,7 @@ function NetworkConfigManager() {
remoteIp: remoteIp, remoteIp: remoteIp,
serverId: templateServer1, serverId: templateServer1,
serverId2: templateServer2, serverId2: templateServer2,
ipsecPasswordId: templateType === 'IPSec' && templateIpsecPasswordId ? templateIpsecPasswordId : '' ipsecPasswordId: (templateType === 'IPSec' || templateType === 'GRE') && templateIpsecPasswordId ? templateIpsecPasswordId : ''
}; };
const conflicts = checkInterfaceIpConflict(newInterface); const conflicts = checkInterfaceIpConflict(newInterface);
@@ -917,12 +917,12 @@ function NetworkConfigManager() {
} }
// Если создан новый пароль и открыт шаблон интерфейса, автоматически выбираем его // Если создан новый пароль и открыт шаблон интерфейса, автоматически выбираем его
if (savedPasswordId && interfaceTemplateModalOpen && templateType === 'IPSec') { if (savedPasswordId && interfaceTemplateModalOpen && (templateType === 'IPSec' || templateType === 'GRE')) {
setTemplateIpsecPasswordId(savedPasswordId); setTemplateIpsecPasswordId(savedPasswordId);
} }
// Если создан новый пароль и открыт модал интерфейса с типом IPSec, автоматически выбираем его // Если создан новый пароль и открыт модал интерфейса с типом IPSec или GRE, автоматически выбираем его
if (savedPasswordId && interfaceModalOpen && editingInterface?.type === 'IPSec') { if (savedPasswordId && interfaceModalOpen && (editingInterface?.type === 'IPSec' || editingInterface?.type === 'GRE')) {
setEditingInterface({ ...editingInterface, ipsecPasswordId: savedPasswordId }); setEditingInterface({ ...editingInterface, ipsecPasswordId: savedPasswordId });
} }
} catch (error) { } catch (error) {
@@ -1019,7 +1019,7 @@ function NetworkConfigManager() {
}; };
// === Генерация кода MikroTik для настройки IP адресов интерфейсов === // === Генерация кода MikroTik для настройки IP адресов интерфейсов ===
const generateMikrotikInterfaceAddresses = () => { const generateMikrotikInterfaceAddresses = async () => {
const interfacesWithBothServers = (config.tunnelInterfaces || []).filter(i => const interfacesWithBothServers = (config.tunnelInterfaces || []).filter(i =>
i.serverId && i.serverId2 && i.localIp && i.remoteIp i.serverId && i.serverId2 && i.localIp && i.remoteIp
); );
@@ -1028,6 +1028,23 @@ function NetworkConfigManager() {
return []; return [];
} }
// Загружаем пароли для интерфейсов с IPSec
const passwordIds = [...new Set(interfacesWithBothServers
.filter(i => i.ipsecPasswordId)
.map(i => i.ipsecPasswordId)
)];
const passwordMap = {};
for (const passwordId of passwordIds) {
try {
const response = await api.get(`/ipsec-passwords/${passwordId}`);
passwordMap[passwordId] = response.data?.password || '';
} catch (error) {
console.error(`Error loading IPSec password ${passwordId}:`, error);
passwordMap[passwordId] = '';
}
}
// Группируем по серверам // Группируем по серверам
const interfacesByServer = {}; const interfacesByServer = {};
interfacesWithBothServers.forEach(iface => { interfacesWithBothServers.forEach(iface => {
@@ -1057,7 +1074,9 @@ function NetworkConfigManager() {
remoteIp: iface.remoteIp, remoteIp: iface.remoteIp,
type: iface.type, type: iface.type,
server2Name, server2Name,
server2Info: server2 server2Info: server2,
ipsecPasswordId: iface.ipsecPasswordId || null,
ipsecPassword: iface.ipsecPasswordId ? passwordMap[iface.ipsecPasswordId] : null
}); });
// Для второго сервера - МЕНЯЕМ МЕСТАМИ: remoteIp становится localIp, localIp становится remoteIp // Для второго сервера - МЕНЯЕМ МЕСТАМИ: remoteIp становится localIp, localIp становится remoteIp
@@ -1074,7 +1093,9 @@ function NetworkConfigManager() {
remoteIp: iface.localIp, // На втором сервере localIp первого становится remoteIp remoteIp: iface.localIp, // На втором сервере localIp первого становится remoteIp
type: iface.type, type: iface.type,
server2Name: server1Name, server2Name: server1Name,
server2Info: server1 server2Info: server1,
ipsecPasswordId: iface.ipsecPasswordId || null,
ipsecPassword: iface.ipsecPasswordId ? passwordMap[iface.ipsecPasswordId] : null
}); });
}); });
@@ -1102,6 +1123,41 @@ function NetworkConfigManager() {
code += `# Связь с сервером: ${iface.server2Name}\n`; code += `# Связь с сервером: ${iface.server2Name}\n`;
code += `# Local IP: ${iface.localIp}\n`; code += `# Local IP: ${iface.localIp}\n`;
code += `# Remote IP: ${iface.remoteIp}\n`; code += `# Remote IP: ${iface.remoteIp}\n`;
// Для GRE туннелей - создаем туннель, опционально с IPSec
if (iface.type === 'GRE') {
const remoteServerIp = iface.server2Info?.ip || iface.server2Name;
const localServerIp = server.ip || serverName;
if (iface.ipsecPasswordId && iface.ipsecPassword) {
// GRE туннель с IPSec
code += `# Создание GRE туннеля с IPSec\n`;
code += `/interface gre add name="${iface.interfaceName}" remote-address=${remoteServerIp} local-address=${localServerIp} keepalive=10s\n`;
code += '\n';
// Создаем IPSec peer
code += `# Настройка IPSec peer для ${iface.interfaceName}\n`;
code += `/ip ipsec peer add name="${iface.interfaceName}-peer" address=${remoteServerIp}/32 local-address=${localServerIp} exchange-mode=ike2 passive=no\n`;
code += '\n';
// Создаем IPSec identity
code += `# Настройка IPSec identity для ${iface.interfaceName}\n`;
code += `/ip ipsec identity add peer="${iface.interfaceName}-peer" auth-method=pre-shared-key secret="${iface.ipsecPassword}"\n`;
code += '\n';
// Создаем IPSec policy
code += `# Настройка IPSec policy для ${iface.interfaceName}\n`;
code += `/ip ipsec policy add src-address=${iface.localIp}/30 dst-address=${iface.remoteIp}/30 sa-src-address=${localServerIp} sa-dst-address=${remoteServerIp} peer="${iface.interfaceName}-peer" tunnel=yes\n`;
code += '\n';
} else {
// GRE туннель без IPSec (голый GRE)
code += `# Создание GRE туннеля (без IPSec)\n`;
code += `/interface gre add name="${iface.interfaceName}" remote-address=${remoteServerIp} local-address=${localServerIp} keepalive=10s\n`;
code += '\n';
}
}
// Добавляем IP адрес на интерфейс
code += `/ip address add address=${iface.localIp}/30 interface="${iface.interfaceName}" comment="Interface: ${iface.interfaceName} (${iface.type}) to ${iface.server2Name}"\n`; code += `/ip address add address=${iface.localIp}/30 interface="${iface.interfaceName}" comment="Interface: ${iface.interfaceName} (${iface.type}) to ${iface.server2Name}"\n`;
code += '\n'; code += '\n';
}); });
@@ -1121,7 +1177,7 @@ function NetworkConfigManager() {
}; };
// === Генерация кода MikroTik для рекурсивных маршрутов === // === Генерация кода MikroTik для рекурсивных маршрутов ===
const generateMikrotikRecursiveRoutes = () => { const generateMikrotikRecursiveRoutes = async () => {
const recursiveGateways = (config.gateways || []).filter(gw => gw.type === 'recursive'); const recursiveGateways = (config.gateways || []).filter(gw => gw.type === 'recursive');
const blocks = []; const blocks = [];
@@ -1241,7 +1297,7 @@ function NetworkConfigManager() {
} }
// Генерируем блоки для IP адресов интерфейсов // Генерируем блоки для IP адресов интерфейсов
const interfaceBlocks = generateMikrotikInterfaceAddresses(); const interfaceBlocks = await generateMikrotikInterfaceAddresses();
if (interfaceBlocks && interfaceBlocks.length > 0) { if (interfaceBlocks && interfaceBlocks.length > 0) {
blocks.push(...interfaceBlocks); blocks.push(...interfaceBlocks);
} }
@@ -1250,10 +1306,18 @@ function NetworkConfigManager() {
}; };
// === Обработчик кнопки "Код для MikroTik" === // === Обработчик кнопки "Код для MikroTik" ===
const handleGenerateMikrotikCode = () => { const handleGenerateMikrotikCode = async () => {
const code = generateMikrotikRecursiveRoutes(); setLoading(true);
setGeneratedMikrotikCode(code); try {
setMikrotikCodeModalOpen(true); const code = await generateMikrotikRecursiveRoutes();
setGeneratedMikrotikCode(code);
setMikrotikCodeModalOpen(true);
} catch (error) {
console.error('Error generating MikroTik code:', error);
notify.error('Ошибка при генерации кода MikroTik');
} finally {
setLoading(false);
}
}; };
// === Копирование кода MikroTik === // === Копирование кода MikroTik ===
@@ -2744,11 +2808,11 @@ function NetworkConfigManager() {
name="type" name="type"
type="select" type="select"
value={editingInterface.type} value={editingInterface.type}
onChange={(val) => setEditingInterface({ ...editingInterface, type: val, ipsecPasswordId: val !== 'IPSec' ? '' : editingInterface.ipsecPasswordId })} onChange={(val) => setEditingInterface({ ...editingInterface, type: val, ipsecPasswordId: (val !== 'IPSec' && val !== 'GRE') ? '' : editingInterface.ipsecPasswordId })}
options={INTERFACE_TYPES} options={INTERFACE_TYPES}
/> />
</div> </div>
{editingInterface.type === 'IPSec' && ( {(editingInterface.type === 'IPSec' || editingInterface.type === 'GRE') && (
<div className="col-md-6"> <div className="col-md-6">
<label className="form-label">IPSec пароль</label> <label className="form-label">IPSec пароль</label>
<div className="input-group"> <div className="input-group">
@@ -2778,7 +2842,7 @@ function NetworkConfigManager() {
</button> </button>
</div> </div>
<div className="form-text"> <div className="form-text">
Выберите сохраненный IPSec пароль или создайте новый Выберите сохраненный IPSec пароль или создайте новый (опционально для GRE)
</div> </div>
</div> </div>
)} )}
@@ -3043,7 +3107,7 @@ function NetworkConfigManager() {
/> />
<div className="form-text">Оставьте пустым для автогенерации </div> <div className="form-text">Оставьте пустым для автогенерации </div>
</div> </div>
{templateType === 'IPSec' && ( {(templateType === 'IPSec' || templateType === 'GRE') && (
<div className="col-12"> <div className="col-12">
<label className="form-label">IPSec пароль</label> <label className="form-label">IPSec пароль</label>
<div className="input-group"> <div className="input-group">
@@ -3073,7 +3137,7 @@ function NetworkConfigManager() {
</button> </button>
</div> </div>
<div className="form-text"> <div className="form-text">
Выберите сохраненный IPSec пароль или создайте новый Выберите сохраненный IPSec пароль или создайте новый (опционально для GRE)
</div> </div>
</div> </div>
)} )}