From baa230c0d21b5324a7fbe8b7fccc91398a284b19 Mon Sep 17 00:00:00 2001 From: shats Date: Thu, 5 Mar 2026 11:08:16 +0700 Subject: [PATCH] feat(route-optimizer): implement community full routes display with optimized scoring and detailed metrics in UI --- frontend/src/RouteOptimizerPage.jsx | 131 ++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/frontend/src/RouteOptimizerPage.jsx b/frontend/src/RouteOptimizerPage.jsx index d927d5a..de1f4ae 100644 --- a/frontend/src/RouteOptimizerPage.jsx +++ b/frontend/src/RouteOptimizerPage.jsx @@ -29,6 +29,10 @@ function fmtSpeed(dl, ul, fallback) { return `speed: ${fallback ?? '—'} Mbps`; } +function routeNodeKey(node) { + return String(node?.id || node?.dns || node?.ip || node?.label || '').trim(); +} + const OPT_TABLE_STYLE = { tableLayout: 'fixed', width: '100%' }; const OPT_COLS = ['24%', '20%', '22%', '18%', '16%']; @@ -72,6 +76,56 @@ export default function RouteOptimizerPage() { const communityOptimization = Array.isArray(data?.communityOptimizationByJumphost) ? data.communityOptimizationByJumphost : []; + const communityFullRoutes = (() => { + const out = []; + const homesSafe = Array.isArray(homes) ? homes : []; + const byJh = new Map(); + homesSafe.forEach((h) => { + const home = h?.home || null; + const routes = Array.isArray(h?.fullRouteCandidates) ? h.fullRouteCandidates : []; + routes.forEach((r) => { + const jhKey = routeNodeKey(r?.jumphost); + const exKey = routeNodeKey(r?.exit); + if (!jhKey || !exKey) return; + const mapKey = `${jhKey}:::${exKey}`; + const prev = byJh.get(mapKey); + if (!prev || Number(r?.score || 0) > Number(prev?.route?.score || 0)) { + byJh.set(mapKey, { home, route: r }); + } + }); + }); + + communityOptimization.forEach((item) => { + const jh = item?.jumphost || null; + const jhKey = routeNodeKey(jh); + const rows = Array.isArray(item?.recommendations) ? item.recommendations : []; + rows.forEach((r) => { + const rec = r?.recommended || null; + const exKey = routeNodeKey(rec?.exit); + if (!rec || !jhKey || !exKey) return; + const bestFull = byJh.get(`${jhKey}:::${exKey}`); + if (!bestFull?.route) return; + out.push({ + community: r.community, + communityInfo: r.communityInfo, + home: bestFull.home || bestFull.route.home, + jumphost: bestFull.route.jumphost || jh, + exit: bestFull.route.exit || rec.exit, + fullRouteScore: bestFull.route.score, + fullRouteProbability: bestFull.route.probabilityOptimal, + fullRouteConfidence: bestFull.route.confidence, + homeToJumphost: bestFull.route.homeToJumphost || null, + jumphostToExit: bestFull.route.jumphostToExit || null, + recommendedGateway: rec.gateway || null, + tunnelGateway: rec.tunnelGateway || null, + pinnedBySettings: Boolean(r?.pinnedBySettings), + shouldSwitch: Boolean(r?.shouldSwitch), + }); + }); + }); + + return out.sort((a, b) => Number(b.fullRouteScore || 0) - Number(a.fullRouteScore || 0)); + })(); return (
@@ -265,6 +319,83 @@ export default function RouteOptimizerPage() { })}
+
+

Оптимизированные маршруты Home -> JumpHost -> Exit по BGP community

+
+ Таблица показывает лучший полный маршрут для каждой community с учетом рекомендованного + gateway на Jumphost. +
+
+ + + + + + + + + + + + + {communityFullRoutes.length === 0 ? ( + + + + ) : communityFullRoutes.map((r, idx) => ( + + + + + + + + ))} + +
CommunityМаршрут (Home -> JumpHost -> Exit)СегментыGateway / ДействиеВероятность
Недостаточно данных для построения полных маршрутов по community.
+
{r.community}
+ {(r.communityInfo?.name || r.communityInfo?.description) && ( +
{r.communityInfo?.name || r.communityInfo?.description}
+ )} +
+ {r.home?.label || r.home?.dns || r.home?.ip || 'home'} + + {r.jumphost?.label || r.jumphost?.dns || r.jumphost?.ip || 'jumphost'} + + {r.exit?.label || r.exit?.dns || r.exit?.ip || 'exit'} + +
+ H→J: ping {r.homeToJumphost?.pingMs ?? '—'} ms, {fmtSpeed( + r.homeToJumphost?.speedDownloadMbps, + r.homeToJumphost?.speedUploadMbps, + r.homeToJumphost?.speedMbps + )} +
+
+ J→E: ping {r.jumphostToExit?.pingMs ?? '—'} ms, {fmtSpeed( + r.jumphostToExit?.speedDownloadMbps, + r.jumphostToExit?.speedUploadMbps, + r.jumphostToExit?.speedMbps + )} +
+
+
{r.recommendedGateway || '—'}
+ {r.tunnelGateway &&
{'->'} {r.tunnelGateway}
} +
+ {r.pinnedBySettings ? ( + pin + ) : r.shouldSwitch ? ( + switch + ) : ( + ok + )} +
+
+
{fmtProb(r.fullRouteProbability)}
+
score {r.fullRouteScore} / conf {r.fullRouteConfidence ?? '—'}
+
+
+

Оптимизация по community и filters