feat(NetworkConfigManager): add server-wide MikroTik code generation for interfaces, improving bulk configuration capabilities and user feedback
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m40s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m40s
This commit is contained in:
@@ -1836,6 +1836,62 @@ function NetworkConfigManager() {
|
||||
}
|
||||
};
|
||||
|
||||
// === Генерация кода MikroTik для всех интерфейсов сервера ===
|
||||
const handleGenerateMikrotikCodeForServer = async (serverId, type) => {
|
||||
if (!serverId) {
|
||||
notify.error('Сервер не указан');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
if (type === 'interface') {
|
||||
// Получаем все интерфейсы для этого сервера (где сервер участвует как первый или второй)
|
||||
// Фильтруем только те, где оба сервера указаны и есть IP адреса
|
||||
const serverInterfaces = (config.tunnelInterfaces || []).filter(iface =>
|
||||
(iface.serverId === serverId || iface.serverId2 === serverId) &&
|
||||
iface.serverId && iface.serverId2 && iface.localIp && iface.remoteIp
|
||||
);
|
||||
|
||||
if (serverInterfaces.length === 0) {
|
||||
notify.error('Нет интерфейсов с полной конфигурацией для этого сервера');
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const blocks = await generateMikrotikInterfaceAddressesForSelection(serverInterfaces);
|
||||
if (!blocks || blocks.length === 0) {
|
||||
notify.error('Нет данных для генерации кода MikroTik для этого сервера');
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Фильтруем блоки, чтобы показать только те, что относятся к данному серверу
|
||||
const server = getServerInfo(serverId);
|
||||
const serverName = server?.dns || server?.ip || serverId;
|
||||
const filteredBlocks = blocks.filter(block =>
|
||||
block.serverName === serverName || block.server?.id === serverId
|
||||
);
|
||||
|
||||
if (filteredBlocks.length === 0) {
|
||||
notify.error('Не удалось сгенерировать код для этого сервера');
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setGeneratedMikrotikCode(filteredBlocks);
|
||||
setMikrotikCodeModalOpen(true);
|
||||
} else {
|
||||
notify.error('Генерация кода для gateways по серверу пока не реализована');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error generating MikroTik code for server:', error);
|
||||
notify.error('Ошибка при генерации кода MikroTik для этого сервера');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// === Генерация кода MikroTik только для одного интерфейса ===
|
||||
const handleGenerateMikrotikCodeForInterface = async (iface) => {
|
||||
if (!iface) {
|
||||
@@ -2186,55 +2242,93 @@ function NetworkConfigManager() {
|
||||
const isUnassigned = serverId === '__unassigned__';
|
||||
|
||||
return (
|
||||
<div key={serverId} className="mb-4">
|
||||
{/* Server header */}
|
||||
<div className="d-flex align-items-center mb-2 px-1">
|
||||
<div className="d-flex align-items-center">
|
||||
{isUnassigned ? (
|
||||
<>
|
||||
<span className="avatar avatar-sm bg-secondary-lt me-2">
|
||||
<IconServer size={16} className="text-muted" />
|
||||
</span>
|
||||
<span className="text-muted">Без привязки к серверу</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="avatar avatar-sm bg-primary-lt me-2">
|
||||
<IconServer size={16} className="text-primary" />
|
||||
</span>
|
||||
<div>
|
||||
<span className="fw-medium">
|
||||
{server?.dns ? server.dns.split('.')[0] : (server?.name || serverId)}
|
||||
<div key={serverId} className="card mb-3">
|
||||
{/* Server header - более заметный и читаемый */}
|
||||
<div
|
||||
className="card-header py-3"
|
||||
style={{
|
||||
backgroundColor: 'rgba(32, 107, 196, 0.08)',
|
||||
borderBottom: '2px solid rgba(32, 107, 196, 0.2)'
|
||||
}}
|
||||
>
|
||||
<div className="d-flex align-items-center justify-content-between">
|
||||
<div className="d-flex align-items-center flex-grow-1">
|
||||
{isUnassigned ? (
|
||||
<>
|
||||
<span className="avatar avatar-md bg-secondary-lt me-3">
|
||||
<IconServer size={20} className="text-muted" />
|
||||
</span>
|
||||
{server?.dns && (
|
||||
<span className="text-muted ms-2 small">
|
||||
{server.dns}
|
||||
</span>
|
||||
)}
|
||||
{server && (
|
||||
<span className="text-muted ms-2 small">
|
||||
{server.ip}
|
||||
{server.country && (
|
||||
<span className="ms-1">{countryToFlag(server.country)} {server.country}</span>
|
||||
<div>
|
||||
<h4 className="mb-0 fw-semibold text-muted">Без привязки к серверу</h4>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="avatar avatar-md bg-primary-lt me-3">
|
||||
<IconServer size={20} className="text-primary" />
|
||||
</span>
|
||||
<div className="flex-grow-1">
|
||||
<div className="d-flex align-items-center flex-wrap gap-2">
|
||||
<h4 className="mb-0 fw-semibold">
|
||||
{server?.dns ? server.dns.split('.')[0] : (server?.name || serverId)}
|
||||
</h4>
|
||||
{server?.dns && (
|
||||
<code className="text-muted small" style={{ fontSize: '0.875rem' }}>
|
||||
{server.dns}
|
||||
</code>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{server?.ip && (
|
||||
<code className="text-muted small" style={{ fontSize: '0.875rem' }}>
|
||||
{server.ip}
|
||||
</code>
|
||||
)}
|
||||
{server?.country && (
|
||||
<span className="badge bg-blue-lt text-blue" style={{ fontSize: '1rem' }}>
|
||||
{countryToFlag(server.country)} {server.country}
|
||||
</span>
|
||||
)}
|
||||
{server?.provider && (
|
||||
<span className={`badge bg-${getProviderColor(server.provider)}-lt text-${getProviderColor(server.provider)}`}>
|
||||
{server.provider}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="d-flex align-items-center gap-2">
|
||||
<span className="badge bg-primary-lt text-primary" style={{ fontSize: '0.875rem', padding: '6px 12px' }}>
|
||||
{items.length} {type === 'gateway' ? (items.length === 1 ? 'gateway' : 'gateways') : (items.length === 1 ? 'интерфейс' : 'интерфейсов')}
|
||||
</span>
|
||||
{type === 'interface' && !isUnassigned && items.length > 0 && (
|
||||
<button
|
||||
className="btn btn-outline-primary btn-sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleGenerateMikrotikCodeForServer(serverId, type);
|
||||
}}
|
||||
title="Получить код MikroTik для всех интерфейсов этого сервера"
|
||||
>
|
||||
<IconCode size={16} className="me-1" />
|
||||
Код для MikroTik
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<span className="badge bg-secondary-lt ms-auto">{items.length}</span>
|
||||
</div>
|
||||
|
||||
{/* Items */}
|
||||
<div className="row row-cards">
|
||||
{items.map(item => (
|
||||
<div key={item.id} className="col-12 col-md-6 col-lg-6">
|
||||
{type === 'gateway' ? renderGatewayCard(item) :
|
||||
type === 'interface' ? renderInterfaceCard(item, serverId) :
|
||||
type === 'pool' ? renderPoolCard(item) : null}
|
||||
</div>
|
||||
))}
|
||||
<div className="card-body p-3">
|
||||
<div className="row row-cards">
|
||||
{items.map(item => (
|
||||
<div key={item.id} className="col-12 col-md-6 col-lg-6">
|
||||
{type === 'gateway' ? renderGatewayCard(item) :
|
||||
type === 'interface' ? renderInterfaceCard(item, serverId) :
|
||||
type === 'pool' ? renderPoolCard(item) : null}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user