refactor(NetworkConfigManager): update MikroTik code generation to return structured blocks for interface addresses and recursive routes; enhance modal display for generated code
Publish Fast Tabler Docker image / build-and-push-fast (push) Failing after 58s

This commit is contained in:
2026-01-22 13:28:47 +07:00
parent fdc8b1f458
commit b0ccbc5c54
+141 -69
View File
@@ -131,7 +131,7 @@ function NetworkConfigManager() {
// === MikroTik Code Generation === // === MikroTik Code Generation ===
const [mikrotikCodeModalOpen, setMikrotikCodeModalOpen] = useState(false); const [mikrotikCodeModalOpen, setMikrotikCodeModalOpen] = useState(false);
const [generatedMikrotikCode, setGeneratedMikrotikCode] = useState(''); const [generatedMikrotikCode, setGeneratedMikrotikCode] = useState([]);
// === Загрузка данных === // === Загрузка данных ===
useEffect(() => { useEffect(() => {
@@ -509,13 +509,8 @@ function NetworkConfigManager() {
); );
if (interfacesWithBothServers.length === 0) { if (interfacesWithBothServers.length === 0) {
return ''; return [];
} }
let code = '\n# ====================================================================\n';
code += '# === НАСТРОЙКА IP АДРЕСОВ ИНТЕРФЕЙСОВ НА ОБОИХ СЕРВЕРАХ ===\n';
code += '# Эти команды нужно выполнить на соответствующих серверах\n';
code += '# ====================================================================\n\n';
// Группируем по серверам // Группируем по серверам
const interfacesByServer = {}; const interfacesByServer = {};
@@ -530,7 +525,7 @@ function NetworkConfigManager() {
// Имя интерфейса для первого сервера // Имя интерфейса для первого сервера
const interfaceName1 = iface.name || `${iface.type}-tunnel`; const interfaceName1 = iface.name || `${iface.type}-tunnel`;
// Имя интерфейса для второго сервера (может отличаться) // Имя интерфейса для второго сервера - ВСЕГДА используем name2 если указано, иначе name
const interfaceName2 = iface.name2 || iface.name || `${iface.type}-tunnel`; const interfaceName2 = iface.name2 || iface.name || `${iface.type}-tunnel`;
// Для первого сервера - используем localIp как local, remoteIp как remote // Для первого сервера - используем localIp как local, remoteIp как remote
@@ -550,6 +545,7 @@ function NetworkConfigManager() {
}); });
// Для второго сервера - МЕНЯЕМ МЕСТАМИ: remoteIp становится localIp, localIp становится remoteIp // Для второго сервера - МЕНЯЕМ МЕСТАМИ: remoteIp становится localIp, localIp становится remoteIp
// И ВСЕГДА используем name2 (или name если name2 не указано)
if (!interfacesByServer[server2Name]) { if (!interfacesByServer[server2Name]) {
interfacesByServer[server2Name] = { interfacesByServer[server2Name] = {
serverInfo: server2, serverInfo: server2,
@@ -557,7 +553,7 @@ function NetworkConfigManager() {
}; };
} }
interfacesByServer[server2Name].interfaces.push({ interfacesByServer[server2Name].interfaces.push({
interfaceName: interfaceName2, interfaceName: interfaceName2, // Используем name2 для второго сервера
localIp: iface.remoteIp, // На втором сервере remoteIp первого становится localIp localIp: iface.remoteIp, // На втором сервере remoteIp первого становится localIp
remoteIp: iface.localIp, // На втором сервере localIp первого становится remoteIp remoteIp: iface.localIp, // На втором сервере localIp первого становится remoteIp
type: iface.type, type: iface.type,
@@ -569,13 +565,11 @@ function NetworkConfigManager() {
// Сортируем серверы по имени для консистентности // Сортируем серверы по имени для консистентности
const sortedServers = Object.entries(interfacesByServer).sort(([a], [b]) => a.localeCompare(b)); const sortedServers = Object.entries(interfacesByServer).sort(([a], [b]) => a.localeCompare(b));
sortedServers.forEach(([serverName, serverData], index) => { const blocks = [];
sortedServers.forEach(([serverName, serverData]) => {
const server = serverData.serverInfo; const server = serverData.serverInfo;
// Отдельный блок для каждого сервера let code = `# Настройка IP адресов интерфейсов для сервера: ${serverName}\n`;
code += '\n';
code += '# ' + '='.repeat(70) + '\n';
code += `# СЕРВЕР ${index + 1}: ${serverName}\n`;
if (server.provider) { if (server.provider) {
code += `# Провайдер: ${server.provider}\n`; code += `# Провайдер: ${server.provider}\n`;
} }
@@ -585,7 +579,6 @@ function NetworkConfigManager() {
if (server.ip && server.ip !== serverName) { if (server.ip && server.ip !== serverName) {
code += `# IP: ${server.ip}\n`; code += `# IP: ${server.ip}\n`;
} }
code += '# ' + '='.repeat(70) + '\n';
code += '\n'; code += '\n';
serverData.interfaces.forEach((iface, ifaceIndex) => { serverData.interfaces.forEach((iface, ifaceIndex) => {
@@ -599,58 +592,64 @@ function NetworkConfigManager() {
code += '# Проверка настроенных адресов на этом сервере:\n'; code += '# Проверка настроенных адресов на этом сервере:\n';
code += '# /ip address print\n'; code += '# /ip address print\n';
code += '\n';
blocks.push({
type: 'interface-addresses',
serverName,
server,
code
});
}); });
code += '# ' + '='.repeat(70) + '\n'; return blocks;
code += '# КОНЕЦ КОНФИГУРАЦИИ IP АДРЕСОВ ИНТЕРФЕЙСОВ\n';
code += '# ' + '='.repeat(70) + '\n';
return code;
}; };
// === Генерация кода MikroTik для рекурсивных маршрутов === // === Генерация кода MikroTik для рекурсивных маршрутов ===
const generateMikrotikRecursiveRoutes = () => { const generateMikrotikRecursiveRoutes = () => {
const recursiveGateways = (config.gateways || []).filter(gw => gw.type === 'recursive'); const recursiveGateways = (config.gateways || []).filter(gw => gw.type === 'recursive');
if (recursiveGateways.length === 0) { const blocks = [];
return '// Нет рекурсивных gateway для генерации маршрутов\n// Добавьте рекурсивные gateway в настройках';
}
let code = '// Конфигурация MikroTik RouterOS для рекурсивных маршрутов\n'; // Если есть рекурсивные gateway, генерируем блоки для них
code += '// Генерируется автоматически\n'; if (recursiveGateways.length > 0) {
code += `// Дата: ${new Date().toISOString()}\n\n`; // Группируем по серверам для удобства
code += '# Удаление существующих рекурсивных маршрутов (опционально)\n'; const gatewaysByServer = {};
code += '/ip route remove [find comment~"Recursive"]\n\n'; recursiveGateways.forEach(gw => {
const serverId = gw.serverId || '__unassigned__';
// Группируем по серверам для удобства if (!gatewaysByServer[serverId]) {
const gatewaysByServer = {}; gatewaysByServer[serverId] = [];
recursiveGateways.forEach(gw => { }
const serverId = gw.serverId || '__unassigned__'; gatewaysByServer[serverId].push(gw);
if (!gatewaysByServer[serverId]) { });
gatewaysByServer[serverId] = [];
}
gatewaysByServer[serverId].push(gw);
});
let validRoutesCount = 0; // Генерируем блоки для рекурсивных маршрутов по серверам
Object.entries(gatewaysByServer).forEach(([serverId, gateways]) => {
Object.entries(gatewaysByServer).forEach(([serverId, gateways]) => {
const server = getServerInfo(serverId); const server = getServerInfo(serverId);
const serverName = server?.dns || server?.ip || serverId; const serverName = server?.dns || server?.ip || serverId;
code += `\n# === Рекурсивные маршруты для сервера: ${serverName} ===\n`; let code = `# Рекурсивные маршруты для сервера: ${serverName}\n`;
if (server?.provider) {
code += `# Провайдер: ${server.provider}\n`;
}
if (server?.country) {
code += `# Страна: ${server.country}\n`;
}
code += '\n';
code += '# Удаление существующих рекурсивных маршрутов (опционально)\n';
code += '/ip route remove [find comment~"Recursive"]\n\n';
let validRoutesCount = 0;
gateways.forEach(gw => { gateways.forEach(gw => {
const parent = getParentGateway(gw.parentGatewayId); const parent = getParentGateway(gw.parentGatewayId);
if (!parent) { if (!parent) {
code += `\n# Gateway "${gw.ip || 'без IP'}" - родительский gateway/интерфейс не найден, пропущен\n`; code += `# Gateway "${gw.ip || 'без IP'}" - родительский gateway/интерфейс не найден, пропущен\n`;
return; return;
} }
if (!gw.ip) { if (!gw.ip) {
code += `\n# Gateway без IP адреса - пропущен\n`; code += `# Gateway без IP адреса - пропущен\n`;
return; return;
} }
@@ -659,7 +658,7 @@ function NetworkConfigManager() {
if (!parentIp) { if (!parentIp) {
const parentType = parent.parentType === 'interface' ? 'интерфейс' : 'gateway'; const parentType = parent.parentType === 'interface' ? 'интерфейс' : 'gateway';
code += `\n# Gateway "${gw.ip}" - родительский ${parentType} не имеет IP, пропущен\n`; code += `# Gateway "${gw.ip}" - родительский ${parentType} не имеет IP, пропущен\n`;
return; return;
} }
@@ -670,7 +669,7 @@ function NetworkConfigManager() {
? `интерфейс ${parent.name || parent.interfaceType || 'interface'} (${parent.remoteIp})` ? `интерфейс ${parent.name || parent.interfaceType || 'interface'} (${parent.remoteIp})`
: `gateway ${parent.ip}`; : `gateway ${parent.ip}`;
code += `\n# Рекурсивный gateway: ${gw.ip}\n`; code += `# Рекурсивный gateway: ${gw.ip}\n`;
code += `# Родительский: ${parentLabel}\n`; code += `# Родительский: ${parentLabel}\n`;
// Создаём маршрут к IP рекурсивного gateway через родительский // Создаём маршрут к IP рекурсивного gateway через родительский
@@ -685,20 +684,30 @@ function NetworkConfigManager() {
const defaultComment = gw.description ? `${gw.description} (default)` : `Recursive default: ${gw.ip}`; const defaultComment = gw.description ? `${gw.description} (default)` : `Recursive default: ${gw.ip}`;
code += `/ip route add dst-address=0.0.0.0/0 gateway=${parentIp} comment="${defaultComment}"\n`; code += `/ip route add dst-address=0.0.0.0/0 gateway=${parentIp} comment="${defaultComment}"\n`;
} }
code += '\n';
}); });
code += '# Проверка созданных маршрутов:\n';
code += '# /ip route print where comment~"Recursive"\n';
code += `# Всего создано маршрутов: ${validRoutesCount}\n`;
if (validRoutesCount > 0) {
blocks.push({
type: 'recursive-routes',
serverName,
server,
code
});
}
}); });
code += '\n# === Проверка созданных маршрутов ===\n'; // Генерируем блоки для IP адресов интерфейсов
code += '# /ip route print where comment~"Recursive"\n'; const interfaceBlocks = generateMikrotikInterfaceAddresses();
code += `\n# Всего создано маршрутов: ${validRoutesCount}\n`; if (interfaceBlocks && interfaceBlocks.length > 0) {
blocks.push(...interfaceBlocks);
// Добавляем код для настройки IP адресов интерфейсов
const interfaceAddressesCode = generateMikrotikInterfaceAddresses();
if (interfaceAddressesCode) {
code += interfaceAddressesCode;
} }
return code; return blocks;
}; };
// === Обработчик кнопки "Код для MikroTik" === // === Обработчик кнопки "Код для MikroTik" ===
@@ -710,14 +719,24 @@ function NetworkConfigManager() {
// === Копирование кода MikroTik === // === Копирование кода MikroTik ===
const handleCopyMikrotikCode = async () => { const handleCopyMikrotikCode = async () => {
// Объединяем все блоки в одну строку для копирования
const fullCode = Array.isArray(generatedMikrotikCode) && generatedMikrotikCode.length > 0
? generatedMikrotikCode.map(block => block.code).join('\n\n')
: '';
if (!fullCode) {
notify.error('Нет кода для копирования');
return;
}
try { try {
if (navigator.clipboard && navigator.clipboard.writeText) { if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(generatedMikrotikCode); await navigator.clipboard.writeText(fullCode);
notify.success('Код скопирован в буфер обмена!'); notify.success('Код скопирован в буфер обмена!');
} else { } else {
// Fallback для старых браузеров // Fallback для старых браузеров
const textArea = document.createElement('textarea'); const textArea = document.createElement('textarea');
textArea.value = generatedMikrotikCode; textArea.value = fullCode;
textArea.style.position = 'fixed'; textArea.style.position = 'fixed';
textArea.style.left = '-999999px'; textArea.style.left = '-999999px';
document.body.appendChild(textArea); document.body.appendChild(textArea);
@@ -1091,8 +1110,11 @@ function NetworkConfigManager() {
type="button" type="button"
className="btn btn-outline-primary" className="btn btn-outline-primary"
onClick={handleGenerateMikrotikCode} onClick={handleGenerateMikrotikCode}
disabled={loading || (config.gateways || []).filter(gw => gw.type === 'recursive').length === 0} disabled={loading || (
title="Генерировать код MikroTik для рекурсивных маршрутов" (config.gateways || []).filter(gw => gw.type === 'recursive').length === 0 &&
(config.tunnelInterfaces || []).filter(i => i.serverId && i.serverId2 && i.localIp && i.remoteIp).length === 0
)}
title="Генерировать код MikroTik для рекурсивных маршрутов и IP адресов интерфейсов"
> >
<IconCode size={16} className="me-1" /> <IconCode size={16} className="me-1" />
Код для MikroTik Код для MikroTik
@@ -1924,15 +1946,65 @@ function NetworkConfigManager() {
/> />
{/* MikroTik Code Modal */} {/* MikroTik Code Modal */}
<PreviewConfigModal {mikrotikCodeModalOpen && (
show={mikrotikCodeModalOpen} <div className="modal show d-block" style={{ backgroundColor: 'rgba(0,0,0,0.5)' }}>
config={generatedMikrotikCode} <div className="modal-dialog modal-xl">
mode="advanced" <div className="modal-content">
title="Предварительный просмотр конфигурации" <div className="modal-header">
description="Конфигурация MikroTik RouterOS для рекурсивных маршрутов" <h5 className="modal-title">
onClose={() => setMikrotikCodeModalOpen(false)} Предварительный просмотр конфигурации MikroTik
onCopy={handleCopyMikrotikCode} </h5>
/> <button type="button" className="btn-close" onClick={() => setMikrotikCodeModalOpen(false)}></button>
</div>
<div className="modal-body">
<div className="alert alert-info mb-3">
Конфигурация MikroTik RouterOS для рекурсивных маршрутов и IP адресов интерфейсов
</div>
{Array.isArray(generatedMikrotikCode) && generatedMikrotikCode.length > 0 ? (
<div className="d-flex flex-column gap-3">
{generatedMikrotikCode.map((block, index) => (
<div key={index} className="bg-dark text-white p-3 rounded">
<pre className="mb-0" style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
<code className="text-white">{block.code}</code>
</pre>
</div>
))}
</div>
) : (
<div className="alert alert-warning mb-0">
Нет данных для генерации конфигурации
</div>
)}
<div className="mt-3 text-muted small">
<p className="mb-1"><strong>Инструкция по применению:</strong></p>
<ol className="mb-0">
<li>Скопируйте конфигурацию в буфер обмена</li>
<li>Подключитесь к MikroTik через SSH или Winbox</li>
<li>Вставьте конфигурацию в терминал</li>
<li>Проверьте применение командой: <code>/ip route print</code> или <code>/ip address print</code></li>
</ol>
</div>
</div>
<div className="modal-footer">
<button type="button" className="btn btn-secondary" onClick={() => setMikrotikCodeModalOpen(false)}>
Закрыть
</button>
<button
type="button"
className="btn btn-primary"
onClick={handleCopyMikrotikCode}
disabled={!Array.isArray(generatedMikrotikCode) || generatedMikrotikCode.length === 0}
>
<IconCopy className="me-2" />
Копировать в буфер
</button>
</div>
</div>
</div>
</div>
)}
</div> </div>
); );
} }