Docker images / prepare-release (push) Successful in 7s
Docker images / backend-image (push) Successful in 1m37s
Docker images / frontend-image (push) Successful in 2m44s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 40s
Docker images / publish-release (push) Successful in 10s
Added support for managing peer information in interface bindings, including new fields for peerPublicKey and peerName in the BoundIfaceTraffic interface. Updated the database schema to include these fields in user_interface_bindings and traffic_samples tables. Enhanced the UI components to display peer details alongside interface names, improving user experience and clarity in the traffic management system. Updated relevant functions and services to handle peer-specific logic, ensuring robust integration across the application.
126 lines
6.0 KiB
TypeScript
126 lines
6.0 KiB
TypeScript
import assert from "node:assert/strict"
|
|
import {
|
|
bpsToMbps,
|
|
bucketAvg,
|
|
buildTrafficFromSamples,
|
|
isLoopbackName,
|
|
mergeBuiltTraffic,
|
|
parseMonitorTraffic,
|
|
rateBpsFromDelta,
|
|
shouldIncludeIface,
|
|
type TrafficSampleLike,
|
|
} from "./traffic-rate.js"
|
|
|
|
assert.equal(isLoopbackName("lo"), true)
|
|
assert.equal(isLoopbackName("loopback"), true)
|
|
assert.equal(isLoopbackName("ether1"), false)
|
|
assert.equal(shouldIncludeIface("lo", true, false), false)
|
|
assert.equal(shouldIncludeIface("ether1", true, false), true)
|
|
assert.equal(shouldIncludeIface("ether1", false, false), false)
|
|
assert.equal(shouldIncludeIface("ether1", true, true), false)
|
|
assert.equal(shouldIncludeIface("lo", true, false, "lo"), true)
|
|
|
|
assert.equal(rateBpsFromDelta(1000, 2000, 0, 1000), 8000)
|
|
assert.equal(rateBpsFromDelta(1000, 500, 0, 1000), null)
|
|
assert.equal(rateBpsFromDelta(1000, 2000, 1000, 1000), null)
|
|
assert.equal(bpsToMbps(1_500_000), 1.5)
|
|
assert.equal(bpsToMbps(400_000), 0.4)
|
|
|
|
const buckets = bucketAvg(
|
|
[
|
|
{ t: 0, v: 10 },
|
|
{ t: 1000, v: 20 },
|
|
],
|
|
0,
|
|
1000,
|
|
4,
|
|
)
|
|
assert.equal(buckets.length, 4)
|
|
assert.equal(buckets[0], 10)
|
|
assert.equal(buckets[3], 20)
|
|
assert.equal(buckets[1], 0)
|
|
assert.equal(buckets[2], 0)
|
|
|
|
const t0 = "2026-09-06T10:00:00.000Z"
|
|
const t1 = "2026-09-06T10:00:30.000Z"
|
|
const t2 = "2026-09-06T10:01:00.000Z"
|
|
const start = Date.parse(t0)
|
|
const end = Date.parse(t2)
|
|
|
|
const samples: TrafficSampleLike[] = [
|
|
{ interfaceName: "ether1", sampledAt: t0, rxBytes: 1_000_000, txBytes: 500_000, rxBps: 0, txBps: 0, running: true, disabled: false },
|
|
{ interfaceName: "ether1", sampledAt: t1, rxBytes: 1_000_000 + 3_750_000, txBytes: 500_000 + 1_875_000, rxBps: 0, txBps: 0, running: true, disabled: false },
|
|
{ interfaceName: "ether1", sampledAt: t2, rxBytes: 100, txBytes: 50, rxBps: 0, txBps: 0, running: true, disabled: false },
|
|
{ interfaceName: "lo", sampledAt: t0, rxBytes: 0, txBytes: 0, rxBps: 0, txBps: 0, running: true, disabled: false },
|
|
{ interfaceName: "lo", sampledAt: t1, rxBytes: 9_000_000, txBytes: 9_000_000, rxBps: 0, txBps: 0, running: true, disabled: false },
|
|
]
|
|
|
|
const built = buildTrafficFromSamples(samples, start, end)
|
|
assert.ok(built.rxPeak > 0, "peak RX from delta")
|
|
assert.equal(built.rxNow, 1, "last valid delta after reset skip")
|
|
assert.ok(built.rxSeries.some((v) => v > 0), "bucket series not flat")
|
|
assert.ok(built.rxPeak <= 1.1, "lo excluded from peak")
|
|
|
|
const live = parseMonitorTraffic([
|
|
{ name: "ether1", "rx-bits-per-second": "2000000", "tx-bits-per-second": "500000" },
|
|
{ name: "lo", "rx-bits-per-second": "8000000", "tx-bits-per-second": "8000000" },
|
|
])
|
|
assert.equal(live.rxMbps, 2)
|
|
assert.equal(live.txMbps, 0.5)
|
|
|
|
const onceOnly = parseMonitorTraffic(
|
|
{ name: "ether1", "rx-bits-per-second": "1000000", "tx-bits-per-second": "0" },
|
|
{ onlyInterface: "ether1" },
|
|
)
|
|
assert.equal(onceOnly.rxMbps, 1)
|
|
|
|
const boundOnly = buildTrafficFromSamples(samples, start, end, ["ether1", "lo"])
|
|
assert.ok(boundOnly.rxPeak > 0, "bound list includes ether1")
|
|
assert.ok(boundOnly.rxPeak > built.rxPeak, "lo included when listed")
|
|
|
|
const merged = mergeBuiltTraffic([
|
|
buildTrafficFromSamples(samples, start, end, "ether1"),
|
|
buildTrafficFromSamples(samples, start, end, "ether1"),
|
|
])
|
|
assert.equal(merged.rxNow, built.rxNow * 2)
|
|
|
|
const wgSamples: TrafficSampleLike[] = [
|
|
{ interfaceName: "wg-msk", sampledAt: t0, rxBytes: 2_000_000, txBytes: 1_000_000, rxBps: 0, txBps: 0, running: true, disabled: false },
|
|
{ interfaceName: "wg-msk", sampledAt: t1, rxBytes: 2_000_000 + 1_875_000, txBytes: 1_000_000 + 937_500, rxBps: 0, txBps: 0, running: true, disabled: false },
|
|
{ interfaceName: "wg-msk", sampledAt: t2, rxBytes: 50, txBytes: 25, rxBps: 0, txBps: 0, running: true, disabled: false },
|
|
]
|
|
const userAgg = mergeBuiltTraffic([
|
|
buildTrafficFromSamples(samples, start, end, "ether1"),
|
|
buildTrafficFromSamples(wgSamples, start, end, "wg-msk"),
|
|
])
|
|
const listed = buildTrafficFromSamples([...samples, ...wgSamples], start, end, ["ether1", "wg-msk"])
|
|
assert.equal(userAgg.rxNow, listed.rxNow, "сумма привязанных ifaces = фильтр по списку имён")
|
|
assert.ok(userAgg.rxNow > built.rxNow, "агрегация пользователя больше одного iface")
|
|
|
|
const peerA: TrafficSampleLike[] = [
|
|
{ interfaceName: "wg-server", peerPublicKey: "peer-a", sampledAt: t0, rxBytes: 1_000_000, txBytes: 100_000, rxBps: 0, txBps: 0, running: true, disabled: false },
|
|
{ interfaceName: "wg-server", peerPublicKey: "peer-a", sampledAt: t1, rxBytes: 1_000_000 + 3_750_000, txBytes: 100_000 + 375_000, rxBps: 0, txBps: 0, running: true, disabled: false },
|
|
]
|
|
const peerB: TrafficSampleLike[] = [
|
|
{ interfaceName: "wg-server", peerPublicKey: "peer-b", sampledAt: t0, rxBytes: 500_000, txBytes: 50_000, rxBps: 0, txBps: 0, running: true, disabled: false },
|
|
{ interfaceName: "wg-server", peerPublicKey: "peer-b", sampledAt: t1, rxBytes: 500_000 + 1_875_000, txBytes: 50_000 + 187_500, rxBps: 0, txBps: 0, running: true, disabled: false },
|
|
]
|
|
const ifaceWg: TrafficSampleLike[] = [
|
|
{ interfaceName: "wg-server", sampledAt: t0, rxBytes: 10_000_000, txBytes: 2_000_000, rxBps: 0, txBps: 0, running: true, disabled: false },
|
|
{ interfaceName: "wg-server", sampledAt: t1, rxBytes: 10_000_000 + 7_500_000, txBytes: 2_000_000 + 750_000, rxBps: 0, txBps: 0, running: true, disabled: false },
|
|
]
|
|
const mixed = [...peerA, ...peerB, ...ifaceWg]
|
|
const rateA = buildTrafficFromSamples(mixed, start, end, "wg-server", "peer-a")
|
|
const rateB = buildTrafficFromSamples(mixed, start, end, "wg-server", "peer-b")
|
|
const rateIface = buildTrafficFromSamples(mixed, start, end, "wg-server")
|
|
assert.ok(rateA.rxNow > 0 && rateB.rxNow > 0, "скорость по каждому пиру")
|
|
assert.notEqual(rateA.rxNow, rateB.rxNow, "два пира одного iface — разный rate")
|
|
assert.ok(rateIface.rxNow > rateA.rxNow, "iface-level не суммирует пиров")
|
|
assert.equal(
|
|
buildTrafficFromSamples(mixed, start, end).rxNow,
|
|
rateIface.rxNow,
|
|
"режим сервера игнорирует семплы пиров",
|
|
)
|
|
|
|
console.log("traffic-rate tests ok")
|