fix(ui): показывать подтверждённые метрики участков пути

This commit is contained in:
Denozordec committed 2026-10-04 00:18:21 +07:00
1 parent d4ae1c3127
commit c3a9c00aed
9 files changed
+78 -117

No files matched your search

+24 -109
View File
@@ -1,16 +1,5 @@
import {readProbeOutput} from "@/lib/probe-response"
import type { GreTunnel, Server, WanUplink } from "@/lib/data"
import {
assignSpeedProbesToGreTunnels,
assignSpeedProbesToWanJhEdges,
mergeGreMetricsWithSpeedProbe,
wanJhEdgeMapKey,
} from "@/lib/map-gre-speed-probe"
import {
buildWanJhEdges,
findServerByGreRemote,
greTunnelProbe,
} from "@/lib/network-map-layout"
import {
buildLiveOptimizerData,
DEFAULT_ROUTE_AI_OPTIMIZER_SETTINGS,
@@ -84,6 +73,17 @@ export interface HomeWanRuntime {
}>
}
type DashboardPathProbe = RouteOptimizerSpeedProbe & {lastRunAt?: string | null; lastStatus?: string | null; lastError?: string | null; lastPingError?: string | null}
export function dashboardPathMetrics(probe: DashboardPathProbe | null, reverse = false): InternetPathHop["wanJhMetrics"] {
const dated=(time: string | null | undefined)=>Boolean(time)&&Number.isFinite(Date.parse(time!))
const known=(value: number | null | undefined)=>typeof value==="number"&&Number.isFinite(value)&&value>=0?value:null
const ping=probe?.enabled && dated(probe.lastPingAt) && (probe.lastPingError===null||probe.lastPingError==="") ? known(probe.lastPingRttMs) : null
const speed=probe?.enabled && dated(probe.lastRunAt) && probe.lastStatus==="done" && (probe.lastError===null||probe.lastError==="")
const dl=speed?known(reverse?probe.lastTxAvgMbps:probe.lastRxAvgMbps):null
const ul=speed?known(reverse?probe.lastRxAvgMbps:probe.lastTxAvgMbps):null
return {pingMs:ping,dlMbps:dl,ulMbps:ul,fromMonitoring:ping!==null||dl!==null||ul!==null}
}
const INTERNET_TARGET = "1.1.1.1"
function norm(v: string | null | undefined): string {
@@ -133,31 +133,25 @@ function chooseActiveWan(
const byGatewayIp = wans.find((w) => norm(w.ip) === norm(runtime.defaultGateway))
if (byGatewayIp) return byGatewayIp
}
const runtimeBound = runtime.uplinks.find((u) => (u.dhcpStatus ?? "").toLowerCase() === "bound")
if (runtimeBound) {
const byId = wans.find((w) => norm(w.id) === norm(runtimeBound.id))
if (byId) return byId
const byIface = wans.find((w) => norm(w.iface) === norm(runtimeBound.iface))
if (byIface) return byIface
}
}
// 2) Fallback: route-lookup
const gw = norm(lookup?.gateway)
if (gw) {
const byIface = wans.find((w) => norm(w.iface) === gw || gw.includes(norm(w.iface)))
const byIface = wans.find((w) => Boolean(norm(w.iface)) && (norm(w.iface) === gw || gw.includes("%"+norm(w.iface))))
if (byIface) return byIface
const byIp = wans.find((w) => norm(w.ip) === gw || gw.startsWith(`${norm(w.ip)}%`))
const byIp = wans.find((w) => Boolean(norm(w.ip)) && (norm(w.ip) === gw || gw.startsWith(`${norm(w.ip)}%`)))
if (byIp) return byIp
}
return wans[0] ?? null
return null
}
function bestJhForWan(
home: Server,
wan: WanUplink,
jhs: Server[],
probes: RouteOptimizerSpeedProbe[],
probes: DashboardPathProbe[],
): Server | null {
const pool = probes.filter((p) => p.srcServerId === home.id && p.enabled !== false)
const ranked = jhs
@@ -176,7 +170,7 @@ function bestJhForWan(
return ranked[0]?.jh ?? null
}
function bestExitForJh(jh: Server, exits: Server[], probes: RouteOptimizerSpeedProbe[]): Server | null {
function bestExitForJh(jh: Server, exits: Server[], probes: DashboardPathProbe[]): Server | null {
const pool = probes.filter((p) => p.enabled !== false)
const ranked = exits
.map((ex) => {
@@ -197,7 +191,7 @@ function pickHomeJhProbe(
homeId: string,
wanIface: string,
jhId: string,
probes: RouteOptimizerSpeedProbe[],
probes: DashboardPathProbe[],
): RouteOptimizerSpeedProbe | null {
const ifaceNorm = norm(wanIface)
const list = probes
@@ -216,7 +210,7 @@ function pickHomeJhProbe(
return list[0] ?? null
}
function pickJhExitProbe(jhId: string, exitId: string, probes: RouteOptimizerSpeedProbe[]): RouteOptimizerSpeedProbe | null {
function pickJhExitProbe(jhId: string, exitId: string, probes: DashboardPathProbe[]): RouteOptimizerSpeedProbe | null {
const list = probes
.filter((p) => p.enabled !== false)
.filter((p) => (
@@ -232,14 +226,10 @@ function pickJhExitProbe(jhId: string, exitId: string, probes: RouteOptimizerSpe
return list[0] ?? null
}
function metricValue(primary: number | null | undefined, secondary: number | null | undefined): number | null {
return primary ?? secondary ?? null
}
export function buildDashboardInternetPath(args: {
servers: Server[]
greTunnels: GreTunnel[]
probes: RouteOptimizerSpeedProbe[]
probes: DashboardPathProbe[]
filtersRulesets: FiltersRulesetRow[]
routeLookupByServerId: Record<string, RouteLookupResult | null>
wanRuntimeByHomeId?: Record<string, HomeWanRuntime | null>
@@ -317,18 +307,8 @@ export function buildDashboardInternetPath(args: {
wan: primaryWan,
jumpHost: primaryJh,
exitNode: primaryEx,
wanJhMetrics: {
pingMs: primaryJh.latency,
dlMbps: primaryWan.maxDl,
ulMbps: primaryWan.maxUl,
fromMonitoring: false,
},
jhExitMetrics: {
pingMs: primaryEx.latency,
dlMbps: null,
ulMbps: null,
fromMonitoring: false,
},
wanJhMetrics: dashboardPathMetrics(pickHomeJhProbe(home.id, primaryWan.iface, primaryJh.id, args.probes)),
jhExitMetrics: (()=>{const p=pickJhExitProbe(primaryJh.id,primaryEx.id,args.probes);return dashboardPathMetrics(p,p?.srcServerId!==primaryJh.id)})(),
}
: null
@@ -339,73 +319,8 @@ export function buildDashboardInternetPath(args: {
wan: currentWan,
jumpHost: currentJh,
exitNode: currentExit,
wanJhMetrics: (() => {
const wanJh = buildWanJhEdges(args.servers).find((e) =>
e.homeId === home.id
&& e.jhId === currentJh.id
&& (home.wanUplinks?.[e.wanIdx]?.id ?? "") === currentWan.id,
)
const fallback = {
pingMs: wanJh?.pingMs ?? currentJh.latency,
dlMbps: wanJh?.dlMbps ?? currentWan.maxDl,
ulMbps: currentWan.maxUl,
}
if (!wanJh) return { ...fallback, fromMonitoring: false }
const byEdge = assignSpeedProbesToWanJhEdges([wanJh], args.servers, args.probes)
const sp = byEdge.get(wanJhEdgeMapKey(wanJh))
const merged = mergeGreMetricsWithSpeedProbe(sp, fallback)
return {
pingMs: merged.pingMs,
dlMbps: metricValue(merged.dlMbps, merged.ulMbps),
ulMbps: metricValue(merged.ulMbps, merged.dlMbps),
fromMonitoring: merged.hasSpeedMonitor,
}
})(),
jhExitMetrics: (() => {
const tunnel = args.greTunnels.find((t) => {
const from = args.servers.find((s) => s.id === String(t.serverId))
if (!from) return false
const to = findServerByGreRemote(args.servers, t.remoteAddress)
if (!to) return false
return (
(from.id === currentJh.id && to.id === currentExit.id)
|| (from.id === currentExit.id && to.id === currentJh.id)
)
})
const fallback = tunnel
? greTunnelProbe(tunnel)
: { pingMs: currentExit.latency, dlMbps: null, ulMbps: null }
if (!tunnel) {
return {
pingMs: fallback.pingMs,
dlMbps: fallback.dlMbps,
ulMbps: fallback.ulMbps,
fromMonitoring: false,
}
}
const fromServer = args.servers.find((s) => s.id === String(tunnel.serverId))
const toServer = fromServer ? findServerByGreRemote(args.servers, tunnel.remoteAddress) : undefined
if (!fromServer || !toServer) {
return {
pingMs: fallback.pingMs,
dlMbps: fallback.dlMbps,
ulMbps: fallback.ulMbps,
fromMonitoring: false,
}
}
const byTunnel = assignSpeedProbesToGreTunnels(
[{ tunnel, fromServer, toServer }],
args.probes,
)
const sp = byTunnel.get(tunnel.id)
const merged = mergeGreMetricsWithSpeedProbe(sp, fallback)
return {
pingMs: merged.pingMs,
dlMbps: metricValue(merged.dlMbps, merged.ulMbps),
ulMbps: metricValue(merged.ulMbps, merged.dlMbps),
fromMonitoring: merged.hasSpeedMonitor,
}
})(),
wanJhMetrics: dashboardPathMetrics(pickHomeJhProbe(home.id,currentWan.iface,currentJh.id,args.probes)),
jhExitMetrics: (()=>{const p=pickJhExitProbe(currentJh.id,currentExit.id,args.probes);return dashboardPathMetrics(p,p?.srcServerId!==currentJh.id)})(),
}
: null