fix(traffic): показывать причину неполного опроса peers
This commit is contained in:
1 parent
d8f8d3c7a4
commit
c69d99ba80
19 files changed
+1367
-23
No files matched your search
@@ -198,11 +198,12 @@ const trafficRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
return reply.status(502).send({ ok: false, error, failures, succeeded: result.servers.filter(s => s.ok).map(s => s.serverId) })
|
||||
}
|
||||
const updated = await getTrafficSettings()
|
||||
const warnings = result.servers.filter(s => s.warning).map(s => ({ serverId: s.serverId, warning: s.warning }))
|
||||
await appendEvent({
|
||||
level: "info",
|
||||
level: warnings.length ? "warning" : "info",
|
||||
eventType: "traffic.collect.manual.ok",
|
||||
sourceModule: "traffic",
|
||||
title: "Ручной сбор трафика завершен",
|
||||
title: warnings.length ? "Ручной сбор трафика завершён с ограничениями" : "Ручной сбор трафика завершен",
|
||||
message: `Длительность: ${updated.lastDurationMs ?? 0} мс`,
|
||||
payload: {
|
||||
lastCollectedAt: updated.lastCollectedAt ?? null,
|
||||
@@ -210,6 +211,7 @@ const trafficRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
})
|
||||
return reply.send({
|
||||
ok: true,
|
||||
warnings,
|
||||
lastCollectedAt: updated.lastCollectedAt ?? null,
|
||||
lastDurationMs: updated.lastDurationMs ?? null,
|
||||
lastError: updated.lastError || null,
|
||||
|
||||
@@ -165,6 +165,7 @@ export async function collectTrafficOnce(): Promise<TrafficRunSnapshot> {
|
||||
flags: encodeTrafficFlags(running, disabled, true, true),
|
||||
}
|
||||
})
|
||||
let peerWarning: string | undefined
|
||||
try {
|
||||
const peers = await client.get<RosWgPeerTraffic[]>("/interface/wireguard/peers")
|
||||
for (const p of parseTrafficPeers(peers)) {
|
||||
@@ -189,18 +190,19 @@ export async function collectTrafficOnce(): Promise<TrafficRunSnapshot> {
|
||||
flags: encodeTrafficFlags(running, disabled, true, true),
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
/* WG peers optional — iface samples already recorded */
|
||||
} catch (error) {
|
||||
peerWarning = `Измерения WireGuard peers недоступны: ${error instanceof Error ? error.message : String(error)}`
|
||||
}
|
||||
if (rows.length > 0) {
|
||||
await db.insert(trafficSamples).values(rows)
|
||||
}
|
||||
await recordTrafficCollectionAttempt(srv, "ok", now)
|
||||
await recordTrafficCollectionAttempt(srv, "ok", now, peerWarning)
|
||||
snapshot.servers.push({
|
||||
serverId: srv.id,
|
||||
name,
|
||||
host: srv.host,
|
||||
ok: true,
|
||||
...(peerWarning ? { warning: peerWarning } : {}),
|
||||
interfaces: ifaces.length,
|
||||
...(includedInterfaces > 0 && totalsMeasured ? {
|
||||
sumRxMbps,
|
||||
@@ -222,7 +224,7 @@ export async function collectTrafficOnce(): Promise<TrafficRunSnapshot> {
|
||||
await db.update(trafficSettings).set({
|
||||
lastCollectedAt: now,
|
||||
lastDurationMs: Date.now() - startedAt,
|
||||
lastError: snapshot.servers.filter(s => !s.ok).map(s => `${s.name}: ${s.error ?? "Ошибка опроса"}`).join("; "),
|
||||
lastError: snapshot.servers.filter(s => !s.ok || s.warning).map(s => `${s.name}: ${s.warning ?? s.error ?? "Ошибка опроса"}`).join("; "),
|
||||
updatedAt: now,
|
||||
}).where(eq(trafficSettings.id, 1))
|
||||
} catch (e) {
|
||||
|
||||
@@ -9,8 +9,8 @@ const database=process.env.MMAPP_OWN_SQL_DB
|
||||
if(!database)console.log("traffic status SQL: skipped; explicit owned database required")
|
||||
else{
|
||||
assert(/^mm_accept_166_\d+$/.test(database));assert.equal(new URL(process.env.DATABASE_URL!).pathname,"/"+database);assert.equal((await pool.query("SELECT current_database() AS name")).rows[0].name,database)
|
||||
const prefix="own-status181-"+Date.now()+"-",userId=prefix+"user";let serverId:number|undefined,cases=0,deny=false,flag:unknown=true,peerDisabled=false,peerVariant="valid"
|
||||
const http=createServer((req,res)=>{res.setHeader("Content-Type","application/json");if(deny){res.statusCode=401;return res.end('{"message":"Unauthorized"}')}res.end(JSON.stringify(req.url?.includes("/wireguard/")?(peerVariant==="shape"?{}:peerVariant==="duplicate"?[{interface:"wg1","public-key":"fixturekey",rx:"1000",tx:"1000",disabled:false},{interface:"wg1","public-key":"fixturekey",rx:"1000",tx:"1000",disabled:false}]:[{interface:"wg1","public-key":"fixturekey",rx:"1000",tx:"1000",disabled:peerDisabled},...(peerVariant==="partial"?[{interface:"wg1","public-key":"broken",rx:"invalid",tx:"1000",disabled:false}]:[])]):[{name:"ether1",running:flag,disabled:false,"rx-byte":"1000","tx-byte":"1000"}]))})
|
||||
const prefix="own-status181-"+Date.now()+"-",userId=prefix+"user";let serverId:number|undefined,cases=0,deny=false,flag:unknown=true,peerDisabled=false,peerVariant="valid",denyPeer=false
|
||||
const http=createServer((req,res)=>{res.setHeader("Content-Type","application/json");if(deny || denyPeer && req.url?.includes("/wireguard/")){res.statusCode=401;return res.end('{"message":"Unauthorized"}')}res.end(JSON.stringify(req.url?.includes("/wireguard/")?(peerVariant==="shape"?{}:peerVariant==="duplicate"?[{interface:"wg1","public-key":"fixturekey",rx:"1000",tx:"1000",disabled:false},{interface:"wg1","public-key":"fixturekey",rx:"1000",tx:"1000",disabled:false}]:[{interface:"wg1","public-key":"fixturekey",rx:"1000",tx:"1000",disabled:peerDisabled},...(peerVariant==="partial"?[{interface:"wg1","public-key":"broken",rx:"invalid",tx:"1000",disabled:false}]:[])]):[{name:"ether1",running:flag,disabled:false,"rx-byte":"1000","tx-byte":"1000"}]))})
|
||||
await new Promise<void>(resolve=>http.listen(0,"127.0.0.1",resolve));const address=http.address();assert(address&&typeof address==="object");const app=Fastify();await app.register(trafficRoutes)
|
||||
const bindings=async()=>{const r=await app.inject({method:"GET",url:"/traffic/bound-interfaces?range=1h"});assert.equal(r.statusCode,200);return r.json().interfaces.filter((i:{userId:string})=>i.userId===userId)}
|
||||
try{
|
||||
@@ -28,10 +28,13 @@ else{
|
||||
deny=false;await collectTrafficOnce();await pool.query("UPDATE traffic_samples SET flags=flags & 7 WHERE server_id=$1",[serverId]);rows=await bindings();assert(rows.every((r:{status:string})=>r.status==="unknown"));cases++
|
||||
await collectTrafficOnce();await pool.query("UPDATE traffic_collection_state SET last_attempt_at=$1 WHERE server_id=$2",[new Date(Date.now()-100_000).toISOString(),serverId]);rows=await bindings();assert(rows.every((r:{status:string})=>r.status==="unknown"));cases++
|
||||
for(const variant of["partial","duplicate","shape"]){
|
||||
peerVariant=variant;await pool.query("DELETE FROM traffic_samples WHERE server_id=$1",[serverId]);const result=await collectTrafficOnce();assert.equal(result.servers[0].ok,true)
|
||||
peerVariant=variant;await pool.query("DELETE FROM traffic_samples WHERE server_id=$1",[serverId]);const result=await collectTrafficOnce();assert.equal(result.servers[0].ok,true);assert(result.servers[0].warning?.includes("WireGuard"));assert((await pool.query("SELECT error FROM traffic_collection_state WHERE server_id=$1",[serverId])).rows[0].error.includes("WireGuard"))
|
||||
const saved=(await pool.query("SELECT peer_public_key FROM traffic_samples WHERE server_id=$1",[serverId])).rows;assert.equal(saved.length,1);assert.equal(saved[0].peer_public_key,"")
|
||||
rows=await bindings();assert.equal(rows.find((r:{interfaceName:string})=>r.interfaceName==="wg1").status,"unknown");assert.equal(rows.find((r:{interfaceName:string})=>r.interfaceName==="ether1").status,"online");cases++
|
||||
}
|
||||
denyPeer=true;peerVariant="valid";let partial=await collectTrafficOnce();assert.equal(partial.servers[0].ok,true);assert(partial.servers[0].warning?.includes("401"));rows=await bindings();assert(rows.find((r:{interfaceName:string})=>r.interfaceName==="ether1").observations.error.includes("401"));cases++
|
||||
const manualPartial=await app.inject({method:"POST",url:"/traffic/collect-now"});assert.equal(manualPartial.statusCode,200);assert.equal(manualPartial.json().ok,true);assert.equal(manualPartial.json().warnings.length,1);assert(manualPartial.json().lastError.includes("401"));
|
||||
denyPeer=false;partial=await collectTrafficOnce();assert.equal(partial.servers[0].warning,undefined);assert.equal((await pool.query("SELECT error FROM traffic_collection_state WHERE server_id=$1",[serverId])).rows[0].error,null);cases++
|
||||
await pool.query("UPDATE servers SET enabled=false WHERE id=$1",[serverId]);rows=await bindings();assert(rows.every((r:{status:string})=>r.status==="unknown"));cases++
|
||||
console.log(`traffic status owned SQL: ${cases} cases; first-wave native flags/peer config/invalid/error/legacy/stale/disabled passed`)
|
||||
}finally{await app.close();await new Promise<void>(resolve=>http.close(()=>resolve()));await pool.query("DELETE FROM app_users WHERE id=$1",[userId]);if(serverId!==undefined){await pool.query("DELETE FROM traffic_samples WHERE server_id=$1",[serverId]);await pool.query("DELETE FROM servers WHERE id=$1",[serverId])}await closePool()}
|
||||
|
||||
@@ -11,6 +11,8 @@ export interface TrafficServerSnapshot {
|
||||
host: string
|
||||
ok: boolean
|
||||
error?: string
|
||||
/** Optional peer slice failed; successful interface measurements are retained. */
|
||||
warning?: string
|
||||
/** Сколько интерфейсов записано в сэмплы */
|
||||
interfaces?: number
|
||||
/** Сумма RX по интерфейсам, Мбит/с (округл.) */
|
||||
|
||||
Reference in new issue
Block a user