refactor(NetworkConfigManager): improve MikroTik code copying functionality and group blocks by server; enhance interface card rendering for dual server support
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m38s

This commit is contained in:
2026-01-22 13:55:06 +07:00
parent 9328e205a0
commit 75a7778148
+75 -18
View File
@@ -718,25 +718,20 @@ function NetworkConfigManager() {
};
// === Копирование кода MikroTik ===
const handleCopyMikrotikCode = async () => {
// Объединяем все блоки в одну строку для копирования
const fullCode = Array.isArray(generatedMikrotikCode) && generatedMikrotikCode.length > 0
? generatedMikrotikCode.map(block => block.code).join('\n\n')
: '';
if (!fullCode) {
const handleCopyMikrotikCode = async (codeToCopy) => {
if (!codeToCopy) {
notify.error('Нет кода для копирования');
return;
}
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(fullCode);
await navigator.clipboard.writeText(codeToCopy);
notify.success('Код скопирован в буфер обмена!');
} else {
// Fallback для старых браузеров
const textArea = document.createElement('textarea');
textArea.value = fullCode;
textArea.value = codeToCopy;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
document.body.appendChild(textArea);
@@ -756,6 +751,40 @@ function NetworkConfigManager() {
}
};
// === Группировка блоков по серверам ===
const groupedMikrotikCode = useMemo(() => {
if (!Array.isArray(generatedMikrotikCode) || generatedMikrotikCode.length === 0) {
return {};
}
const grouped = {};
generatedMikrotikCode.forEach(block => {
const serverName = block.serverName || '__unknown__';
if (!grouped[serverName]) {
grouped[serverName] = {
server: block.server,
serverName: serverName,
blocks: []
};
}
grouped[serverName].blocks.push(block);
});
return grouped;
}, [generatedMikrotikCode]);
// === Получение названия типа блока ===
const getBlockTypeLabel = (type) => {
switch (type) {
case 'recursive-routes':
return 'Рекурсивные маршруты';
case 'interface-addresses':
return 'IP адреса интерфейсов';
default:
return 'Конфигурация';
}
};
// === Получение цвета провайдера ===
const getProviderColor = (provider) => {
if (!provider) return 'secondary';
@@ -820,7 +849,7 @@ function NetworkConfigManager() {
{/* Items */}
<div className="row row-cards">
{items.map(item => (
type === 'gateway' ? renderGatewayCard(item) : renderInterfaceCard(item)
type === 'gateway' ? renderGatewayCard(item) : renderInterfaceCard(item, serverId)
))}
</div>
</div>
@@ -955,10 +984,38 @@ function NetworkConfigManager() {
};
// === Рендер карточки Interface ===
const renderInterfaceCard = (iface) => {
const renderInterfaceCard = (iface, currentServerId) => {
const server = getServerInfo(iface.serverId);
const server2 = getServerInfo(iface.serverId2);
// Определяем, для какого сервера отображается карточка
// Сравниваем currentServerId с serverId2 используя функцию getServerInfo для надежности
const isForServer2 = currentServerId && iface.serverId2 && (() => {
const currentServer = getServerInfo(currentServerId);
const targetServer2 = getServerInfo(iface.serverId2);
if (!currentServer || !targetServer2) return false;
// Сравниваем по id, ip или dns
return (
currentServer.id === targetServer2.id ||
currentServer.ip === targetServer2.ip ||
currentServer.dns === targetServer2.dns ||
currentServerId === iface.serverId2 ||
currentServerId === targetServer2.id ||
currentServerId === targetServer2.ip ||
currentServerId === targetServer2.dns
);
})();
// Имя интерфейса зависит от того, для какого сервера отображается
const interfaceName = isForServer2
? (iface.name2 || iface.name || '—')
: (iface.name || '—');
// IP адреса также зависят от того, для какого сервера отображается
// Для второго сервера localIp и remoteIp меняются местами
const displayLocalIp = isForServer2 ? (iface.remoteIp || '—') : (iface.localIp || '—');
const displayRemoteIp = isForServer2 ? (iface.localIp || '—') : (iface.remoteIp || '—');
return (
<div key={iface.id} className="col-sm-6 col-lg-4">
<div className="card card-sm">
@@ -968,7 +1025,7 @@ function NetworkConfigManager() {
<IconRouter size={16} className={`text-${getInterfaceTypeColor(iface.type)}`} />
</span>
<div className="flex-fill">
<div className="fw-medium">{iface.name || '—'}</div>
<div className="fw-medium">{interfaceName}</div>
<div className="text-muted small">
<span className={`badge bg-${getInterfaceTypeColor(iface.type)}-lt text-${getInterfaceTypeColor(iface.type)}`}>
{iface.type}
@@ -994,11 +1051,11 @@ function NetworkConfigManager() {
<div className="col-6">
<div className="text-muted">Local IP</div>
<div className="d-flex align-items-center gap-1">
<code className="text-truncate">{iface.localIp || '—'}</code>
{iface.localIp && (
<code className="text-truncate">{displayLocalIp}</code>
{displayLocalIp && displayLocalIp !== '—' && (
<button
className="btn btn-ghost-secondary btn-icon p-0"
onClick={() => copyToClipboard(iface.localIp)}
onClick={() => copyToClipboard(displayLocalIp)}
style={{ width: 20, height: 20 }}
>
<IconCopy size={12} />
@@ -1009,11 +1066,11 @@ function NetworkConfigManager() {
<div className="col-6">
<div className="text-muted">Remote IP</div>
<div className="d-flex align-items-center gap-1">
<code className="text-truncate">{iface.remoteIp || '—'}</code>
{iface.remoteIp && (
<code className="text-truncate">{displayRemoteIp}</code>
{displayRemoteIp && displayRemoteIp !== '—' && (
<button
className="btn btn-ghost-secondary btn-icon p-0"
onClick={() => copyToClipboard(iface.remoteIp)}
onClick={() => copyToClipboard(displayRemoteIp)}
style={{ width: 20, height: 20 }}
>
<IconCopy size={12} />