refactor(NetworkConfigManager): streamline IP registry processing; enhance conflict detection and improve interface display with clearer server information
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m38s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m38s
This commit is contained in:
@@ -477,72 +477,55 @@ function NetworkConfigManager() {
|
|||||||
const ipRegistry = useMemo(() => {
|
const ipRegistry = useMemo(() => {
|
||||||
const registry = [];
|
const registry = [];
|
||||||
|
|
||||||
// Собираем IP из интерфейсов
|
// Собираем IP из интерфейсов - каждая запись это отдельное использование IP
|
||||||
(config.tunnelInterfaces || []).forEach(iface => {
|
(config.tunnelInterfaces || []).forEach(iface => {
|
||||||
const server = getServerInfo(iface.serverId);
|
|
||||||
const server2 = getServerInfo(iface.serverId2);
|
|
||||||
|
|
||||||
if (iface.localIp && iface.localIp.trim()) {
|
if (iface.localIp && iface.localIp.trim()) {
|
||||||
|
const server = getServerInfo(iface.serverId);
|
||||||
registry.push({
|
registry.push({
|
||||||
ip: iface.localIp.trim(),
|
ip: iface.localIp.trim(),
|
||||||
type: 'localIp',
|
type: 'localIp',
|
||||||
interface: iface,
|
interface: iface,
|
||||||
interfaceName: iface.name || iface.id,
|
interfaceName: iface.name || iface.id,
|
||||||
server: server,
|
server: server, // localIp привязан к serverId
|
||||||
server2: server2
|
serverName: server?.dns || server?.ip || iface.serverId || '—'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (iface.remoteIp && iface.remoteIp.trim()) {
|
if (iface.remoteIp && iface.remoteIp.trim()) {
|
||||||
|
const server2 = getServerInfo(iface.serverId2);
|
||||||
registry.push({
|
registry.push({
|
||||||
ip: iface.remoteIp.trim(),
|
ip: iface.remoteIp.trim(),
|
||||||
type: 'remoteIp',
|
type: 'remoteIp',
|
||||||
interface: iface,
|
interface: iface,
|
||||||
interfaceName: iface.name || iface.id,
|
interfaceName: iface.name || iface.id,
|
||||||
server: server,
|
server: server2, // remoteIp привязан к serverId2
|
||||||
server2: server2
|
serverName: server2?.dns || server2?.ip || iface.serverId2 || '—'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Группируем по IP адресам
|
// Проверяем конфликты (один IP используется в нескольких интерфейсах)
|
||||||
const groupedByIp = {};
|
const ipUsageMap = {};
|
||||||
registry.forEach(item => {
|
registry.forEach(item => {
|
||||||
if (!groupedByIp[item.ip]) {
|
if (!ipUsageMap[item.ip]) {
|
||||||
groupedByIp[item.ip] = {
|
ipUsageMap[item.ip] = [];
|
||||||
ip: item.ip,
|
|
||||||
interfaces: []
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
groupedByIp[item.ip].interfaces.push({
|
ipUsageMap[item.ip].push(item);
|
||||||
type: item.type,
|
|
||||||
interface: item.interface,
|
|
||||||
interfaceName: item.interfaceName,
|
|
||||||
server: item.server,
|
|
||||||
server2: item.server2
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Преобразуем в массив и сортируем
|
// Добавляем флаг конфликта
|
||||||
return Object.values(groupedByIp)
|
return registry.map(item => ({
|
||||||
.map(item => ({
|
...item,
|
||||||
...item,
|
hasConflict: ipUsageMap[item.ip].length > 1
|
||||||
usageCount: item.interfaces.length,
|
})).sort((a, b) => {
|
||||||
hasConflict: item.interfaces.length > 1, // Конфликт если IP используется в нескольких интерфейсах
|
// Сначала конфликты, потом по IP, потом по типу
|
||||||
// Собираем уникальные серверы для этого IP
|
if (a.hasConflict !== b.hasConflict) {
|
||||||
servers: [...new Set(
|
return a.hasConflict ? -1 : 1;
|
||||||
item.interfaces.flatMap(i => [
|
}
|
||||||
i.server?.dns || i.server?.ip || i.interface.serverId,
|
if (a.ip !== b.ip) {
|
||||||
i.server2?.dns || i.server2?.ip || i.interface.serverId2
|
|
||||||
].filter(Boolean))
|
|
||||||
)]
|
|
||||||
}))
|
|
||||||
.sort((a, b) => {
|
|
||||||
// Сначала конфликты, потом по IP
|
|
||||||
if (a.hasConflict !== b.hasConflict) {
|
|
||||||
return a.hasConflict ? -1 : 1;
|
|
||||||
}
|
|
||||||
return a.ip.localeCompare(b.ip);
|
return a.ip.localeCompare(b.ip);
|
||||||
});
|
}
|
||||||
|
return a.type.localeCompare(b.type);
|
||||||
|
});
|
||||||
}, [config.tunnelInterfaces, servers]);
|
}, [config.tunnelInterfaces, servers]);
|
||||||
|
|
||||||
// === Фильтрация реестра IP ===
|
// === Фильтрация реестра IP ===
|
||||||
@@ -551,7 +534,8 @@ function NetworkConfigManager() {
|
|||||||
const term = ipRegistrySearch.toLowerCase();
|
const term = ipRegistrySearch.toLowerCase();
|
||||||
return ipRegistry.filter(item =>
|
return ipRegistry.filter(item =>
|
||||||
item.ip.toLowerCase().includes(term) ||
|
item.ip.toLowerCase().includes(term) ||
|
||||||
item.interfaces.some(i => i.interfaceName.toLowerCase().includes(term))
|
item.interfaceName.toLowerCase().includes(term) ||
|
||||||
|
item.serverName.toLowerCase().includes(term)
|
||||||
);
|
);
|
||||||
}, [ipRegistry, ipRegistrySearch]);
|
}, [ipRegistry, ipRegistrySearch]);
|
||||||
|
|
||||||
@@ -2005,14 +1989,15 @@ function NetworkConfigManager() {
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>IP адрес</th>
|
<th>IP адрес</th>
|
||||||
|
<th>Тип</th>
|
||||||
<th>Сервер</th>
|
<th>Сервер</th>
|
||||||
<th>Интерфейсы</th>
|
<th>Интерфейс</th>
|
||||||
<th className="text-end">Действия</th>
|
<th className="text-end">Действия</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{filteredIpRegistry.map((item, idx) => (
|
{filteredIpRegistry.map((item, idx) => (
|
||||||
<tr key={`${item.ip}-${idx}`} className={item.hasConflict ? 'table-danger' : ''}>
|
<tr key={`${item.ip}-${item.type}-${item.interface.id}-${idx}`} className={item.hasConflict ? 'table-danger' : ''}>
|
||||||
<td>
|
<td>
|
||||||
<div className="d-flex align-items-center">
|
<div className="d-flex align-items-center">
|
||||||
<code className="fw-bold">{item.ip}</code>
|
<code className="fw-bold">{item.ip}</code>
|
||||||
@@ -2024,33 +2009,26 @@ function NetworkConfigManager() {
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div className="d-flex flex-column gap-1">
|
<span className={`badge ${item.type === 'localIp' ? 'bg-info-lt text-info' : 'bg-success-lt text-success'}`}>
|
||||||
{item.servers.map((serverName, sIdx) => (
|
{item.type === 'localIp' ? 'Local' : 'Remote'}
|
||||||
<div key={sIdx} className="d-flex align-items-center">
|
</span>
|
||||||
<IconServer size={14} className="text-muted me-1" />
|
</td>
|
||||||
<span>{serverName}</span>
|
<td>
|
||||||
</div>
|
<div className="d-flex align-items-center">
|
||||||
))}
|
<IconServer size={14} className="text-muted me-1" />
|
||||||
{item.servers.length === 0 && <span className="text-muted">—</span>}
|
<span>{item.serverName}</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div className="d-flex flex-column gap-1">
|
<div className="d-flex align-items-center gap-2">
|
||||||
{item.interfaces.map((usage, uIdx) => (
|
<span>{item.interfaceName}</span>
|
||||||
<div key={uIdx} className="d-flex align-items-center gap-2">
|
<button
|
||||||
<span className={`badge ${usage.type === 'localIp' ? 'bg-info-lt text-info' : 'bg-success-lt text-success'}`}>
|
className="btn btn-ghost-primary btn-icon btn-sm"
|
||||||
{usage.type === 'localIp' ? 'Local' : 'Remote'}
|
onClick={() => handleEditInterface(item.interface)}
|
||||||
</span>
|
title="Редактировать интерфейс"
|
||||||
<span>{usage.interfaceName}</span>
|
>
|
||||||
<button
|
<IconEdit size={14} />
|
||||||
className="btn btn-ghost-primary btn-icon btn-sm ms-auto"
|
</button>
|
||||||
onClick={() => handleEditInterface(usage.interface)}
|
|
||||||
title="Редактировать интерфейс"
|
|
||||||
>
|
|
||||||
<IconEdit size={14} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="text-end">
|
<td className="text-end">
|
||||||
|
|||||||
Reference in New Issue
Block a user