feat(NetworkConfigManager): add MikroTik code generation for recursive routes; implement modal for previewing and copying generated code
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m43s

This commit is contained in:
2026-01-22 11:08:21 +07:00
parent 1e6f5badec
commit 682c2ce96b
+113 -1
View File
@@ -5,6 +5,7 @@ import FormModal from './components/FormModal.jsx';
import FormField from './components/FormField.jsx';
import ConfirmModal from './components/ConfirmModal.jsx';
import ServerAutocompleteInput from './components/ServerAutocompleteInput.jsx';
import PreviewConfigModal from './components/filter/PreviewConfigModal.jsx';
import { countryToFlag } from './utils/serverUtils.js';
import {
IconNetwork,
@@ -22,7 +23,8 @@ import {
IconX,
IconCircleFilled,
IconLayoutGrid,
IconList
IconList,
IconCode
} from '@tabler/icons-react';
/**
@@ -125,6 +127,10 @@ function NetworkConfigManager() {
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
const [itemToDelete, setItemToDelete] = useState(null);
const [deleteType, setDeleteType] = useState('');
// === MikroTik Code Generation ===
const [mikrotikCodeModalOpen, setMikrotikCodeModalOpen] = useState(false);
const [generatedMikrotikCode, setGeneratedMikrotikCode] = useState('');
// === Загрузка данных ===
useEffect(() => {
@@ -462,6 +468,93 @@ function NetworkConfigManager() {
}
};
// === Генерация кода MikroTik для рекурсивных маршрутов ===
const generateMikrotikRecursiveRoutes = () => {
const recursiveGateways = (config.gateways || []).filter(gw => gw.type === 'recursive');
if (recursiveGateways.length === 0) {
return '// Нет рекурсивных gateway для генерации маршрутов\n// Добавьте рекурсивные gateway в настройках';
}
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);
});
let validRoutesCount = 0;
Object.entries(gatewaysByServer).forEach(([serverId, gateways]) => {
const server = getServerInfo(serverId);
const serverName = server?.dns || server?.ip || serverId;
code += `\n# === Рекурсивные маршруты для сервера: ${serverName} ===\n`;
gateways.forEach(gw => {
const parentGateway = getParentGateway(gw.parentGatewayId);
if (!parentGateway) {
code += `\n# Gateway "${gw.name}" - родительский gateway не найден, пропущен\n`;
return;
}
if (!gw.ip) {
code += `\n# Gateway "${gw.name}" - IP адрес не указан, пропущен\n`;
return;
}
if (!parentGateway.ip) {
code += `\n# Gateway "${gw.name}" - родительский gateway "${parentGateway.name}" не имеет IP, пропущен\n`;
return;
}
validRoutesCount++;
// Создаём маршрут для рекурсивного gateway
// Маршрут к IP рекурсивного gateway направляется через родительский gateway
code += `\n# Рекурсивный gateway: ${gw.name} (IP: ${gw.ip})\n`;
code += `# Родительский gateway: ${parentGateway.name} (IP: ${parentGateway.ip})\n`;
// Создаём маршрут к IP рекурсивного gateway через родительский
code += `/ip route add dst-address=${gw.ip}/32 gateway=${parentGateway.ip} comment="Recursive: ${gw.name} -> ${parentGateway.name}"\n`;
// Если у рекурсивного gateway есть описание с указанием на default route
if (gw.description && (gw.description.toLowerCase().includes('default') || gw.description.toLowerCase().includes('0.0.0.0'))) {
code += `# Дополнительный default route через рекурсивный gateway\n`;
code += `/ip route add dst-address=0.0.0.0/0 gateway=${parentGateway.ip} comment="Recursive default: ${gw.name} -> ${parentGateway.name}"\n`;
}
});
});
code += '\n# === Проверка созданных маршрутов ===\n';
code += '# /ip route print where comment~"Recursive"\n';
code += `\n# Всего создано маршрутов: ${validRoutesCount}\n`;
return code;
};
// === Обработчик кнопки "Код для MikroTik" ===
const handleGenerateMikrotikCode = () => {
const code = generateMikrotikRecursiveRoutes();
setGeneratedMikrotikCode(code);
setMikrotikCodeModalOpen(true);
};
// === Копирование кода MikroTik ===
const handleCopyMikrotikCode = async () => {
await copyToClipboard(generatedMikrotikCode);
};
// === Получение цвета провайдера ===
const getProviderColor = (provider) => {
if (!provider) return 'secondary';
@@ -754,6 +847,16 @@ function NetworkConfigManager() {
>
<IconRefresh size={16} className={loading ? 'spin' : ''} />
</button>
<button
type="button"
className="btn btn-outline-primary"
onClick={handleGenerateMikrotikCode}
disabled={loading || (config.gateways || []).filter(gw => gw.type === 'recursive').length === 0}
title="Генерировать код MikroTik для рекурсивных маршрутов"
>
<IconCode size={16} className="me-1" />
Код для MikroTik
</button>
<button
type="button"
className="btn btn-primary"
@@ -1512,6 +1615,15 @@ function NetworkConfigManager() {
variant="danger"
icon={IconTrash}
/>
{/* MikroTik Code Modal */}
<PreviewConfigModal
show={mikrotikCodeModalOpen}
config={generatedMikrotikCode}
mode="advanced"
onClose={() => setMikrotikCodeModalOpen(false)}
onCopy={handleCopyMikrotikCode}
/>
</div>
);
}