fix(ui): различать нулевые и неизвестные счётчики WireGuard
This commit is contained in:
1 parent
53c1bfbd3b
commit
7693b32372
9 files changed
+59
-16
No files matched your search
@@ -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"] ?? "")
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 ?? "нет рукопожатия"}
|
||||
</span>
|
||||
<div className="flex items-center gap-2 text-muted-foreground whitespace-nowrap">
|
||||
<span className="flex items-center gap-0.5">
|
||||
<ArrowDownIcon className="size-3 text-success" />
|
||||
<span aria-label="RX" className="flex items-center gap-0.5">
|
||||
<ArrowDownIcon aria-hidden="true" className="size-3 text-success" />
|
||||
{fmtBytes(peer.transferRx)}
|
||||
</span>
|
||||
<span className="flex items-center gap-0.5">
|
||||
<ArrowUpIcon className="size-3 text-info" />
|
||||
<span aria-label="TX" className="flex items-center gap-0.5">
|
||||
<ArrowUpIcon aria-hidden="true" className="size-3 text-info" />
|
||||
{fmtBytes(peer.transferTx)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -156,12 +156,12 @@ function WireguardPeersGrid({
|
||||
enableSorting: false,
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center gap-2 whitespace-nowrap text-xs text-muted-foreground">
|
||||
<span className="flex items-center gap-0.5">
|
||||
<ArrowDownIcon className="size-3 text-success" />
|
||||
<span aria-label="RX" className="flex items-center gap-0.5">
|
||||
<ArrowDownIcon aria-hidden="true" className="size-3 text-success" />
|
||||
{fmtBytes(row.original.transferRx)}
|
||||
</span>
|
||||
<span className="flex items-center gap-0.5">
|
||||
<ArrowUpIcon className="size-3 text-info" />
|
||||
<span aria-label="TX" className="flex items-center gap-0.5">
|
||||
<ArrowUpIcon aria-hidden="true" className="size-3 text-info" />
|
||||
{fmtBytes(row.original.transferTx)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -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 принLine truncated
|
||||
Этап 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 принLine truncated
|
||||
|
||||
Реальные pg_dump/pg_restore, PG rollback и остановка UDP worker требуют внешней приёмки. Датированные продолжения ниже — история; точный следующий шаг также указан в начале [журнала](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 с предупреждением.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
})
|
||||
Reference in new issue
Block a user