feat(NetworkConfigManager): implement MikroTik code generation for individual gateways, enhancing user experience with detailed route generation and error handling
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m41s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m41s
This commit is contained in:
@@ -1739,6 +1739,165 @@ function NetworkConfigManager() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// === Генерация кода MikroTik только для одного gateway ===
|
||||||
|
const handleGenerateMikrotikCodeForGateway = async (gateway) => {
|
||||||
|
if (!gateway) {
|
||||||
|
notify.error('Gateway не найден');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const blocks = [];
|
||||||
|
const server = getServerInfo(gateway.serverId);
|
||||||
|
const serverName = server?.dns || server?.ip || gateway.serverId || '__unassigned__';
|
||||||
|
|
||||||
|
if (gateway.type === 'recursive') {
|
||||||
|
// Генерация кода для рекурсивного gateway
|
||||||
|
if (!gateway.ip) {
|
||||||
|
notify.error('Gateway без IP адреса - невозможно сгенерировать код');
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Поддержка старого формата (parentGatewayId) и нового (parentGateways)
|
||||||
|
const parentGatewaysList = gateway.parentGateways && Array.isArray(gateway.parentGateways) && gateway.parentGateways.length > 0
|
||||||
|
? gateway.parentGateways
|
||||||
|
: (gateway.parentGatewayId ? [{ id: gateway.parentGatewayId, distance: undefined }] : []);
|
||||||
|
|
||||||
|
if (parentGatewaysList.length === 0) {
|
||||||
|
notify.error('Родительские gateway/интерфейсы не указаны');
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let code = `# Рекурсивный маршрут для gateway: ${gateway.ip}\n`;
|
||||||
|
if (server?.provider) {
|
||||||
|
code += `# Провайдер: ${server.provider}\n`;
|
||||||
|
}
|
||||||
|
if (server?.country) {
|
||||||
|
code += `# Страна: ${server.country}\n`;
|
||||||
|
}
|
||||||
|
code += '\n';
|
||||||
|
|
||||||
|
let validRoutesCount = 0;
|
||||||
|
|
||||||
|
parentGatewaysList.forEach((parentRef, parentIndex) => {
|
||||||
|
const parent = getParentGateway(parentRef.id);
|
||||||
|
|
||||||
|
if (!parent) {
|
||||||
|
code += `# Родительский gateway/интерфейс "${parentRef.id}" не найден, пропущен\n`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Определяем IP родителя в зависимости от типа
|
||||||
|
const parentIp = parent.parentType === 'interface' ? parent.remoteIp : parent.ip;
|
||||||
|
|
||||||
|
if (!parentIp) {
|
||||||
|
const parentType = parent.parentType === 'interface' ? 'интерфейс' : 'gateway';
|
||||||
|
code += `# Родительский ${parentType} не имеет IP, пропущен\n`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
validRoutesCount++;
|
||||||
|
|
||||||
|
// Создаём маршрут для рекурсивного gateway
|
||||||
|
const parentLabel = parent.parentType === 'interface'
|
||||||
|
? `интерфейс ${parent.name || parent.interfaceType || 'interface'} (${parent.remoteIp})`
|
||||||
|
: `gateway ${parent.ip}`;
|
||||||
|
|
||||||
|
const distanceSuffix = parentRef.distance !== undefined && parentRef.distance !== null ? ` (distance: ${parentRef.distance})` : '';
|
||||||
|
code += `# Рекурсивный gateway: ${gateway.ip}${distanceSuffix}\n`;
|
||||||
|
code += `# Родительский: ${parentLabel}\n`;
|
||||||
|
|
||||||
|
// Формируем gateway: для интерфейсов добавляем имя через %, для gateway - только IP
|
||||||
|
const gatewayValue = parent.parentType === 'interface'
|
||||||
|
? `${parentIp}%${parent.name || parent.interfaceType || 'interface'}`
|
||||||
|
: parentIp;
|
||||||
|
|
||||||
|
// Создаём маршрут к IP рекурсивного gateway через родительский
|
||||||
|
const comment = gateway.description
|
||||||
|
? `Recursive: ${gateway.description} -> ${parentIp}${distanceSuffix}`
|
||||||
|
: `Recursive: ${gateway.ip} -> ${parentIp}${distanceSuffix}`;
|
||||||
|
|
||||||
|
// Формируем команду с distance, если указан
|
||||||
|
const distanceParam = parentRef.distance !== undefined && parentRef.distance !== null ? ` distance=${parentRef.distance}` : '';
|
||||||
|
code += `/ip route add dst-address=${gateway.ip}/32 gateway=${gatewayValue}${distanceParam} comment="${comment}"\n`;
|
||||||
|
|
||||||
|
// Если у рекурсивного gateway есть описание с указанием на default route
|
||||||
|
if (gateway.description && (gateway.description.toLowerCase().includes('default') || gateway.description.toLowerCase().includes('0.0.0.0'))) {
|
||||||
|
code += `# Дополнительный default route через рекурсивный gateway${distanceSuffix}\n`;
|
||||||
|
const defaultComment = gateway.description ? `Recursive: ${gateway.description} (default)${distanceSuffix}` : `Recursive: ${gateway.ip} (default)${distanceSuffix}`;
|
||||||
|
code += `/ip route add dst-address=0.0.0.0/0 gateway=${gatewayValue}${distanceParam} 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (gateway.type === 'direct') {
|
||||||
|
// Генерация кода для прямого gateway
|
||||||
|
if (!gateway.ip) {
|
||||||
|
notify.error('Gateway без IP адреса - невозможно сгенерировать код');
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let code = `# Прямой маршрут для gateway: ${gateway.ip}\n`;
|
||||||
|
if (server?.provider) {
|
||||||
|
code += `# Провайдер: ${server.provider}\n`;
|
||||||
|
}
|
||||||
|
if (server?.country) {
|
||||||
|
code += `# Страна: ${server.country}\n`;
|
||||||
|
}
|
||||||
|
code += '\n';
|
||||||
|
|
||||||
|
const comment = gateway.description
|
||||||
|
? `Direct: ${gateway.description}`
|
||||||
|
: `Direct: ${gateway.ip}`;
|
||||||
|
|
||||||
|
code += `/ip route add dst-address=0.0.0.0/0 gateway=${gateway.ip} comment="${comment}"\n`;
|
||||||
|
code += '\n';
|
||||||
|
code += '# Проверка созданного маршрута:\n';
|
||||||
|
code += '# /ip route print where comment~"Direct"\n';
|
||||||
|
|
||||||
|
blocks.push({
|
||||||
|
type: 'direct-route',
|
||||||
|
serverName,
|
||||||
|
server,
|
||||||
|
code
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notify.error('Неизвестный тип gateway');
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blocks.length === 0) {
|
||||||
|
notify.error('Нет данных для генерации кода MikroTik для этого gateway');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setGeneratedMikrotikCode(blocks);
|
||||||
|
setMikrotikCodeModalOpen(true);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error generating MikroTik code for gateway:', error);
|
||||||
|
notify.error('Ошибка при генерации кода MikroTik для этого gateway');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// === Копирование кода MikroTik ===
|
// === Копирование кода MikroTik ===
|
||||||
const handleCopyMikrotikCode = async (codeToCopy) => {
|
const handleCopyMikrotikCode = async (codeToCopy) => {
|
||||||
if (!codeToCopy) {
|
if (!codeToCopy) {
|
||||||
@@ -2132,6 +2291,13 @@ function NetworkConfigManager() {
|
|||||||
|
|
||||||
{/* Правая часть: действия */}
|
{/* Правая часть: действия */}
|
||||||
<div className="d-flex align-items-center gap-2 px-3 border-start" style={{ flexShrink: 0 }}>
|
<div className="d-flex align-items-center gap-2 px-3 border-start" style={{ flexShrink: 0 }}>
|
||||||
|
<button
|
||||||
|
className="btn btn-outline-primary btn-sm"
|
||||||
|
onClick={() => handleGenerateMikrotikCodeForGateway(gateway)}
|
||||||
|
>
|
||||||
|
<IconCode size={16} className="me-1" />
|
||||||
|
Код
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
className="btn btn-outline-secondary btn-sm"
|
className="btn btn-outline-secondary btn-sm"
|
||||||
onClick={() => handleEditGateway(gateway)}
|
onClick={() => handleEditGateway(gateway)}
|
||||||
@@ -2677,6 +2843,13 @@ function NetworkConfigManager() {
|
|||||||
</td>
|
</td>
|
||||||
<td className="text-end">
|
<td className="text-end">
|
||||||
<div className="btn-list gap-1 mb-0 justify-content-end">
|
<div className="btn-list gap-1 mb-0 justify-content-end">
|
||||||
|
<button
|
||||||
|
className="btn btn-ghost-primary btn-icon btn-sm"
|
||||||
|
onClick={() => handleGenerateMikrotikCodeForGateway(gateway)}
|
||||||
|
title="Код для MikroTik (только для этого gateway)"
|
||||||
|
>
|
||||||
|
<IconCode size={16} />
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
className="btn btn-ghost-primary btn-icon btn-sm"
|
className="btn btn-ghost-primary btn-icon btn-sm"
|
||||||
onClick={() => handleEditGateway(gateway)}
|
onClick={() => handleEditGateway(gateway)}
|
||||||
|
|||||||
Reference in New Issue
Block a user