feat(route-optimizer): implement community optimization logic and enhance UI for server filters recommendations
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m38s

This commit is contained in:
2026-03-04 23:17:59 +07:00
parent 661b42f776
commit 963828f9e8
2 changed files with 230 additions and 3 deletions
+82 -1
View File
@@ -43,6 +43,9 @@ export default function RouteOptimizerPage() {
}, [load]);
const homes = Array.isArray(data?.homes) ? data.homes : [];
const communityOptimization = Array.isArray(data?.communityOptimizationByJumphost)
? data.communityOptimizationByJumphost
: [];
return (
<div>
@@ -81,7 +84,7 @@ export default function RouteOptimizerPage() {
const home = entry.home || {};
const routes = Array.isArray(entry.fullRouteCandidates) ? entry.fullRouteCandidates : [];
return (
<div className="col-12" key={home.id || home.ip || home.dns || Math.random()}>
<div className="col-12" key={home.id || home.ip || home.dns || home.label || 'home'}>
<div className="card">
<div className="card-header d-flex flex-wrap gap-2 align-items-center justify-content-between">
<h3 className="card-title mb-0 d-flex align-items-center">
@@ -162,6 +165,84 @@ export default function RouteOptimizerPage() {
);
})}
</div>
<div className="mt-4">
<h3 className="mb-3">Оптимизация по community и filters</h3>
<div className="text-muted small mb-3">
Рекомендации строятся на основе ваших `server-filters` (`community -> gateway`) и текущих метрик канала
`jumphost -> exit`.
</div>
{communityOptimization.length === 0 ? (
<div className="alert alert-info">Нет данных по `server-filters` для jumphost.</div>
) : (
<div className="row g-3">
{communityOptimization.map((item) => {
const jh = item.jumphost || {};
const rows = Array.isArray(item.recommendations) ? item.recommendations : [];
return (
<div className="col-12" key={jh.id || jh.ip || jh.dns || jh.label || 'jumphost'}>
<div className="card">
<div className="card-header d-flex flex-wrap align-items-center justify-content-between gap-2">
<h3 className="card-title mb-0">
Jumphost: {jh.label || jh.dns || jh.ip || 'unknown'}
</h3>
<MetricBadge label="Communities" value={rows.length} tone="blue" />
</div>
<div className="card-body">
<div className="table-responsive">
<table className="table table-vcenter card-table">
<thead>
<tr>
<th>Community</th>
<th>Текущий gateway</th>
<th>Рекомендуемый gateway</th>
<th>Вероятность (тек/реком)</th>
<th>Действие</th>
</tr>
</thead>
<tbody>
{rows.length === 0 ? (
<tr>
<td colSpan={5} className="text-muted">
Для этого jumphost нет фильтров.
</td>
</tr>
) : rows.map((r) => (
<tr key={`${jh.label}-${r.community}`}>
<td>
<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">{r.current?.gateway || r.currentGateway || '—'}</td>
<td className="small">{r.recommended?.gateway || '—'}</td>
<td className="small">
{r.current?.probabilityOptimal ?? '—'}% / {r.recommended?.probabilityOptimal ?? ''}%
</td>
<td>
{r.shouldSwitch ? (
<span className="badge bg-orange-lt text-orange">Рекомендовано переключить</span>
) : (
<span className="badge bg-green-lt text-green">Оставить текущий</span>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
);
})}
</div>
)}
</div>
</div>
);
}