feat(network-config-manager): enhance OSPF router identity handling and improve template management logic
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m54s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m54s
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { useState, useEffect, useMemo, useRef } from 'react';
|
||||
import api from './lib/api.js';
|
||||
import { useNotify } from './components/NotifyProvider.jsx';
|
||||
import FormModal from './components/FormModal.jsx';
|
||||
@@ -291,6 +291,18 @@ function moveInArray(arr, fromIndex, toIndex) {
|
||||
return copy;
|
||||
}
|
||||
|
||||
function getOspfRouterIdentity(server) {
|
||||
if (!server || typeof server !== 'object') {
|
||||
return { key: 'unknown', label: 'Unknown router' };
|
||||
}
|
||||
const host = String(server.mikrotikHost || server.ip || server.dns || server.id || '').trim();
|
||||
const port = String(server.mikrotikPort || 80).trim();
|
||||
const user = String(server.mikrotikUser || 'admin').trim();
|
||||
const key = `${host}::${port}::${user}`;
|
||||
const label = host || String(server.dns || server.ip || server.id || 'Unknown router');
|
||||
return { key, label };
|
||||
}
|
||||
|
||||
// Пустые объекты
|
||||
const getEmptyGateway = () => ({
|
||||
id: `gw-${Date.now()}-${Math.random().toString(16).slice(2, 6)}`,
|
||||
@@ -392,6 +404,7 @@ function NetworkConfigManager() {
|
||||
const [ospfDraggingItem, setOspfDraggingItem] = useState(null);
|
||||
const [ospfLoading, setOspfLoading] = useState(false);
|
||||
const [ospfSaving, setOspfSaving] = useState(false);
|
||||
const ospfAutoLoadedRef = useRef(false);
|
||||
|
||||
// === IPSec Passwords Management ===
|
||||
const [ipsecPasswords, setIpsecPasswords] = useState([]);
|
||||
@@ -669,28 +682,45 @@ function NetworkConfigManager() {
|
||||
});
|
||||
}, [homeOspfCandidatesByServer]);
|
||||
|
||||
const ospfTemplatesByHomeServer = useMemo(() => {
|
||||
const ospfTemplatesByRouter = useMemo(() => {
|
||||
const grouped = {};
|
||||
(config.ospfInterfaceTemplates || []).forEach((item) => {
|
||||
const sid = String(item?.serverId || '');
|
||||
if (!sid) return;
|
||||
if (!grouped[sid]) grouped[sid] = [];
|
||||
grouped[sid].push(item);
|
||||
const server = getServerInfo(sid);
|
||||
const identity = getOspfRouterIdentity(server || { id: sid, ip: sid });
|
||||
if (!grouped[identity.key]) {
|
||||
grouped[identity.key] = {
|
||||
routerKey: identity.key,
|
||||
routerLabel: identity.label,
|
||||
serverIds: new Set(),
|
||||
representativeServerId: sid,
|
||||
items: [],
|
||||
};
|
||||
}
|
||||
grouped[identity.key].serverIds.add(sid);
|
||||
grouped[identity.key].items.push(item);
|
||||
});
|
||||
Object.keys(grouped).forEach((sid) => {
|
||||
grouped[sid].sort((a, b) => Number(b?.cost || 0) - Number(a?.cost || 0));
|
||||
|
||||
const groups = Object.values(grouped).map((group) => {
|
||||
const sorted = [...group.items].sort((a, b) => Number(b?.cost || 0) - Number(a?.cost || 0));
|
||||
return {
|
||||
...group,
|
||||
serverIds: Array.from(group.serverIds),
|
||||
items: sorted,
|
||||
};
|
||||
});
|
||||
return grouped;
|
||||
}, [config.ospfInterfaceTemplates]);
|
||||
|
||||
const ospfHomeServerIds = useMemo(() => {
|
||||
let ids = Object.keys(homeOspfCandidatesByServer);
|
||||
if (serverFilter) ids = ids.filter((sid) => String(sid) === String(serverFilter));
|
||||
return ids;
|
||||
}, [homeOspfCandidatesByServer, serverFilter]);
|
||||
if (serverFilter) {
|
||||
const filterId = String(serverFilter);
|
||||
return groups.filter((group) => group.serverIds.includes(filterId));
|
||||
}
|
||||
|
||||
const handleDropOspfTemplate = (serverId, targetKey) => {
|
||||
if (!ospfDraggingItem || String(ospfDraggingItem.serverId) !== String(serverId)) return;
|
||||
return groups;
|
||||
}, [config.ospfInterfaceTemplates, serverFilter, servers]);
|
||||
|
||||
const handleDropOspfTemplate = (routerKey, targetKey) => {
|
||||
if (!ospfDraggingItem || String(ospfDraggingItem.routerKey) !== String(routerKey)) return;
|
||||
const draggedKey = String(ospfDraggingItem.key || '');
|
||||
const destinationKey = String(targetKey || '');
|
||||
if (!draggedKey || !destinationKey || draggedKey === destinationKey) {
|
||||
@@ -700,7 +730,12 @@ function NetworkConfigManager() {
|
||||
|
||||
setConfig((prev) => {
|
||||
const current = (prev.ospfInterfaceTemplates || [])
|
||||
.filter((item) => String(item.serverId || '') === String(serverId))
|
||||
.filter((item) => {
|
||||
const sid = String(item.serverId || '');
|
||||
const server = getServerInfo(sid);
|
||||
const identity = getOspfRouterIdentity(server || { id: sid, ip: sid });
|
||||
return identity.key === String(routerKey);
|
||||
})
|
||||
.sort((a, b) => Number(b?.cost || 0) - Number(a?.cost || 0));
|
||||
const fromIndex = current.findIndex((item) => String(item.key || '') === draggedKey);
|
||||
const toIndex = current.findIndex((item) => String(item.key || '') === destinationKey);
|
||||
@@ -711,9 +746,12 @@ function NetworkConfigManager() {
|
||||
cost: (arr.length - idx) * OSPF_COST_STEP,
|
||||
}));
|
||||
|
||||
const otherServers = (prev.ospfInterfaceTemplates || []).filter(
|
||||
(item) => String(item.serverId || '') !== String(serverId)
|
||||
);
|
||||
const otherServers = (prev.ospfInterfaceTemplates || []).filter((item) => {
|
||||
const sid = String(item.serverId || '');
|
||||
const server = getServerInfo(sid);
|
||||
const identity = getOspfRouterIdentity(server || { id: sid, ip: sid });
|
||||
return identity.key !== String(routerKey);
|
||||
});
|
||||
|
||||
return {
|
||||
...prev,
|
||||
@@ -724,7 +762,7 @@ function NetworkConfigManager() {
|
||||
setOspfDraggingItem(null);
|
||||
};
|
||||
|
||||
const handleLoadOspfFromMikrotik = async () => {
|
||||
const handleLoadOspfFromMikrotik = async ({ silent = false } = {}) => {
|
||||
setOspfLoading(true);
|
||||
try {
|
||||
const response = await api.get('/mikrotik/ospf-interface-templates');
|
||||
@@ -736,25 +774,26 @@ function NetworkConfigManager() {
|
||||
ospfInterfaceTemplates: mergeOspfCostsFromRouter(prev.ospfInterfaceTemplates || [], results),
|
||||
}));
|
||||
|
||||
if (results.length > 0) {
|
||||
if (!silent && results.length > 0) {
|
||||
notify.success(`OSPF загружен с HOME роутеров: ${results.length}`);
|
||||
} else {
|
||||
} else if (!silent) {
|
||||
notify.warning('Не удалось получить OSPF шаблоны ни с одного HOME роутера');
|
||||
}
|
||||
if (errors.length > 0) {
|
||||
if (!silent && errors.length > 0) {
|
||||
notify.warning(`Ошибки чтения OSPF: ${errors.length} роутер(ов)`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading OSPF templates:', error);
|
||||
notify.error('Не удалось загрузить OSPF Interface Template с MikroTik');
|
||||
if (!silent) {
|
||||
notify.error('Не удалось загрузить OSPF Interface Template с MikroTik');
|
||||
}
|
||||
} finally {
|
||||
setOspfLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveOspfToMikrotik = async () => {
|
||||
const serverEntries = Object.entries(ospfTemplatesByHomeServer);
|
||||
if (serverEntries.length === 0) {
|
||||
if (ospfTemplatesByRouter.length === 0) {
|
||||
notify.warning('Нет OSPF шаблонов для сохранения');
|
||||
return;
|
||||
}
|
||||
@@ -762,13 +801,23 @@ function NetworkConfigManager() {
|
||||
setOspfSaving(true);
|
||||
try {
|
||||
const payload = {
|
||||
servers: serverEntries.map(([serverId, templates]) => ({
|
||||
serverId,
|
||||
templates: templates.map((item) => ({
|
||||
interfaceName: item.interfaceName,
|
||||
cost: item.cost,
|
||||
})),
|
||||
})),
|
||||
servers: ospfTemplatesByRouter.map((group) => {
|
||||
const dedup = new Map();
|
||||
group.items.forEach((item) => {
|
||||
const key = String(item.interfaceName || '').trim().toUpperCase();
|
||||
if (!key) return;
|
||||
if (!dedup.has(key)) {
|
||||
dedup.set(key, {
|
||||
interfaceName: item.interfaceName,
|
||||
cost: item.cost,
|
||||
});
|
||||
}
|
||||
});
|
||||
return {
|
||||
serverId: group.representativeServerId,
|
||||
templates: Array.from(dedup.values()),
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
const response = await api.post('/mikrotik/ospf-interface-templates/apply', payload);
|
||||
@@ -791,6 +840,13 @@ function NetworkConfigManager() {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTab !== 'interfaces') return;
|
||||
if (ospfAutoLoadedRef.current) return;
|
||||
ospfAutoLoadedRef.current = true;
|
||||
handleLoadOspfFromMikrotik({ silent: true });
|
||||
}, [activeTab]);
|
||||
|
||||
// === Группировка по серверам ===
|
||||
const gatewaysByServer = useMemo(() => {
|
||||
const grouped = {};
|
||||
@@ -4010,20 +4066,19 @@ function NetworkConfigManager() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
{ospfHomeServerIds.length === 0 ? (
|
||||
{ospfTemplatesByRouter.length === 0 ? (
|
||||
<div className="text-muted">
|
||||
Для HOME роутеров пока нет GRE-интерфейсов. Создайте GRE-туннели, и шаблоны появятся автоматически.
|
||||
</div>
|
||||
) : (
|
||||
<div className="row g-3">
|
||||
{ospfHomeServerIds.map((serverId) => {
|
||||
const server = getServerInfo(serverId);
|
||||
const templates = ospfTemplatesByHomeServer[serverId] || [];
|
||||
{ospfTemplatesByRouter.map((group) => {
|
||||
const templates = group.items || [];
|
||||
return (
|
||||
<div key={`ospf-home-${serverId}`} className="col-12 col-xl-6">
|
||||
<div key={`ospf-router-${group.routerKey}`} className="col-12 col-xl-6">
|
||||
<div className="card card-sm border">
|
||||
<div className="card-header">
|
||||
<div className="fw-semibold">{server?.dns || server?.ip || serverId}</div>
|
||||
<div className="fw-semibold">{group.routerLabel}</div>
|
||||
</div>
|
||||
<div className="list-group list-group-flush">
|
||||
{templates.length === 0 ? (
|
||||
@@ -4037,10 +4092,10 @@ function NetworkConfigManager() {
|
||||
className="list-group-item d-flex align-items-center justify-content-between"
|
||||
draggable
|
||||
onDragStart={() =>
|
||||
setOspfDraggingItem({ serverId, key: template.key })
|
||||
setOspfDraggingItem({ routerKey: group.routerKey, key: template.key })
|
||||
}
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
onDrop={() => handleDropOspfTemplate(serverId, template.key)}
|
||||
onDrop={() => handleDropOspfTemplate(group.routerKey, template.key)}
|
||||
onDragEnd={() => setOspfDraggingItem(null)}
|
||||
style={{ cursor: 'grab' }}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user