Files
MikrotikManager/lib/dashboard-traffic.ts
T

19 lines
2.4 KiB
TypeScript

import {z} from "zod"
const rate=z.number().finite().nonnegative()
const observations=z.object({rxNow:z.boolean(),txNow:z.boolean(),rxSeries:z.array(z.boolean()),txSeries:z.array(z.boolean()),sampledAt:z.string().refine(v=>Number.isFinite(Date.parse(v))).nullable()})
const row=z.object({id:z.string().trim().min(1),rxNow:rate,txNow:rate,rxSeries:z.array(rate),txSeries:z.array(rate),observations:observations.optional()}).refine(r=>!r.observations||(r.observations.rxSeries.length===r.rxSeries.length&&r.observations.txSeries.length===r.txSeries.length&&(!(r.observations.rxNow||r.observations.txNow||r.observations.rxSeries.some(Boolean)||r.observations.txSeries.some(Boolean))||r.observations.sampledAt!==null)))
const payload=z.object({servers:z.array(row)}).refine(data=>new Set(data.servers.map(r=>r.id)).size===data.servers.length&&data.servers.every(r=>r.rxSeries.length===r.txSeries.length&&r.rxSeries.length===data.servers[0].rxSeries.length))
export interface DashboardTraffic {rx:(number|null)[];tx:(number|null)[];rxNow:number|null;txNow:number|null;observationsKnown:boolean;sampledAt:string|null}
/** Numeric legacy zero alone cannot confirm that the collector observed an interval. */
export function parseDashboardTraffic(raw:unknown):DashboardTraffic {
const parsed=payload.safeParse(raw)
if(!parsed.success)throw Error("Некорректные данные трафика: проверьте счётчики и согласованность серий")
const rows=parsed.data.servers,length=rows[0]?.rxSeries.length??0
const sum=(key:"rxNow"|"txNow")=>rows.every(r=>r.observations?.[key])?rows.reduce((sum,r)=>sum+r[key],0):null
const series=(key:"rxSeries"|"txSeries")=>Array.from({length},(_,i)=>rows.every(r=>r.observations?.[key][i])?rows.reduce((sum,r)=>sum+r[key][i],0):null)
const timestamps=rows.map(r=>r.observations?.sampledAt).filter((v):v is string=>!!v)
const total={rx:series("rxSeries"),tx:series("txSeries"),rxNow:sum("rxNow"),txNow:sum("txNow"),observationsKnown:rows.every(r=>!!r.observations),sampledAt:timestamps.length===rows.length&&timestamps.length?new Date(timestamps.reduce((oldest,v)=>Math.min(oldest,Date.parse(v)),Infinity)).toISOString():null}
if(![total.rxNow,total.txNow,...total.rx,...total.tx].every(v=>v===null||Number.isFinite(v)))throw Error("Суммарный трафик превышает допустимый диапазон")
return total
}