From 2b3d583e3bc3d43d01981f64f1875a8ca60bbd06 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Mon, 12 Oct 2026 21:59:50 +0700 Subject: [PATCH] =?UTF-8?q?fix(traffic):=20=D1=81=D0=BE=D1=85=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D1=8F=D1=82=D1=8C=20=D1=82=D0=BE=D1=87=D0=BD=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D1=8C=20=D0=BE=D0=B1=D1=8A=D1=91=D0=BC=D0=BE=D0=B2=20?= =?UTF-8?q?=D0=B8=20=D0=BE=D0=B1=D0=BE=D0=B7=D0=BD=D0=B0=D1=87=D0=B0=D1=82?= =?UTF-8?q?=D1=8C=20=D0=B1=D0=B8=D0=BD=D0=B0=D1=80=D0=BD=D1=8B=D0=B5=20?= =?UTF-8?q?=D0=B5=D0=B4=D0=B8=D0=BD=D0=B8=D1=86=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(main)/traffic/page.tsx | 16 +- backend/package.json | 2 +- backend/src/routes/traffic.ts | 2 + backend/src/services/traffic-rate.test.ts | 1 + backend/src/services/traffic-rate.ts | 6 +- backend/src/services/traffic-users.ts | 12 +- .../src/services/traffic-volume-sql.test.ts | 40 + docs/ux-ui-external-acceptance.md | 25 +- docs/ux-ui-plan.md | 6 +- docs/ux-ui-progress.md | 23 +- docs/ux-ui-remaining-acceptance.md | 27 +- docs/ux-ui-stage-12.md | 23 +- docs/ux-ui-test-server.md | 25 +- .../traffic-volume-api-2026-10-12.json | 26 + .../traffic-volume-baseline-2026-10-12.json | 39 + .../traffic-volume-compiled-2026-10-12.json | 1046 +++++++++++++++++ .../traffic-volume-deployed-2026-10-12.json | 46 + .../traffic-volume-interop-2026-10-12.json | 10 + .../traffic-volume-runtime-2026-10-12.json | 25 + .../traffic-volume-source-2026-10-12.json | 10 + .../traffic-volume-sql-2026-10-12.json | 30 + .../traffic-volume-1440-2026-10-12.png | Bin 0 -> 4474 bytes .../screens/traffic-volume-320-2026-10-12.png | Bin 0 -> 5306 bytes lib/fmt-rate.test.ts | 7 +- lib/fmt-rate.ts | 15 +- lib/traffic-observations.test.ts | 3 + lib/traffic-observations.ts | 6 +- tests/e2e/traffic-observation-fixture.ts | 1 + tests/e2e/traffic-volume.spec.ts | 19 + 29 files changed, 1452 insertions(+), 39 deletions(-) create mode 100644 backend/src/services/traffic-volume-sql.test.ts create mode 100644 docs/ux-ui/acceptance/traffic-volume-api-2026-10-12.json create mode 100644 docs/ux-ui/acceptance/traffic-volume-baseline-2026-10-12.json create mode 100644 docs/ux-ui/acceptance/traffic-volume-compiled-2026-10-12.json create mode 100644 docs/ux-ui/acceptance/traffic-volume-deployed-2026-10-12.json create mode 100644 docs/ux-ui/acceptance/traffic-volume-interop-2026-10-12.json create mode 100644 docs/ux-ui/acceptance/traffic-volume-runtime-2026-10-12.json create mode 100644 docs/ux-ui/acceptance/traffic-volume-source-2026-10-12.json create mode 100644 docs/ux-ui/acceptance/traffic-volume-sql-2026-10-12.json create mode 100644 docs/ux-ui/screens/traffic-volume-1440-2026-10-12.png create mode 100644 docs/ux-ui/screens/traffic-volume-320-2026-10-12.png create mode 100644 tests/e2e/traffic-volume.spec.ts diff --git a/app/(main)/traffic/page.tsx b/app/(main)/traffic/page.tsx index 83393ee1..0fd51dac 100644 --- a/app/(main)/traffic/page.tsx +++ b/app/(main)/traffic/page.tsx @@ -19,7 +19,7 @@ import { ArrowUpIcon, ArrowDownIcon, ActivityIcon, UsersIcon, CableIcon, ServerIcon, SearchIcon, GitBranchIcon, PlusIcon, } from "lucide-react" import { cn } from "@/lib/utils" -import { fmtGB, fmtRate } from "@/lib/fmt-rate" +import { fmtGiB, fmtRate } from "@/lib/fmt-rate" import { useDataSource } from "@/lib/data-source" import { useTrafficLive } from "@/hooks/use-traffic-live" import { useFlowLive } from "@/hooks/use-flow-live" @@ -86,6 +86,7 @@ function flowEmptyHint(stats: FlowStatsDto | null, collectorAlive?: boolean): st // ─── data model ─────────────────────────────────────────────────────────────── interface BoundIfaceTraffic { + volumeUnit?: "GiB" observations?: TrafficObservationMeta id: string bindingId: string @@ -113,6 +114,7 @@ interface BoundIfaceTraffic { } interface ServerTraffic { + volumeUnit?: "GiB" observations?: TrafficObservationMeta id: string name: string @@ -132,6 +134,7 @@ interface ServerTraffic { } interface UserTraffic { + volumeUnit?: "GiB" observations?: TrafficObservationMeta id: string login: string @@ -150,6 +153,7 @@ interface UserTraffic { } interface LiveTrafficServer { + volumeUnit?: "GiB" observations?: TrafficObservationMeta id: string name: string @@ -497,8 +501,8 @@ function IfaceRow({ c, showServer = false }: { c: BoundIfaceTraffic; showServer?
-

{fmtGB(c.rxTotal)}

-

{fmtGB(c.txTotal)}

+

{fmtGiB(c.rxTotal)}

+

{fmtGiB(c.txTotal)}

+

Учтено 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 0000000000000000000000000000000000000000..46d450f30b0b7941619ee3124ee9036f3c7250e9 GIT binary patch literal 4474 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 G&3^%m5^vXZj>HU9EOsR92x;>Ndf8Z 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 literal 0 HcmV?d00001 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) +})