fix(traffic): прекращать опрос изменённого источника

This commit is contained in:
Denozordec committed 2026-10-13 02:08:26 +07:00
1 parent 5d19b6a258
commit 504f62fc0b
15 files changed
+1296 -14

No files matched your search

+10
View File
@@ -131,10 +131,17 @@ export async function collectTrafficOnce(): Promise<TrafficRunSnapshot> {
for (const srv of enabledServers) {
const name = srv.name || srv.host
const ensureSource = async () => {
const current = (await db.select({enabled:servers.enabled,updatedAt:servers.updatedAt}).from(servers).where(eq(servers.id,srv.id)).limit(1))[0]
if (!current?.enabled || Date.parse(current.updatedAt) !== Date.parse(srv.updatedAt))
throw new Error("Сервер выключен, удалён или его конфигурация изменилась во время опроса")
}
try {
await recordTrafficCollectionAttempt(srv, "pending", now)
const client = MikrotikClient.fromServer(srv)
await ensureSource()
const ifaces = await client.get<RosIfaceTraffic[]>("/interface")
await ensureSource()
rememberServerIfaces(srv.id, ifaces)
const prevWave = await readPreviousWave(srv.id)
const nowMs = Date.parse(now)
@@ -174,6 +181,7 @@ export async function collectTrafficOnce(): Promise<TrafficRunSnapshot> {
})
let peerWarning: string | undefined
try {
await ensureSource()
const peers = await client.get<RosWgPeerTraffic[]>("/interface/wireguard/peers")
for (const p of parseTrafficPeers(peers)) {
const { interfaceName, peerPublicKey, rxBytes, txBytes, disabled } = p
@@ -205,9 +213,11 @@ export async function collectTrafficOnce(): Promise<TrafficRunSnapshot> {
} catch (error) {
peerWarning = `Измерения WireGuard peers недоступны: ${error instanceof Error ? error.message : String(error)}`
}
await ensureSource()
if (rows.length > 0) {
await db.insert(trafficSamples).values(rows)
}
await ensureSource()
await recordTrafficCollectionAttempt(srv, "ok", now, peerWarning)
snapshot.servers.push({
serverId: srv.id,
@@ -50,7 +50,7 @@ const context = vm.createContext({
MikrotikClient: { fromServer: () => ({ get: async (path: string) => path === "/interface" ?
[{ ".id":"*1",name: "ether1", running: String(included), disabled: "false", "rx-byte": counters, "tx-byte": counters }] : [] }) },
db: {
select: () => ({ from: () => ({ where: async () => [{ updatedAt:iso,id: 1, name: "audit", host: "audit.example", enabled: true }] }) }),
select: () => ({ from: () => ({ where: () => {const rows=[{updatedAt:iso,id:1,name:"audit",host:"audit.example",enabled:true}];return Object.assign(Promise.resolve(rows),{limit:async()=>rows})} }) }),
insert: () => ({ values: async (rows: unknown[]) => { inserted.push(...rows) } }),
update: () => ({ set: () => ({ where: async () => {} }) }),
},
@@ -10,7 +10,13 @@ if(!database)console.log("traffic status SQL: skipped; explicit owned database r
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",denyPeer=false,ifaceId:unknown="*1",counterValue="1000",peerId="*2",peerCounter="1000"
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"?[{".id":peerId,interface:"wg1","public-key":"fixturekey",rx:"1000",tx:"1000",disabled:false},{".id":peerId,interface:"wg1","public-key":"fixturekey",rx:"1000",tx:"1000",disabled:false}]:[{".id":peerId,interface:"wg1","public-key":"fixturekey",rx:peerCounter,tx:peerCounter,disabled:peerDisabled},...(peerVariant==="partial"?[{".id":peerId,interface:"wg1","public-key":"broken",rx:"invalid",tx:"1000",disabled:false}]:[])]):[{".id":ifaceId,name:"ether1",running:flag,disabled:false,"rx-byte":counterValue,"tx-byte":counterValue}]))})
let switchSource="",peerRequests=0;const http=createServer(async(req,res)=>{if(req.url?.includes("/wireguard/"))peerRequests++;
if(switchSource && !req.url?.includes("/wireguard/")){
const action=switchSource;switchSource="";
if(action==="disable")await pool.query("UPDATE servers SET enabled=false WHERE id=$1",[serverId]);
else await pool.query("UPDATE servers SET updated_at=$1 WHERE id=$2",[new Date().toISOString(),serverId]);
}
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"?[{".id":peerId,interface:"wg1","public-key":"fixturekey",rx:"1000",tx:"1000",disabled:false},{".id":peerId,interface:"wg1","public-key":"fixturekey",rx:"1000",tx:"1000",disabled:false}]:[{".id":peerId,interface:"wg1","public-key":"fixturekey",rx:peerCounter,tx:peerCounter,disabled:peerDisabled},...(peerVariant==="partial"?[{".id":peerId,interface:"wg1","public-key":"broken",rx:"invalid",tx:"1000",disabled:false}]:[])]):[{".id":ifaceId,name:"ether1",running:flag,disabled:false,"rx-byte":counterValue,"tx-byte":counterValue}]))})
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{
@@ -47,6 +53,9 @@ else{
}
ifaceId="*10";await collectTrafficOnce();await collectTrafficOnce();peerId="*20";peerCounter="2000";await collectTrafficOnce();rows=await bindings();let peer=rows.find((r:{interfaceName:string})=>r.interfaceName==="wg1");assert.equal(peer.observations.rxNow,false);assert.equal(peer.rxTotal,0);
await collectTrafficOnce();rows=await bindings();peer=rows.find((r:{interfaceName:string})=>r.interfaceName==="wg1");assert.equal(peer.observations.rxNow,true);assert.equal(peer.rxNow,0);cases++;
for(const action of["disable","config"]){
const before=peerRequests,count=Number((await pool.query("SELECT count(*) AS n FROM traffic_samples WHERE server_id=$1",[serverId])).rows[0].n);switchSource=action;const switched=await collectTrafficOnce();assert.equal(switched.servers[0].ok,false);assert(switched.servers[0].error?.includes("Сервер"));assert.equal(peerRequests,before);assert.equal(Number((await pool.query("SELECT count(*) AS n FROM traffic_samples WHERE server_id=$1",[serverId])).rows[0].n),count);await pool.query("UPDATE servers SET enabled=true WHERE id=$1",[serverId]);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()}