feat(route-optimizer): implement community full routes display with optimized scoring and detailed metrics in UI
Publish Fast Tabler Docker image / build-and-push-fast (push) Failing after 57s
Publish Fast Tabler Docker image / build-and-push-fast (push) Failing after 57s
This commit is contained in:
@@ -29,6 +29,10 @@ function fmtSpeed(dl, ul, fallback) {
|
|||||||
return `speed: ${fallback ?? '—'} Mbps`;
|
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_TABLE_STYLE = { tableLayout: 'fixed', width: '100%' };
|
||||||
const OPT_COLS = ['24%', '20%', '22%', '18%', '16%'];
|
const OPT_COLS = ['24%', '20%', '22%', '18%', '16%'];
|
||||||
|
|
||||||
@@ -72,6 +76,56 @@ export default function RouteOptimizerPage() {
|
|||||||
const communityOptimization = Array.isArray(data?.communityOptimizationByJumphost)
|
const communityOptimization = Array.isArray(data?.communityOptimizationByJumphost)
|
||||||
? 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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -265,6 +319,83 @@ export default function RouteOptimizerPage() {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4">
|
||||||
|
<h3 className="mb-3">Оптимизированные маршруты Home -> JumpHost -> Exit по BGP community</h3>
|
||||||
|
<div className="text-muted small mb-3">
|
||||||
|
Таблица показывает лучший полный маршрут для каждой community с учетом рекомендованного
|
||||||
|
gateway на Jumphost.
|
||||||
|
</div>
|
||||||
|
<div className="table-responsive mb-4">
|
||||||
|
<table className="table table-vcenter card-table align-middle" style={OPT_TABLE_STYLE}>
|
||||||
|
<OptimizerColgroup />
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Community</th>
|
||||||
|
<th>Маршрут (Home -> JumpHost -> Exit)</th>
|
||||||
|
<th>Сегменты</th>
|
||||||
|
<th>Gateway / Действие</th>
|
||||||
|
<th>Вероятность</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{communityFullRoutes.length === 0 ? (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={5} className="text-muted">Недостаточно данных для построения полных маршрутов по community.</td>
|
||||||
|
</tr>
|
||||||
|
) : communityFullRoutes.map((r, idx) => (
|
||||||
|
<tr key={`${r.community}-${routeNodeKey(r.home)}-${routeNodeKey(r.jumphost)}-${routeNodeKey(r.exit)}-${idx}`}>
|
||||||
|
<td className="text-break">
|
||||||
|
<div className="fw-semibold">{r.community}</div>
|
||||||
|
{(r.communityInfo?.name || r.communityInfo?.description) && (
|
||||||
|
<div className="small text-muted">{r.communityInfo?.name || r.communityInfo?.description}</div>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td className="small text-break">
|
||||||
|
<span className="fw-semibold">{r.home?.label || r.home?.dns || r.home?.ip || 'home'}</span>
|
||||||
|
<span className="text-muted"> → </span>
|
||||||
|
<span className="fw-semibold">{r.jumphost?.label || r.jumphost?.dns || r.jumphost?.ip || 'jumphost'}</span>
|
||||||
|
<span className="text-muted"> → </span>
|
||||||
|
<span className="fw-semibold">{r.exit?.label || r.exit?.dns || r.exit?.ip || 'exit'}</span>
|
||||||
|
</td>
|
||||||
|
<td className="small text-break">
|
||||||
|
<div>
|
||||||
|
H→J: ping {r.homeToJumphost?.pingMs ?? '—'} ms, {fmtSpeed(
|
||||||
|
r.homeToJumphost?.speedDownloadMbps,
|
||||||
|
r.homeToJumphost?.speedUploadMbps,
|
||||||
|
r.homeToJumphost?.speedMbps
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="text-muted">
|
||||||
|
J→E: ping {r.jumphostToExit?.pingMs ?? '—'} ms, {fmtSpeed(
|
||||||
|
r.jumphostToExit?.speedDownloadMbps,
|
||||||
|
r.jumphostToExit?.speedUploadMbps,
|
||||||
|
r.jumphostToExit?.speedMbps
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="small text-break">
|
||||||
|
<div>{r.recommendedGateway || '—'}</div>
|
||||||
|
{r.tunnelGateway && <div className="text-muted">{'->'} {r.tunnelGateway}</div>}
|
||||||
|
<div className="mt-1">
|
||||||
|
{r.pinnedBySettings ? (
|
||||||
|
<span className="badge bg-blue-lt text-blue">pin</span>
|
||||||
|
) : r.shouldSwitch ? (
|
||||||
|
<span className="badge bg-orange-lt text-orange">switch</span>
|
||||||
|
) : (
|
||||||
|
<span className="badge bg-green-lt text-green">ok</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="small">
|
||||||
|
<div>{fmtProb(r.fullRouteProbability)}</div>
|
||||||
|
<div className="text-muted">score {r.fullRouteScore} / conf {r.fullRouteConfidence ?? '—'}</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<h3 className="mb-3">Оптимизация по community и filters</h3>
|
<h3 className="mb-3">Оптимизация по community и filters</h3>
|
||||||
<div className="text-muted small mb-3">
|
<div className="text-muted small mb-3">
|
||||||
|
|||||||
Reference in New Issue
Block a user