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
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m31s
This commit is contained in:
@@ -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>Текущий (рекурсивный -> туннель)</th>
|
||||
<th>Рекомендуемый (рекурсивный -> туннель)</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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user