From 7693b32372d271cce07d5aaa8e7603a0cf1dd88b Mon Sep 17 00:00:00 2001 From: Denozordec Date: Sun, 4 Oct 2026 11:58:10 +0700 Subject: [PATCH] =?UTF-8?q?fix(ui):=20=D1=80=D0=B0=D0=B7=D0=BB=D0=B8=D1=87?= =?UTF-8?q?=D0=B0=D1=82=D1=8C=20=D0=BD=D1=83=D0=BB=D0=B5=D0=B2=D1=8B=D0=B5?= =?UTF-8?q?=20=D0=B8=20=D0=BD=D0=B5=D0=B8=D0=B7=D0=B2=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D0=BD=D1=8B=D0=B5=20=D1=81=D1=87=D1=91=D1=82=D1=87=D0=B8=D0=BA?= =?UTF-8?q?=D0=B8=20WireGuard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/services/wireguard-live.ts | 7 +------ .../wireguard-observed-values.test.ts | 9 +++++++++ .../src/services/wireguard-observed-values.ts | 6 ++++++ .../data-grids/wireguard-peers-detail.tsx | 10 +++++----- .../data-grids/wireguard-peers-grid.tsx | 8 ++++---- docs/ux-ui-plan.md | 2 +- docs/ux-ui-progress.md | 6 ++++++ docs/ux-ui-stage-12.md | 7 +++++++ tests/e2e/wireguard-tables.spec.ts | 20 +++++++++++++++++++ 9 files changed, 59 insertions(+), 16 deletions(-) create mode 100644 backend/src/services/wireguard-observed-values.test.ts create mode 100644 backend/src/services/wireguard-observed-values.ts diff --git a/backend/src/services/wireguard-live.ts b/backend/src/services/wireguard-live.ts index f0481f54..42d5b06c 100644 --- a/backend/src/services/wireguard-live.ts +++ b/backend/src/services/wireguard-live.ts @@ -1,3 +1,4 @@ +import { parseWireGuardBytes as parseBytes } from "./wireguard-observed-values.js" import {boundedCompleteCount,completeCollectionCount} from "./complete-count.js" import { eq } from "drizzle-orm" import { db } from "../db/index.js" @@ -53,12 +54,6 @@ interface RosIpAddress { disabled?: string } -function parseBytes(v: string | undefined): number | undefined { - if (v == null || v === "") return undefined - const n = Number.parseInt(v, 10) - return Number.isFinite(n) ? n : undefined -} - function mapPeer(p: RosWireGuardPeer, idx: number): WgPeerDto { const rosId = String(p[".id"] ?? `peer-${idx}`) const allowed = (p["allowed-address"] ?? "") diff --git a/backend/src/services/wireguard-observed-values.test.ts b/backend/src/services/wireguard-observed-values.test.ts new file mode 100644 index 00000000..4d267c77 --- /dev/null +++ b/backend/src/services/wireguard-observed-values.test.ts @@ -0,0 +1,9 @@ +import { test } from "node:test" +import assert from "node:assert/strict" +import { parseWireGuardBytes } from "./wireguard-observed-values.js" +test("WireGuard byte counters preserve zero and reject partial, negative or unsafe values", () => { + assert.equal(parseWireGuardBytes("0"), 0) + assert.equal(parseWireGuardBytes(" 1234 "), 1234) + assert.equal(parseWireGuardBytes("9007199254740991"), Number.MAX_SAFE_INTEGER) + for (const raw of [undefined, null, 0, {}, "", " ", "12junk", "1.5", "-1", "Infinity", "1e3", "9007199254740992"]) assert.equal(parseWireGuardBytes(raw), undefined) +}) diff --git a/backend/src/services/wireguard-observed-values.ts b/backend/src/services/wireguard-observed-values.ts new file mode 100644 index 00000000..795b5aba --- /dev/null +++ b/backend/src/services/wireguard-observed-values.ts @@ -0,0 +1,6 @@ +// RouterOS byte counters are whole decimal values; absent or malformed values are unknown. +export function parseWireGuardBytes(raw: unknown): number | undefined { + if (typeof raw !== "string" || !/^[0-9]+$/.test(raw.trim())) return undefined + const value = Number(raw.trim()) + return Number.isSafeInteger(value) && value >= 0 ? value : undefined +} diff --git a/components/data-grids/wireguard-peers-detail.tsx b/components/data-grids/wireguard-peers-detail.tsx index 81520862..b55dc691 100644 --- a/components/data-grids/wireguard-peers-detail.tsx +++ b/components/data-grids/wireguard-peers-detail.tsx @@ -12,7 +12,7 @@ import { } from "lucide-react" function fmtBytes(n: number | undefined): string { - if (!n) return "—" + if (n === undefined || !Number.isFinite(n) || n < 0) return "—" if (n >= 1_000_000_000) return `${(n / 1_000_000_000).toFixed(1)} ГБ` if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)} МБ` if (n >= 1_000) return `${(n / 1_000).toFixed(0)} КБ` @@ -80,12 +80,12 @@ function WireGuardPeersDetail({ {peer.latestHandshake ?? "нет рукопожатия"}
- - + + - - + +
diff --git a/components/data-grids/wireguard-peers-grid.tsx b/components/data-grids/wireguard-peers-grid.tsx index 7a181086..f744b278 100644 --- a/components/data-grids/wireguard-peers-grid.tsx +++ b/components/data-grids/wireguard-peers-grid.tsx @@ -156,12 +156,12 @@ function WireguardPeersGrid({ enableSorting: false, cell: ({ row }) => (
- - + + - - + +
diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index ed1fa96b..d864af10 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -4,7 +4,7 @@ ## Актуальная точка продолжения — 2026-10-04 -Этап 11Б завершён в границах локальной приёмки (11Б.7); внешний выпуск не проверен. 12.1 принят локально: доступность оболочки/клавиатура/health в описанных сценариях. 12.2.1 принят локально: strict BGP catalog/partial/nullable counters. 12.2.2 принят локально: strict traffic DTO и агрегация без выдуманных точек. 12.2.3 принят локально: observations для server traffic от backend до dashboard. 12.2.4 принят локально: strict servers/probes и unknown server status. 12.2.5 принят локально: реальные ping samples/unknown/ось RTT/Recharts. 12.2.6 принят локально: strict GRE/WireGuard/VXLAN и device failures. 12.2.7 принят локально: strict Internet path snapshot/fallback. 12.2.8 принят локально: подтверждение полного backend сбора. 12.2.9 принят локально: метрики участков и наблюдаемый WAN без эвристической подмены. 12.3.1 принят локально:1000 серверов/штатная пагинация. 12.3.2 принят локально: пагинация CompactDataGrid/1000 строк статистики. 12.3.3 принят локально: порционный DOM очереди проблем. 12.3.4 принят локально: большой BGP каталог/страница при обновлении. Production build и123 профильных browser приняты локально на3978976. 12.2.10 принят локально: обязательные RouterOS поля. 12.3.5 принят локально: страница CompactDataGrid сохраняется при опросе,89 browser и повторный production build прошли. 12.3.6 сохранён: baseline1000 speed probes (ограничение DOM пока не выполнено). 12.3.7 принят локально:50 строк speed-проб/сохранение страницы/глобальная сортировка/1000→1; каталог1001 устройства остаётся в DOM. 12.3.8 принят локально:50 строк ресурсов, URL/поиск/сохранение страницы/1000→1; исправлена pagination оболочки с children, production build и8 больших browser прошли. 12.3.9 принят локально:порционное раскрытие1000 предупреждений ресурсов/клавиатура/axe; исходная ошибка ESLint speedDraft подтверждена. 12.3.10 принят локально:недоступные интерфейсы очищаются в обработчиках,54 browser/полный ESLint/production build прошли. 12.3.11 принят локально:ServerTileRail50 устройств/URL/поиск/клавиатура/две площадки/mobile Sheet/axe обе темы;47 browser и production build прошли. 12.3.12 принят локально:BGP ручная страница при выбранной URL сессии/refresh/reload,16 browser/build прошли. 12.4.1 записан:main1091 сценарий/1088 passed/3 устаревших fixtures; оба исправленных файла6 passed. 12.4.2 принят локально:context205/24 файла и unit28 прошли. 12.3.13 принят локально:wide pivot51×16/ScrollArea/axe/24+2 browser/builds. 12.3.14 принят локально:history200/50 DOM/54+1 browser/build. 12.3.15 принят локально:live расписание1000/page20 error cache/15 browser. 12.3.16 принят локально:certificates1000/50 DOM/refresh деталей/19+3 browser/build. 12.3.17 принят локально:pinning идентификатора/видимость значений/29 browser/build. 12.3.18 принят локально:users1000/поиск всего списка/детали и ошибка refresh/32 browser/build. 12.3.19 принят локально:backups1000/все мобильные действия/возврат focus/39+4 browser/build. 12.3.20 принят локально:независимые speed GET/cache/freshness/58+4 browser/build. 12.3.21 принят локально:strict history DTO/null rates от DB до UI/60+5 browser/unit4/builds. 12.4.3 принят локально:users/backups delta context36 на a558412. 12.3.22 принят локально:shared speed-probes DTO/context8/60+6+47 browser/unit5/builds. 12.3.23 принят локально:strict write DTO/atomic replacement/confirmed scheduler warning/65 browser/HTTP DI JWT/builds. 12.3.24 принят локально:OSPF routes1000/50 DOM/page20/cache/320px/22+1 browser/build. 12.3.25 принят локально:BFD1000/50 DOM/cache/320px/12 browser/build. 12.4.4 принят локально:context213/25 файлов и unit36 прошли на5b2a291. 12.4.5 принят локально:main1110/171 прошли на21859be. 12.3.26 принят локально:strict atomic Ping write/подтверждение каталога и сохранения/история удаления/62 browser/context11/DI6/builds. Следующий блок12.3.27: WireGuard missing/zero bytes и network grids; далее network grids и полная main/context регрессия.12.5 отдельно. См. docs/ux-ui-stage-12.md. Внешние ограничения 11Б сохраняются. +Этап 11Б завершён в границах локальной приёмки (11Б.7); внешний выпуск не проверен. 12.1 принят локально: доступность оболочки/клавиатура/health в описанных сценариях. 12.2.1 принят локально: strict BGP catalog/partial/nullable counters. 12.2.2 принят локально: strict traffic DTO и агрегация без выдуманных точек. 12.2.3 принят локально: observations для server traffic от backend до dashboard. 12.2.4 принят локально: strict servers/probes и unknown server status. 12.2.5 принят локально: реальные ping samples/unknown/ось RTT/Recharts. 12.2.6 принят локально: strict GRE/WireGuard/VXLAN и device failures. 12.2.7 принят локально: strict Internet path snapshot/fallback. 12.2.8 принят локально: подтверждение полного backend сбора. 12.2.9 принят локально: метрики участков и наблюдаемый WAN без эвристической подмены. 12.3.1 принят локально:1000 серверов/штатная пагинация. 12.3.2 принят локально: пагинация CompactDataGrid/1000 строк статистики. 12.3.3 принят локально: порционный DOM очереди проблем. 12.3.4 принят локально: большой BGP каталог/страница при обновлении. Production build и123 профильных browser приняты локально на3978976. 12.2.10 принят локально: обязательные RouterOS поля. 12.3.5 принят локально: страница CompactDataGrid сохраняется при опросе,89 browser и повторный production build прошли. 12.3.6 сохранён: baseline1000 speed probes (ограничение DOM пока не выполнено). 12.3.7 принят локально:50 строк speed-проб/сохранение страницы/глобальная сортировка/1000→1; каталог1001 устройства остаётся в DOM. 12.3.8 принят локально:50 строк ресурсов, URL/поиск/сохранение страницы/1000→1; исправлена pagination оболочки с children, production build и8 больших browser прошли. 12.3.9 принят локально:порционное раскрытие1000 предупреждений ресурсов/клавиатура/axe; исходная ошибка ESLint speedDraft подтверждена. 12.3.10 принят локально:недоступные интерфейсы очищаются в обработчиках,54 browser/полный ESLint/production build прошли. 12.3.11 принят локально:ServerTileRail50 устройств/URL/поиск/клавиатура/две площадки/mobile Sheet/axe обе темы;47 browser и production build прошли. 12.3.12 принят локально:BGP ручная страница при выбранной URL сессии/refresh/reload,16 browser/build прошли. 12.4.1 записан:main1091 сценарий/1088 passed/3 устаревших fixtures; оба исправленных файла6 passed. 12.4.2 принят локально:context205/24 файла и unit28 прошли. 12.3.13 принят локально:wide pivot51×16/ScrollArea/axe/24+2 browser/builds. 12.3.14 принят локально:history200/50 DOM/54+1 browser/build. 12.3.15 принят локально:live расписание1000/page20 error cache/15 browser. 12.3.16 принят локально:certificates1000/50 DOM/refresh деталей/19+3 browser/build. 12.3.17 принят локально:pinning идентификатора/видимость значений/29 browser/build. 12.3.18 принят локально:users1000/поиск всего списка/детали и ошибка refresh/32 browser/build. 12.3.19 принят локально:backups1000/все мобильные действия/возврат focus/39+4 browser/build. 12.3.20 принят локально:независимые speed GET/cache/freshness/58+4 browser/build. 12.3.21 принят локально:strict history DTO/null rates от DB до UI/60+5 browser/unit4/builds. 12.4.3 принят локально:users/backups delta context36 на a558412. 12.3.22 принят локально:shared speed-probes DTO/context8/60+6+47 browser/unit5/builds. 12.3.23 принят локально:strict write DTO/atomic replacement/confirmed scheduler warning/65 browser/HTTP DI JWT/builds. 12.3.24 принят локально:OSPF routes1000/50 DOM/page20/cache/320px/22+1 browser/build. 12.3.25 принят локально:BFD1000/50 DOM/cache/320px/12 browser/build. 12.4.4 принят локально:context213/25 файлов и unit36 прошли на5b2a291. 12.4.5 принят локально:main1110/171 прошли на21859be. 12.3.26 принят локально:strict atomic Ping write/подтверждение каталога и сохранения/история удаления/62 browser/context11/DI6/builds. 12.3.27 принят локально:WireGuard zero/missing counters/UI/backend mapper/29 browser/unit/builds. Следующий блок12.3.28: большой каталог WireGuard interfaces; далее network grids и полная main/context регрессия.12.5 отдельно. См. docs/ux-ui-stage-12.md. Внешние ограничения 11Б сохраняются. Реальные pg_dump/pg_restore, PG rollback и остановка UDP worker требуют внешней приёмки. Датированные продолжения ниже — история; точный следующий шаг также указан в начале [журнала](ux-ui-progress.md). diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index 7e1caf10..37058d1c 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -36,6 +36,12 @@ +## 12.3.27 — достоверные счётчики WireGuard (2026-10-04) + +Нулевые RX/TX отображаются как 0 Б, отсутствующие значения — как — в таблице пиров и раскрытии интерфейса. Направления подписаны для accessibility. Backend принимает только целые неотрицательные десятичные счётчики в safe integer диапазоне: строки вроде12junk, дроби/минус/unsafe больше не превращаются в показания. Существующие правила сетевых операций не изменены. + +Проверки: wireguard-links/tables29/29, включая обе композиции zero/missing и старые сценарии операций/ссылок; pure mapper1/1; TypeScript без incremental, ESLint изменённых файлов и backend/frontend production build прошли. Реальные RouterOS ответы не проверены. Следующий блок12.3.28: большой каталог интерфейсов WireGuard, пагинация50/refresh/search/320px; полный peer grid и раскрытие большого списка пиров остаются отдельным объёмом. Коммит локальный без push. + ## 12.3.26 — атомарная запись каталога Ping-проб (2026-10-04) Shared write DTO проверяет весь список до DB: обязательный массив, уникальные ID, безопасные ID устройств, непустые имена/цели, boolean и целые интервалы. Некорректные строки не отбрасываются с последующим удалением других проб. Замена выполняется в PostgreSQL transaction с advisory lock; конфигурация обновляется отдельно от измерений. Оmitted interval/dashboard сохраняются для существующих строк, явный пустой список означает удаление. Ошибка refreshScheduler после commit возвращает подтверждённое ok:true с предупреждением. diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index 19291e2b..ecc9bcf2 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -391,3 +391,10 @@ UI проверяет Ping-каталог до публикации, сохра Реальный PG rollback/advisory lock/RouterOS не проверены; DI не заменяет внешнюю12.5. Whole-catalog PUT остаётся last writer wins без revision/ETag. Resources entries остаются legacy DTO (проверена только array оболочка). Следующий блок12.3.27: WireGuard missing/zero RX/TX в деталях и таблице пиров; далее network grids. Коммит локальный, без push. + +## 12.3.27 — достоверные счётчики WireGuard (2026-10-04) + +Нулевые RX/TX отображаются как 0 Б, отсутствующие значения — как — в таблице пиров и раскрытии интерфейса. Направления подписаны для accessibility. Backend принимает только целые неотрицательные десятичные счётчики в safe integer диапазоне: строки вроде12junk, дроби/минус/unsafe больше не превращаются в показания. Существующие правила сетевых операций не изменены. + +Проверки: wireguard-links/tables29/29, включая обе композиции zero/missing и старые сценарии операций/ссылок; pure mapper1/1; TypeScript без incremental, ESLint изменённых файлов и backend/frontend production build прошли. Реальные RouterOS ответы не проверены. Следующий блок12.3.28: большой каталог интерфейсов WireGuard, пагинация50/refresh/search/320px; полный peer grid и раскрытие большого списка пиров остаются отдельным объёмом. Коммит локальный без push. + diff --git a/tests/e2e/wireguard-tables.spec.ts b/tests/e2e/wireguard-tables.spec.ts index d8942135..f50fe49d 100644 --- a/tests/e2e/wireguard-tables.spec.ts +++ b/tests/e2e/wireguard-tables.spec.ts @@ -45,3 +45,23 @@ test("WireGuard: пустые интерфейсы и пиры", async ({ page } await page.getByRole("tab", { name: /Пиры/ }).click() await expect(page.getByText("Нет пиров", { exact: true })).toBeVisible() }) + +for (const view of ["details", "peers"] as const) test("WireGuard traffic: observed zero differs from missing/" + view, async ({ page }) => { + await setup(page) + const interfaces = fixture.interfaces.map((row, i) => ({ ...row, peers: row.peers.map(peer => { + const { transferRx, transferTx, ...config } = peer + return i === 0 ? { ...config, transferRx: 0, transferTx: 0 } : i === 1 ? config : { ...config, transferRx, transferTx } + }) })) + await page.route("**/api/wireguard", route => route.fulfill({ json: wgListResponseSchema.parse({ interfaces }) })) + await page.reload() + const rows = page.locator("tr[data-row-id]") + if (view === "peers") await page.getByRole("tab", { name: /Пиры/ }).click() + for (const [index, value] of [[0, "0 Б"], [1, "—"], [2, "3 Б"]] as const) { + const row = rows.nth(index) + if (view === "details") await row.click() + const target = view === "details" ? row.locator("xpath=following-sibling::tr[1]") : row + await expect(target.getByLabel("RX", { exact: true })).toHaveText(value) + await expect(target.getByLabel("TX", { exact: true })).toHaveText(value) + if (view === "details") await row.click() + } +})