feat(route-optimizer): add distance penalty configuration and enhance UI for gateway recommendations
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m31s

This commit is contained in:
2026-03-04 23:28:24 +07:00
parent 7b7ef1ab45
commit f5829fb35c
3 changed files with 237 additions and 14 deletions
+30 -6
View File
@@ -17,6 +17,10 @@ function MetricBadge({ label, value, tone = 'secondary' }) {
);
}
function fmtProb(v) {
return typeof v === 'number' ? `${v}%` : '—';
}
export default function RouteOptimizerPage() {
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
@@ -202,8 +206,8 @@ export default function RouteOptimizerPage() {
<thead>
<tr>
<th>Community</th>
<th>Текущий gateway</th>
<th>Рекомендуемый gateway</th>
<th>Текущий (рекурсивный -&gt; туннель)</th>
<th>Рекомендуемый (рекурсивный -&gt; туннель)</th>
<th>Вероятность (тек/реком)</th>
<th>Действие</th>
</tr>
@@ -225,13 +229,33 @@ export default function RouteOptimizerPage() {
</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 ?? ''}%
<div>{r.current?.gateway || r.currentGateway || '—'}</div>
{r.current?.tunnelGateway && (
<div className="text-muted">
{'->'} {r.current.tunnelGateway}
{r.current.distance != null ? ` (dist ${r.current.distance})` : ''}
</div>
)}
</td>
<td className="small">
<div>{r.recommended?.gateway || '—'}</div>
{r.recommended?.tunnelGateway && (
<div className="text-muted">
{'->'} {r.recommended.tunnelGateway}
{r.recommended.distance != null ? ` (dist ${r.recommended.distance})` : ''}
</div>
)}
</td>
<td className="small">
{fmtProb(r.current?.probabilityOptimal)} / {fmtProb(r.recommended?.probabilityOptimal)}
</td>
<td>
{r.shouldSwitch ? (
{r.current == null && r.recommended == null ? (
<span className="badge bg-secondary-lt text-secondary">Недостаточно данных</span>
) : r.current == null ? (
<span className="badge bg-orange-lt text-orange">Текущий gateway не сопоставлен</span>
) : r.shouldSwitch ? (
<span className="badge bg-orange-lt text-orange">Рекомендовано переключить</span>
) : (
<span className="badge bg-green-lt text-green">Оставить текущий</span>
+24
View File
@@ -132,6 +132,7 @@ export default function SettingsPage() {
const [aiNoPingScore, setAiNoPingScore] = useState('0.2');
const [aiNoSpeedScore, setAiNoSpeedScore] = useState('0.15');
const [aiStaleScore, setAiStaleScore] = useState('0.35');
const [aiDistancePenaltyPerStep, setAiDistancePenaltyPerStep] = useState('0.03');
const [aiFreshnessExcellentSeconds, setAiFreshnessExcellentSeconds] = useState('120');
const [aiFreshnessGoodSeconds, setAiFreshnessGoodSeconds] = useState('600');
const [aiFreshnessFairSeconds, setAiFreshnessFairSeconds] = useState('1800');
@@ -486,6 +487,11 @@ export default function SettingsPage() {
setAiStaleScore(
ai?.staleScore != null ? String(ai.staleScore) : '0.35'
);
setAiDistancePenaltyPerStep(
ai?.distancePenaltyPerStep != null
? String(ai.distancePenaltyPerStep)
: '0.03'
);
setAiFreshnessExcellentSeconds(
ai?.freshnessExcellentSeconds != null
? String(ai.freshnessExcellentSeconds)
@@ -690,6 +696,10 @@ export default function SettingsPage() {
0,
Math.min(1, parseFloat(aiStaleScore) || 0.35)
),
distancePenaltyPerStep: Math.max(
0,
Math.min(1, parseFloat(aiDistancePenaltyPerStep) || 0.03)
),
freshnessExcellentSeconds: Math.max(
10,
Math.min(86400, parseInt(aiFreshnessExcellentSeconds, 10) || 120)
@@ -1561,6 +1571,20 @@ export default function SettingsPage() {
step="0.01"
/>
</div>
<div className="col-12 col-md-4">
<FormField
label="Штраф за шаг distance"
name="aiDistancePenaltyPerStep"
type="number"
value={aiDistancePenaltyPerStep}
onChange={setAiDistancePenaltyPerStep}
helpText="Вычитается из score за каждый шаг distance выше 1."
disabled={saving}
min={0}
max={1}
step="0.01"
/>
</div>
<div className="col-12 mt-2"><h4 className="subheader">Пороги свежести (сек)</h4></div>
<div className="col-12 col-md-4">