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`;
|
||||
}
|
||||
|
||||
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 (
|
||||
<div>
|
||||
@@ -265,6 +319,83 @@ export default function RouteOptimizerPage() {
|
||||
})}
|
||||
</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">
|
||||
<h3 className="mb-3">Оптимизация по community и filters</h3>
|
||||
<div className="text-muted small mb-3">
|
||||
|
||||
Reference in New Issue
Block a user