Учтено RX по замерам
- {fmtGB(rxTotal)}
+ {fmtGiB(rxTotal)}
Учтено TX по замерам
- {fmtGB(txTotal)}
+ {fmtGiB(txTotal)}
diff --git a/backend/package.json b/backend/package.json
index 064fa5a0..67f78cd3 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -19,7 +19,7 @@
"test:exec": "tsx src/services/mikrotik-logs.test.ts && tsx src/routes/exec.test.ts && tsx src/plugins/auth.exec.test.ts",
"test:auth": "tsx src/lib/permissions.test.ts && tsx src/plugins/auth.smoke.test.ts && tsx src/plugins/auth.ospf-optimizer.test.ts",
"test:wireguard": "npx tsx src/services/wireguard-config.test.ts && npx tsx src/services/wireguard-ipv6.test.ts && npx tsx --test src/services/wireguard-import.test.ts src/services/wireguard-import-apply.test.ts src/services/wireguard-import-revision.test.ts src/services/wireguard-delete.test.ts && npx tsx src/routes/wireguard-import.test.ts",
- "test:traffic-rate": "tsx src/services/traffic-rate.test.ts && tsx src/services/traffic-observation.test.ts && tsx src/services/traffic-freshness.test.ts && tsx src/services/traffic-live.test.ts && tsx src/services/traffic-observation-sql.test.ts && tsx src/services/traffic-history-sql.test.ts && tsx src/services/traffic-live-sql.test.ts",
+ "test:traffic-rate": "tsx src/services/traffic-rate.test.ts && tsx src/services/traffic-observation.test.ts && tsx src/services/traffic-freshness.test.ts && tsx src/services/traffic-live.test.ts && tsx src/services/traffic-observation-sql.test.ts && tsx src/services/traffic-history-sql.test.ts && tsx src/services/traffic-live-sql.test.ts && tsx src/services/traffic-volume-sql.test.ts",
"test:traffic-flow": "tsx src/routes/traffic-flow-query.test.ts && tsx src/services/traffic-flow-month.test.ts && tsx src/services/traffic-flow-parse.test.ts && tsx src/services/traffic-flow-ipv6-pipeline.test.ts && tsx src/services/traffic-flow-map-exporter.test.ts && tsx src/services/traffic-flow-ifaces.test.ts && tsx src/services/traffic-flow-ifindex.test.ts && tsx src/services/traffic-flow-dedup.test.ts && tsx src/services/traffic-flow-planes.test.ts && tsx src/services/traffic-flow-ip.test.ts && tsx src/services/traffic-flow-dest.test.ts && tsx src/services/traffic-flow-classify.test.ts && tsx src/services/traffic-flow-ripe.test.ts && tsx src/services/traffic-flow-brands.test.ts && tsx src/services/traffic-flow-ingest.test.ts && tsx src/services/traffic-flow-analytics.test.ts && tsx src/services/traffic-flow-map-hops.test.ts && tsx src/services/traffic-flow-purge.test.ts && tsx src/services/traffic-flow-geoip.test.ts && tsx src/services/traffic-flow-facts.test.ts && tsx src/services/traffic-flow-facts-filter.test.ts && tsx src/services/traffic-flow-facts-rebuild.test.ts && tsx src/services/statistics-aggregate.test.ts",
"test:users": "tsx src/modules/users/iface-type.test.ts && tsx src/modules/users/bindings.test.ts && tsx src/modules/users/interface-inventory.test.ts && tsx src/routes/users-catalog.test.ts",
"test:pg": "tsx src/db/pg-pool.test.ts && tsx src/db/sql-bind.test.ts && tsx src/db/sqlite-json.test.ts && tsx src/db/traffic-flags.test.ts && tsx src/db/pg-schema.test.ts && tsx src/services/config-revisions.test.ts",
diff --git a/backend/src/routes/traffic.ts b/backend/src/routes/traffic.ts
index f58d8787..e49cb2ad 100644
--- a/backend/src/routes/traffic.ts
+++ b/backend/src/routes/traffic.ts
@@ -28,6 +28,7 @@ import {
} from "../services/traffic-users.js"
interface TrafficServerDto {
+ volumeUnit: "GiB"
observations?:TrafficObservations
id: string
name: string
@@ -95,6 +96,7 @@ function buildServerTraffic(
): TrafficServerDto {
const built = buildTrafficFromSamples(rows, rangeStartMs, rangeEndMs, onlyInterface,undefined,true)
return {
+ volumeUnit: "GiB",
id: String(s.id),
name: s.name || s.host,
site: s.site || "—",
diff --git a/backend/src/services/traffic-rate.test.ts b/backend/src/services/traffic-rate.test.ts
index 6ae44f1c..4d516465 100644
--- a/backend/src/services/traffic-rate.test.ts
+++ b/backend/src/services/traffic-rate.test.ts
@@ -58,6 +58,7 @@ const samples: TrafficSampleLike[] = [
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.equal(built.rxTotalGiB,3_750_000/1024**3,"only observed positive delta counts; reset is not volume")
assert.ok(built.rxSeries.some((v) => v > 0), "bucket series not flat")
assert.ok(built.rxPeak <= 1.1, "lo excluded from peak")
diff --git a/backend/src/services/traffic-rate.ts b/backend/src/services/traffic-rate.ts
index 6d9ecb14..75d7ceab 100644
--- a/backend/src/services/traffic-rate.ts
+++ b/backend/src/services/traffic-rate.ts
@@ -216,8 +216,8 @@ export function buildTrafficFromSamples(
txNow: last?.tx ?? 0,
rxPeak,
txPeak,
- rxTotalGiB: Number((rxBytesDelta / (1024 ** 3)).toFixed(1)),
- txTotalGiB: Number((txBytesDelta / (1024 ** 3)).toFixed(1)),
+ rxTotalGiB: rxBytesDelta / (1024 ** 3),
+ txTotalGiB: txBytesDelta / (1024 ** 3),
sessions,
rxSeries,
txSeries,
@@ -268,8 +268,6 @@ export function mergeBuiltTraffic(parts: BuiltTrafficSeries[]): BuiltTrafficSeri
acc.txSeries[i] += p.txSeries[i] ?? 0
}
}
- acc.rxTotalGiB = Number(acc.rxTotalGiB.toFixed(1))
- acc.txTotalGiB = Number(acc.txTotalGiB.toFixed(1))
return parts.every(p => p.observations) ? { ...acc, observations: mergeTrafficObservations(parts.map(p => p.observations!)) } : acc
}
diff --git a/backend/src/services/traffic-users.ts b/backend/src/services/traffic-users.ts
index 8ac48053..0bd1e8ef 100644
--- a/backend/src/services/traffic-users.ts
+++ b/backend/src/services/traffic-users.ts
@@ -1,4 +1,3 @@
-import { eq } from "drizzle-orm"
import { db } from "../db/index.js"
import { servers } from "../db/schema.js"
import { listUsers } from "../modules/users/service/users-service.js"
@@ -13,6 +12,7 @@ import { applyTrafficFreshness } from "./traffic-freshness.js"
import type { TrafficObservations } from "./traffic-rate.js"
export interface BoundIfaceTrafficDto {
+ volumeUnit: "GiB"
observations?: TrafficObservations
id: string
bindingId: string
@@ -40,6 +40,7 @@ export interface BoundIfaceTrafficDto {
}
export interface UserTrafficDto {
+ volumeUnit: "GiB"
observations?: TrafficObservations
id: string
login: string
@@ -59,6 +60,7 @@ export interface UserTrafficDto {
function seriesFromBuilt(built: BuiltTrafficSeries) {
return {
+ volumeUnit: "GiB" as const,
observations: built.observations,
rxNow: built.rxNow,
txNow: built.txNow,
@@ -71,12 +73,6 @@ function seriesFromBuilt(built: BuiltTrafficSeries) {
}
}
-async function serverStatus(serverId: number): Promise<"online" | "offline"> {
- const row = (await db.select().from(servers).where(eq(servers.id, serverId)).limit(1))[0]
- if (!row?.enabled) return "offline"
- return "online"
-}
-
export async function buildUserTrafficList(rangeStartMs: number, rangeEndMs: number): Promise
{
const sinceIso = new Date(rangeStartMs).toISOString()
const users = await listUsers()
@@ -122,7 +118,7 @@ export async function buildUserTrafficList(rangeStartMs: number, rangeEndMs: num
serverSite: b.serverSite,
serverCountry: b.serverCountry,
...seriesFromBuilt(built),
- status: running && (await serverStatus(b.serverId)) === "online" ? "online" : "offline",
+ status: running && server?.enabled ? "online" : "offline",
})
}
diff --git a/backend/src/services/traffic-volume-sql.test.ts b/backend/src/services/traffic-volume-sql.test.ts
new file mode 100644
index 00000000..b8980dfe
--- /dev/null
+++ b/backend/src/services/traffic-volume-sql.test.ts
@@ -0,0 +1,40 @@
+import assert from "node:assert/strict"
+import Fastify from "fastify"
+import { pool, closePool } from "../db/index.js"
+import { initDatabase } from "../db/bootstrap.js"
+import trafficRoutes from "../routes/traffic.js"
+const database=process.env.MMAPP_OWN_SQL_DB
+if(!database)console.log("traffic volume 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-volume180-"+Date.now()+"-",userId=prefix+"user",at=new Date(Date.now()-5000).toISOString(),updatedAt=new Date(Date.now()-3600_000).toISOString()
+ let serverId:number|undefined,cases=0
+ const app=Fastify();await app.register(trafficRoutes)
+ const get=async(path:string)=>{const r=await app.inject({method:"GET",url:path});assert.equal(r.statusCode,200);return r.json()}
+ const put=async(name:string,offset:number,bytes:number)=>pool.query("INSERT INTO traffic_samples(server_id,interface_name,sampled_at,rx_bytes,tx_bytes,flags) VALUES($1,$2,$3,$4,$4,5)",[serverId,name,new Date(Date.parse(at)+offset).toISOString(),bytes])
+ try {
+ await initDatabase()
+ serverId=Number((await pool.query("INSERT INTO servers(name,host,port,use_ssl,enabled,username,password,updated_at) VALUES($1,'127.0.0.1',1,false,true,'fixture','fixture',$2) RETURNING id",[prefix+"server",updatedAt])).rows[0].id)
+ await pool.query("INSERT INTO traffic_settings(id,enabled,interval_sec) VALUES(1,true,30) ON CONFLICT(id) DO UPDATE SET enabled=true,interval_sec=30")
+ await pool.query("INSERT INTO traffic_collection_state(server_id,server_updated_at,last_attempt_at,last_succeeded_at,status) VALUES($1,$2,$3,$3,'ok')",[serverId,updatedAt,at])
+ await pool.query("INSERT INTO app_users(id,login,name) VALUES($1,$2,'Volume fixture')",[userId,prefix+"login"])
+ await pool.query("INSERT INTO user_interface_bindings(id,user_id,server_id,interface_name,interface_type) VALUES($1,$2,$3,'ether1','ether')",[prefix+"binding",userId,serverId])
+ for(const bytes of[1,1024,1024**2,1024**3]){
+ await pool.query("DELETE FROM traffic_samples WHERE server_id=$1",[serverId]);await put("ether1",-30_000,0);await put("ether1",0,bytes)
+ const s=(await get(`/traffic/servers/${serverId}?range=1h`)).server
+ assert.equal(s.volumeUnit,"GiB");assert.equal(s.rxTotal,bytes/1024**3);assert.equal(s.observations.rxNow,true)
+ const user=(await get("/traffic/users?range=1h")).users.find((u:{id:string})=>u.id===userId)
+ assert.equal(user.volumeUnit,"GiB");assert.equal(user.rxTotal,s.rxTotal)
+ const iface=(await get("/traffic/bound-interfaces?range=1h")).interfaces.find((i:{userId:string})=>i.userId===userId)
+ assert.equal(iface.volumeUnit,"GiB");assert.equal(iface.rxTotal,s.rxTotal);cases++
+ }
+ await pool.query("DELETE FROM traffic_samples WHERE server_id=$1",[serverId]);await pool.query("DELETE FROM user_interface_bindings WHERE user_id=$1",[userId])
+ for(let i=0;i<100;i++){const name=`ether${i}`;await put(name,-30_000,0);await put(name,0,1024**2);await pool.query("INSERT INTO user_interface_bindings(id,user_id,server_id,interface_name,interface_type) VALUES($1,$2,$3,$4,'ether')",[prefix+"binding"+i,userId,serverId,name])}
+ let s=(await get(`/traffic/servers/${serverId}?range=1h`)).server;assert.equal(s.rxTotal,100/1024)
+ const user=(await get("/traffic/users?range=1h")).users.find((u:{id:string})=>u.id===userId);assert.equal(user.rxTotal,100/1024);cases++
+ await pool.query("DELETE FROM traffic_samples WHERE server_id=$1",[serverId]);await put("ether1",-90_000,0);await put("ether1",-60_000,1024**2);await put("ether1",-30_000,512);await put("ether1",0,1024)
+ s=(await get(`/traffic/servers/${serverId}?range=1h`)).server;assert.equal(s.rxTotal,(1024**2+512)/1024**3);cases++
+ console.log(`traffic volume owned SQL: ${cases} cases; exact bytes/binary unit/server/user/binding/100-part aggregate/reset passed`)
+ }finally{await app.close();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()}
+}
diff --git a/docs/ux-ui-external-acceptance.md b/docs/ux-ui-external-acceptance.md
index 6d6bdfd0..438cb743 100644
--- a/docs/ux-ui-external-acceptance.md
+++ b/docs/ux-ui-external-acceptance.md
@@ -1,6 +1,6 @@
# UX/UI: протокол внешней приёмки 12.5
-Актуальное продолжение 2026-10-12: завершён12.5.179 — live complete payload/expected interfaces/auth/disable-config guard; scoped retry/retain cause/latest sample, abort cleanup/receipt timestamp, positive tiny rates. OwnSQL9+14+10/24570ms,7E2E passed13.3s,types/build/ESLint/1036compiled parity. Both ux-live179 healthy/running,2 actual CHR SSE samples+live UI2 passed; ownDB/role/container removed. Следующий180 — точность и binary units учтённых объёмов: baseline positive1KiB/1MiB rounded0 и1GiB mislabeledGB подтверждён, ещё не исправлен.12.5 открыта.
+Актуальное продолжение 2026-10-12: завершён12.5.180 — точные учтённые объёмы без early rounding, explicit GiB API/КиБ-МиБ-ГиБ UI, unknown unit→—, N+1 server-status query removed. OwnSQL9+14+10+6/31474ms,18E2E/36.3s/types/build/ESLint/1036compiled parity. Both ux-volume180 healthy/running/API2/real-live browser2 passed; ownDB/role/container removed. Следующий181 — проверить freshness статусов привязок и peer/interface flags;12.5 открыта.
IPsec78: реальный PSK responder established, mode-config198.18.78.10 и online=true подтверждены API; точные собственные объекты удалены. Сертификатный/DN online, late failure и history restore остаются открытыми. [Отчёт](ux-ui/acceptance/ipsec-mode-config-2026-10-09.json), [журнал](ux-ui-progress.md).
@@ -696,7 +696,7 @@ flowchart LR
## 12.5.77 — основной блок IPsec (2026-10-09)
-Актуальное продолжение 2026-10-12: завершён12.5.179 — live complete payload/expected interfaces/auth/disable-config guard; scoped retry/retain cause/latest sample, abort cleanup/receipt timestamp, positive tiny rates. OwnSQL9+14+10/24570ms,7E2E passed13.3s,types/build/ESLint/1036compiled parity. Both ux-live179 healthy/running,2 actual CHR SSE samples+live UI2 passed; ownDB/role/container removed. Следующий180 — точность и binary units учтённых объёмов: baseline positive1KiB/1MiB rounded0 и1GiB mislabeledGB подтверждён, ещё не исправлен.12.5 открыта.
+Актуальное продолжение 2026-10-12: завершён12.5.180 — точные учтённые объёмы без early rounding, explicit GiB API/КиБ-МиБ-ГиБ UI, unknown unit→—, N+1 server-status query removed. OwnSQL9+14+10+6/31474ms,18E2E/36.3s/types/build/ESLint/1036compiled parity. Both ux-volume180 healthy/running/API2/real-live browser2 passed; ownDB/role/container removed. Следующий181 — проверить freshness статусов привязок и peer/interface flags;12.5 открыта.
Подробный объём, протоколы, схема и ограничения: [12.5.77 в журнале](ux-ui-progress.md).
@@ -2070,3 +2070,24 @@ Owned Node22/PG18.6 profile test:traffic-rate24570ms без skip:9 counters/sour
[Настоящие CHR SSE](ux-ui/acceptance/traffic-live-native-2026-10-12.json) получили первый полный sample на обоих sourceId1/2, затем streams отменены: native interface GET + monitor-traffic read-only POST. Пример краткого наблюдения194 RX0.031584/TX0.002016Mbps,104 RX/TX0.002112Mbps; это не SLA/capacity/WAN proof. Пароли/конфигурация не менялись, messages0. [Production browser1440/320](ux-ui/acceptance/traffic-live-deployed-2026-10-12.json): actual live labels/receipt provenance,3APIs200,no overflow/pageerrors. Lab AUTH_REQUIRED=false; JWT/RBAC scopes не объявлены полностью принятыми по этой smoke.
Следующий180 — volume precision/binary units. Read-only baseline уже показал: measured1KiB/1MiB API round to0;100×1MiB также aggregate0;1GiB подписан1.0GB. Baseline сохранён в ignored tmp/volume180-baseline.json и пока не выдан за исправление. Sparse intervals/peer completeness/identity recreation/restore/multi-instance/scale и внешние integrations остаются12.5. Локальный коммит без push.
+
+## 12.5.180 — точность учтённых объёмов и бинарные единицы (2026-10-12)
+
+Read-only [baseline](ux-ui/acceptance/traffic-volume-baseline-2026-10-12.json) подтверждён до правки: measured1KiB/1MiB превращались в API0 и0.0GB;100×1MiB также aggregate0;1GiB выдавался с decimal labelGB. Убрано toFixed(1) из buildTrafficFromSamples/mergeBuiltTraffic: округление отдельных слагаемых больше не теряет суммарный трафик. Учёт остаётся по known соседним counter deltas, reset/unknown не добавляют выдуманные bytes. Отсутствующий baseline и coverage gaps по-прежнему ограничивают полноту периода.
+
+DTO серверов, пользователей и привязок объявляет volumeUnit=GiB. Frontend принимает эту единицу явно, absent unit оставляет volume — без потери known speed/history; unsupportedGB marker отклоняет DTO. fmtGiB отображает Б/КиБ/МиБ/ГиБ/ТиБ/ПиБ, positive ниже1B как<1B, true0 как0B. Число округляется только при форматировании. Existing rate formatter остаётся decimal Кбит/Мбит/Гбит, binary объёмы не смешиваются с decimal rates. TotalsRow получил semantic group label. ReUI layout/theme/components сохранены. Удалён N+1 server-status SELECT на каждую binding: используется уже прочитанный catalog snapshot.
+
+| Состояние | Схема | Пример |
+| --- | --- | --- |
+| Small positive bytes | bytes → exact GiB DTO → подходящая binary unit | [ПК](ux-ui/screens/traffic-volume-1440-2026-10-12.png), [320](ux-ui/screens/traffic-volume-320-2026-10-12.png), API-fixture component capture |
+| Old response without unit | unknown unit → —, speeds/history retained | E2E actual API path fixture |
+
+Backend formulas/volume assertion (known positive delta counts; reset does not), frontend unit boundary/unknown/zero/projection, TypeScript/backend build/ESLint passed.18 E2E passed36.3s (2new volume+10detail+6mobile), screenshot delta2 passed10.5s не новые cases; final widget captures просмотрены. Новые positive fixtures явно объявляют GiB. Existing user8 GRE/VXLAN screenshots не тронуты.
+
+Owned Node22/PG18.6 test:traffic-rate31474ms:9 counters+14history+10actual TCP SSE+6new volume SQL cases, без SQL skip. Новые6 — storage/API GET для1B/1KiB/1MiB/1GiB на server/user/binding;100-part aggregate100MiB; reset lower-bound1MiB+512B. Seeded marker5 rows — собственные SQL fixtures, не физические device counts; отсутствие RouterOS calls в volume-only case не объявлено native acceptance. OwnDB mm_accept_166_1791816469068/ограниченная role/container удалены, absence verified. [SQL](ux-ui/acceptance/traffic-volume-sql-2026-10-12.json).
+
+[Source452/522](ux-ui/acceptance/traffic-volume-source-2026-10-12.json)/[compiled1036](ux-ui/acceptance/traffic-volume-compiled-2026-10-12.json) SHA256 parity passed. Обе production builds прошли. Frontend ux-volume180 staged with backend ux-live179 [interop](ux-ui/acceptance/traffic-volume-interop-2026-10-12.json): volumes без unit unknown до backend update. Затем both ux-volume180/version1.0.0-test.volume180 [runtime](ux-ui/acceptance/traffic-volume-runtime-2026-10-12.json) healthy/running/imageIds. Только.env tags, rollback both ux-live179; no secret env copy/new migration. Старую вкладку следует перезагрузить для новых unit labels.
+
+[Actual volume API](ux-ui/acceptance/traffic-volume-api-2026-10-12.json) показывает GiB и неокруглённые quantities обоих CHR из штатных collector rows; этот checker не отправлял новых native commands. [Browser1440/320](ux-ui/acceptance/traffic-volume-deployed-2026-10-12.json):3APIs200, actual live/receipt labels/provenance, no errors/overflow. Рабочая конфигурация/пароли не менялись, messages0; auto collectors остаются штатными. Это не WAN byte billing/capacity/SLA proof и не exact arithmetic acceptance за пределами safe integer/scale bounds.
+
+Следующий181 — freshness статуса привязанных интерфейсов: enabled device+old running flag не должен объявлять online после failed/stale poll; peer enabled не доказывает handshake. Проверить native flag provenance и scalar shape, не переписывать legacy rows по догадке. Coverage/peer completeness/recreated interfaces/restore/multi-instance/scale/external integrations остаются12.5. Локальный коммит без push.
diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md
index a471e63b..5ff29c4c 100644
--- a/docs/ux-ui-plan.md
+++ b/docs/ux-ui-plan.md
@@ -1,12 +1,12 @@
# План реализации UX/UI по пятичасовым окнам
-Актуальное продолжение 2026-10-12: завершён12.5.179 — live complete payload/expected interfaces/auth/disable-config guard; scoped retry/retain cause/latest sample, abort cleanup/receipt timestamp, positive tiny rates. OwnSQL9+14+10/24570ms,7E2E passed13.3s,types/build/ESLint/1036compiled parity. Both ux-live179 healthy/running,2 actual CHR SSE samples+live UI2 passed; ownDB/role/container removed. Следующий180 — точность и binary units учтённых объёмов: baseline positive1KiB/1MiB rounded0 и1GiB mislabeledGB подтверждён, ещё не исправлен.12.5 открыта.
+Актуальное продолжение 2026-10-12: завершён12.5.180 — точные учтённые объёмы без early rounding, explicit GiB API/КиБ-МиБ-ГиБ UI, unknown unit→—, N+1 server-status query removed. OwnSQL9+14+10+6/31474ms,18E2E/36.3s/types/build/ESLint/1036compiled parity. Both ux-volume180 healthy/running/API2/real-live browser2 passed; ownDB/role/container removed. Следующий181 — проверить freshness статусов привязок и peer/interface flags;12.5 открыта.
Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-12. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат.
## Актуальная точка продолжения — 2026-10-12
-Актуальное продолжение 2026-10-12: завершён12.5.179 — live complete payload/expected interfaces/auth/disable-config guard; scoped retry/retain cause/latest sample, abort cleanup/receipt timestamp, positive tiny rates. OwnSQL9+14+10/24570ms,7E2E passed13.3s,types/build/ESLint/1036compiled parity. Both ux-live179 healthy/running,2 actual CHR SSE samples+live UI2 passed; ownDB/role/container removed. Следующий180 — точность и binary units учтённых объёмов: baseline positive1KiB/1MiB rounded0 и1GiB mislabeledGB подтверждён, ещё не исправлен.12.5 открыта.
+Актуальное продолжение 2026-10-12: завершён12.5.180 — точные учтённые объёмы без early rounding, explicit GiB API/КиБ-МиБ-ГиБ UI, unknown unit→—, N+1 server-status query removed. OwnSQL9+14+10+6/31474ms,18E2E/36.3s/types/build/ESLint/1036compiled parity. Both ux-volume180 healthy/running/API2/real-live browser2 passed; ownDB/role/container removed. Следующий181 — проверить freshness статусов привязок и peer/interface flags;12.5 открыта.
Этапы0–11 завершены в локальных границах журнала. История выполненных внешних блоков и доказательства сохранены в [журнале](ux-ui-stage-12.md) и [протоколе](ux-ui-external-acceptance.md). Реальная Telegram доставка, Portal SSO, multi-instance и оставшиеся профильные сетевые ограничения открыты.
@@ -371,6 +371,6 @@ flowchart LR
## 12.5.77 — основной блок IPsec (2026-10-09)
-Актуальное продолжение 2026-10-12: завершён12.5.179 — live complete payload/expected interfaces/auth/disable-config guard; scoped retry/retain cause/latest sample, abort cleanup/receipt timestamp, positive tiny rates. OwnSQL9+14+10/24570ms,7E2E passed13.3s,types/build/ESLint/1036compiled parity. Both ux-live179 healthy/running,2 actual CHR SSE samples+live UI2 passed; ownDB/role/container removed. Следующий180 — точность и binary units учтённых объёмов: baseline positive1KiB/1MiB rounded0 и1GiB mislabeledGB подтверждён, ещё не исправлен.12.5 открыта.
+Актуальное продолжение 2026-10-12: завершён12.5.180 — точные учтённые объёмы без early rounding, explicit GiB API/КиБ-МиБ-ГиБ UI, unknown unit→—, N+1 server-status query removed. OwnSQL9+14+10+6/31474ms,18E2E/36.3s/types/build/ESLint/1036compiled parity. Both ux-volume180 healthy/running/API2/real-live browser2 passed; ownDB/role/container removed. Следующий181 — проверить freshness статусов привязок и peer/interface flags;12.5 открыта.
Подробный объём, протоколы, схема и ограничения: [12.5.77 в журнале](ux-ui-progress.md).
diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md
index b1474b56..092a18e1 100644
--- a/docs/ux-ui-progress.md
+++ b/docs/ux-ui-progress.md
@@ -1,6 +1,6 @@
# UX/UI: журнал реализации
-Актуальное продолжение 2026-10-12: завершён12.5.179 — live complete payload/expected interfaces/auth/disable-config guard; scoped retry/retain cause/latest sample, abort cleanup/receipt timestamp, positive tiny rates. OwnSQL9+14+10/24570ms,7E2E passed13.3s,types/build/ESLint/1036compiled parity. Both ux-live179 healthy/running,2 actual CHR SSE samples+live UI2 passed; ownDB/role/container removed. Следующий180 — точность и binary units учтённых объёмов: baseline positive1KiB/1MiB rounded0 и1GiB mislabeledGB подтверждён, ещё не исправлен.12.5 открыта.
+Актуальное продолжение 2026-10-12: завершён12.5.180 — точные учтённые объёмы без early rounding, explicit GiB API/КиБ-МиБ-ГиБ UI, unknown unit→—, N+1 server-status query removed. OwnSQL9+14+10+6/31474ms,18E2E/36.3s/types/build/ESLint/1036compiled parity. Both ux-volume180 healthy/running/API2/real-live browser2 passed; ownDB/role/container removed. Следующий181 — проверить freshness статусов привязок и peer/interface flags;12.5 открыта.
Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md).
@@ -38,6 +38,27 @@
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 |
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–168 и ограничения ниже. Runtime backend ux-month149/frontend ux-sessions150. |
+## 12.5.180 — точность учтённых объёмов и бинарные единицы (2026-10-12)
+
+Read-only [baseline](ux-ui/acceptance/traffic-volume-baseline-2026-10-12.json) подтверждён до правки: measured1KiB/1MiB превращались в API0 и0.0GB;100×1MiB также aggregate0;1GiB выдавался с decimal labelGB. Убрано toFixed(1) из buildTrafficFromSamples/mergeBuiltTraffic: округление отдельных слагаемых больше не теряет суммарный трафик. Учёт остаётся по known соседним counter deltas, reset/unknown не добавляют выдуманные bytes. Отсутствующий baseline и coverage gaps по-прежнему ограничивают полноту периода.
+
+DTO серверов, пользователей и привязок объявляет volumeUnit=GiB. Frontend принимает эту единицу явно, absent unit оставляет volume — без потери known speed/history; unsupportedGB marker отклоняет DTO. fmtGiB отображает Б/КиБ/МиБ/ГиБ/ТиБ/ПиБ, positive ниже1B как<1B, true0 как0B. Число округляется только при форматировании. Existing rate formatter остаётся decimal Кбит/Мбит/Гбит, binary объёмы не смешиваются с decimal rates. TotalsRow получил semantic group label. ReUI layout/theme/components сохранены. Удалён N+1 server-status SELECT на каждую binding: используется уже прочитанный catalog snapshot.
+
+| Состояние | Схема | Пример |
+| --- | --- | --- |
+| Small positive bytes | bytes → exact GiB DTO → подходящая binary unit | [ПК](ux-ui/screens/traffic-volume-1440-2026-10-12.png), [320](ux-ui/screens/traffic-volume-320-2026-10-12.png), API-fixture component capture |
+| Old response without unit | unknown unit → —, speeds/history retained | E2E actual API path fixture |
+
+Backend formulas/volume assertion (known positive delta counts; reset does not), frontend unit boundary/unknown/zero/projection, TypeScript/backend build/ESLint passed.18 E2E passed36.3s (2new volume+10detail+6mobile), screenshot delta2 passed10.5s не новые cases; final widget captures просмотрены. Новые positive fixtures явно объявляют GiB. Existing user8 GRE/VXLAN screenshots не тронуты.
+
+Owned Node22/PG18.6 test:traffic-rate31474ms:9 counters+14history+10actual TCP SSE+6new volume SQL cases, без SQL skip. Новые6 — storage/API GET для1B/1KiB/1MiB/1GiB на server/user/binding;100-part aggregate100MiB; reset lower-bound1MiB+512B. Seeded marker5 rows — собственные SQL fixtures, не физические device counts; отсутствие RouterOS calls в volume-only case не объявлено native acceptance. OwnDB mm_accept_166_1791816469068/ограниченная role/container удалены, absence verified. [SQL](ux-ui/acceptance/traffic-volume-sql-2026-10-12.json).
+
+[Source452/522](ux-ui/acceptance/traffic-volume-source-2026-10-12.json)/[compiled1036](ux-ui/acceptance/traffic-volume-compiled-2026-10-12.json) SHA256 parity passed. Обе production builds прошли. Frontend ux-volume180 staged with backend ux-live179 [interop](ux-ui/acceptance/traffic-volume-interop-2026-10-12.json): volumes без unit unknown до backend update. Затем both ux-volume180/version1.0.0-test.volume180 [runtime](ux-ui/acceptance/traffic-volume-runtime-2026-10-12.json) healthy/running/imageIds. Только.env tags, rollback both ux-live179; no secret env copy/new migration. Старую вкладку следует перезагрузить для новых unit labels.
+
+[Actual volume API](ux-ui/acceptance/traffic-volume-api-2026-10-12.json) показывает GiB и неокруглённые quantities обоих CHR из штатных collector rows; этот checker не отправлял новых native commands. [Browser1440/320](ux-ui/acceptance/traffic-volume-deployed-2026-10-12.json):3APIs200, actual live/receipt labels/provenance, no errors/overflow. Рабочая конфигурация/пароли не менялись, messages0; auto collectors остаются штатными. Это не WAN byte billing/capacity/SLA proof и не exact arithmetic acceptance за пределами safe integer/scale bounds.
+
+Следующий181 — freshness статуса привязанных интерфейсов: enabled device+old running flag не должен объявлять online после failed/stale poll; peer enabled не доказывает handshake. Проверить native flag provenance и scalar shape, не переписывать legacy rows по догадке. Coverage/peer completeness/recreated interfaces/restore/multi-instance/scale/external integrations остаются12.5. Локальный коммит без push.
+
## 12.5.179 — достоверный live-трафик, остановка и переподключение (2026-10-12)
parseMonitorTraffic больше не превращает empty/missing/malformed/negative/prefix values в0. Требуются именованные уникальные интерфейсы и обе конечные неотрицательные скорости; для aggregate — полный expected set актуальных активных интерфейсов, loopback исключён. Duplicate/wrong/missing interface и неполный ответ дают error. Настоящий measured0 сохраняется. Дельты и Mbps больше не округляются до хранения/передачи: positive1byte/30s не исчезает; legacy bigint rxBps/txBps округляются только при insert, read consumers используют counters. sum snapshot сохраняет полную точность. UI positive меньше разрешения отображается «<1 Кбит/с», а не fake idle0. Это не подтверждение максимальной производительности интерфейса.
diff --git a/docs/ux-ui-remaining-acceptance.md b/docs/ux-ui-remaining-acceptance.md
index 8165c073..a6f38ec4 100644
--- a/docs/ux-ui-remaining-acceptance.md
+++ b/docs/ux-ui-remaining-acceptance.md
@@ -1,8 +1,8 @@
# Оставшаяся внешняя приёмка — 2026-10-12
-Это актуальная матрица после 12.5.179. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
+Это актуальная матрица после 12.5.180. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
-Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-live179`, frontend `ux-live179`; production lab использует AUTH_REQUIRED=false. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись.
+Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-volume180`, frontend `ux-volume180`; production lab использует AUTH_REQUIRED=false. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись.
| Раздел | Подтверждено | Осталось / условие завершения |
| --- | --- | --- |
@@ -18,7 +18,7 @@
| Recursive routes | Production PUT/change/own history restore88, disabled guards, partial/unknown/applied outcomes, live формы/контекст | Автоматический rollback/retry, durable receipts и несколько экземпляров отсутствуют; PUT не является idempotent noop. Полная произвольная legacy catalog миграция не принята |
| Diagnostics/terminal | Allowlist/native reads, Ping/trace/route/MTU/DNS, actual avg-rtt, disabled guards, JWT/live PC/mobile89; real default memory tail/log limits/API/UI133 | DNS использует backend resolver; MTU — ограниченная оценка, не гарантированный максимум. Stop ожидания не доказывает остановку RouterOS команды; arbitrary CLI и physical disconnect не приняты |
| Portal/интеграции | Локальные и isolated JWT guards; UI/API error states в границах журналов | Настоящий Portal issuer/login/отзыв прав; RIPE SDK two-IP/no-fetch passed140, native IPv6 ULA flow pipeline passed145 (IPv4 transport), public IPv6 WAN/local licensed MaxMind/S3/container/certificate jobs и Gitea delivery требуют отдельных тестовых endpoints/аккаунтов. Не отправлять сообщения и не публиковать релиз без соответствующего поручения |
-| Общая приёмка | Локальная12.3.83; UI1244/context221/compiled984 parity139; профильные build/E2E/live проверки последующих блоков | Traffic initial live mock-id gate passed141; IPv6 special ranges/cache passed142; unknown RIPE coordinates passed143; IPv6 synthetic UDP/worker/SQL passed144; native CHR ULA/IPFIX/API/UI passed145; ICMPv6/IPv6 endpoint labels passed146; HTTP DTO validation passed147; scoped flow catalog/serverId guards passed148; strict monthly calendar passed149; Sessions labels passed150; application GRE static IPv6 lifecycle151 passed; WireGuard static IPv6 lifecycle152 и disabled SQL/API gates passed; OSPF/BFD boundary/warning153 passed; OSPFv3 routes154 accepted in recorded bounds; BGP IPv6 basic155 passed; bigint ID/SQL guards156 passed; общая UI1260/context221/backend24 regression157 passed; IPv6 BFD correlation158 profile/deployed passed; native OSPFv3/BFDv6 boundary/recovery159 passed; BGP IPv6/BFD withdrawal/recovery160 passed; selected BGP/alert unknown guards161 replay/profiles/deployed passed (SQL stability skipped); private4-byte ASN162 native/API/UI passed; duplicate BGP alert keys163 replay/profiles/deployed passed; nullable selected BGP164 replay/profiles/API/UI passed; collector165 parallel/current guards/read-only PG passed; positive partial/error SQL166/full24 with PG passed (optional network SQL not repeated); GRE/BGP freshness167/SQL8 passed; other source audit168 выявил identity/disabled/freshness gaps; локальный server/traffic identity guard169 passed без SQL/deploy, source/per-probe/freshness170/ownSQL16/compiled1008/deployed passed; source diagnostics171/42E2E/ownSQL metadata/live2 passed; monitoring history172/ownSQL26/compiled1020/deployed passed; Ping known/error173/ownHTTP-SQL8/85uniqueE2E/native4/4/deployed passed; traffic confidence174 baseline reproduced/local guard passed; owned SQL9/9358ms/deploy175/1024compiled/live smoke passed; legacy history/provenance open; compound RTT176 local/native replay2 passed, SQL10/deploy177/1024compiled/live smoke passed; history178/legacy provenance/current TTL/manual truth/SQL9+14/35unique E2E/native GET/deployed passed; live179 complete/disable-config/bounded fallback/ownSQL10/nativeSSE2/liveUI2 passed; volume precision/units180 next; последующие изменения проверять отдельно; scoped axe не означает полную WCAG сертификацию |
+| Общая приёмка | Локальная12.3.83; UI1244/context221/compiled984 parity139; профильные build/E2E/live проверки последующих блоков | Traffic initial live mock-id gate passed141; IPv6 special ranges/cache passed142; unknown RIPE coordinates passed143; IPv6 synthetic UDP/worker/SQL passed144; native CHR ULA/IPFIX/API/UI passed145; ICMPv6/IPv6 endpoint labels passed146; HTTP DTO validation passed147; scoped flow catalog/serverId guards passed148; strict monthly calendar passed149; Sessions labels passed150; application GRE static IPv6 lifecycle151 passed; WireGuard static IPv6 lifecycle152 и disabled SQL/API gates passed; OSPF/BFD boundary/warning153 passed; OSPFv3 routes154 accepted in recorded bounds; BGP IPv6 basic155 passed; bigint ID/SQL guards156 passed; общая UI1260/context221/backend24 regression157 passed; IPv6 BFD correlation158 profile/deployed passed; native OSPFv3/BFDv6 boundary/recovery159 passed; BGP IPv6/BFD withdrawal/recovery160 passed; selected BGP/alert unknown guards161 replay/profiles/deployed passed (SQL stability skipped); private4-byte ASN162 native/API/UI passed; duplicate BGP alert keys163 replay/profiles/deployed passed; nullable selected BGP164 replay/profiles/API/UI passed; collector165 parallel/current guards/read-only PG passed; positive partial/error SQL166/full24 with PG passed (optional network SQL not repeated); GRE/BGP freshness167/SQL8 passed; other source audit168 выявил identity/disabled/freshness gaps; локальный server/traffic identity guard169 passed без SQL/deploy, source/per-probe/freshness170/ownSQL16/compiled1008/deployed passed; source diagnostics171/42E2E/ownSQL metadata/live2 passed; monitoring history172/ownSQL26/compiled1020/deployed passed; Ping known/error173/ownHTTP-SQL8/85uniqueE2E/native4/4/deployed passed; traffic confidence174 baseline reproduced/local guard passed; owned SQL9/9358ms/deploy175/1024compiled/live smoke passed; legacy history/provenance open; compound RTT176 local/native replay2 passed, SQL10/deploy177/1024compiled/live smoke passed; history178/legacy provenance/current TTL/manual truth/SQL9+14/35unique E2E/native GET/deployed passed; live179 complete/disable-config/bounded fallback/ownSQL10/nativeSSE2/liveUI2 passed; volume180 exact/unit marker/ownSQL6/18E2E/API/liveUI/deployed passed; binding status freshness181 next; последующие изменения проверять отдельно; scoped axe не означает полную WCAG сертификацию |
Доказательства и точные ограничения: [полная история этапа12](ux-ui-stage-12.md), [внешний протокол](ux-ui-external-acceptance.md), [BGP93](ux-ui/acceptance/bgp-failover-2026-10-10.json), [паспорт стенда](ux-ui-test-server.md). Невыполненные пункты не являются все блокерами данного лабораторного сценария: для выпуска необходимо явно определить требуемые конфигурации и принять их либо согласовать ограничение.
@@ -985,3 +985,24 @@ Owned Node22/PG18.6 profile test:traffic-rate24570ms без skip:9 counters/sour
[Настоящие CHR SSE](ux-ui/acceptance/traffic-live-native-2026-10-12.json) получили первый полный sample на обоих sourceId1/2, затем streams отменены: native interface GET + monitor-traffic read-only POST. Пример краткого наблюдения194 RX0.031584/TX0.002016Mbps,104 RX/TX0.002112Mbps; это не SLA/capacity/WAN proof. Пароли/конфигурация не менялись, messages0. [Production browser1440/320](ux-ui/acceptance/traffic-live-deployed-2026-10-12.json): actual live labels/receipt provenance,3APIs200,no overflow/pageerrors. Lab AUTH_REQUIRED=false; JWT/RBAC scopes не объявлены полностью принятыми по этой smoke.
Следующий180 — volume precision/binary units. Read-only baseline уже показал: measured1KiB/1MiB API round to0;100×1MiB также aggregate0;1GiB подписан1.0GB. Baseline сохранён в ignored tmp/volume180-baseline.json и пока не выдан за исправление. Sparse intervals/peer completeness/identity recreation/restore/multi-instance/scale и внешние integrations остаются12.5. Локальный коммит без push.
+
+## 12.5.180 — точность учтённых объёмов и бинарные единицы (2026-10-12)
+
+Read-only [baseline](ux-ui/acceptance/traffic-volume-baseline-2026-10-12.json) подтверждён до правки: measured1KiB/1MiB превращались в API0 и0.0GB;100×1MiB также aggregate0;1GiB выдавался с decimal labelGB. Убрано toFixed(1) из buildTrafficFromSamples/mergeBuiltTraffic: округление отдельных слагаемых больше не теряет суммарный трафик. Учёт остаётся по known соседним counter deltas, reset/unknown не добавляют выдуманные bytes. Отсутствующий baseline и coverage gaps по-прежнему ограничивают полноту периода.
+
+DTO серверов, пользователей и привязок объявляет volumeUnit=GiB. Frontend принимает эту единицу явно, absent unit оставляет volume — без потери known speed/history; unsupportedGB marker отклоняет DTO. fmtGiB отображает Б/КиБ/МиБ/ГиБ/ТиБ/ПиБ, positive ниже1B как<1B, true0 как0B. Число округляется только при форматировании. Existing rate formatter остаётся decimal Кбит/Мбит/Гбит, binary объёмы не смешиваются с decimal rates. TotalsRow получил semantic group label. ReUI layout/theme/components сохранены. Удалён N+1 server-status SELECT на каждую binding: используется уже прочитанный catalog snapshot.
+
+| Состояние | Схема | Пример |
+| --- | --- | --- |
+| Small positive bytes | bytes → exact GiB DTO → подходящая binary unit | [ПК](ux-ui/screens/traffic-volume-1440-2026-10-12.png), [320](ux-ui/screens/traffic-volume-320-2026-10-12.png), API-fixture component capture |
+| Old response without unit | unknown unit → —, speeds/history retained | E2E actual API path fixture |
+
+Backend formulas/volume assertion (known positive delta counts; reset does not), frontend unit boundary/unknown/zero/projection, TypeScript/backend build/ESLint passed.18 E2E passed36.3s (2new volume+10detail+6mobile), screenshot delta2 passed10.5s не новые cases; final widget captures просмотрены. Новые positive fixtures явно объявляют GiB. Existing user8 GRE/VXLAN screenshots не тронуты.
+
+Owned Node22/PG18.6 test:traffic-rate31474ms:9 counters+14history+10actual TCP SSE+6new volume SQL cases, без SQL skip. Новые6 — storage/API GET для1B/1KiB/1MiB/1GiB на server/user/binding;100-part aggregate100MiB; reset lower-bound1MiB+512B. Seeded marker5 rows — собственные SQL fixtures, не физические device counts; отсутствие RouterOS calls в volume-only case не объявлено native acceptance. OwnDB mm_accept_166_1791816469068/ограниченная role/container удалены, absence verified. [SQL](ux-ui/acceptance/traffic-volume-sql-2026-10-12.json).
+
+[Source452/522](ux-ui/acceptance/traffic-volume-source-2026-10-12.json)/[compiled1036](ux-ui/acceptance/traffic-volume-compiled-2026-10-12.json) SHA256 parity passed. Обе production builds прошли. Frontend ux-volume180 staged with backend ux-live179 [interop](ux-ui/acceptance/traffic-volume-interop-2026-10-12.json): volumes без unit unknown до backend update. Затем both ux-volume180/version1.0.0-test.volume180 [runtime](ux-ui/acceptance/traffic-volume-runtime-2026-10-12.json) healthy/running/imageIds. Только.env tags, rollback both ux-live179; no secret env copy/new migration. Старую вкладку следует перезагрузить для новых unit labels.
+
+[Actual volume API](ux-ui/acceptance/traffic-volume-api-2026-10-12.json) показывает GiB и неокруглённые quantities обоих CHR из штатных collector rows; этот checker не отправлял новых native commands. [Browser1440/320](ux-ui/acceptance/traffic-volume-deployed-2026-10-12.json):3APIs200, actual live/receipt labels/provenance, no errors/overflow. Рабочая конфигурация/пароли не менялись, messages0; auto collectors остаются штатными. Это не WAN byte billing/capacity/SLA proof и не exact arithmetic acceptance за пределами safe integer/scale bounds.
+
+Следующий181 — freshness статуса привязанных интерфейсов: enabled device+old running flag не должен объявлять online после failed/stale poll; peer enabled не доказывает handshake. Проверить native flag provenance и scalar shape, не переписывать legacy rows по догадке. Coverage/peer completeness/recreated interfaces/restore/multi-instance/scale/external integrations остаются12.5. Локальный коммит без push.
diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md
index 0e2f7257..49e1d4d0 100644
--- a/docs/ux-ui-stage-12.md
+++ b/docs/ux-ui-stage-12.md
@@ -1,6 +1,6 @@
# Этап 12: итоговая приёмка UX/UI
-Актуальное продолжение 2026-10-12: завершён12.5.179 — live complete payload/expected interfaces/auth/disable-config guard; scoped retry/retain cause/latest sample, abort cleanup/receipt timestamp, positive tiny rates. OwnSQL9+14+10/24570ms,7E2E passed13.3s,types/build/ESLint/1036compiled parity. Both ux-live179 healthy/running,2 actual CHR SSE samples+live UI2 passed; ownDB/role/container removed. Следующий180 — точность и binary units учтённых объёмов: baseline positive1KiB/1MiB rounded0 и1GiB mislabeledGB подтверждён, ещё не исправлен.12.5 открыта.
+Актуальное продолжение 2026-10-12: завершён12.5.180 — точные учтённые объёмы без early rounding, explicit GiB API/КиБ-МиБ-ГиБ UI, unknown unit→—, N+1 server-status query removed. OwnSQL9+14+10+6/31474ms,18E2E/36.3s/types/build/ESLint/1036compiled parity. Both ux-volume180 healthy/running/API2/real-live browser2 passed; ownDB/role/container removed. Следующий181 — проверить freshness статусов привязок и peer/interface flags;12.5 открыта.
План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push.
@@ -3375,3 +3375,24 @@ Owned Node22/PG18.6 profile test:traffic-rate24570ms без skip:9 counters/sour
[Настоящие CHR SSE](ux-ui/acceptance/traffic-live-native-2026-10-12.json) получили первый полный sample на обоих sourceId1/2, затем streams отменены: native interface GET + monitor-traffic read-only POST. Пример краткого наблюдения194 RX0.031584/TX0.002016Mbps,104 RX/TX0.002112Mbps; это не SLA/capacity/WAN proof. Пароли/конфигурация не менялись, messages0. [Production browser1440/320](ux-ui/acceptance/traffic-live-deployed-2026-10-12.json): actual live labels/receipt provenance,3APIs200,no overflow/pageerrors. Lab AUTH_REQUIRED=false; JWT/RBAC scopes не объявлены полностью принятыми по этой smoke.
Следующий180 — volume precision/binary units. Read-only baseline уже показал: measured1KiB/1MiB API round to0;100×1MiB также aggregate0;1GiB подписан1.0GB. Baseline сохранён в ignored tmp/volume180-baseline.json и пока не выдан за исправление. Sparse intervals/peer completeness/identity recreation/restore/multi-instance/scale и внешние integrations остаются12.5. Локальный коммит без push.
+
+## 12.5.180 — точность учтённых объёмов и бинарные единицы (2026-10-12)
+
+Read-only [baseline](ux-ui/acceptance/traffic-volume-baseline-2026-10-12.json) подтверждён до правки: measured1KiB/1MiB превращались в API0 и0.0GB;100×1MiB также aggregate0;1GiB выдавался с decimal labelGB. Убрано toFixed(1) из buildTrafficFromSamples/mergeBuiltTraffic: округление отдельных слагаемых больше не теряет суммарный трафик. Учёт остаётся по known соседним counter deltas, reset/unknown не добавляют выдуманные bytes. Отсутствующий baseline и coverage gaps по-прежнему ограничивают полноту периода.
+
+DTO серверов, пользователей и привязок объявляет volumeUnit=GiB. Frontend принимает эту единицу явно, absent unit оставляет volume — без потери known speed/history; unsupportedGB marker отклоняет DTO. fmtGiB отображает Б/КиБ/МиБ/ГиБ/ТиБ/ПиБ, positive ниже1B как<1B, true0 как0B. Число округляется только при форматировании. Existing rate formatter остаётся decimal Кбит/Мбит/Гбит, binary объёмы не смешиваются с decimal rates. TotalsRow получил semantic group label. ReUI layout/theme/components сохранены. Удалён N+1 server-status SELECT на каждую binding: используется уже прочитанный catalog snapshot.
+
+| Состояние | Схема | Пример |
+| --- | --- | --- |
+| Small positive bytes | bytes → exact GiB DTO → подходящая binary unit | [ПК](ux-ui/screens/traffic-volume-1440-2026-10-12.png), [320](ux-ui/screens/traffic-volume-320-2026-10-12.png), API-fixture component capture |
+| Old response without unit | unknown unit → —, speeds/history retained | E2E actual API path fixture |
+
+Backend formulas/volume assertion (known positive delta counts; reset does not), frontend unit boundary/unknown/zero/projection, TypeScript/backend build/ESLint passed.18 E2E passed36.3s (2new volume+10detail+6mobile), screenshot delta2 passed10.5s не новые cases; final widget captures просмотрены. Новые positive fixtures явно объявляют GiB. Existing user8 GRE/VXLAN screenshots не тронуты.
+
+Owned Node22/PG18.6 test:traffic-rate31474ms:9 counters+14history+10actual TCP SSE+6new volume SQL cases, без SQL skip. Новые6 — storage/API GET для1B/1KiB/1MiB/1GiB на server/user/binding;100-part aggregate100MiB; reset lower-bound1MiB+512B. Seeded marker5 rows — собственные SQL fixtures, не физические device counts; отсутствие RouterOS calls в volume-only case не объявлено native acceptance. OwnDB mm_accept_166_1791816469068/ограниченная role/container удалены, absence verified. [SQL](ux-ui/acceptance/traffic-volume-sql-2026-10-12.json).
+
+[Source452/522](ux-ui/acceptance/traffic-volume-source-2026-10-12.json)/[compiled1036](ux-ui/acceptance/traffic-volume-compiled-2026-10-12.json) SHA256 parity passed. Обе production builds прошли. Frontend ux-volume180 staged with backend ux-live179 [interop](ux-ui/acceptance/traffic-volume-interop-2026-10-12.json): volumes без unit unknown до backend update. Затем both ux-volume180/version1.0.0-test.volume180 [runtime](ux-ui/acceptance/traffic-volume-runtime-2026-10-12.json) healthy/running/imageIds. Только.env tags, rollback both ux-live179; no secret env copy/new migration. Старую вкладку следует перезагрузить для новых unit labels.
+
+[Actual volume API](ux-ui/acceptance/traffic-volume-api-2026-10-12.json) показывает GiB и неокруглённые quantities обоих CHR из штатных collector rows; этот checker не отправлял новых native commands. [Browser1440/320](ux-ui/acceptance/traffic-volume-deployed-2026-10-12.json):3APIs200, actual live/receipt labels/provenance, no errors/overflow. Рабочая конфигурация/пароли не менялись, messages0; auto collectors остаются штатными. Это не WAN byte billing/capacity/SLA proof и не exact arithmetic acceptance за пределами safe integer/scale bounds.
+
+Следующий181 — freshness статуса привязанных интерфейсов: enabled device+old running flag не должен объявлять online после failed/stale poll; peer enabled не доказывает handshake. Проверить native flag provenance и scalar shape, не переписывать legacy rows по догадке. Coverage/peer completeness/recreated interfaces/restore/multi-instance/scale/external integrations остаются12.5. Локальный коммит без push.
diff --git a/docs/ux-ui-test-server.md b/docs/ux-ui-test-server.md
index baa53247..8c4fe3ab 100644
--- a/docs/ux-ui-test-server.md
+++ b/docs/ux-ui-test-server.md
@@ -1,6 +1,6 @@
# Тестовый сервер UX/UI — 2026-10-10
-Сервер161.104.46.222: завершён12.5.179 — live complete payload/expected interfaces/auth/disable-config guard; scoped retry/retain cause/latest sample, abort cleanup/receipt timestamp, positive tiny rates. OwnSQL9+14+10/24570ms,7E2E passed13.3s,types/build/ESLint/1036compiled parity. Both ux-live179 healthy/running,2 actual CHR SSE samples+live UI2 passed; ownDB/role/container removed. Следующий180 — точность и binary units учтённых объёмов: baseline positive1KiB/1MiB rounded0 и1GiB mislabeledGB подтверждён, ещё не исправлен.12.5 открыта.
+Сервер161.104.46.222: завершён12.5.180 — точные учтённые объёмы без early rounding, explicit GiB API/КиБ-МиБ-ГиБ UI, unknown unit→—, N+1 server-status query removed. OwnSQL9+14+10+6/31474ms,18E2E/36.3s/types/build/ESLint/1036compiled parity. Both ux-volume180 healthy/running/API2/real-live browser2 passed; ownDB/role/container removed. Следующий181 — проверить freshness статусов привязок и peer/interface flags;12.5 открыта.
| Параметр | Проверенное состояние |
| --- | --- |
@@ -14,7 +14,7 @@
| PostgreSQL | postgres:18-alpine,18.6;mmapp-test-postgres healthy;SELECT version() passed |
| Данные | Отдельный Compose project mmapp-test,том mmapp-test_mmapp-pgdata и каталог ./data |
| Доступ | PostgreSQL не публикуется;frontend3000/backend8000 привязаны к127.0.0.1 после запуска |
-| Образы приложения | mmapp-test/backend:ux-live179 и mmapp-test/frontend:ux-live179; запущены |
+| Образы приложения | mmapp-test/backend:ux-volume180 и mmapp-test/frontend:ux-volume180; запущены |
Compose SHA256:3052bd6ab5d9865f97605cebfb5ff27a4410cd3357b81153a65a87fb7c92582f. OptionalBACKEND_IMAGE_TAG позволяетобновлятьbackendотдельно. Локальный и удалённый файлы совпали. Шаблоны: [Compose](../deploy/docker-compose.test.yml), [env без секретов](../deploy/env.test.example).
@@ -1801,3 +1801,24 @@ Owned Node22/PG18.6 profile test:traffic-rate24570ms без skip:9 counters/sour
[Настоящие CHR SSE](ux-ui/acceptance/traffic-live-native-2026-10-12.json) получили первый полный sample на обоих sourceId1/2, затем streams отменены: native interface GET + monitor-traffic read-only POST. Пример краткого наблюдения194 RX0.031584/TX0.002016Mbps,104 RX/TX0.002112Mbps; это не SLA/capacity/WAN proof. Пароли/конфигурация не менялись, messages0. [Production browser1440/320](ux-ui/acceptance/traffic-live-deployed-2026-10-12.json): actual live labels/receipt provenance,3APIs200,no overflow/pageerrors. Lab AUTH_REQUIRED=false; JWT/RBAC scopes не объявлены полностью принятыми по этой smoke.
Следующий180 — volume precision/binary units. Read-only baseline уже показал: measured1KiB/1MiB API round to0;100×1MiB также aggregate0;1GiB подписан1.0GB. Baseline сохранён в ignored tmp/volume180-baseline.json и пока не выдан за исправление. Sparse intervals/peer completeness/identity recreation/restore/multi-instance/scale и внешние integrations остаются12.5. Локальный коммит без push.
+
+## 12.5.180 — точность учтённых объёмов и бинарные единицы (2026-10-12)
+
+Read-only [baseline](ux-ui/acceptance/traffic-volume-baseline-2026-10-12.json) подтверждён до правки: measured1KiB/1MiB превращались в API0 и0.0GB;100×1MiB также aggregate0;1GiB выдавался с decimal labelGB. Убрано toFixed(1) из buildTrafficFromSamples/mergeBuiltTraffic: округление отдельных слагаемых больше не теряет суммарный трафик. Учёт остаётся по known соседним counter deltas, reset/unknown не добавляют выдуманные bytes. Отсутствующий baseline и coverage gaps по-прежнему ограничивают полноту периода.
+
+DTO серверов, пользователей и привязок объявляет volumeUnit=GiB. Frontend принимает эту единицу явно, absent unit оставляет volume — без потери known speed/history; unsupportedGB marker отклоняет DTO. fmtGiB отображает Б/КиБ/МиБ/ГиБ/ТиБ/ПиБ, positive ниже1B как<1B, true0 как0B. Число округляется только при форматировании. Existing rate formatter остаётся decimal Кбит/Мбит/Гбит, binary объёмы не смешиваются с decimal rates. TotalsRow получил semantic group label. ReUI layout/theme/components сохранены. Удалён N+1 server-status SELECT на каждую binding: используется уже прочитанный catalog snapshot.
+
+| Состояние | Схема | Пример |
+| --- | --- | --- |
+| Small positive bytes | bytes → exact GiB DTO → подходящая binary unit | [ПК](ux-ui/screens/traffic-volume-1440-2026-10-12.png), [320](ux-ui/screens/traffic-volume-320-2026-10-12.png), API-fixture component capture |
+| Old response without unit | unknown unit → —, speeds/history retained | E2E actual API path fixture |
+
+Backend formulas/volume assertion (known positive delta counts; reset does not), frontend unit boundary/unknown/zero/projection, TypeScript/backend build/ESLint passed.18 E2E passed36.3s (2new volume+10detail+6mobile), screenshot delta2 passed10.5s не новые cases; final widget captures просмотрены. Новые positive fixtures явно объявляют GiB. Existing user8 GRE/VXLAN screenshots не тронуты.
+
+Owned Node22/PG18.6 test:traffic-rate31474ms:9 counters+14history+10actual TCP SSE+6new volume SQL cases, без SQL skip. Новые6 — storage/API GET для1B/1KiB/1MiB/1GiB на server/user/binding;100-part aggregate100MiB; reset lower-bound1MiB+512B. Seeded marker5 rows — собственные SQL fixtures, не физические device counts; отсутствие RouterOS calls в volume-only case не объявлено native acceptance. OwnDB mm_accept_166_1791816469068/ограниченная role/container удалены, absence verified. [SQL](ux-ui/acceptance/traffic-volume-sql-2026-10-12.json).
+
+[Source452/522](ux-ui/acceptance/traffic-volume-source-2026-10-12.json)/[compiled1036](ux-ui/acceptance/traffic-volume-compiled-2026-10-12.json) SHA256 parity passed. Обе production builds прошли. Frontend ux-volume180 staged with backend ux-live179 [interop](ux-ui/acceptance/traffic-volume-interop-2026-10-12.json): volumes без unit unknown до backend update. Затем both ux-volume180/version1.0.0-test.volume180 [runtime](ux-ui/acceptance/traffic-volume-runtime-2026-10-12.json) healthy/running/imageIds. Только.env tags, rollback both ux-live179; no secret env copy/new migration. Старую вкладку следует перезагрузить для новых unit labels.
+
+[Actual volume API](ux-ui/acceptance/traffic-volume-api-2026-10-12.json) показывает GiB и неокруглённые quantities обоих CHR из штатных collector rows; этот checker не отправлял новых native commands. [Browser1440/320](ux-ui/acceptance/traffic-volume-deployed-2026-10-12.json):3APIs200, actual live/receipt labels/provenance, no errors/overflow. Рабочая конфигурация/пароли не менялись, messages0; auto collectors остаются штатными. Это не WAN byte billing/capacity/SLA proof и не exact arithmetic acceptance за пределами safe integer/scale bounds.
+
+Следующий181 — freshness статуса привязанных интерфейсов: enabled device+old running flag не должен объявлять online после failed/stale poll; peer enabled не доказывает handshake. Проверить native flag provenance и scalar shape, не переписывать legacy rows по догадке. Coverage/peer completeness/recreated interfaces/restore/multi-instance/scale/external integrations остаются12.5. Локальный коммит без push.
diff --git a/docs/ux-ui/acceptance/traffic-volume-api-2026-10-12.json b/docs/ux-ui/acceptance/traffic-volume-api-2026-10-12.json
new file mode 100644
index 00000000..c4944407
--- /dev/null
+++ b/docs/ux-ui/acceptance/traffic-volume-api-2026-10-12.json
@@ -0,0 +1,26 @@
+{
+ "date": "2026-10-12",
+ "block": "12.5.180",
+ "version": "1.0.0-test.volume180",
+ "passed": true,
+ "method": "Read-only actual application volume API; no new native RouterOS commands; exact edge values covered separately by owned SQL",
+ "cases": [
+ {
+ "sourceId": "1",
+ "volumeUnit": "GiB",
+ "rxTotalGiB": 0.013485109433531761,
+ "txTotalGiB": 0.001898050308227539,
+ "currentState": "current"
+ },
+ {
+ "sourceId": "2",
+ "volumeUnit": "GiB",
+ "rxTotalGiB": 0.0021370751783251762,
+ "txTotalGiB": 0.0023917602375149727,
+ "currentState": "current"
+ }
+ ],
+ "newRouterOSCommands": 0,
+ "passwordChanges": 0,
+ "messages": 0
+}
\ No newline at end of file
diff --git a/docs/ux-ui/acceptance/traffic-volume-baseline-2026-10-12.json b/docs/ux-ui/acceptance/traffic-volume-baseline-2026-10-12.json
new file mode 100644
index 00000000..85a34e7a
--- /dev/null
+++ b/docs/ux-ui/acceptance/traffic-volume-baseline-2026-10-12.json
@@ -0,0 +1,39 @@
+{
+ "date": "2026-10-12",
+ "method": "Read-only current source reproduction: positive measured deltas are rounded before aggregation; binary quantity carries decimal label",
+ "cases": [
+ {
+ "bytes": 1024,
+ "actualGiB": 9.5367431640625e-7,
+ "apiTotal": 0,
+ "display": "0.0 ГБ",
+ "observed": true
+ },
+ {
+ "bytes": 1048576,
+ "actualGiB": 0.0009765625,
+ "apiTotal": 0,
+ "display": "0.0 ГБ",
+ "observed": true
+ },
+ {
+ "bytes": 1073741824,
+ "actualGiB": 1,
+ "apiTotal": 1,
+ "display": "1.0 ГБ",
+ "observed": true
+ },
+ {
+ "bytes": 1000000000,
+ "actualGiB": 0.9313225746154785,
+ "apiTotal": 0.9,
+ "display": "0.9 ГБ",
+ "observed": true
+ }
+ ],
+ "merged100MiB": {
+ "apiTotal": 0,
+ "actualGiB": 0.09765625
+ },
+ "fixed": false
+}
diff --git a/docs/ux-ui/acceptance/traffic-volume-compiled-2026-10-12.json b/docs/ux-ui/acceptance/traffic-volume-compiled-2026-10-12.json
new file mode 100644
index 00000000..17746b9b
--- /dev/null
+++ b/docs/ux-ui/acceptance/traffic-volume-compiled-2026-10-12.json
@@ -0,0 +1,1046 @@
+{
+ "date": "2026-10-12",
+ "block": "12.5.180",
+ "backendVersion": "1.0.0-test.volume180",
+ "normalization": "CRLF to LF",
+ "matched": [
+ "backend/dist/config.js",
+ "backend/dist/config.js.map",
+ "backend/dist/config.d.ts",
+ "backend/dist/config.d.ts.map",
+ "backend/dist/constants/ping-probe.js",
+ "backend/dist/constants/ping-probe.js.map",
+ "backend/dist/constants/ping-probe.d.ts",
+ "backend/dist/constants/ping-probe.d.ts.map",
+ "backend/dist/db/bootstrap.js",
+ "backend/dist/db/bootstrap.js.map",
+ "backend/dist/db/bootstrap.d.ts",
+ "backend/dist/db/bootstrap.d.ts.map",
+ "backend/dist/db/index.js",
+ "backend/dist/db/index.js.map",
+ "backend/dist/db/index.d.ts",
+ "backend/dist/db/index.d.ts.map",
+ "backend/dist/db/json.js",
+ "backend/dist/db/json.js.map",
+ "backend/dist/db/json.d.ts",
+ "backend/dist/db/json.d.ts.map",
+ "backend/dist/db/migrate.js",
+ "backend/dist/db/migrate.js.map",
+ "backend/dist/db/migrate.d.ts",
+ "backend/dist/db/migrate.d.ts.map",
+ "backend/dist/db/operation-lock.js",
+ "backend/dist/db/operation-lock.js.map",
+ "backend/dist/db/operation-lock.d.ts",
+ "backend/dist/db/operation-lock.d.ts.map",
+ "backend/dist/db/partitions.js",
+ "backend/dist/db/partitions.js.map",
+ "backend/dist/db/partitions.d.ts",
+ "backend/dist/db/partitions.d.ts.map",
+ "backend/dist/db/schema.js",
+ "backend/dist/db/schema.js.map",
+ "backend/dist/db/schema.d.ts",
+ "backend/dist/db/schema.d.ts.map",
+ "backend/dist/db/sql-bind.js",
+ "backend/dist/db/sql-bind.js.map",
+ "backend/dist/db/sql-bind.d.ts",
+ "backend/dist/db/sql-bind.d.ts.map",
+ "backend/dist/db/sqlite-import.js",
+ "backend/dist/db/sqlite-import.js.map",
+ "backend/dist/db/sqlite-import.d.ts",
+ "backend/dist/db/sqlite-import.d.ts.map",
+ "backend/dist/db/traffic-flags.js",
+ "backend/dist/db/traffic-flags.js.map",
+ "backend/dist/db/traffic-flags.d.ts",
+ "backend/dist/db/traffic-flags.d.ts.map",
+ "backend/dist/index.js",
+ "backend/dist/index.js.map",
+ "backend/dist/index.d.ts",
+ "backend/dist/index.d.ts.map",
+ "backend/dist/lib/permissions.js",
+ "backend/dist/lib/permissions.js.map",
+ "backend/dist/lib/permissions.d.ts",
+ "backend/dist/lib/permissions.d.ts.map",
+ "backend/dist/managed-markers.js",
+ "backend/dist/managed-markers.js.map",
+ "backend/dist/managed-markers.d.ts",
+ "backend/dist/managed-markers.d.ts.map",
+ "backend/dist/modules/events/repository/events-repository.js",
+ "backend/dist/modules/events/repository/events-repository.js.map",
+ "backend/dist/modules/events/repository/events-repository.d.ts",
+ "backend/dist/modules/events/repository/events-repository.d.ts.map",
+ "backend/dist/modules/events/service/events-service.js",
+ "backend/dist/modules/events/service/events-service.js.map",
+ "backend/dist/modules/events/service/events-service.d.ts",
+ "backend/dist/modules/events/service/events-service.d.ts.map",
+ "backend/dist/modules/servers/mapper/servers-mapper.js",
+ "backend/dist/modules/servers/mapper/servers-mapper.js.map",
+ "backend/dist/modules/servers/mapper/servers-mapper.d.ts",
+ "backend/dist/modules/servers/mapper/servers-mapper.d.ts.map",
+ "backend/dist/modules/servers/repository/servers-repository.js",
+ "backend/dist/modules/servers/repository/servers-repository.js.map",
+ "backend/dist/modules/servers/repository/servers-repository.d.ts",
+ "backend/dist/modules/servers/repository/servers-repository.d.ts.map",
+ "backend/dist/modules/servers/service/servers-service.js",
+ "backend/dist/modules/servers/service/servers-service.js.map",
+ "backend/dist/modules/servers/service/servers-service.d.ts",
+ "backend/dist/modules/servers/service/servers-service.d.ts.map",
+ "backend/dist/modules/users/iface-type.js",
+ "backend/dist/modules/users/iface-type.js.map",
+ "backend/dist/modules/users/iface-type.d.ts",
+ "backend/dist/modules/users/iface-type.d.ts.map",
+ "backend/dist/modules/users/interface-inventory.js",
+ "backend/dist/modules/users/interface-inventory.js.map",
+ "backend/dist/modules/users/interface-inventory.d.ts",
+ "backend/dist/modules/users/interface-inventory.d.ts.map",
+ "backend/dist/modules/users/peer-bind.js",
+ "backend/dist/modules/users/peer-bind.js.map",
+ "backend/dist/modules/users/peer-bind.d.ts",
+ "backend/dist/modules/users/peer-bind.d.ts.map",
+ "backend/dist/modules/users/repository/users-repository.js",
+ "backend/dist/modules/users/repository/users-repository.js.map",
+ "backend/dist/modules/users/repository/users-repository.d.ts",
+ "backend/dist/modules/users/repository/users-repository.d.ts.map",
+ "backend/dist/modules/users/service/users-service.js",
+ "backend/dist/modules/users/service/users-service.js.map",
+ "backend/dist/modules/users/service/users-service.d.ts",
+ "backend/dist/modules/users/service/users-service.d.ts.map",
+ "backend/dist/plugins/auth.js",
+ "backend/dist/plugins/auth.js.map",
+ "backend/dist/plugins/auth.d.ts",
+ "backend/dist/plugins/auth.d.ts.map",
+ "backend/dist/plugins/database-request-gate.js",
+ "backend/dist/plugins/database-request-gate.js.map",
+ "backend/dist/plugins/database-request-gate.d.ts",
+ "backend/dist/plugins/database-request-gate.d.ts.map",
+ "backend/dist/routes/alerts.js",
+ "backend/dist/routes/alerts.js.map",
+ "backend/dist/routes/alerts.d.ts",
+ "backend/dist/routes/alerts.d.ts.map",
+ "backend/dist/routes/backups.js",
+ "backend/dist/routes/backups.js.map",
+ "backend/dist/routes/backups.d.ts",
+ "backend/dist/routes/backups.d.ts.map",
+ "backend/dist/routes/bgp.js",
+ "backend/dist/routes/bgp.js.map",
+ "backend/dist/routes/bgp.d.ts",
+ "backend/dist/routes/bgp.d.ts.map",
+ "backend/dist/routes/certificates.js",
+ "backend/dist/routes/certificates.js.map",
+ "backend/dist/routes/certificates.d.ts",
+ "backend/dist/routes/certificates.d.ts.map",
+ "backend/dist/routes/containers.js",
+ "backend/dist/routes/containers.js.map",
+ "backend/dist/routes/containers.d.ts",
+ "backend/dist/routes/containers.d.ts.map",
+ "backend/dist/routes/events.js",
+ "backend/dist/routes/events.js.map",
+ "backend/dist/routes/events.d.ts",
+ "backend/dist/routes/events.d.ts.map",
+ "backend/dist/routes/evobgp-catalog.js",
+ "backend/dist/routes/evobgp-catalog.js.map",
+ "backend/dist/routes/evobgp-catalog.d.ts",
+ "backend/dist/routes/evobgp-catalog.d.ts.map",
+ "backend/dist/routes/evobgp-settings.js",
+ "backend/dist/routes/evobgp-settings.js.map",
+ "backend/dist/routes/evobgp-settings.d.ts",
+ "backend/dist/routes/evobgp-settings.d.ts.map",
+ "backend/dist/routes/evobgp.js",
+ "backend/dist/routes/evobgp.js.map",
+ "backend/dist/routes/evobgp.d.ts",
+ "backend/dist/routes/evobgp.d.ts.map",
+ "backend/dist/routes/exec.js",
+ "backend/dist/routes/exec.js.map",
+ "backend/dist/routes/exec.d.ts",
+ "backend/dist/routes/exec.d.ts.map",
+ "backend/dist/routes/filters.js",
+ "backend/dist/routes/filters.js.map",
+ "backend/dist/routes/filters.d.ts",
+ "backend/dist/routes/filters.d.ts.map",
+ "backend/dist/routes/firewall.js",
+ "backend/dist/routes/firewall.js.map",
+ "backend/dist/routes/firewall.d.ts",
+ "backend/dist/routes/firewall.d.ts.map",
+ "backend/dist/routes/geoip.js",
+ "backend/dist/routes/geoip.js.map",
+ "backend/dist/routes/geoip.d.ts",
+ "backend/dist/routes/geoip.d.ts.map",
+ "backend/dist/routes/gre.js",
+ "backend/dist/routes/gre.js.map",
+ "backend/dist/routes/gre.d.ts",
+ "backend/dist/routes/gre.d.ts.map",
+ "backend/dist/routes/internet-path.js",
+ "backend/dist/routes/internet-path.js.map",
+ "backend/dist/routes/internet-path.d.ts",
+ "backend/dist/routes/internet-path.d.ts.map",
+ "backend/dist/routes/ipsec.js",
+ "backend/dist/routes/ipsec.js.map",
+ "backend/dist/routes/ipsec.d.ts",
+ "backend/dist/routes/ipsec.d.ts.map",
+ "backend/dist/routes/network.js",
+ "backend/dist/routes/network.js.map",
+ "backend/dist/routes/network.d.ts",
+ "backend/dist/routes/network.d.ts.map",
+ "backend/dist/routes/ospf.js",
+ "backend/dist/routes/ospf.js.map",
+ "backend/dist/routes/ospf.d.ts",
+ "backend/dist/routes/ospf.d.ts.map",
+ "backend/dist/routes/ping-probes-write.js",
+ "backend/dist/routes/ping-probes-write.js.map",
+ "backend/dist/routes/ping-probes-write.d.ts",
+ "backend/dist/routes/ping-probes-write.d.ts.map",
+ "backend/dist/routes/probes.js",
+ "backend/dist/routes/probes.js.map",
+ "backend/dist/routes/probes.d.ts",
+ "backend/dist/routes/probes.d.ts.map",
+ "backend/dist/routes/recursive-routes.js",
+ "backend/dist/routes/recursive-routes.js.map",
+ "backend/dist/routes/recursive-routes.d.ts",
+ "backend/dist/routes/recursive-routes.d.ts.map",
+ "backend/dist/routes/scheduler.js",
+ "backend/dist/routes/scheduler.js.map",
+ "backend/dist/routes/scheduler.d.ts",
+ "backend/dist/routes/scheduler.d.ts.map",
+ "backend/dist/routes/servers-api-ping.js",
+ "backend/dist/routes/servers-api-ping.js.map",
+ "backend/dist/routes/servers-api-ping.d.ts",
+ "backend/dist/routes/servers-api-ping.d.ts.map",
+ "backend/dist/routes/servers.js",
+ "backend/dist/routes/servers.js.map",
+ "backend/dist/routes/servers.d.ts",
+ "backend/dist/routes/servers.d.ts.map",
+ "backend/dist/routes/sidebar-counts.js",
+ "backend/dist/routes/sidebar-counts.js.map",
+ "backend/dist/routes/sidebar-counts.d.ts",
+ "backend/dist/routes/sidebar-counts.d.ts.map",
+ "backend/dist/routes/speed-probes-write.js",
+ "backend/dist/routes/speed-probes-write.js.map",
+ "backend/dist/routes/speed-probes-write.d.ts",
+ "backend/dist/routes/speed-probes-write.d.ts.map",
+ "backend/dist/routes/speed-test.js",
+ "backend/dist/routes/speed-test.js.map",
+ "backend/dist/routes/speed-test.d.ts",
+ "backend/dist/routes/speed-test.d.ts.map",
+ "backend/dist/routes/statistics.js",
+ "backend/dist/routes/statistics.js.map",
+ "backend/dist/routes/statistics.d.ts",
+ "backend/dist/routes/statistics.d.ts.map",
+ "backend/dist/routes/system-database.js",
+ "backend/dist/routes/system-database.js.map",
+ "backend/dist/routes/system-database.d.ts",
+ "backend/dist/routes/system-database.d.ts.map",
+ "backend/dist/routes/traffic-flow-purge.js",
+ "backend/dist/routes/traffic-flow-purge.js.map",
+ "backend/dist/routes/traffic-flow-purge.d.ts",
+ "backend/dist/routes/traffic-flow-purge.d.ts.map",
+ "backend/dist/routes/traffic-flow-settings.js",
+ "backend/dist/routes/traffic-flow-settings.js.map",
+ "backend/dist/routes/traffic-flow-settings.d.ts",
+ "backend/dist/routes/traffic-flow-settings.d.ts.map",
+ "backend/dist/routes/traffic-flow.js",
+ "backend/dist/routes/traffic-flow.js.map",
+ "backend/dist/routes/traffic-flow.d.ts",
+ "backend/dist/routes/traffic-flow.d.ts.map",
+ "backend/dist/routes/traffic.js",
+ "backend/dist/routes/traffic.js.map",
+ "backend/dist/routes/traffic.d.ts",
+ "backend/dist/routes/traffic.d.ts.map",
+ "backend/dist/routes/uptime.js",
+ "backend/dist/routes/uptime.js.map",
+ "backend/dist/routes/uptime.d.ts",
+ "backend/dist/routes/uptime.d.ts.map",
+ "backend/dist/routes/users.js",
+ "backend/dist/routes/users.js.map",
+ "backend/dist/routes/users.d.ts",
+ "backend/dist/routes/users.d.ts.map",
+ "backend/dist/routes/vxlan.js",
+ "backend/dist/routes/vxlan.js.map",
+ "backend/dist/routes/vxlan.d.ts",
+ "backend/dist/routes/vxlan.d.ts.map",
+ "backend/dist/routes/wireguard.js",
+ "backend/dist/routes/wireguard.js.map",
+ "backend/dist/routes/wireguard.d.ts",
+ "backend/dist/routes/wireguard.d.ts.map",
+ "backend/dist/scripts/migrate-sqlite-to-pg.js",
+ "backend/dist/scripts/migrate-sqlite-to-pg.js.map",
+ "backend/dist/scripts/migrate-sqlite-to-pg.d.ts",
+ "backend/dist/scripts/migrate-sqlite-to-pg.d.ts.map",
+ "backend/dist/scripts/rebuild-flow-facts.js",
+ "backend/dist/scripts/rebuild-flow-facts.js.map",
+ "backend/dist/scripts/rebuild-flow-facts.d.ts",
+ "backend/dist/scripts/rebuild-flow-facts.d.ts.map",
+ "backend/dist/services/acme-cloudflare.js",
+ "backend/dist/services/acme-cloudflare.js.map",
+ "backend/dist/services/acme-cloudflare.d.ts",
+ "backend/dist/services/acme-cloudflare.d.ts.map",
+ "backend/dist/services/alert-collector-hooks.js",
+ "backend/dist/services/alert-collector-hooks.js.map",
+ "backend/dist/services/alert-collector-hooks.d.ts",
+ "backend/dist/services/alert-collector-hooks.d.ts.map",
+ "backend/dist/services/alert-engine/bgp-signals.js",
+ "backend/dist/services/alert-engine/bgp-signals.js.map",
+ "backend/dist/services/alert-engine/bgp-signals.d.ts",
+ "backend/dist/services/alert-engine/bgp-signals.d.ts.map",
+ "backend/dist/services/alert-engine/collector-context-reader.js",
+ "backend/dist/services/alert-engine/collector-context-reader.js.map",
+ "backend/dist/services/alert-engine/collector-context-reader.d.ts",
+ "backend/dist/services/alert-engine/collector-context-reader.d.ts.map",
+ "backend/dist/services/alert-engine/collector-source-signals.js",
+ "backend/dist/services/alert-engine/collector-source-signals.js.map",
+ "backend/dist/services/alert-engine/collector-source-signals.d.ts",
+ "backend/dist/services/alert-engine/collector-source-signals.d.ts.map",
+ "backend/dist/services/alert-engine/confirm-stability.js",
+ "backend/dist/services/alert-engine/confirm-stability.js.map",
+ "backend/dist/services/alert-engine/confirm-stability.d.ts",
+ "backend/dist/services/alert-engine/confirm-stability.d.ts.map",
+ "backend/dist/services/alert-engine/cooldown.js",
+ "backend/dist/services/alert-engine/cooldown.js.map",
+ "backend/dist/services/alert-engine/cooldown.d.ts",
+ "backend/dist/services/alert-engine/cooldown.d.ts.map",
+ "backend/dist/services/alert-engine/decision-engine.js",
+ "backend/dist/services/alert-engine/decision-engine.js.map",
+ "backend/dist/services/alert-engine/decision-engine.d.ts",
+ "backend/dist/services/alert-engine/decision-engine.d.ts.map",
+ "backend/dist/services/alert-engine/evaluate-rule.js",
+ "backend/dist/services/alert-engine/evaluate-rule.js.map",
+ "backend/dist/services/alert-engine/evaluate-rule.d.ts",
+ "backend/dist/services/alert-engine/evaluate-rule.d.ts.map",
+ "backend/dist/services/alert-engine/gre-signals.js",
+ "backend/dist/services/alert-engine/gre-signals.js.map",
+ "backend/dist/services/alert-engine/gre-signals.d.ts",
+ "backend/dist/services/alert-engine/gre-signals.d.ts.map",
+ "backend/dist/services/alert-engine/groups.js",
+ "backend/dist/services/alert-engine/groups.js.map",
+ "backend/dist/services/alert-engine/groups.d.ts",
+ "backend/dist/services/alert-engine/groups.d.ts.map",
+ "backend/dist/services/alert-engine/monitoring-history.js",
+ "backend/dist/services/alert-engine/monitoring-history.js.map",
+ "backend/dist/services/alert-engine/monitoring-history.d.ts",
+ "backend/dist/services/alert-engine/monitoring-history.d.ts.map",
+ "backend/dist/services/alert-engine/outbox-execution.js",
+ "backend/dist/services/alert-engine/outbox-execution.js.map",
+ "backend/dist/services/alert-engine/outbox-execution.d.ts",
+ "backend/dist/services/alert-engine/outbox-execution.d.ts.map",
+ "backend/dist/services/alert-engine/outbox.js",
+ "backend/dist/services/alert-engine/outbox.js.map",
+ "backend/dist/services/alert-engine/outbox.d.ts",
+ "backend/dist/services/alert-engine/outbox.d.ts.map",
+ "backend/dist/services/alert-engine/prev-live-store.js",
+ "backend/dist/services/alert-engine/prev-live-store.js.map",
+ "backend/dist/services/alert-engine/prev-live-store.d.ts",
+ "backend/dist/services/alert-engine/prev-live-store.d.ts.map",
+ "backend/dist/services/alert-engine/rule-matcher.js",
+ "backend/dist/services/alert-engine/rule-matcher.js.map",
+ "backend/dist/services/alert-engine/rule-matcher.d.ts",
+ "backend/dist/services/alert-engine/rule-matcher.d.ts.map",
+ "backend/dist/services/alert-engine/run-once.js",
+ "backend/dist/services/alert-engine/run-once.js.map",
+ "backend/dist/services/alert-engine/run-once.d.ts",
+ "backend/dist/services/alert-engine/run-once.d.ts.map",
+ "backend/dist/services/alert-engine/server-signal-identity.js",
+ "backend/dist/services/alert-engine/server-signal-identity.js.map",
+ "backend/dist/services/alert-engine/server-signal-identity.d.ts",
+ "backend/dist/services/alert-engine/server-signal-identity.d.ts.map",
+ "backend/dist/services/alert-engine/signal-ingestor.js",
+ "backend/dist/services/alert-engine/signal-ingestor.js.map",
+ "backend/dist/services/alert-engine/signal-ingestor.d.ts",
+ "backend/dist/services/alert-engine/signal-ingestor.d.ts.map",
+ "backend/dist/services/alert-engine/signals-from-collectors.js",
+ "backend/dist/services/alert-engine/signals-from-collectors.js.map",
+ "backend/dist/services/alert-engine/signals-from-collectors.d.ts",
+ "backend/dist/services/alert-engine/signals-from-collectors.d.ts.map",
+ "backend/dist/services/alert-engine/snapshot.js",
+ "backend/dist/services/alert-engine/snapshot.js.map",
+ "backend/dist/services/alert-engine/snapshot.d.ts",
+ "backend/dist/services/alert-engine/snapshot.d.ts.map",
+ "backend/dist/services/alert-engine/source-watermark.js",
+ "backend/dist/services/alert-engine/source-watermark.js.map",
+ "backend/dist/services/alert-engine/source-watermark.d.ts",
+ "backend/dist/services/alert-engine/source-watermark.d.ts.map",
+ "backend/dist/services/alert-engine/state-machine.js",
+ "backend/dist/services/alert-engine/state-machine.js.map",
+ "backend/dist/services/alert-engine/state-machine.d.ts",
+ "backend/dist/services/alert-engine/state-machine.d.ts.map",
+ "backend/dist/services/alert-engine/target-issues.js",
+ "backend/dist/services/alert-engine/target-issues.js.map",
+ "backend/dist/services/alert-engine/target-issues.d.ts",
+ "backend/dist/services/alert-engine/target-issues.d.ts.map",
+ "backend/dist/services/alert-engine/telegram-emoji.js",
+ "backend/dist/services/alert-engine/telegram-emoji.js.map",
+ "backend/dist/services/alert-engine/telegram-emoji.d.ts",
+ "backend/dist/services/alert-engine/telegram-emoji.d.ts.map",
+ "backend/dist/services/alert-engine/types.js",
+ "backend/dist/services/alert-engine/types.js.map",
+ "backend/dist/services/alert-engine/types.d.ts",
+ "backend/dist/services/alert-engine/types.d.ts.map",
+ "backend/dist/services/alerts-config-execution.js",
+ "backend/dist/services/alerts-config-execution.js.map",
+ "backend/dist/services/alerts-config-execution.d.ts",
+ "backend/dist/services/alerts-config-execution.d.ts.map",
+ "backend/dist/services/alerts-outbox-overview.js",
+ "backend/dist/services/alerts-outbox-overview.js.map",
+ "backend/dist/services/alerts-outbox-overview.d.ts",
+ "backend/dist/services/alerts-outbox-overview.d.ts.map",
+ "backend/dist/services/alerts-outbox-retry.js",
+ "backend/dist/services/alerts-outbox-retry.js.map",
+ "backend/dist/services/alerts-outbox-retry.d.ts",
+ "backend/dist/services/alerts-outbox-retry.d.ts.map",
+ "backend/dist/services/alerts-overview.js",
+ "backend/dist/services/alerts-overview.js.map",
+ "backend/dist/services/alerts-overview.d.ts",
+ "backend/dist/services/alerts-overview.d.ts.map",
+ "backend/dist/services/alerts-revision.js",
+ "backend/dist/services/alerts-revision.js.map",
+ "backend/dist/services/alerts-revision.d.ts",
+ "backend/dist/services/alerts-revision.d.ts.map",
+ "backend/dist/services/alerts-service.js",
+ "backend/dist/services/alerts-service.js.map",
+ "backend/dist/services/alerts-service.d.ts",
+ "backend/dist/services/alerts-service.d.ts.map",
+ "backend/dist/services/alerts-telegram-delivery.js",
+ "backend/dist/services/alerts-telegram-delivery.js.map",
+ "backend/dist/services/alerts-telegram-delivery.d.ts",
+ "backend/dist/services/alerts-telegram-delivery.d.ts.map",
+ "backend/dist/services/alerts-telegram-send.js",
+ "backend/dist/services/alerts-telegram-send.js.map",
+ "backend/dist/services/alerts-telegram-send.d.ts",
+ "backend/dist/services/alerts-telegram-send.d.ts.map",
+ "backend/dist/services/alerts-telegram-settings.js",
+ "backend/dist/services/alerts-telegram-settings.js.map",
+ "backend/dist/services/alerts-telegram-settings.d.ts",
+ "backend/dist/services/alerts-telegram-settings.d.ts.map",
+ "backend/dist/services/backup-deletion.js",
+ "backend/dist/services/backup-deletion.js.map",
+ "backend/dist/services/backup-deletion.d.ts",
+ "backend/dist/services/backup-deletion.d.ts.map",
+ "backend/dist/services/backup-dto.js",
+ "backend/dist/services/backup-dto.js.map",
+ "backend/dist/services/backup-dto.d.ts",
+ "backend/dist/services/backup-dto.d.ts.map",
+ "backend/dist/services/backup-job-execution.js",
+ "backend/dist/services/backup-job-execution.js.map",
+ "backend/dist/services/backup-job-execution.d.ts",
+ "backend/dist/services/backup-job-execution.d.ts.map",
+ "backend/dist/services/backup-prune.js",
+ "backend/dist/services/backup-prune.js.map",
+ "backend/dist/services/backup-prune.d.ts",
+ "backend/dist/services/backup-prune.d.ts.map",
+ "backend/dist/services/backup-restore.js",
+ "backend/dist/services/backup-restore.js.map",
+ "backend/dist/services/backup-restore.d.ts",
+ "backend/dist/services/backup-restore.d.ts.map",
+ "backend/dist/services/backup-scheduler-collector.js",
+ "backend/dist/services/backup-scheduler-collector.js.map",
+ "backend/dist/services/backup-scheduler-collector.d.ts",
+ "backend/dist/services/backup-scheduler-collector.d.ts.map",
+ "backend/dist/services/backup-scheduler-run.js",
+ "backend/dist/services/backup-scheduler-run.js.map",
+ "backend/dist/services/backup-scheduler-run.d.ts",
+ "backend/dist/services/backup-scheduler-run.d.ts.map",
+ "backend/dist/services/backup-service.js",
+ "backend/dist/services/backup-service.js.map",
+ "backend/dist/services/backup-service.d.ts",
+ "backend/dist/services/backup-service.d.ts.map",
+ "backend/dist/services/backup-storage-update.js",
+ "backend/dist/services/backup-storage-update.js.map",
+ "backend/dist/services/backup-storage-update.d.ts",
+ "backend/dist/services/backup-storage-update.d.ts.map",
+ "backend/dist/services/bandwidth-test-rates.js",
+ "backend/dist/services/bandwidth-test-rates.js.map",
+ "backend/dist/services/bandwidth-test-rates.d.ts",
+ "backend/dist/services/bandwidth-test-rates.d.ts.map",
+ "backend/dist/services/bandwidth-test-status.js",
+ "backend/dist/services/bandwidth-test-status.js.map",
+ "backend/dist/services/bandwidth-test-status.d.ts",
+ "backend/dist/services/bandwidth-test-status.d.ts.map",
+ "backend/dist/services/bgp-catalog-poll.js",
+ "backend/dist/services/bgp-catalog-poll.js.map",
+ "backend/dist/services/bgp-catalog-poll.d.ts",
+ "backend/dist/services/bgp-catalog-poll.d.ts.map",
+ "backend/dist/services/bgp-parse-sessions.js",
+ "backend/dist/services/bgp-parse-sessions.js.map",
+ "backend/dist/services/bgp-parse-sessions.d.ts",
+ "backend/dist/services/bgp-parse-sessions.d.ts.map",
+ "backend/dist/services/bgp-peers-live.js",
+ "backend/dist/services/bgp-peers-live.js.map",
+ "backend/dist/services/bgp-peers-live.d.ts",
+ "backend/dist/services/bgp-peers-live.d.ts.map",
+ "backend/dist/services/btest-node-locks.js",
+ "backend/dist/services/btest-node-locks.js.map",
+ "backend/dist/services/btest-node-locks.d.ts",
+ "backend/dist/services/btest-node-locks.d.ts.map",
+ "backend/dist/services/certificate-issue-dto.js",
+ "backend/dist/services/certificate-issue-dto.js.map",
+ "backend/dist/services/certificate-issue-dto.d.ts",
+ "backend/dist/services/certificate-issue-dto.d.ts.map",
+ "backend/dist/services/certificate-issue-execution.js",
+ "backend/dist/services/certificate-issue-execution.js.map",
+ "backend/dist/services/certificate-issue-execution.d.ts",
+ "backend/dist/services/certificate-issue-execution.d.ts.map",
+ "backend/dist/services/certificate-issue-request.js",
+ "backend/dist/services/certificate-issue-request.js.map",
+ "backend/dist/services/certificate-issue-request.d.ts",
+ "backend/dist/services/certificate-issue-request.d.ts.map",
+ "backend/dist/services/certificate-issue-runner.js",
+ "backend/dist/services/certificate-issue-runner.js.map",
+ "backend/dist/services/certificate-issue-runner.d.ts",
+ "backend/dist/services/certificate-issue-runner.d.ts.map",
+ "backend/dist/services/certificate-parse.js",
+ "backend/dist/services/certificate-parse.js.map",
+ "backend/dist/services/certificate-parse.d.ts",
+ "backend/dist/services/certificate-parse.d.ts.map",
+ "backend/dist/services/certificate-renew-collector.js",
+ "backend/dist/services/certificate-renew-collector.js.map",
+ "backend/dist/services/certificate-renew-collector.d.ts",
+ "backend/dist/services/certificate-renew-collector.d.ts.map",
+ "backend/dist/services/certificates-service.js",
+ "backend/dist/services/certificates-service.js.map",
+ "backend/dist/services/certificates-service.d.ts",
+ "backend/dist/services/certificates-service.d.ts.map",
+ "backend/dist/services/certificates-state.js",
+ "backend/dist/services/certificates-state.js.map",
+ "backend/dist/services/certificates-state.d.ts",
+ "backend/dist/services/certificates-state.d.ts.map",
+ "backend/dist/services/complete-count.js",
+ "backend/dist/services/complete-count.js.map",
+ "backend/dist/services/complete-count.d.ts",
+ "backend/dist/services/complete-count.d.ts.map",
+ "backend/dist/services/config-apply-plan.js",
+ "backend/dist/services/config-apply-plan.js.map",
+ "backend/dist/services/config-apply-plan.d.ts",
+ "backend/dist/services/config-apply-plan.d.ts.map",
+ "backend/dist/services/config-revisions.js",
+ "backend/dist/services/config-revisions.js.map",
+ "backend/dist/services/config-revisions.d.ts",
+ "backend/dist/services/config-revisions.d.ts.map",
+ "backend/dist/services/containers-live.js",
+ "backend/dist/services/containers-live.js.map",
+ "backend/dist/services/containers-live.d.ts",
+ "backend/dist/services/containers-live.d.ts.map",
+ "backend/dist/services/containers-state.js",
+ "backend/dist/services/containers-state.js.map",
+ "backend/dist/services/containers-state.d.ts",
+ "backend/dist/services/containers-state.d.ts.map",
+ "backend/dist/services/database-background-work.js",
+ "backend/dist/services/database-background-work.js.map",
+ "backend/dist/services/database-background-work.d.ts",
+ "backend/dist/services/database-background-work.d.ts.map",
+ "backend/dist/services/database-request-gate.js",
+ "backend/dist/services/database-request-gate.js.map",
+ "backend/dist/services/database-request-gate.d.ts",
+ "backend/dist/services/database-request-gate.d.ts.map",
+ "backend/dist/services/entity-snapshots.js",
+ "backend/dist/services/entity-snapshots.js.map",
+ "backend/dist/services/entity-snapshots.d.ts",
+ "backend/dist/services/entity-snapshots.d.ts.map",
+ "backend/dist/services/evobgp-catalog.js",
+ "backend/dist/services/evobgp-catalog.js.map",
+ "backend/dist/services/evobgp-catalog.d.ts",
+ "backend/dist/services/evobgp-catalog.d.ts.map",
+ "backend/dist/services/firewall-live.js",
+ "backend/dist/services/firewall-live.js.map",
+ "backend/dist/services/firewall-live.d.ts",
+ "backend/dist/services/firewall-live.d.ts.map",
+ "backend/dist/services/firewall-poll.js",
+ "backend/dist/services/firewall-poll.js.map",
+ "backend/dist/services/firewall-poll.d.ts",
+ "backend/dist/services/firewall-poll.d.ts.map",
+ "backend/dist/services/geoip-settings.js",
+ "backend/dist/services/geoip-settings.js.map",
+ "backend/dist/services/geoip-settings.d.ts",
+ "backend/dist/services/geoip-settings.d.ts.map",
+ "backend/dist/services/geoip-update-collector.js",
+ "backend/dist/services/geoip-update-collector.js.map",
+ "backend/dist/services/geoip-update-collector.d.ts",
+ "backend/dist/services/geoip-update-collector.d.ts.map",
+ "backend/dist/services/gre-bgp-snapshot-collector.js",
+ "backend/dist/services/gre-bgp-snapshot-collector.js.map",
+ "backend/dist/services/gre-bgp-snapshot-collector.d.ts",
+ "backend/dist/services/gre-bgp-snapshot-collector.d.ts.map",
+ "backend/dist/services/gre-bgp-snapshot-history.js",
+ "backend/dist/services/gre-bgp-snapshot-history.js.map",
+ "backend/dist/services/gre-bgp-snapshot-history.d.ts",
+ "backend/dist/services/gre-bgp-snapshot-history.d.ts.map",
+ "backend/dist/services/gre-bgp-timing.js",
+ "backend/dist/services/gre-bgp-timing.js.map",
+ "backend/dist/services/gre-bgp-timing.d.ts",
+ "backend/dist/services/gre-bgp-timing.d.ts.map",
+ "backend/dist/services/gre-catalog-poll.js",
+ "backend/dist/services/gre-catalog-poll.js.map",
+ "backend/dist/services/gre-catalog-poll.d.ts",
+ "backend/dist/services/gre-catalog-poll.d.ts.map",
+ "backend/dist/services/gre-comment.js",
+ "backend/dist/services/gre-comment.js.map",
+ "backend/dist/services/gre-comment.d.ts",
+ "backend/dist/services/gre-comment.d.ts.map",
+ "backend/dist/services/gre-live.js",
+ "backend/dist/services/gre-live.js.map",
+ "backend/dist/services/gre-live.d.ts",
+ "backend/dist/services/gre-live.d.ts.map",
+ "backend/dist/services/gre-tunnels-live.js",
+ "backend/dist/services/gre-tunnels-live.js.map",
+ "backend/dist/services/gre-tunnels-live.d.ts",
+ "backend/dist/services/gre-tunnels-live.d.ts.map",
+ "backend/dist/services/internet-path-collector.js",
+ "backend/dist/services/internet-path-collector.js.map",
+ "backend/dist/services/internet-path-collector.d.ts",
+ "backend/dist/services/internet-path-collector.d.ts.map",
+ "backend/dist/services/internet-path-snapshot.js",
+ "backend/dist/services/internet-path-snapshot.js.map",
+ "backend/dist/services/internet-path-snapshot.d.ts",
+ "backend/dist/services/internet-path-snapshot.d.ts.map",
+ "backend/dist/services/ipsec-ca.js",
+ "backend/dist/services/ipsec-ca.js.map",
+ "backend/dist/services/ipsec-ca.d.ts",
+ "backend/dist/services/ipsec-ca.d.ts.map",
+ "backend/dist/services/ipsec-certificate-dn.js",
+ "backend/dist/services/ipsec-certificate-dn.js.map",
+ "backend/dist/services/ipsec-certificate-dn.d.ts",
+ "backend/dist/services/ipsec-certificate-dn.d.ts.map",
+ "backend/dist/services/ipsec-cleanup.js",
+ "backend/dist/services/ipsec-cleanup.js.map",
+ "backend/dist/services/ipsec-cleanup.d.ts",
+ "backend/dist/services/ipsec-cleanup.d.ts.map",
+ "backend/dist/services/ipsec-config.js",
+ "backend/dist/services/ipsec-config.js.map",
+ "backend/dist/services/ipsec-config.d.ts",
+ "backend/dist/services/ipsec-config.d.ts.map",
+ "backend/dist/services/ipsec-live.js",
+ "backend/dist/services/ipsec-live.js.map",
+ "backend/dist/services/ipsec-live.d.ts",
+ "backend/dist/services/ipsec-live.d.ts.map",
+ "backend/dist/services/ipsec-operation.js",
+ "backend/dist/services/ipsec-operation.js.map",
+ "backend/dist/services/ipsec-operation.d.ts",
+ "backend/dist/services/ipsec-operation.d.ts.map",
+ "backend/dist/services/ipsec-ros.js",
+ "backend/dist/services/ipsec-ros.js.map",
+ "backend/dist/services/ipsec-ros.d.ts",
+ "backend/dist/services/ipsec-ros.d.ts.map",
+ "backend/dist/services/mikrotik.js",
+ "backend/dist/services/mikrotik.js.map",
+ "backend/dist/services/mikrotik.d.ts",
+ "backend/dist/services/mikrotik.d.ts.map",
+ "backend/dist/services/monitoring-server-catalog.js",
+ "backend/dist/services/monitoring-server-catalog.js.map",
+ "backend/dist/services/monitoring-server-catalog.d.ts",
+ "backend/dist/services/monitoring-server-catalog.d.ts.map",
+ "backend/dist/services/ospf-catalog-poll.js",
+ "backend/dist/services/ospf-catalog-poll.js.map",
+ "backend/dist/services/ospf-catalog-poll.d.ts",
+ "backend/dist/services/ospf-catalog-poll.d.ts.map",
+ "backend/dist/services/ospf-effective-disabled.js",
+ "backend/dist/services/ospf-effective-disabled.js.map",
+ "backend/dist/services/ospf-effective-disabled.d.ts",
+ "backend/dist/services/ospf-effective-disabled.d.ts.map",
+ "backend/dist/services/ospf-observed-values.js",
+ "backend/dist/services/ospf-observed-values.js.map",
+ "backend/dist/services/ospf-observed-values.d.ts",
+ "backend/dist/services/ospf-observed-values.d.ts.map",
+ "backend/dist/services/ospf-optimizer-identity.js",
+ "backend/dist/services/ospf-optimizer-identity.js.map",
+ "backend/dist/services/ospf-optimizer-identity.d.ts",
+ "backend/dist/services/ospf-optimizer-identity.d.ts.map",
+ "backend/dist/services/ospf-optimizer-metrics.js",
+ "backend/dist/services/ospf-optimizer-metrics.js.map",
+ "backend/dist/services/ospf-optimizer-metrics.d.ts",
+ "backend/dist/services/ospf-optimizer-metrics.d.ts.map",
+ "backend/dist/services/ospf-optimizer-plan.js",
+ "backend/dist/services/ospf-optimizer-plan.js.map",
+ "backend/dist/services/ospf-optimizer-plan.d.ts",
+ "backend/dist/services/ospf-optimizer-plan.d.ts.map",
+ "backend/dist/services/ospf-route-parse.js",
+ "backend/dist/services/ospf-route-parse.js.map",
+ "backend/dist/services/ospf-route-parse.d.ts",
+ "backend/dist/services/ospf-route-parse.d.ts.map",
+ "backend/dist/services/ping-probe-replacement.js",
+ "backend/dist/services/ping-probe-replacement.js.map",
+ "backend/dist/services/ping-probe-replacement.d.ts",
+ "backend/dist/services/ping-probe-replacement.d.ts.map",
+ "backend/dist/services/poller.js",
+ "backend/dist/services/poller.js.map",
+ "backend/dist/services/poller.d.ts",
+ "backend/dist/services/poller.d.ts.map",
+ "backend/dist/services/recursive-apply.js",
+ "backend/dist/services/recursive-apply.js.map",
+ "backend/dist/services/recursive-apply.d.ts",
+ "backend/dist/services/recursive-apply.d.ts.map",
+ "backend/dist/services/recursive-route-catalog.js",
+ "backend/dist/services/recursive-route-catalog.js.map",
+ "backend/dist/services/recursive-route-catalog.d.ts",
+ "backend/dist/services/recursive-route-catalog.d.ts.map",
+ "backend/dist/services/recursive-route-validation.js",
+ "backend/dist/services/recursive-route-validation.js.map",
+ "backend/dist/services/recursive-route-validation.d.ts",
+ "backend/dist/services/recursive-route-validation.d.ts.map",
+ "backend/dist/services/ros-metric-parse.js",
+ "backend/dist/services/ros-metric-parse.js.map",
+ "backend/dist/services/ros-metric-parse.d.ts",
+ "backend/dist/services/ros-metric-parse.d.ts.map",
+ "backend/dist/services/ros-ops.js",
+ "backend/dist/services/ros-ops.js.map",
+ "backend/dist/services/ros-ops.d.ts",
+ "backend/dist/services/ros-ops.d.ts.map",
+ "backend/dist/services/s3-backup-client.js",
+ "backend/dist/services/s3-backup-client.js.map",
+ "backend/dist/services/s3-backup-client.d.ts",
+ "backend/dist/services/s3-backup-client.d.ts.map",
+ "backend/dist/services/scheduler-recorded-run.js",
+ "backend/dist/services/scheduler-recorded-run.js.map",
+ "backend/dist/services/scheduler-recorded-run.d.ts",
+ "backend/dist/services/scheduler-recorded-run.d.ts.map",
+ "backend/dist/services/scheduler-running.js",
+ "backend/dist/services/scheduler-running.js.map",
+ "backend/dist/services/scheduler-running.d.ts",
+ "backend/dist/services/scheduler-running.d.ts.map",
+ "backend/dist/services/scheduler.js",
+ "backend/dist/services/scheduler.js.map",
+ "backend/dist/services/scheduler.d.ts",
+ "backend/dist/services/scheduler.d.ts.map",
+ "backend/dist/services/serial-operation.js",
+ "backend/dist/services/serial-operation.js.map",
+ "backend/dist/services/serial-operation.d.ts",
+ "backend/dist/services/serial-operation.d.ts.map",
+ "backend/dist/services/servers-rest-ping-collector.js",
+ "backend/dist/services/servers-rest-ping-collector.js.map",
+ "backend/dist/services/servers-rest-ping-collector.d.ts",
+ "backend/dist/services/servers-rest-ping-collector.d.ts.map",
+ "backend/dist/services/speed-history-dto.js",
+ "backend/dist/services/speed-history-dto.js.map",
+ "backend/dist/services/speed-history-dto.d.ts",
+ "backend/dist/services/speed-history-dto.d.ts.map",
+ "backend/dist/services/speed-probe-dto.js",
+ "backend/dist/services/speed-probe-dto.js.map",
+ "backend/dist/services/speed-probe-dto.d.ts",
+ "backend/dist/services/speed-probe-dto.d.ts.map",
+ "backend/dist/services/speed-probe-replacement.js",
+ "backend/dist/services/speed-probe-replacement.js.map",
+ "backend/dist/services/speed-probe-replacement.d.ts",
+ "backend/dist/services/speed-probe-replacement.d.ts.map",
+ "backend/dist/services/speed-run-error-message.js",
+ "backend/dist/services/speed-run-error-message.js.map",
+ "backend/dist/services/speed-run-error-message.d.ts",
+ "backend/dist/services/speed-run-error-message.d.ts.map",
+ "backend/dist/services/speed-run-persistence.js",
+ "backend/dist/services/speed-run-persistence.js.map",
+ "backend/dist/services/speed-run-persistence.d.ts",
+ "backend/dist/services/speed-run-persistence.d.ts.map",
+ "backend/dist/services/statistics-aggregate.js",
+ "backend/dist/services/statistics-aggregate.js.map",
+ "backend/dist/services/statistics-aggregate.d.ts",
+ "backend/dist/services/statistics-aggregate.d.ts.map",
+ "backend/dist/services/system-database-backup.js",
+ "backend/dist/services/system-database-backup.js.map",
+ "backend/dist/services/system-database-backup.d.ts",
+ "backend/dist/services/system-database-backup.d.ts.map",
+ "backend/dist/services/system-database-cache.js",
+ "backend/dist/services/system-database-cache.js.map",
+ "backend/dist/services/system-database-cache.d.ts",
+ "backend/dist/services/system-database-cache.d.ts.map",
+ "backend/dist/services/system-database-operation.js",
+ "backend/dist/services/system-database-operation.js.map",
+ "backend/dist/services/system-database-operation.d.ts",
+ "backend/dist/services/system-database-operation.d.ts.map",
+ "backend/dist/services/system-database-partition-restore.js",
+ "backend/dist/services/system-database-partition-restore.js.map",
+ "backend/dist/services/system-database-partition-restore.d.ts",
+ "backend/dist/services/system-database-partition-restore.d.ts.map",
+ "backend/dist/services/system-database-restore-execution.js",
+ "backend/dist/services/system-database-restore-execution.js.map",
+ "backend/dist/services/system-database-restore-execution.d.ts",
+ "backend/dist/services/system-database-restore-execution.d.ts.map",
+ "backend/dist/services/traffic-collection-state.js",
+ "backend/dist/services/traffic-collection-state.js.map",
+ "backend/dist/services/traffic-collection-state.d.ts",
+ "backend/dist/services/traffic-collection-state.d.ts.map",
+ "backend/dist/services/traffic-collector.js",
+ "backend/dist/services/traffic-collector.js.map",
+ "backend/dist/services/traffic-collector.d.ts",
+ "backend/dist/services/traffic-collector.d.ts.map",
+ "backend/dist/services/traffic-flow-analytics.js",
+ "backend/dist/services/traffic-flow-analytics.js.map",
+ "backend/dist/services/traffic-flow-analytics.d.ts",
+ "backend/dist/services/traffic-flow-analytics.d.ts.map",
+ "backend/dist/services/traffic-flow-apps.js",
+ "backend/dist/services/traffic-flow-apps.js.map",
+ "backend/dist/services/traffic-flow-apps.d.ts",
+ "backend/dist/services/traffic-flow-apps.d.ts.map",
+ "backend/dist/services/traffic-flow-brands.js",
+ "backend/dist/services/traffic-flow-brands.js.map",
+ "backend/dist/services/traffic-flow-brands.d.ts",
+ "backend/dist/services/traffic-flow-brands.d.ts.map",
+ "backend/dist/services/traffic-flow-classify.js",
+ "backend/dist/services/traffic-flow-classify.js.map",
+ "backend/dist/services/traffic-flow-classify.d.ts",
+ "backend/dist/services/traffic-flow-classify.d.ts.map",
+ "backend/dist/services/traffic-flow-collector-ipc.js",
+ "backend/dist/services/traffic-flow-collector-ipc.js.map",
+ "backend/dist/services/traffic-flow-collector-ipc.d.ts",
+ "backend/dist/services/traffic-flow-collector-ipc.d.ts.map",
+ "backend/dist/services/traffic-flow-collector-worker.js",
+ "backend/dist/services/traffic-flow-collector-worker.js.map",
+ "backend/dist/services/traffic-flow-collector-worker.d.ts",
+ "backend/dist/services/traffic-flow-collector-worker.d.ts.map",
+ "backend/dist/services/traffic-flow-dedup.js",
+ "backend/dist/services/traffic-flow-dedup.js.map",
+ "backend/dist/services/traffic-flow-dedup.d.ts",
+ "backend/dist/services/traffic-flow-dedup.d.ts.map",
+ "backend/dist/services/traffic-flow-dest.js",
+ "backend/dist/services/traffic-flow-dest.js.map",
+ "backend/dist/services/traffic-flow-dest.d.ts",
+ "backend/dist/services/traffic-flow-dest.d.ts.map",
+ "backend/dist/services/traffic-flow-engine.js",
+ "backend/dist/services/traffic-flow-engine.js.map",
+ "backend/dist/services/traffic-flow-engine.d.ts",
+ "backend/dist/services/traffic-flow-engine.d.ts.map",
+ "backend/dist/services/traffic-flow-facts-filter.js",
+ "backend/dist/services/traffic-flow-facts-filter.js.map",
+ "backend/dist/services/traffic-flow-facts-filter.d.ts",
+ "backend/dist/services/traffic-flow-facts-filter.d.ts.map",
+ "backend/dist/services/traffic-flow-facts-rebuild.js",
+ "backend/dist/services/traffic-flow-facts-rebuild.js.map",
+ "backend/dist/services/traffic-flow-facts-rebuild.d.ts",
+ "backend/dist/services/traffic-flow-facts-rebuild.d.ts.map",
+ "backend/dist/services/traffic-flow-facts.js",
+ "backend/dist/services/traffic-flow-facts.js.map",
+ "backend/dist/services/traffic-flow-facts.d.ts",
+ "backend/dist/services/traffic-flow-facts.d.ts.map",
+ "backend/dist/services/traffic-flow-geoip.js",
+ "backend/dist/services/traffic-flow-geoip.js.map",
+ "backend/dist/services/traffic-flow-geoip.d.ts",
+ "backend/dist/services/traffic-flow-geoip.d.ts.map",
+ "backend/dist/services/traffic-flow-host-file-builders.js",
+ "backend/dist/services/traffic-flow-host-file-builders.js.map",
+ "backend/dist/services/traffic-flow-host-file-builders.d.ts",
+ "backend/dist/services/traffic-flow-host-file-builders.d.ts.map",
+ "backend/dist/services/traffic-flow-host-files.js",
+ "backend/dist/services/traffic-flow-host-files.js.map",
+ "backend/dist/services/traffic-flow-host-files.d.ts",
+ "backend/dist/services/traffic-flow-host-files.d.ts.map",
+ "backend/dist/services/traffic-flow-ifaces.js",
+ "backend/dist/services/traffic-flow-ifaces.js.map",
+ "backend/dist/services/traffic-flow-ifaces.d.ts",
+ "backend/dist/services/traffic-flow-ifaces.d.ts.map",
+ "backend/dist/services/traffic-flow-ifindex.js",
+ "backend/dist/services/traffic-flow-ifindex.js.map",
+ "backend/dist/services/traffic-flow-ifindex.d.ts",
+ "backend/dist/services/traffic-flow-ifindex.d.ts.map",
+ "backend/dist/services/traffic-flow-ingest.js",
+ "backend/dist/services/traffic-flow-ingest.js.map",
+ "backend/dist/services/traffic-flow-ingest.d.ts",
+ "backend/dist/services/traffic-flow-ingest.d.ts.map",
+ "backend/dist/services/traffic-flow-ip.js",
+ "backend/dist/services/traffic-flow-ip.js.map",
+ "backend/dist/services/traffic-flow-ip.d.ts",
+ "backend/dist/services/traffic-flow-ip.d.ts.map",
+ "backend/dist/services/traffic-flow-key-execution.js",
+ "backend/dist/services/traffic-flow-key-execution.js.map",
+ "backend/dist/services/traffic-flow-key-execution.d.ts",
+ "backend/dist/services/traffic-flow-key-execution.d.ts.map",
+ "backend/dist/services/traffic-flow-map-exporter.js",
+ "backend/dist/services/traffic-flow-map-exporter.js.map",
+ "backend/dist/services/traffic-flow-map-exporter.d.ts",
+ "backend/dist/services/traffic-flow-map-exporter.d.ts.map",
+ "backend/dist/services/traffic-flow-map-hops.js",
+ "backend/dist/services/traffic-flow-map-hops.js.map",
+ "backend/dist/services/traffic-flow-map-hops.d.ts",
+ "backend/dist/services/traffic-flow-map-hops.d.ts.map",
+ "backend/dist/services/traffic-flow-overlay.js",
+ "backend/dist/services/traffic-flow-overlay.js.map",
+ "backend/dist/services/traffic-flow-overlay.d.ts",
+ "backend/dist/services/traffic-flow-overlay.d.ts.map",
+ "backend/dist/services/traffic-flow-parse.js",
+ "backend/dist/services/traffic-flow-parse.js.map",
+ "backend/dist/services/traffic-flow-parse.d.ts",
+ "backend/dist/services/traffic-flow-parse.d.ts.map",
+ "backend/dist/services/traffic-flow-planes.js",
+ "backend/dist/services/traffic-flow-planes.js.map",
+ "backend/dist/services/traffic-flow-planes.d.ts",
+ "backend/dist/services/traffic-flow-planes.d.ts.map",
+ "backend/dist/services/traffic-flow-purge-execution.js",
+ "backend/dist/services/traffic-flow-purge-execution.js.map",
+ "backend/dist/services/traffic-flow-purge-execution.d.ts",
+ "backend/dist/services/traffic-flow-purge-execution.d.ts.map",
+ "backend/dist/services/traffic-flow-ripe.js",
+ "backend/dist/services/traffic-flow-ripe.js.map",
+ "backend/dist/services/traffic-flow-ripe.d.ts",
+ "backend/dist/services/traffic-flow-ripe.d.ts.map",
+ "backend/dist/services/traffic-flow-settings.js",
+ "backend/dist/services/traffic-flow-settings.js.map",
+ "backend/dist/services/traffic-flow-settings.d.ts",
+ "backend/dist/services/traffic-flow-settings.d.ts.map",
+ "backend/dist/services/traffic-flow-topology.js",
+ "backend/dist/services/traffic-flow-topology.js.map",
+ "backend/dist/services/traffic-flow-topology.d.ts",
+ "backend/dist/services/traffic-flow-topology.d.ts.map",
+ "backend/dist/services/traffic-flow-worker-shutdown.js",
+ "backend/dist/services/traffic-flow-worker-shutdown.js.map",
+ "backend/dist/services/traffic-flow-worker-shutdown.d.ts",
+ "backend/dist/services/traffic-flow-worker-shutdown.d.ts.map",
+ "backend/dist/services/traffic-freshness.js",
+ "backend/dist/services/traffic-freshness.js.map",
+ "backend/dist/services/traffic-freshness.d.ts",
+ "backend/dist/services/traffic-freshness.d.ts.map",
+ "backend/dist/services/traffic-live.js",
+ "backend/dist/services/traffic-live.js.map",
+ "backend/dist/services/traffic-live.d.ts",
+ "backend/dist/services/traffic-live.d.ts.map",
+ "backend/dist/services/traffic-observation.js",
+ "backend/dist/services/traffic-observation.js.map",
+ "backend/dist/services/traffic-observation.d.ts",
+ "backend/dist/services/traffic-observation.d.ts.map",
+ "backend/dist/services/traffic-rate.js",
+ "backend/dist/services/traffic-rate.js.map",
+ "backend/dist/services/traffic-rate.d.ts",
+ "backend/dist/services/traffic-rate.d.ts.map",
+ "backend/dist/services/traffic-users.js",
+ "backend/dist/services/traffic-users.js.map",
+ "backend/dist/services/traffic-users.d.ts",
+ "backend/dist/services/traffic-users.d.ts.map",
+ "backend/dist/services/uptime-collector.js",
+ "backend/dist/services/uptime-collector.js.map",
+ "backend/dist/services/uptime-collector.d.ts",
+ "backend/dist/services/uptime-collector.d.ts.map",
+ "backend/dist/services/uptime-probe-observations.js",
+ "backend/dist/services/uptime-probe-observations.js.map",
+ "backend/dist/services/uptime-probe-observations.d.ts",
+ "backend/dist/services/uptime-probe-observations.d.ts.map",
+ "backend/dist/services/uptime-speed-service.js",
+ "backend/dist/services/uptime-speed-service.js.map",
+ "backend/dist/services/uptime-speed-service.d.ts",
+ "backend/dist/services/uptime-speed-service.d.ts.map",
+ "backend/dist/services/vxlan-live.js",
+ "backend/dist/services/vxlan-live.js.map",
+ "backend/dist/services/vxlan-live.d.ts",
+ "backend/dist/services/vxlan-live.d.ts.map",
+ "backend/dist/services/wg-keys.js",
+ "backend/dist/services/wg-keys.js.map",
+ "backend/dist/services/wg-keys.d.ts",
+ "backend/dist/services/wg-keys.d.ts.map",
+ "backend/dist/services/wireguard-config.js",
+ "backend/dist/services/wireguard-config.js.map",
+ "backend/dist/services/wireguard-config.d.ts",
+ "backend/dist/services/wireguard-config.d.ts.map",
+ "backend/dist/services/wireguard-delete.js",
+ "backend/dist/services/wireguard-delete.js.map",
+ "backend/dist/services/wireguard-delete.d.ts",
+ "backend/dist/services/wireguard-delete.d.ts.map",
+ "backend/dist/services/wireguard-import-apply.js",
+ "backend/dist/services/wireguard-import-apply.js.map",
+ "backend/dist/services/wireguard-import-apply.d.ts",
+ "backend/dist/services/wireguard-import-apply.d.ts.map",
+ "backend/dist/services/wireguard-import-revision.js",
+ "backend/dist/services/wireguard-import-revision.js.map",
+ "backend/dist/services/wireguard-import-revision.d.ts",
+ "backend/dist/services/wireguard-import-revision.d.ts.map",
+ "backend/dist/services/wireguard-import.js",
+ "backend/dist/services/wireguard-import.js.map",
+ "backend/dist/services/wireguard-import.d.ts",
+ "backend/dist/services/wireguard-import.d.ts.map",
+ "backend/dist/services/wireguard-live.js",
+ "backend/dist/services/wireguard-live.js.map",
+ "backend/dist/services/wireguard-live.d.ts",
+ "backend/dist/services/wireguard-live.d.ts.map",
+ "backend/dist/services/wireguard-observed-values.js",
+ "backend/dist/services/wireguard-observed-values.js.map",
+ "backend/dist/services/wireguard-observed-values.d.ts",
+ "backend/dist/services/wireguard-observed-values.d.ts.map",
+ "backend/dist/services/wireguard-ros.js",
+ "backend/dist/services/wireguard-ros.js.map",
+ "backend/dist/services/wireguard-ros.d.ts",
+ "backend/dist/services/wireguard-ros.d.ts.map",
+ "backend/dist/test/pg.js",
+ "backend/dist/test/pg.js.map",
+ "backend/dist/test/pg.d.ts",
+ "backend/dist/test/pg.d.ts.map",
+ "backend/dist/types/scheduler-run-snapshot.js",
+ "backend/dist/types/scheduler-run-snapshot.js.map",
+ "backend/dist/types/scheduler-run-snapshot.d.ts",
+ "backend/dist/types/scheduler-run-snapshot.d.ts.map",
+ "backend/dist/types/server.js",
+ "backend/dist/types/server.js.map",
+ "backend/dist/types/server.d.ts",
+ "backend/dist/types/server.d.ts.map",
+ "backend/dist/utils/abort-signals.js",
+ "backend/dist/utils/abort-signals.js.map",
+ "backend/dist/utils/abort-signals.d.ts",
+ "backend/dist/utils/abort-signals.d.ts.map",
+ "backend/dist/utils/ros-src-address.js",
+ "backend/dist/utils/ros-src-address.js.map",
+ "backend/dist/utils/ros-src-address.d.ts",
+ "backend/dist/utils/ros-src-address.d.ts.map",
+ "backend/dist/utils/server-id.js",
+ "backend/dist/utils/server-id.js.map",
+ "backend/dist/utils/server-id.d.ts",
+ "backend/dist/utils/server-id.d.ts.map",
+ "packages/contracts/dist/alerts.js",
+ "packages/contracts/dist/alerts.js.map",
+ "packages/contracts/dist/alerts.d.ts",
+ "packages/contracts/dist/alerts.d.ts.map",
+ "packages/contracts/dist/backups.js",
+ "packages/contracts/dist/backups.js.map",
+ "packages/contracts/dist/backups.d.ts",
+ "packages/contracts/dist/backups.d.ts.map",
+ "packages/contracts/dist/certificates.js",
+ "packages/contracts/dist/certificates.js.map",
+ "packages/contracts/dist/certificates.d.ts",
+ "packages/contracts/dist/certificates.d.ts.map",
+ "packages/contracts/dist/containers.js",
+ "packages/contracts/dist/containers.js.map",
+ "packages/contracts/dist/containers.d.ts",
+ "packages/contracts/dist/containers.d.ts.map",
+ "packages/contracts/dist/events.js",
+ "packages/contracts/dist/events.js.map",
+ "packages/contracts/dist/events.d.ts",
+ "packages/contracts/dist/events.d.ts.map",
+ "packages/contracts/dist/evobgp.js",
+ "packages/contracts/dist/evobgp.js.map",
+ "packages/contracts/dist/evobgp.d.ts",
+ "packages/contracts/dist/evobgp.d.ts.map",
+ "packages/contracts/dist/geoip.js",
+ "packages/contracts/dist/geoip.js.map",
+ "packages/contracts/dist/geoip.d.ts",
+ "packages/contracts/dist/geoip.d.ts.map",
+ "packages/contracts/dist/index.js",
+ "packages/contracts/dist/index.js.map",
+ "packages/contracts/dist/index.d.ts",
+ "packages/contracts/dist/index.d.ts.map",
+ "packages/contracts/dist/ipsec.js",
+ "packages/contracts/dist/ipsec.js.map",
+ "packages/contracts/dist/ipsec.d.ts",
+ "packages/contracts/dist/ipsec.d.ts.map",
+ "packages/contracts/dist/ospf-optimizer.js",
+ "packages/contracts/dist/ospf-optimizer.js.map",
+ "packages/contracts/dist/ospf-optimizer.d.ts",
+ "packages/contracts/dist/ospf-optimizer.d.ts.map",
+ "packages/contracts/dist/servers.js",
+ "packages/contracts/dist/servers.js.map",
+ "packages/contracts/dist/servers.d.ts",
+ "packages/contracts/dist/servers.d.ts.map",
+ "packages/contracts/dist/statistics.js",
+ "packages/contracts/dist/statistics.js.map",
+ "packages/contracts/dist/statistics.d.ts",
+ "packages/contracts/dist/statistics.d.ts.map",
+ "packages/contracts/dist/traffic-flow.js",
+ "packages/contracts/dist/traffic-flow.js.map",
+ "packages/contracts/dist/traffic-flow.d.ts",
+ "packages/contracts/dist/traffic-flow.d.ts.map",
+ "packages/contracts/dist/uptime-probes.js",
+ "packages/contracts/dist/uptime-probes.js.map",
+ "packages/contracts/dist/uptime-probes.d.ts",
+ "packages/contracts/dist/uptime-probes.d.ts.map",
+ "packages/contracts/dist/uptime-speed.js",
+ "packages/contracts/dist/uptime-speed.js.map",
+ "packages/contracts/dist/uptime-speed.d.ts",
+ "packages/contracts/dist/uptime-speed.d.ts.map",
+ "packages/contracts/dist/users.js",
+ "packages/contracts/dist/users.js.map",
+ "packages/contracts/dist/users.d.ts",
+ "packages/contracts/dist/users.d.ts.map",
+ "packages/contracts/dist/wireguard.js",
+ "packages/contracts/dist/wireguard.js.map",
+ "packages/contracts/dist/wireguard.d.ts",
+ "packages/contracts/dist/wireguard.d.ts.map"
+ ],
+ "missing": [],
+ "different": []
+}
\ No newline at end of file
diff --git a/docs/ux-ui/acceptance/traffic-volume-deployed-2026-10-12.json b/docs/ux-ui/acceptance/traffic-volume-deployed-2026-10-12.json
new file mode 100644
index 00000000..3be88c3c
--- /dev/null
+++ b/docs/ux-ui/acceptance/traffic-volume-deployed-2026-10-12.json
@@ -0,0 +1,46 @@
+{
+ "date": "2026-10-12",
+ "block": "12.5.180",
+ "runtime": {
+ "backend": "ux-volume180",
+ "frontend": "ux-volume180"
+ },
+ "apis": [
+ {
+ "path": "/api/traffic/servers",
+ "status": 200
+ },
+ {
+ "path": "/api/traffic/users",
+ "status": 200
+ },
+ {
+ "path": "/api/traffic/bound-interfaces",
+ "status": 200
+ }
+ ],
+ "cases": [
+ {
+ "width": 1440,
+ "trafficServers200": true,
+ "attemptProvenanceVisible": true,
+ "actualLiveSample": true,
+ "backendReceiptLabel": true,
+ "pageErrors": 0,
+ "noOverflow": true
+ },
+ {
+ "width": 320,
+ "trafficServers200": true,
+ "attemptProvenanceVisible": true,
+ "actualLiveSample": true,
+ "backendReceiptLabel": true,
+ "pageErrors": 0,
+ "noOverflow": true
+ }
+ ],
+ "bounds": [
+ "Read-only smoke; error/stale/legacy branches proven by owned SQL and UI fixtures",
+ "No manual production scheduler/config/password/message writes"
+ ]
+}
diff --git a/docs/ux-ui/acceptance/traffic-volume-interop-2026-10-12.json b/docs/ux-ui/acceptance/traffic-volume-interop-2026-10-12.json
new file mode 100644
index 00000000..aac8be6f
--- /dev/null
+++ b/docs/ux-ui/acceptance/traffic-volume-interop-2026-10-12.json
@@ -0,0 +1,10 @@
+{
+ "date": "2026-10-12",
+ "block": "12.5.180",
+ "newFrontend": "ux-volume180",
+ "oldBackend": "ux-live179",
+ "readerAccepted": true,
+ "serverCount": 2,
+ "pageErrors": 0,
+ "bound": "Additive observed DTO reader compatibility; legacy numeric-only paths verified separately as unknown"
+}
\ No newline at end of file
diff --git a/docs/ux-ui/acceptance/traffic-volume-runtime-2026-10-12.json b/docs/ux-ui/acceptance/traffic-volume-runtime-2026-10-12.json
new file mode 100644
index 00000000..ce70df71
--- /dev/null
+++ b/docs/ux-ui/acceptance/traffic-volume-runtime-2026-10-12.json
@@ -0,0 +1,25 @@
+{
+ "date": "2026-10-12",
+ "block": "12.5.180",
+ "secretEnvCopied": false,
+ "rollbackTags": {
+ "MMAPP_IMAGE_TAG": "ux-live179",
+ "BACKEND_IMAGE_TAG": "ux-live179"
+ },
+ "services": {
+ "backend": {
+ "image": "mmapp-test/backend:ux-volume180",
+ "imageId": "sha256:225a7a5613eb3cdc15f1b7d4e8f54c4c0dc6c4395c74769eaed9ca9296a6cca1",
+ "state": "running",
+ "health": "healthy",
+ "matchedVerifiedImage": true
+ },
+ "frontend": {
+ "image": "mmapp-test/frontend:ux-volume180",
+ "imageId": "sha256:93b132c1ab859c5511275e14e95a74325909002cf28ae6cb053d3e362f0ae391",
+ "state": "running",
+ "health": null,
+ "matchedVerifiedImage": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/docs/ux-ui/acceptance/traffic-volume-source-2026-10-12.json b/docs/ux-ui/acceptance/traffic-volume-source-2026-10-12.json
new file mode 100644
index 00000000..cc65c893
--- /dev/null
+++ b/docs/ux-ui/acceptance/traffic-volume-source-2026-10-12.json
@@ -0,0 +1,10 @@
+{
+ "date": "2026-10-12",
+ "block": "12.5.180",
+ "matched": {
+ "backend": 452,
+ "frontend": 522
+ },
+ "different": [],
+ "missing": []
+}
\ No newline at end of file
diff --git a/docs/ux-ui/acceptance/traffic-volume-sql-2026-10-12.json b/docs/ux-ui/acceptance/traffic-volume-sql-2026-10-12.json
new file mode 100644
index 00000000..2d87af16
--- /dev/null
+++ b/docs/ux-ui/acceptance/traffic-volume-sql-2026-10-12.json
@@ -0,0 +1,30 @@
+{
+ "date": "2026-10-12",
+ "block": "12.5.180",
+ "passed": true,
+ "profiles": [
+ {
+ "date": "2026-10-12",
+ "block": "12.5.180",
+ "database": "mm_accept_166_1791816469068",
+ "scope": "ephemeral role and precreated owned database; no copied production credentials",
+ "tests": [
+ {
+ "command": "npm --prefix backend run test:traffic-rate",
+ "passed": true,
+ "durationMs": 31474,
+ "ownedTrafficSqlCases": 9,
+ "ownedHistorySqlCases": 14,
+ "ownedLiveSqlCases": 10,
+ "ownedVolumeSqlCases": 6,
+ "postgresUnavailableSkip": false
+ }
+ ],
+ "scopedRoleVerified": true,
+ "passed": true,
+ "databaseRemoved": true,
+ "roleRemoved": true,
+ "testContainerRemoved": true
+ }
+ ]
+}
\ No newline at end of file
diff --git a/docs/ux-ui/screens/traffic-volume-1440-2026-10-12.png b/docs/ux-ui/screens/traffic-volume-1440-2026-10-12.png
new file mode 100644
index 00000000..46d450f3
Binary files /dev/null and b/docs/ux-ui/screens/traffic-volume-1440-2026-10-12.png differ
diff --git a/docs/ux-ui/screens/traffic-volume-320-2026-10-12.png b/docs/ux-ui/screens/traffic-volume-320-2026-10-12.png
new file mode 100644
index 00000000..c962bb68
Binary files /dev/null and b/docs/ux-ui/screens/traffic-volume-320-2026-10-12.png differ
diff --git a/lib/fmt-rate.test.ts b/lib/fmt-rate.test.ts
index 5d291f48..b9b7863e 100644
--- a/lib/fmt-rate.test.ts
+++ b/lib/fmt-rate.test.ts
@@ -1,6 +1,11 @@
import assert from "node:assert/strict"
-import { fmtRate } from "./fmt-rate.js"
+import { fmtRate, fmtGiB } from "./fmt-rate.js"
assert.equal(fmtRate(null),"—");assert.equal(fmtRate(undefined),"—");assert.equal(fmtRate(0),"0 Кбит/с")
assert.equal(fmtRate(0.000001),"<1 Кбит/с");assert.equal(fmtRate(0.001),"1 Кбит/с")
assert.equal(fmtRate(1),"1.00 Мбит/с");assert.equal(fmtRate(1000),"1.00 Гбит/с")
console.log("rate formatting unknown/zero/positive-small/units tests ok")
+assert.equal(fmtGiB(null),"—");assert.equal(fmtGiB(0),"0 Б")
+assert.equal(fmtGiB(1/1024**3),"1 Б");assert.equal(fmtGiB(1/1024**2),"1 КиБ")
+assert.equal(fmtGiB(1/1024),"1 МиБ");assert.equal(fmtGiB(1),"1 ГиБ");assert.equal(fmtGiB(1024),"1 ТиБ")
+assert.equal(fmtGiB(100/1024),"100 МиБ");assert.equal(fmtGiB(Number.MAX_VALUE),"—")
+console.log("binary traffic volume precision/unit boundary tests ok")
diff --git a/lib/fmt-rate.ts b/lib/fmt-rate.ts
index 9a31599a..7453f59d 100644
--- a/lib/fmt-rate.ts
+++ b/lib/fmt-rate.ts
@@ -10,10 +10,17 @@ export function fmtRate(v: number | null | undefined): string {
return `${Math.round(v)} Мбит/с`
}
-export function fmtGB(v: number | null | undefined): string {
- if (v == null) return "—"
- if (v >= 1000) return `${(v / 1000).toFixed(2)} ТБ`
- return `${v.toFixed(1)} ГБ`
+const binaryFormatter = new Intl.NumberFormat("ru-RU", { maximumFractionDigits: 2 })
+/** Traffic API volumeUnit=GiB: preserve binary units and never format positive bytes as zero. */
+export function fmtGiB(v: number | null | undefined): string {
+ if (v == null || !Number.isFinite(v) || v < 0) return "—"
+ const bytes = v * 1024 ** 3
+ if (!Number.isFinite(bytes)) return "—"
+ if (bytes === 0) return "0 Б"
+ if (bytes < 1) return "<1 Б"
+ const units = ["Б", "КиБ", "МиБ", "ГиБ", "ТиБ", "ПиБ"]
+ const index = Math.min(units.length - 1, Math.floor(Math.log(bytes) / Math.log(1024)))
+ return `${binaryFormatter.format(bytes / 1024 ** index)} ${units[index]}`
}
export function formatBytes(n: number): string {
diff --git a/lib/traffic-observations.test.ts b/lib/traffic-observations.test.ts
index 49d02fee..b802ecf6 100644
--- a/lib/traffic-observations.test.ts
+++ b/lib/traffic-observations.test.ts
@@ -5,6 +5,9 @@ const row={rxNow:0,txNow:0,rxPeak:0,txPeak:0,rxTotal:0,txTotal:0,rxSeries:[0,0],
const obs={rxNow:true,txNow:true,rxSeries:[false,true],txSeries:[false,true],sampledAt,currentState:"current",expiresAt}
assert.equal(projectTrafficObservations(row,at).rxNow,null)
const known=projectTrafficObservations({...row,observations:obs},at);assert.equal(known.rxNow,0);assert.deepEqual(known.rxSeries,[null,0])
+assert.equal(known.rxTotal,null)
+assert.equal(projectTrafficObservations({...row,volumeUnit:"GiB",observations:obs},at).rxTotal,0)
+assert.throws(()=>projectTrafficObservations({...row,volumeUnit:"GB",observations:obs},at))
const stale=projectTrafficObservations({...row,observations:obs},at+90_001);assert.equal(stale.rxNow,null);assert.equal(stale.observations?.currentState,"stale");assert.deepEqual(stale.rxSeries,[null,0])
assert.equal(projectTrafficObservations({...row,observations:{...obs,currentState:"error"}},at).rxNow,null)
for(const change of [{rxNow:-1},{observations:{...obs,sampledAt:"bad"}},{observations:{...obs,sampledAt:null}},{observations:{...obs,rxSeries:[]}}])assert.throws(()=>projectTrafficObservations({...row,...change},at))
diff --git a/lib/traffic-observations.ts b/lib/traffic-observations.ts
index 84153a6f..5af232bd 100644
--- a/lib/traffic-observations.ts
+++ b/lib/traffic-observations.ts
@@ -5,7 +5,7 @@ const meta = z.object({ rxNow: z.boolean(), txNow: z.boolean(), rxSeries: z.arra
sampledAt: timestamp, expiresAt: timestamp.optional(), lastAttemptAt: timestamp.optional(), lastSucceededAt: timestamp.optional(), error: z.string().nullable().optional(),
currentState: z.enum(["current", "unknown", "pending", "error", "stale", "disabled"]).optional() })
export type TrafficObservationMeta = z.infer
-const metrics = z.object({ rxNow: rate, txNow: rate, rxPeak: rate, txPeak: rate, rxTotal: rate, txTotal: rate,
+const metrics = z.object({ volumeUnit: z.literal("GiB").optional(), rxNow: rate, txNow: rate, rxPeak: rate, txPeak: rate, rxTotal: rate, txTotal: rate,
rxSeries: z.array(rate), txSeries: z.array(rate), observations: meta.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)))
@@ -18,8 +18,8 @@ export function projectTrafficObservations(raw: unknown, now = Date.now()) {
const current = !o?.currentState || o.currentState === "current"
const rxSeries = r.rxSeries.map((v, i) => o?.rxSeries[i] ? v : null)
const txSeries = r.txSeries.map((v, i) => o?.txSeries[i] ? v : null)
- return { observations: o, rxNow: current && o?.rxNow ? r.rxNow : null, txNow: current && o?.txNow ? r.txNow : null,
+ return { observations: o, volumeUnit: r.volumeUnit, rxNow: current && o?.rxNow ? r.rxNow : null, txNow: current && o?.txNow ? r.txNow : null,
rxSeries, txSeries, rxPeak: rxSeries.some(v => v !== null) ? r.rxPeak : null,
txPeak: txSeries.some(v => v !== null) ? r.txPeak : null,
- rxTotal: rxSeries.some(v => v !== null) ? r.rxTotal : null, txTotal: txSeries.some(v => v !== null) ? r.txTotal : null }
+ rxTotal: r.volumeUnit && rxSeries.some(v => v !== null) ? r.rxTotal : null, txTotal: r.volumeUnit && txSeries.some(v => v !== null) ? r.txTotal : null }
}
diff --git a/tests/e2e/traffic-observation-fixture.ts b/tests/e2e/traffic-observation-fixture.ts
index 24186a73..98b7fb84 100644
--- a/tests/e2e/traffic-observation-fixture.ts
+++ b/tests/e2e/traffic-observation-fixture.ts
@@ -4,6 +4,7 @@ export function observedTrafficFixturePayload(value: unknown): unknown {
if (!value || typeof value !== "object") return value
const r = Object.fromEntries(Object.entries(value).map(([key, v]) => [key, observedTrafficFixturePayload(v)]))
if ("rxNow" in r && "txNow" in r && "rxTotal" in r && "txTotal" in r && Array.isArray(r.rxSeries) && Array.isArray(r.txSeries) && !("observations" in r)) {
+ r.volumeUnit = "GiB"
r.observations = { rxNow: true, txNow: true, sampledAt: "2026-10-12T12:00:00Z",
rxSeries: r.rxSeries.map(v => typeof v === "number" && Number.isFinite(v) && v >= 0),
txSeries: r.txSeries.map(v => typeof v === "number" && Number.isFinite(v) && v >= 0) }
diff --git a/tests/e2e/traffic-volume.spec.ts b/tests/e2e/traffic-volume.spec.ts
new file mode 100644
index 00000000..f690754a
--- /dev/null
+++ b/tests/e2e/traffic-volume.spec.ts
@@ -0,0 +1,19 @@
+import { test, expect } from "@playwright/test"
+for(const width of[1440,320])test(`Объём трафика: положительные байты/бинарные единицы/неизвестная единица ${width}`,async({page})=>{
+ await page.setViewportSize({width,height:1000});let unit=true,rxTotal=1/1024,txTotal=1/1024**3
+ const server=()=>({id:"1",name:"Объём CHR",site:"TEST",country:"RU",status:"online",rxNow:0,txNow:0,rxPeak:0,txPeak:0,rxTotal,txTotal,rxSeries:[0,0],txSeries:[0,0],sessions:1,...(unit?{volumeUnit:"GiB"}:{}),observations:{rxNow:true,txNow:true,rxSeries:[false,true],txSeries:[false,true],sampledAt:"2026-10-12T12:00:00Z"}})
+ await page.route("**/health",r=>r.fulfill({json:{ok:true}}));await page.route("**/api/**",r=>{
+ const path=new URL(r.request().url()).pathname;if(path.endsWith("/live"))return r.fulfill({status:503,json:{error:"Fixture без live"}})
+ return r.fulfill({json:path==="/api/auth/config"?{required:false}:path==="/api/traffic/servers"?{servers:[server()]}:path==="/api/traffic/servers/1"?{server:server()}:path.endsWith("/interfaces")||path==="/api/traffic/bound-interfaces"?{interfaces:[]}:path==="/api/traffic/users"?{users:[]}:path==="/api/servers"?[]:{}})
+ });await page.goto("/traffic")
+ const volume=page.getByRole("group",{name:"Учтённый объём по измерениям"})
+ await expect(volume).toContainText("1 МиБ");await expect(volume).toContainText("1 Б")
+ await volume.screenshot({path:`docs/ux-ui/screens/traffic-volume-${width}-2026-10-12.png`})
+ rxTotal=1;txTotal=1024;await page.getByRole("button",{name:"15м",exact:true}).click()
+ await expect(volume).toContainText("1 ГиБ");await expect(volume).toContainText("1 ТиБ")
+ unit=false;await page.getByRole("button",{name:"24ч",exact:true}).click()
+ await expect(volume.getByText("—",{exact:true})).toHaveCount(2)
+ unit=true;rxTotal=0;txTotal=0;await page.getByRole("button",{name:"4ч",exact:true}).click()
+ await expect(volume.getByText("0 Б",{exact:true})).toHaveCount(2)
+ expect(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth+1)).toBe(true)
+})