From 897dd52eac9c11386a2165cfba36494c4a77228b Mon Sep 17 00:00:00 2001 From: shats Date: Thu, 5 Mar 2026 16:47:11 +0700 Subject: [PATCH] feat(OspfToolsPage): implement optimal cost hints for OSPF routes and enhance loading logic --- frontend/src/OspfToolsPage.jsx | 100 +++++++++++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 4 deletions(-) diff --git a/frontend/src/OspfToolsPage.jsx b/frontend/src/OspfToolsPage.jsx index e53f517..b6948ae 100644 --- a/frontend/src/OspfToolsPage.jsx +++ b/frontend/src/OspfToolsPage.jsx @@ -76,13 +76,72 @@ function buildOspfTemplatesFromRouterResults(routerResults = [], servers = []) { return Array.from(byComposite.values()); } +function buildOptimalCostHints(routeOptimizerData, servers = []) { + const homes = Array.isArray(routeOptimizerData?.homes) ? routeOptimizerData.homes : []; + const byRef = new Map(); + (servers || []).forEach((server) => { + [server?.id, server?.ip, server?.dns] + .filter(Boolean) + .forEach((ref) => byRef.set(String(ref), server)); + }); + + const hints = {}; + + homes.forEach((entry) => { + const homeRefCandidates = [ + entry?.home?.id, + entry?.home?.ip, + entry?.home?.dns, + ].filter(Boolean).map(String); + const homeServer = homeRefCandidates.map((ref) => byRef.get(ref)).find(Boolean); + if (!homeServer) return; + + const identity = getOspfRouterIdentity(homeServer); + const candidates = Array.isArray(entry?.homeToJumphostCandidates) + ? [...entry.homeToJumphostCandidates] + : []; + candidates.sort((a, b) => + Number(b?.probabilityOptimal || 0) - Number(a?.probabilityOptimal || 0) || + Number(b?.score || 0) - Number(a?.score || 0) + ); + + const uniqueIface = []; + const seen = new Set(); + candidates.forEach((candidate) => { + const iface = String(candidate?.interfaceName || '').trim(); + if (!iface) return; + const key = iface.toUpperCase(); + if (seen.has(key)) return; + seen.add(key); + uniqueIface.push(candidate); + }); + + uniqueIface.forEach((candidate, idx) => { + const iface = String(candidate?.interfaceName || '').trim(); + const hintKey = `${identity.key}::${iface.toUpperCase()}`; + const nextHint = { + optimalCost: (idx + 1) * OSPF_COST_STEP, + probabilityOptimal: Number(candidate?.probabilityOptimal || 0), + }; + const prev = hints[hintKey]; + if (!prev || nextHint.probabilityOptimal > prev.probabilityOptimal) { + hints[hintKey] = nextHint; + } + }); + }); + + return hints; +} + export default function OspfToolsPage() { const notify = useNotify(); const [servers, setServers] = useState([]); const [templates, setTemplates] = useState([]); const [loading, setLoading] = useState(false); + const [optimizerLoading, setOptimizerLoading] = useState(false); const [saving, setSaving] = useState(false); const [dragging, setDragging] = useState(null); + const [optimalHints, setOptimalHints] = useState({}); const groupedByRouter = useMemo(() => { const grouped = {}; @@ -144,6 +203,21 @@ export default function OspfToolsPage() { } }; + const loadOptimizerHints = async ({ silent = false } = {}) => { + setOptimizerLoading(true); + try { + const response = await api.get('/route-optimizer', { timeout: 30000 }); + const hints = buildOptimalCostHints(response?.data, servers); + setOptimalHints(hints); + } catch (error) { + console.error('Failed to load route optimizer hints:', error); + setOptimalHints({}); + if (!silent) notify.warning('Не удалось загрузить рекомендации из оптимизатора маршрутов'); + } finally { + setOptimizerLoading(false); + } + }; + useEffect(() => { loadServers(); }, []); @@ -151,6 +225,7 @@ export default function OspfToolsPage() { useEffect(() => { if (servers.length === 0) return; loadOspf({ silent: true }); + loadOptimizerHints({ silent: true }); }, [servers.length]); const onDrop = (routerKey, area, targetKey) => { @@ -223,10 +298,13 @@ export default function OspfToolsPage() {