diff --git a/app/(main)/bgp/page.tsx b/app/(main)/bgp/page.tsx index 1d9a1780..3e845db6 100644 --- a/app/(main)/bgp/page.tsx +++ b/app/(main)/bgp/page.tsx @@ -286,6 +286,7 @@ function SessionsTab({ sessions, emptyTitle, selectedSessionId, onSelectSession {urlError && {urlError}} } /> + {selectedSessionId && sessions.some(s => s.id === selectedSessionId) && !filtered.some(s => s.id === selectedSessionId) &&

Сессия из ссылки скрыта текущими фильтрами.

} {hasFilters && !urlError && }
@@ -454,18 +456,19 @@ function AnalyticsTab({ sessions }: { sessions: BgpSession[] }) { return (
, iconClassName: "text-success", }, { id: "active", - label: "Активных маршрутов", + label: "Маршруты", hint: totalActive === null ? "нет данных API" : undefined, value: fmtNum(totalActive), icon: , @@ -473,14 +476,14 @@ function AnalyticsTab({ sessions }: { sessions: BgpSession[] }) { }, { id: "ebgp", - label: "eBGP сессий", + label: "eBGP", value: sessions.some(s => s.type === "Unknown") ? "—" : ebgpSessions, icon: , iconClassName: "text-muted-foreground", }, { id: "ibgp", - label: "iBGP сессий", + label: "iBGP", value: sessions.some(s => s.type === "Unknown") ? "—" : ibgpSessions, icon: , iconClassName: "text-muted-foreground", @@ -809,11 +812,12 @@ function BgpPageContent({ selectedServerId, selectedSessionId, activeTab, onSele )} , @@ -828,7 +832,7 @@ function BgpPageContent({ selectedServerId, selectedSessionId, activeTab, onSele }, { id: "not-estab", - label: "Не установлено", + label: "Не поднято", value: incomplete || sessions.some(s => s.state === "Unknown") ? "—" : notEstab, icon: , iconClassName: notEstab > 0 ? "text-warning" : "text-muted-foreground", @@ -836,7 +840,7 @@ function BgpPageContent({ selectedServerId, selectedSessionId, activeTab, onSele }, { id: "prefixes", - label: "Получено префиксов", + label: "Префиксы", value: incomplete ? "—" : fmtNum(totalRx), icon: , iconClassName: "text-info", diff --git a/components/data-grids/bgp-session-detail.tsx b/components/data-grids/bgp-session-detail.tsx index ce60b8d4..4e259a05 100644 --- a/components/data-grids/bgp-session-detail.tsx +++ b/components/data-grids/bgp-session-detail.tsx @@ -3,7 +3,7 @@ import type { BgpSessionRow } from "@/lib/bgp/types" import { fmtBgpNum } from "@/lib/bgp/helpers" function BgpSessionDetail({ session }: { session: BgpSessionRow }) { - return
+ return
{session.state === "Unknown" &&

Состояние сессии неизвестно. Значения относятся к последнему полученному снимку.

}
{[ diff --git a/components/data-grids/bgp-sessions-data-grid.tsx b/components/data-grids/bgp-sessions-data-grid.tsx index bebdcd1b..f0504675 100644 --- a/components/data-grids/bgp-sessions-data-grid.tsx +++ b/components/data-grids/bgp-sessions-data-grid.tsx @@ -14,6 +14,7 @@ import { DATA_GRID_CELL_PAD_FIRST, } from "@/components/data-grids/shared/data-grid-layout" import { DataGridSortHeader } from "@/components/data-grids/shared/data-grid-sort-header" +import { Button } from "@/components/ui/button" import { EmptyState } from "@/components/empty-state" import { BgpSessionDetail } from "@/components/data-grids/bgp-session-detail" import type { BgpSessionRow, BgpState, BgpType } from "@/lib/bgp/types" @@ -120,11 +121,10 @@ function BgpSessionsDataGrid({ sessions, emptyTitle, selectedSessionId, onSelect const expanded = row.getIsExpanded() return (
- {expanded ? ( - - ) : ( - - )} + {row.original.serverLabel}
) @@ -261,7 +261,7 @@ function BgpSessionsDataGrid({ sessions, emptyTitle, selectedSessionId, onSelect }, }, ], - [], + [onSelectSession], ) const table = useTable({ diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index 4c8e9f5d..80bf2f04 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -141,3 +141,13 @@ HTTP-фикстуры; layout/клавиатура проверены. Productio Этап 9 завершён 2026-09-30 в границах локальной приёмки (209 E2E, frontend/backend build). Внешние ограничения — журнал 9.64. Следующий этап — маршрутизация/диагностика (10); схема и референсы сохраняются, сетевые правила применения не меняются. Этап 10 начат 2026-09-30. Карта API, референсы, схема и совместимость: [ux-ui-stage-10.md](ux-ui-stage-10.md). Точная точка продолжения — в журнале. + +## BGP — локальная визуальная приёмка 2026-09-30 + +| Экран / референс | ПК | Телефон | Схема | +| --- | --- | --- | --- | +| BGP · List 9 / Data Grid Filtering 2 | [Снимок](ux-ui/screens/bgp-desktop.png) | [Снимок](ux-ui/screens/bgp-mobile.png) | Устройство → свежесть → сводка → фильтры/сессии | +| BGP · Data Grid детали | [Детали](ux-ui/screens/bgp-detail-desktop.png) | [Детали](ux-ui/screens/bgp-detail-mobile.png) | URL сессии → подтверждённые поля / неизвестные значения | +| BGP · Stats 12 / Frame | [Аналитика](ux-ui/screens/bgp-analytics-desktop.png) | [Аналитика](ux-ui/screens/bgp-analytics-mobile.png) | Точный срез → достоверные суммы → сравнение | + +27 E2E/сборки прошли; реальные RouterOS/JWT/БД не проверены. Далее — OSPF, точное продолжение в журнале. diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index 50b15d80..19b6ee66 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -26,11 +26,18 @@ | 7 | Серверы и мониторинг: детали, формы, URL-контекст, права | Завершён в границах локальной приёмки 2026-09-23; внешние ограничения ниже | | 8 | Трафик и статистика | Завершён 2026-09-28 в границах локальной приёмки; ограничения в блоке 8.13 | | 9 | VPN, Firewall и Filters | Завершён в границах локальной приёмки 2026-09-30; блоки 9.1–9.64, ограничения в 9.64 | -| 10 | Маршрутизация, диагностика, карта и терминал | В работе: 10.1–10.7, BGP фильтры URL; далее layout | +| 10 | Маршрутизация, диагностика, карта и терминал | В работе: 10.1–10.8, локальная приёмка BGP; далее OSPF | | 11А | Пользователи, контейнеры, сертификаты и резервные копии | Не начат | | 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Не начат | | 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат | +## Блок 10.8 — визуальная и профильная приёмка BGP (2026-09-30) + +- Два viewport 390×844/1440×1000, клавиатура и отсутствие общего переполнения проверены. Раскрытие сессии имеет явную кнопку с доступным именем/aria-expanded, Enter обновляет session URL. Выбранная сессия, скрытая фильтрами, имеет отдельное сообщение. +- Просмотрены шесть PNG BGP/деталей/аналитики. Визуальная проверка выявила обрезанные KPI и широкие детали на телефоне: подписи сокращены, детали ограничены viewport; таблица и state-фильтры сохраняют горизонтальную прокрутку. KpiStatGrid/Frame/Data Grid переиспользованы без изменений registry. +- 23 профильных E2E и четыре source-сценария прошли последовательно (27), четыре unit URL и server catalog/parser чистые. Frontend production build — 34 страницы, backend build, TypeScript/lint прошли. +- BGP завершён в границах локальных HTTP-фикстур и этих viewport. RouterOS/JWT/БД не проверены; общий accessibility/performance — 12. Старый неоднозначный BGP API/alert-engine остаётся для 11Б. Далее — OSPF: fetchServerOspf скрывает отдельные отказы BFD/routes; нужен строгий каталог и кэш с неизвестными состояниями. Квота на начале приёмки: 76% окна использовано. Локальный коммит без push. + ## Блок 10.7 — URL-фильтры BGP (2026-09-30) - Поиск, тип, состояние и завершённый FilterQuery хранятся в URL. Draft не сериализуется как готовое условие; поиск использует replace, изменения фильтра — push. Back/reload восстанавливают срез и сохраняют устройство/сессию/сторонние параметры. diff --git a/docs/ux-ui-stage-10.md b/docs/ux-ui-stage-10.md index 3e4f8063..197b16ff 100644 --- a/docs/ux-ui-stage-10.md +++ b/docs/ux-ui-stage-10.md @@ -68,3 +68,7 @@ server/session/tab и возврат/reload сохраняют контекст; ## Блок 10.7 — фильтры BGP URL Поиск/тип/состояние/FilterQuery сохраняются и валидируются; AFI варианты соответствуют снимку. 21 E2E, четыре unit, TypeScript/lint прошли. Далее — desktop/mobile и сборка BGP, затем OSPF. + +## Блок 10.8 — BGP приёмка + +27 E2E, unit/TypeScript/lint, frontend/backend build прошли. Шесть PNG просмотрены, мобильные подписи/детали исправлены. Далее — строгий каталог OSPF (BFD/routes сейчас скрывают свои ошибки). Внешние границы — журнал. diff --git a/docs/ux-ui/screens/bgp-analytics-desktop.png b/docs/ux-ui/screens/bgp-analytics-desktop.png new file mode 100644 index 00000000..fecf6a91 Binary files /dev/null and b/docs/ux-ui/screens/bgp-analytics-desktop.png differ diff --git a/docs/ux-ui/screens/bgp-analytics-mobile.png b/docs/ux-ui/screens/bgp-analytics-mobile.png new file mode 100644 index 00000000..e3c2a242 Binary files /dev/null and b/docs/ux-ui/screens/bgp-analytics-mobile.png differ diff --git a/docs/ux-ui/screens/bgp-desktop.png b/docs/ux-ui/screens/bgp-desktop.png new file mode 100644 index 00000000..30e0ab18 Binary files /dev/null and b/docs/ux-ui/screens/bgp-desktop.png differ diff --git a/docs/ux-ui/screens/bgp-detail-desktop.png b/docs/ux-ui/screens/bgp-detail-desktop.png new file mode 100644 index 00000000..2c19b81c Binary files /dev/null and b/docs/ux-ui/screens/bgp-detail-desktop.png differ diff --git a/docs/ux-ui/screens/bgp-detail-mobile.png b/docs/ux-ui/screens/bgp-detail-mobile.png new file mode 100644 index 00000000..dd72f929 Binary files /dev/null and b/docs/ux-ui/screens/bgp-detail-mobile.png differ diff --git a/docs/ux-ui/screens/bgp-mobile.png b/docs/ux-ui/screens/bgp-mobile.png new file mode 100644 index 00000000..0075f29b Binary files /dev/null and b/docs/ux-ui/screens/bgp-mobile.png differ diff --git a/tests/e2e/bgp-layout.spec.ts b/tests/e2e/bgp-layout.spec.ts new file mode 100644 index 00000000..a310f50f --- /dev/null +++ b/tests/e2e/bgp-layout.spec.ts @@ -0,0 +1,36 @@ +import { test, expect } from "@playwright/test" +import { mkdirSync } from "node:fs" +const server = (id: number) => ({ id, name: "Роутер " + id, host: "192.0.2." + id, enabled: true, country: "RU", status: null }) +const session = (serverId: number, name = "Сессия " + serverId) => ({ id: "*1", serverId, serverName: "Роутер " + serverId, serverSite: "", serverCountry: "RU", name, peerIp: "192.0.2." + serverId, remoteAs: 64513, localAs: 64512, localId: "192.0.2.100", remoteId: "192.0.2." + serverId, state: "Established", type: "eBGP", uptime: "1h", holdTime: 90, keepalive: 30, prefixesRx: 12, prefixesTx: 2, inputMessages: 20, outputMessages: 10, capabilities: ["Тест capability"], lastError: null }) + +for (const device of ["desktop", "mobile"] as const) { + test("BGP: layout и клавиатура " + device, async ({ page }) => { + await page.setViewportSize(device === "desktop" ? { width: 1440, height: 1000 } : { width: 390, height: 844 }) + await page.clock.install({ time: new Date("2026-09-30T05: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 + return r.fulfill({ json: path === "/api/servers" ? [server(1), server(2)] : path === "/api/bgp/catalog" ? { sessions: [session(1), session(2)], failures: [] } : path === "/api/auth/config" ? { required: false } : {} }) + }) + await page.goto("/bgp?server=2") + await expect(page.locator("tr[data-row-id]")).toHaveCount(1) + expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true) + const scroll = page.getByRole("button", { name: /^Idle/ }).locator("..").first() + expect(await scroll.evaluate(el => el.getBoundingClientRect().right <= innerWidth)).toBe(true) + await page.addStyleTag({ content: "nextjs-portal { display: none !important; }" }) + mkdirSync("docs/ux-ui/screens", { recursive: true }) + await page.screenshot({ path: "docs/ux-ui/screens/bgp-" + device + ".png", animations: "disabled" }) + const expand = page.getByRole("button", { name: "Открыть сессию Роутер 2 · 192.0.2.2", exact: true }) + await expand.focus() + await page.keyboard.press("Enter") + await expect(page.getByText("Тест capability", { exact: true })).toBeVisible() + if (device === "mobile") expect(await page.getByText("Тест capability", { exact: true }).evaluate(el => el.getBoundingClientRect().right <= innerWidth)).toBe(true) + await expect.poll(() => new URL(page.url()).searchParams.get("session")).toBe("2-*1") + await page.screenshot({ path: "docs/ux-ui/screens/bgp-detail-" + device + ".png", animations: "disabled" }) + await page.getByRole("button", { name: "Аналитика", exact: true }).focus() + await page.keyboard.press("Enter") + await expect(page.getByLabel("Сводка префиксов BGP")).toBeVisible() + expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true) + await page.screenshot({ path: "docs/ux-ui/screens/bgp-analytics-" + device + ".png", animations: "disabled" }) + }) +}