Состояние сессии неизвестно. Значения относятся к последнему полученному снимку.
{[
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" })
+ })
+}