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 ===
const [mikrotikCodeModalOpen, setMikrotikCodeModalOpen] = useState(false);
const [generatedMikrotikCode, setGeneratedMikrotikCode] = useState('');
const [generatedMikrotikCode, setGeneratedMikrotikCode] = useState([]);
// === Загрузка данных ===
useEffect(() => {
@@ -509,13 +509,8 @@ function NetworkConfigManager() {
);
if (interfacesWithBothServers.length === 0) {
return '';
return [];
}
let code = '\n# ====================================================================\n';
code += '# === НАСТРОЙКА IP АДРЕСОВ ИНТЕРФЕЙСОВ НА ОБОИХ СЕРВЕРАХ ===\n';
code += '# Эти команды нужно выполнить на соответствующих серверах\n';
code += '# ====================================================================\n\n';
// Группируем по серверам
const interfacesByServer = {};
@@ -530,7 +525,7 @@ function NetworkConfigManager() {
// Имя интерфейса для первого сервера
const interfaceName1 = iface.name || `${iface.type}-tunnel`;
// Имя интерфейса для второго сервера (может отличаться)
// Имя интерфейса для второго сервера - ВСЕГДА используем name2 если указано, иначе name
const interfaceName2 = iface.name2 || iface.name || `${iface.type}-tunnel`;
// Для первого сервера - используем localIp как local, remoteIp как remote
@@ -550,6 +545,7 @@ function NetworkConfigManager() {
});
// Для второго сервера - МЕНЯЕМ МЕСТАМИ: remoteIp становится localIp, localIp становится remoteIp
// И ВСЕГДА используем name2 (или name если name2 не указано)
if (!interfacesByServer[server2Name]) {
interfacesByServer[server2Name] = {
serverInfo: server2,
@@ -557,7 +553,7 @@ function NetworkConfigManager() {
};
}
interfacesByServer[server2Name].interfaces.push({
interfaceName: interfaceName2,
interfaceName: interfaceName2, // Используем name2 для второго сервера
localIp: iface.remoteIp, // На втором сервере remoteIp первого становится localIp
remoteIp: iface.localIp, // На втором сервере localIp первого становится remoteIp
type: iface.type,
@@ -569,13 +565,11 @@ function NetworkConfigManager() {
// Сортируем серверы по имени для консистентности
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;
// Отдельный блок для каждого сервера
code += '\n';
code += '# ' + '='.repeat(70) + '\n';
code += `# СЕРВЕР ${index + 1}: ${serverName}\n`;
let code = `# Настройка IP адресов интерфейсов для сервера: ${serverName}\n`;
if (server.provider) {
code += `# Провайдер: ${server.provider}\n`;
}
@@ -585,7 +579,6 @@ function NetworkConfigManager() {
if (server.ip && server.ip !== serverName) {
code += `# IP: ${server.ip}\n`;
}
code += '# ' + '='.repeat(70) + '\n';
code += '\n';
serverData.interfaces.forEach((iface, ifaceIndex) => {
@@ -599,58 +592,64 @@ function NetworkConfigManager() {
code += '# Проверка настроенных адресов на этом сервере:\n';
code += '# /ip address print\n';
code += '\n';
blocks.push({
type: 'interface-addresses',
serverName,
server,
code
});
});
code += '# ' + '='.repeat(70) + '\n';
code += '# КОНЕЦ КОНФИГУРАЦИИ IP АДРЕСОВ ИНТЕРФЕЙСОВ\n';
code += '# ' + '='.repeat(70) + '\n';
return code;
return blocks;
};
// === Генерация кода MikroTik для рекурсивных маршрутов ===
const generateMikrotikRecursiveRoutes = () => {
const recursiveGateways = (config.gateways || []).filter(gw => gw.type === 'recursive');
if (recursiveGateways.length === 0) {
return '// Нет рекурсивных gateway для генерации маршрутов\n// Добавьте рекурсивные gateway в настройках';
}
const blocks = [];
let code = '// Конфигурация MikroTik RouterOS для рекурсивных маршрутов\n';
code += '// Генерируется автоматически\n';
code += `// Дата: ${new Date().toISOString()}\n\n`;
code += '# Удаление существующих рекурсивных маршрутов (опционально)\n';
code += '/ip route remove [find comment~"Recursive"]\n\n';
// Группируем по серверам для удобства
const gatewaysByServer = {};
recursiveGateways.forEach(gw => {
const serverId = gw.serverId || '__unassigned__';
if (!gatewaysByServer[serverId]) {
gatewaysByServer[serverId] = [];
}
gatewaysByServer[serverId].push(gw);
});
// Если есть рекурсивные gateway, генерируем блоки для них
if (recursiveGateways.length > 0) {
// Группируем по серверам для удобства
const gatewaysByServer = {};
recursiveGateways.forEach(gw => {
const serverId = gw.serverId || '__unassigned__';
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 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 => {
const parent = getParentGateway(gw.parentGatewayId);
if (!parent) {
code += `\n# Gateway "${gw.ip || 'без IP'}" - родительский gateway/интерфейс не найден, пропущен\n`;
code += `# Gateway "${gw.ip || 'без IP'}" - родительский gateway/интерфейс не найден, пропущен\n`;
return;
}
if (!gw.ip) {
code += `\n# Gateway без IP адреса - пропущен\n`;
code += `# Gateway без IP адреса - пропущен\n`;
return;
}
@@ -659,7 +658,7 @@ function NetworkConfigManager() {
if (!parentIp) {
const parentType = parent.parentType === 'interface' ? 'интерфейс' : 'gateway';
code += `\n# Gateway "${gw.ip}" - родительский ${parentType} не имеет IP, пропущен\n`;
code += `# Gateway "${gw.ip}" - родительский ${parentType} не имеет IP, пропущен\n`;
return;
}
@@ -670,7 +669,7 @@ function NetworkConfigManager() {
? `интерфейс ${parent.name || parent.interfaceType || 'interface'} (${parent.remoteIp})`
: `gateway ${parent.ip}`;
code += `\n# Рекурсивный gateway: ${gw.ip}\n`;
code += `# Рекурсивный gateway: ${gw.ip}\n`;
code += `# Родительский: ${parentLabel}\n`;
// Создаём маршрут к IP рекурсивного gateway через родительский
@@ -685,20 +684,30 @@ function NetworkConfigManager() {
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 += '\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';
code += '# /ip route print where comment~"Recursive"\n';
code += `\n# Всего создано маршрутов: ${validRoutesCount}\n`;
// Добавляем код для настройки IP адресов интерфейсов
const interfaceAddressesCode = generateMikrotikInterfaceAddresses();
if (interfaceAddressesCode) {
code += interfaceAddressesCode;
// Генерируем блоки для IP адресов интерфейсов
const interfaceBlocks = generateMikrotikInterfaceAddresses();
if (interfaceBlocks && interfaceBlocks.length > 0) {
blocks.push(...interfaceBlocks);
}
return code;
return blocks;
};
// === Обработчик кнопки "Код для MikroTik" ===
@@ -710,14 +719,24 @@ function NetworkConfigManager() {
// === Копирование кода MikroTik ===
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 {
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(generatedMikrotikCode);
await navigator.clipboard.writeText(fullCode);
notify.success('Код скопирован в буфер обмена!');
} else {
// Fallback для старых браузеров
const textArea = document.createElement('textarea');
textArea.value = generatedMikrotikCode;
textArea.value = fullCode;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
document.body.appendChild(textArea);
@@ -1091,8 +1110,11 @@ function NetworkConfigManager() {
type="button"
className="btn btn-outline-primary"
onClick={handleGenerateMikrotikCode}
disabled={loading || (config.gateways || []).filter(gw => gw.type === 'recursive').length === 0}
title="Генерировать код MikroTik для рекурсивных маршрутов"
disabled={loading || (
(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" />
Код для MikroTik
@@ -1924,15 +1946,65 @@ function NetworkConfigManager() {
/>
{/* MikroTik Code Modal */}
<PreviewConfigModal
show={mikrotikCodeModalOpen}
config={generatedMikrotikCode}
mode="advanced"
title="Предварительный просмотр конфигурации"
description="Конфигурация MikroTik RouterOS для рекурсивных маршрутов"
onClose={() => setMikrotikCodeModalOpen(false)}
onCopy={handleCopyMikrotikCode}
/>
{mikrotikCodeModalOpen && (
<div className="modal show d-block" style={{ backgroundColor: 'rgba(0,0,0,0.5)' }}>
<div className="modal-dialog modal-xl">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title">
Предварительный просмотр конфигурации MikroTik
</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>
);
}