chore: synchronize pending app/backend updates and repository hygiene
Includes current frontend and backend work in progress and removes generated artifacts from tracking to keep the repository clean for дальнейшая разработка. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
+180
-1
@@ -1,4 +1,5 @@
|
||||
import type { GreTunnel, Server } from "@/lib/data"
|
||||
import type { WanJhEdge } from "@/lib/network-map-layout"
|
||||
import {
|
||||
formatGreOuterForDisplay,
|
||||
normalizeGreEndpointAddr,
|
||||
@@ -24,6 +25,13 @@ export type GreTunnelProbeEdge = {
|
||||
toServer: Server
|
||||
}
|
||||
|
||||
/** Неориентированная пара узлов: один ключ для HR→MSK и MSK→HR (две строки GRE в БД). */
|
||||
export function canonicalGreServerPairKey(a: Pick<Server, "id">, b: Pick<Server, "id">): string {
|
||||
const x = String(a.id)
|
||||
const y = String(b.id)
|
||||
return x <= y ? `${x}\t${y}` : `${y}\t${x}`
|
||||
}
|
||||
|
||||
function normIface(i?: string | null): string {
|
||||
return (i ?? "").trim().toLowerCase()
|
||||
}
|
||||
@@ -122,6 +130,10 @@ function probesForServerPair(
|
||||
/**
|
||||
* Для всех GRE на карте назначает speed-пробы без повторного использования одной пробы
|
||||
* на два разных туннеля между одной и той же парой узлов (RT / MTS и т.п.).
|
||||
*
|
||||
* Пара узлов **неориентированная**: туннель в БД на HR→MSK и зеркальная запись MSK→HR
|
||||
* попадают в одну группу — иначе обе стороны независимо выбирают одни и те же пробы
|
||||
* и бейджи дублируются на разных линиях карты.
|
||||
*/
|
||||
export function assignSpeedProbesToGreTunnels(
|
||||
edges: GreTunnelProbeEdge[],
|
||||
@@ -132,7 +144,7 @@ export function assignSpeedProbesToGreTunnels(
|
||||
|
||||
const byPair = new Map<string, GreTunnelProbeEdge[]>()
|
||||
for (const e of edges) {
|
||||
const k = `${String(e.fromServer.id)}\t${String(e.toServer.id)}`
|
||||
const k = canonicalGreServerPairKey(e.fromServer, e.toServer)
|
||||
const arr = byPair.get(k) ?? []
|
||||
arr.push(e)
|
||||
byPair.set(k, arr)
|
||||
@@ -308,3 +320,170 @@ export function mergeGreMetricsWithSpeedProbe(
|
||||
sp?.lastRxAvgMbps != null ? Math.round(sp.lastRxAvgMbps) : fallback.ulMbps
|
||||
return { pingMs, dlMbps, ulMbps, hasSpeedMonitor }
|
||||
}
|
||||
|
||||
// ── WAN satellite → JumpHost: speed-пробы с src = Home Router и выбранным WAN-iface ──
|
||||
|
||||
export function wanJhEdgeMapKey(edge: Pick<WanJhEdge, "homeId" | "wanIdx" | "jhId">): string {
|
||||
return `${edge.homeId}\t${edge.wanIdx}\t${edge.jhId}`
|
||||
}
|
||||
|
||||
function scoreSpeedProbeForWanJhEdge(
|
||||
p: GreSpeedProbeSnapshot,
|
||||
edge: Pick<WanJhEdge, "homeId" | "wanIdx" | "jhId">,
|
||||
home: Server,
|
||||
): number {
|
||||
if (String(p.srcServerId) !== edge.homeId || String(p.dstServerId) !== edge.jhId) return -1
|
||||
if (p.enabled === false) return -1
|
||||
const wanIface = normIface(home.wanUplinks?.[edge.wanIdx]?.iface)
|
||||
const si = normIface(p.srcInterface)
|
||||
if (si && wanIface && si !== wanIface) return -1
|
||||
let s = 0
|
||||
if (si && wanIface && si === wanIface) s += 100
|
||||
else if (!si && wanIface) s += 35
|
||||
else if (!wanIface && si) s += 15
|
||||
else if (!si && !wanIface) s += 5
|
||||
if (p.lastPingRttMs != null || p.lastTxAvgMbps != null || p.lastRxAvgMbps != null) s += 6
|
||||
return s
|
||||
}
|
||||
|
||||
function probesHomeToJh(allProbes: GreSpeedProbeSnapshot[], homeId: string, jhId: string): GreSpeedProbeSnapshot[] {
|
||||
return allProbes.filter((p) => {
|
||||
if (p.enabled === false) return false
|
||||
return String(p.srcServerId) === homeId && String(p.dstServerId) === jhId
|
||||
})
|
||||
}
|
||||
|
||||
function assignSpeedProbesWithinWanJhGroup(
|
||||
edges: WanJhEdge[],
|
||||
home: Server,
|
||||
allProbes: GreSpeedProbeSnapshot[],
|
||||
): Map<string, GreSpeedProbeSnapshot | undefined> {
|
||||
const out = new Map<string, GreSpeedProbeSnapshot | undefined>()
|
||||
if (edges.length === 0) return out
|
||||
const E = [...edges].sort((a, b) => a.wanIdx - b.wanIdx || a.jhId.localeCompare(b.jhId))
|
||||
const pairProbes = probesHomeToJh(allProbes, E[0].homeId, E[0].jhId)
|
||||
|
||||
if (E.length === 1) {
|
||||
let best: GreSpeedProbeSnapshot | undefined
|
||||
let bestS = -1
|
||||
for (const p of pairProbes) {
|
||||
const sc = scoreSpeedProbeForWanJhEdge(p, E[0], home)
|
||||
if (sc > bestS) {
|
||||
bestS = sc
|
||||
best = p
|
||||
}
|
||||
}
|
||||
out.set(wanJhEdgeMapKey(E[0]), best != null && bestS >= 0 ? best : undefined)
|
||||
return out
|
||||
}
|
||||
|
||||
const n = E.length
|
||||
const m = pairProbes.length
|
||||
if (m === 0) {
|
||||
for (const e of E) out.set(wanJhEdgeMapKey(e), undefined)
|
||||
return out
|
||||
}
|
||||
|
||||
let bestSum = -Infinity
|
||||
let bestAssign: Map<string, GreSpeedProbeSnapshot> | null = null
|
||||
|
||||
function dfs(i: number, used: Set<string>, cur: Map<string, GreSpeedProbeSnapshot>) {
|
||||
if (i === n) {
|
||||
let sum = 0
|
||||
for (const e of E) {
|
||||
const p = cur.get(wanJhEdgeMapKey(e))
|
||||
if (!p) return
|
||||
sum += scoreSpeedProbeForWanJhEdge(p, e, home)
|
||||
}
|
||||
if (sum > bestSum) {
|
||||
bestSum = sum
|
||||
bestAssign = new Map(cur)
|
||||
}
|
||||
return
|
||||
}
|
||||
const edge = E[i]!
|
||||
const slotKey = wanJhEdgeMapKey(edge)
|
||||
for (const p of pairProbes) {
|
||||
if (used.has(p.id)) continue
|
||||
if (scoreSpeedProbeForWanJhEdge(p, edge, home) < 0) continue
|
||||
used.add(p.id)
|
||||
cur.set(slotKey, p)
|
||||
dfs(i + 1, used, cur)
|
||||
cur.delete(slotKey)
|
||||
used.delete(p.id)
|
||||
}
|
||||
}
|
||||
|
||||
if (n <= 7 && n <= m) dfs(0, new Set(), new Map())
|
||||
|
||||
const inject = bestAssign as Map<string, GreSpeedProbeSnapshot> | null
|
||||
if (inject !== null && inject.size === n) {
|
||||
for (const ed of E) out.set(wanJhEdgeMapKey(ed), inject.get(wanJhEdgeMapKey(ed)))
|
||||
return out
|
||||
}
|
||||
|
||||
const used = new Set<string>()
|
||||
const remaining = new Set(E.map((e) => wanJhEdgeMapKey(e)))
|
||||
while (remaining.size > 0) {
|
||||
let pickEdge: WanJhEdge | undefined
|
||||
let pickProbe: GreSpeedProbeSnapshot | undefined
|
||||
let pickScore = -Infinity
|
||||
for (const key of remaining) {
|
||||
const e = E.find((x) => wanJhEdgeMapKey(x) === key)!
|
||||
for (const p of pairProbes) {
|
||||
if (used.has(p.id)) continue
|
||||
const sc = scoreSpeedProbeForWanJhEdge(p, e, home)
|
||||
if (sc < 0) continue
|
||||
if (
|
||||
sc > pickScore ||
|
||||
(sc === pickScore && pickProbe != null && p.id.localeCompare(pickProbe.id) < 0)
|
||||
) {
|
||||
pickScore = sc
|
||||
pickEdge = e
|
||||
pickProbe = p
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!pickEdge || !pickProbe) {
|
||||
for (const key of remaining) out.set(key, undefined)
|
||||
break
|
||||
}
|
||||
out.set(wanJhEdgeMapKey(pickEdge), pickProbe)
|
||||
used.add(pickProbe.id)
|
||||
remaining.delete(wanJhEdgeMapKey(pickEdge))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* Назначает speed-пробы сегментам WAN→JH (Home → спутник → JH): по паре серверов и iface источника,
|
||||
* без повторного использования одной пробы на два WAN одного home→jh (RT / MTS).
|
||||
*/
|
||||
export function assignSpeedProbesToWanJhEdges(
|
||||
edges: WanJhEdge[],
|
||||
servers: Server[],
|
||||
probes: GreSpeedProbeSnapshot[],
|
||||
): ReadonlyMap<string, GreSpeedProbeSnapshot | undefined> {
|
||||
const result = new Map<string, GreSpeedProbeSnapshot | undefined>()
|
||||
const homeById = new Map(servers.filter((s) => s.type === "home-router").map((s) => [s.id, s] as const))
|
||||
|
||||
const byPair = new Map<string, WanJhEdge[]>()
|
||||
for (const e of edges) {
|
||||
const k = `${e.homeId}\t${e.jhId}`
|
||||
const arr = byPair.get(k) ?? []
|
||||
arr.push(e)
|
||||
byPair.set(k, arr)
|
||||
}
|
||||
|
||||
for (const group of byPair.values()) {
|
||||
const home = homeById.get(group[0].homeId)
|
||||
if (!home) {
|
||||
for (const e of group) result.set(wanJhEdgeMapKey(e), undefined)
|
||||
continue
|
||||
}
|
||||
const assigned = assignSpeedProbesWithinWanJhGroup(group, home, probes)
|
||||
for (const [k, sp] of assigned) result.set(k, sp)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user