From 06850169bac13444581102f49a1b76770cd0abf1 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Sun, 4 Oct 2026 16:01:41 +0700 Subject: [PATCH] =?UTF-8?q?fix(ui):=20=D0=BE=D0=B3=D1=80=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=87=D0=B8=D1=82=D1=8C=20=D1=84=D0=BE=D1=80=D0=BC=D1=83=20?= =?UTF-8?q?=D0=BC=D0=B0=D1=80=D1=88=D1=80=D1=83=D1=82=D0=B0=20=D0=B1=D0=B5?= =?UTF-8?q?=D0=B7=20=D0=BF=D0=BE=D1=82=D0=B5=D1=80=D0=B8=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BD=D0=B5=D1=87=D0=BD=D1=8B=D1=85=20=D1=82=D0=BE=D1=87=D0=B5?= =?UTF-8?q?=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(main)/recursive-routes/page.tsx | 80 +++++++----------- components/recursive-gateway-choices.tsx | 50 +++++++++++ docs/ux-ui-performance.md | 4 +- docs/ux-ui-plan.md | 2 +- docs/ux-ui-progress.md | 8 ++ docs/ux-ui-stage-12.md | 9 ++ .../screens/large-recursive-form-320.png | Bin 0 -> 52426 bytes tests/e2e/large-recursive-form.spec.ts | 33 ++++++++ 8 files changed, 135 insertions(+), 51 deletions(-) create mode 100644 components/recursive-gateway-choices.tsx create mode 100644 docs/ux-ui/screens/large-recursive-form-320.png create mode 100644 tests/e2e/large-recursive-form.spec.ts diff --git a/app/(main)/recursive-routes/page.tsx b/app/(main)/recursive-routes/page.tsx index 378940aa..9b0ff2d4 100644 --- a/app/(main)/recursive-routes/page.tsx +++ b/app/(main)/recursive-routes/page.tsx @@ -8,6 +8,7 @@ import { RecursiveRoutesDataGrid, type RecursiveRouteGroup, } from "@/components/data-grids/recursive-routes-data-grid" +import { RecursiveGatewayChoices, type GatewayOption } from "@/components/recursive-gateway-choices" import { FormField, SectionTitle } from "@/components/form-kit" import { DataPageCard } from "@/components/data-page-card" import { Frame, FramePanel } from "@/components/reui/frame" @@ -42,13 +43,6 @@ interface BackendServer { latency: number | null } -interface GatewayOption { - id: string - name: string - ip: string - status: "active" | "inactive" | "unknown" -} - const COUNTRY_OPTIONS = [ { code: "RU", label: "Россия" }, { code: "DE", label: "Германия" }, { code: "NL", label: "Нидерланды" }, { code: "SG", label: "Сингапур" }, @@ -195,10 +189,15 @@ function RouteSheet({ returnFocus: () => HTMLElement | null }) { const [form, setForm] = useState(initial) + const [endpointPage, setEndpointPage] = useState(0) + const lastEndpointPage = Math.max(0, Math.ceil(form.endpoints.length / 10) - 1) + if (endpointPage > lastEndpointPage) setEndpointPage(lastEndpointPage) + const currentEndpointPage = Math.min(endpointPage, lastEndpointPage) + const endpointOffset = currentEndpointPage * 10 const [error, setError] = useState(null) // eslint-disable-next-line react-hooks/set-state-in-effect - useEffect(() => { setForm(initial); setError(null) }, [initial, open]) + useEffect(() => { setForm(initial); setError(null); setEndpointPage(0) }, [initial, open]) const set = (k: K, v: RouteForm[K]) => setForm(f => ({ ...f, [k]: v })) const setEp = (id: string, k: K, v: RouteEndpointForm[K]) => setForm(f => ({ ...f, endpoints: f.endpoints.map(ep => ep.id === id ? { ...ep, [k]: v } : ep) })) @@ -208,9 +207,11 @@ function RouteSheet({ if (busy || saveLock.current) return if (!form.dstAddress.trim()) { setError("Dst Address обязателен"); return } if (form.endpoints.length === 0) { setError("Добавь хотя бы одну конечную точку"); return } - if (form.endpoints.some(ep => !ep.gateway.trim())) { setError("У каждой конечной точки должен быть Gateway"); return } + const emptyGateway = form.endpoints.findIndex(ep => !ep.gateway.trim()) + if (emptyGateway >= 0) { setEndpointPage(Math.floor(emptyGateway / 10)); setError(`У каждой конечной точки должен быть Gateway (Endpoint ${emptyGateway + 1})`); return } if (!form.routingTable.trim()) { setError("Укажите таблицу маршрутизации"); return } - if (form.endpoints.some(ep => [ep.distance, ep.scope, ep.targetScope].some(value => value !== null && (!Number.isInteger(value) || value < 0 || value > 255)))) { setError("Distance, Scope и T.Scope должны быть целыми числами от 0 до 255"); return } + const invalidEndpoint = form.endpoints.findIndex(ep => [ep.distance, ep.scope, ep.targetScope].some(value => value !== null && (!Number.isInteger(value) || value < 0 || value > 255))) + if (invalidEndpoint >= 0) { setEndpointPage(Math.floor(invalidEndpoint / 10)); setError(`Distance, Scope и T.Scope должны быть целыми числами от 0 до 255 (Endpoint ${invalidEndpoint + 1})`); return } setError(null) saveLock.current = true try { await onSave(form) } finally { saveLock.current = false } @@ -238,18 +239,31 @@ function RouteSheet({
Конечные точки + {form.endpoints.length > 10 &&
+ { + if (event.key !== "Enter") return + event.preventDefault() + const number = Number(event.currentTarget.value) + if (Number.isInteger(number) && number >= 1 && number <= form.endpoints.length) { setEndpointPage(Math.floor((number - 1) / 10)); setError(null) } + else setError("Укажите номер endpoint от 1 до " + form.endpoints.length) + }} /> + Номер endpoint → Enter + Endpoints {endpointOffset + 1}–{Math.min(endpointOffset + 10, form.endpoints.length)} из {form.endpoints.length} + + +
}
- {form.endpoints.map((ep, idx) => ( -
+ {form.endpoints.slice(endpointOffset, endpointOffset + 10).map((ep, idx) => ( +
- Endpoint {idx + 1}{ep.disabled ? " · отключён" : ""} + Endpoint {endpointOffset + idx + 1}{ep.disabled ? " · отключён" : ""}
-
- {gateways.map((g) => { - const value = `${g.ip}%${g.name}` - const selected = ep.gateway === value - return ( - - ) - })} -
+ setEp(ep.id, "gateway", value)} />
))} - {gateways.length === 0 && ( diff --git a/components/recursive-gateway-choices.tsx b/components/recursive-gateway-choices.tsx new file mode 100644 index 00000000..954e2990 --- /dev/null +++ b/components/recursive-gateway-choices.tsx @@ -0,0 +1,50 @@ +"use client" +import { useMemo, useState } from "react" +import { Input } from "@/components/ui/input" +import { cn } from "@/lib/utils" +import { CheckIcon } from "lucide-react" +export interface GatewayOption { id: string; name: string; ip: string; status: "active" | "inactive" | "unknown" } +export function RecursiveGatewayChoices({ gateways, selectedValue, endpointNumber, onChange }: { gateways: GatewayOption[]; selectedValue: string; endpointNumber: number; onChange: (value: string) => void }) { + const [search, setSearch] = useState("") + const matches = useMemo(() => gateways.filter(g => (g.ip + "%" + g.name).toLowerCase().includes(search.toLowerCase())), [gateways, search]) + const selected = matches.find(g => g.ip + "%" + g.name === selectedValue) + const visible = (selected ? [selected, ...matches.filter(g => g.id !== selected.id)] : matches).slice(0, 5) + return
+ {gateways.length > 5 && setSearch(event.target.value)} />} + {gateways.length > 5 &&

Показано {visible.length} из {matches.length} шлюзов. Поиск охватывает весь список.

} + {search && matches.length === 0 &&

Шлюзы не найдены. Измените поиск или введите адрес вручную.

} + {visible.map((g) => { + const value = `${g.ip}%${g.name}` + const selected = selectedValue === value + return ( + + ) + })} +
+} diff --git a/docs/ux-ui-performance.md b/docs/ux-ui-performance.md index 6f97bd39..e46eb5d9 100644 --- a/docs/ux-ui-performance.md +++ b/docs/ux-ui-performance.md @@ -33,7 +33,7 @@ | [subusers-data-grid.tsx](../components/data-grids/subusers-data-grid.tsx) | Поиск импортов/JSX в app/components не нашёл потребителя: компонент экспортируется, но не подключён. Не доказывает дефект экрана /users. | | [traffic-flows-data-grid.tsx](../components/data-grids/traffic-flows-data-grid.tsx) | FlowAnalyticsPanel/conversationsList: backend traffic-flow-analytics.ts top=min(50,max(10,settings.topN)), slice(0,top). Это top slice, не полный каталог/серверная pagination. Проверить сообщение об ограничении50 и связывание с графиками. | -Очередь: форма recursive routes/gateway choices → Filters порядок → Firewall/DnD → OSPF таблица+topology → optimizer preview/комбинаторика. Scheduler11, traffic cap50 и commRecs24 проверять на реальной границе, не искусственно подставлять1000 в недоступный DTO. Demo и неиспользуемый Subusers отделены от live приёмки. Все новые профили должны сохранять search/sort/refresh/clamp, точный target и права,320px и URL scope. +Очередь: Filters порядок → Firewall/DnD → OSPF таблица+topology → optimizer preview/комбинаторика. Scheduler11, traffic cap50 и commRecs24 проверять на реальной границе, не искусственно подставлять1000 в недоступный DTO. Demo и неиспользуемый Subusers отделены от live приёмки. Все новые профили должны сохранять search/sort/refresh/clamp, точный target и права,320px и URL scope. Принятые после ранней карты: OSPF routes/BFD, WireGuard interfaces/peers/detail, VXLAN/GRE tunnels. Pagination сохраняет полный DTO в памяти и не доказывает серверную производительность. Текущий журнал: [ux-ui-progress.md](ux-ui-progress.md). @@ -80,3 +80,5 @@ Statistics pivot в backend/src/services/statistics-aggregate.ts огранич 12.3.38: Communities1000/190573 JSON bytes →50 DOM rows/2216 page nodes/1206ms dev ready.19 browser/type/lint/build прошли.14 manualPagination:true остаются; обновления/selection по ID подтверждены локально. 12.3.40: recursive1000 групп→50 rows/2118 nodes/1457ms; одна группа1000 endpoints→50 cards/1126 nodes.35 browser/type/lint/build прошли; большой RouteSheet пока вне принятого performance scope.13 manualPagination:true остаются. + +12.3.41: recursive RouteSheet1000 endpoints ×1000 gateways →10 cards/1569 modal nodes, максимум5 gateway choices на карточку; полный PUT1001 сохранён.22+1 browser/type/lint/build. Расчёт/валидация полного draft остаётся на клиенте. diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index b40b0ef9..99e0717a 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 zero/missing counters/UI/backend mapper/29 browser/unit/builds. 12.3.28 принят локально:WireGuard interfaces1000/50 DOM/pinning/actions/cache/29+1 browser/build. 12.3.29 принят локально:WG peers1000/50 DOM/pinning/точный delete target/29+3 browser/build. 12.3.30 принят локально:WG detail1000/50 DOM/320px/клавиатура/31+1 browser/build. 12.3.31 принят локально:WG handshake без ложного online/unknown/7+1 browser/build. 12.3.32 принят локально:WG URL search/tab/status/reload/история/34+4 browser/unit/build. 12.3.33 принят локально:WG strict read/cache/freshness/unknown/40+7 browser/context8/build. 12.3.34 принят локально:VXLAN1000/50 DOM/pinning/export/22 browser/build. 12.3.35 принят локально:GRE1000/50 DOM/pinning/20+1 browser/build. 12.3.36 карта15 оставшихся источников обновлена. 12.3.37 top50 traffic/пустой локальный фильтр/перенос tabs принят в описанных границах. 12.3.38 Communities1000/50 DOM/19 browser/build принят локально. 12.3.39 clipboard ack/error/retry/unmount/5 browser/build принят локально. 12.3.40 recursive groups/endpoints1000/50 DOM/35 browser/build принят локально. Следующий блок12.3.41: большая форма маршрута/шлюзы и полная 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 interfaces1000/50 DOM/pinning/actions/cache/29+1 browser/build. 12.3.29 принят локально:WG peers1000/50 DOM/pinning/точный delete target/29+3 browser/build. 12.3.30 принят локально:WG detail1000/50 DOM/320px/клавиатура/31+1 browser/build. 12.3.31 принят локально:WG handshake без ложного online/unknown/7+1 browser/build. 12.3.32 принят локально:WG URL search/tab/status/reload/история/34+4 browser/unit/build. 12.3.33 принят локально:WG strict read/cache/freshness/unknown/40+7 browser/context8/build. 12.3.34 принят локально:VXLAN1000/50 DOM/pinning/export/22 browser/build. 12.3.35 принят локально:GRE1000/50 DOM/pinning/20+1 browser/build. 12.3.36 карта15 оставшихся источников обновлена. 12.3.37 top50 traffic/пустой локальный фильтр/перенос tabs принят в описанных границах. 12.3.38 Communities1000/50 DOM/19 browser/build принят локально. 12.3.39 clipboard ack/error/retry/unmount/5 browser/build принят локально. 12.3.40 recursive groups/endpoints1000/50 DOM/35 browser/build принят локально. 12.3.41 большая форма1000×1000/10 cards/полный PUT/22+1 browser/build принята локально. Следующий блок12.3.42: Filters pagination/порядок и полная 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 615bf3b0..8ad94727 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -31,6 +31,14 @@ | 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 | | 12 | Итоговая доступность, адаптивность, производительность и регрессия | В работе;12.1/12.2.1–12.2.10 и12.3.1–12.3.5 и12.3.7–12.3.12 приняты в описанных локальных границах;12.4.1 main регрессия и профильный повтор записаны;12.4.2 context205 и unit28 прошли;12.3.13–12.3.25 приняты локально в описанных границах;12.4.4 context213 и12.4.5 main1110 прошли, 12.3.26–12.3.33 приняты локально; 12.3.34 VXLAN и12.3.35 GRE tunnels приняты; остаются network grids/общая регрессия после delta/внешняя приёмка | +## 12.3.41 — большая форма рекурсивного маршрута (2026-10-04) + +RouteSheet показывает10 endpoints, но хранит весь draft. Предыдущая/следующая страница и прямой переход по номеру endpoint доступны с клавиатуры; add открывает страницу нового элемента, удаление корректирует индекс. Валидация ищет ошибки во всём draft и открывает страницу первого невалидного endpoint. RecursiveGatewayChoices сохраняет прежние карточки/семантику статуса; показывает5 вариантов (выбранный первым) и ищет по всему каталогу IP/name. Ручной gateway ввод сохранён. + +22 профильных browser прошли; финальный large повтор1/1 после прямого перехода/скрытой ошибки.1000 endpoints ×1000 gateways →10 cards/1569 modal nodes,50 gateway choices. Проверены первая/последняя страницы, поиск шлюза1000, скрытый невалидный endpoint1000 без PUT, ошибка503/сохранение draft/повтор,320px; PUT содержит1001 строки, обе скрытые правки и неизменную другую группу. TypeScript без incremental/финальный ESLint и production frontend build прошли. [Форма320px](ux-ui/screens/large-recursive-form-320.png) просмотрена. Общие сетевые правила и права не изменены; реальный RouterOS PUT не проверен. + +Следующий блок12.3.42: Filters1000/порядок через границу страниц/whole-catalog PUT/search/sort/refresh/cache/320px.13 manualPagination:true specialized grids остаются. Общая main/context после delta и внешняя12.5 отдельно. Коммит локальный без push. + ## 12.3.40 — большие группы рекурсивных маршрутов (2026-10-04) ReUI pagination50 групп, global sort, refresh без сброса страницы и clamp. Первичное/новое URL раскрытие находит группу в sorted model и открывает её страницу; обычный переход страниц не навязывает возврат к выбранной группе. Строка показывает3 gateway и точное число остальных. Раскрытие имеет отдельные страницы50 endpoints, сортирует всю группу по distance, сохраняет страницу при refresh и корректирует её при сокращении. На320px route закреплён, edit/delete44px и доступны через горизонтальный scroll; детали ограничены viewport и доступны с клавиатуры. diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index d8df9787..a78a7c2f 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -515,3 +515,12 @@ ReUI pagination50 групп, global sort, refresh без сброса стра Осталось13 manualPagination:true specialized grids. Большая RouteSheet пока рендерит все endpoints и все варианты gateways для каждого endpoint; performance формы не принят. Следующий блок12.3.41: ограничить DOM формы/вариантов шлюзов без потери скрытых данных/валидации/PUT payload, проверить1000 endpoints и1000 gateways. Затем Filters порядок/Firewall DnD и остальные этапы карты. Общая main/context после delta и внешняя12.5 отдельно. Коммит локальный без push. + +## 12.3.41 — большая форма рекурсивного маршрута (2026-10-04) + +RouteSheet показывает10 endpoints, но хранит весь draft. Предыдущая/следующая страница и прямой переход по номеру endpoint доступны с клавиатуры; add открывает страницу нового элемента, удаление корректирует индекс. Валидация ищет ошибки во всём draft и открывает страницу первого невалидного endpoint. RecursiveGatewayChoices сохраняет прежние карточки/семантику статуса; показывает5 вариантов (выбранный первым) и ищет по всему каталогу IP/name. Ручной gateway ввод сохранён. + +22 профильных browser прошли; финальный large повтор1/1 после прямого перехода/скрытой ошибки.1000 endpoints ×1000 gateways →10 cards/1569 modal nodes,50 gateway choices. Проверены первая/последняя страницы, поиск шлюза1000, скрытый невалидный endpoint1000 без PUT, ошибка503/сохранение draft/повтор,320px; PUT содержит1001 строки, обе скрытые правки и неизменную другую группу. TypeScript без incremental/финальный ESLint и production frontend build прошли. [Форма320px](ux-ui/screens/large-recursive-form-320.png) просмотрена. Общие сетевые правила и права не изменены; реальный RouterOS PUT не проверен. + +Следующий блок12.3.42: Filters1000/порядок через границу страниц/whole-catalog PUT/search/sort/refresh/cache/320px.13 manualPagination:true specialized grids остаются. Общая main/context после delta и внешняя12.5 отдельно. Коммит локальный без push. + diff --git a/docs/ux-ui/screens/large-recursive-form-320.png b/docs/ux-ui/screens/large-recursive-form-320.png new file mode 100644 index 0000000000000000000000000000000000000000..1da3cccf9fa1fe9ee25b6ba76127b78ad1081593 GIT binary patch literal 52426 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 KvK7*XLH`SyC$@J0 literal 0 HcmV?d00001 diff --git a/tests/e2e/large-recursive-form.spec.ts b/tests/e2e/large-recursive-form.spec.ts new file mode 100644 index 00000000..0f3ef27e --- /dev/null +++ b/tests/e2e/large-recursive-form.spec.ts @@ -0,0 +1,33 @@ +import { test, expect } from "@playwright/test" +test("Recursive form1000 endpoints/gateways: full draft/payload/error/retry/320px", async ({ page }, info) => { + test.setTimeout(150000) + let routes = Array.from({length:1000},(_,i)=>({id:"r"+(i+1),dstAddress:"203.0.113.0/24",gateway:"192.0.2.1%gw-"+(i+1),distance:1,scope:30,targetScope:10,routingTable:"main",checkGateway:"ping",comment:"Большая группа",disabled:false,country:""})) + const other = {...routes[0],id:"other",dstAddress:"203.0.114.0/24",gateway:"198.51.100.1%other",routingTable:"other"};routes.push(other) + const gateways=Array.from({length:1000},(_,i)=>({id:"g"+(i+1),name:"gw-"+(i+1),ip:"192.0.2.1",status:"unknown"})) + let fail=true,attempts=0,submitted:typeof routes=[] + await page.route("**/health",r=>r.fulfill({json:{ok:true}})) + await page.route("**/api/**",r=>{const p=new URL(r.request().url()).pathname + if(p==="/api/recursive-routes"&&r.request().method()==="PUT"){attempts++;submitted=r.request().postDataJSON().routes;if(fail)return r.fulfill({status:503,json:{error:"Запись недоступна тест"}});routes=submitted;return r.fulfill({json:{ok:true,serverId:1,routes,pushedRoutes:routes.length}})} + return r.fulfill({json:p==="/api/auth/config"?{required:false}:p==="/api/servers"?[{id:1,name:"Роутер",host:"192.0.2.1",country:"RU",enabled:true,status:null}]:p==="/api/recursive-routes/catalog"?{serverId:1,routes,live:true,stale:false}:p==="/api/recursive-routes/gateways/catalog"?{serverId:1,gateways}:{} }) + }) + await page.goto("/recursive-routes?server=1") + await page.getByRole("button",{name:"Редактировать маршрут 203.0.113.0/24 · main",exact:true}).click() + const sheet=page.getByRole("dialog"),cards=sheet.locator('[data-recursive-endpoint-form]') + await expect(cards).toHaveCount(10);await expect(sheet.getByRole("button",{name:/^Выбрать шлюз /})).toHaveCount(50) + const profile={endpoints:1000,gateways:1000,cards:await cards.count(),nodes:await sheet.locator("*").count()};console.log("Recursive form profile",JSON.stringify(profile));await info.attach("profile",{body:JSON.stringify(profile),contentType:"application/json"}) + await cards.first().getByRole("textbox",{name:"Шлюз",exact:true}).fill("192.0.2.10%edited-first") + const next=sheet.getByRole("button",{name:"Следующие endpoints формы",exact:true});await next.click();await expect(cards.first()).toContainText("Endpoint 11");const jump=sheet.getByRole("spinbutton",{name:"Перейти к endpoint",exact:true});await jump.fill("1000");await jump.press("Enter") + await expect(next).toBeDisabled();await expect(cards.last()).toContainText("Endpoint 1000") + await cards.first().getByRole("textbox",{name:"Поиск шлюза для endpoint 991",exact:true}).fill("gw-1000") + await cards.first().getByRole("button",{name:"Выбрать шлюз 192.0.2.1%gw-1000",exact:true}).click() + await expect(cards.first().getByRole("textbox",{name:"Шлюз",exact:true})).toHaveValue("192.0.2.1%gw-1000") + await cards.last().getByRole("textbox",{name:"Шлюз",exact:true}).fill("") + await sheet.getByRole("button",{name:"Предыдущие endpoints формы",exact:true}).click();await expect(cards.first()).toContainText("Endpoint 981");const save=sheet.getByRole("button",{name:"Сохранить",exact:true});await save.click();await expect(sheet.getByRole("alert")).toContainText("Endpoint 1000");expect(attempts).toBe(0) + await cards.last().getByRole("textbox",{name:"Шлюз",exact:true}).fill("192.0.2.99%edited-last") + await save.click();await expect(sheet.getByRole("alert")).toContainText("Запись недоступна тест");expect(submitted).toHaveLength(1001) + expect(submitted.find(r=>r.gateway==="192.0.2.10%edited-first")).toBeTruthy();expect(submitted.find(r=>r.gateway==="192.0.2.99%edited-last")).toBeTruthy();expect(submitted.find(r=>r.id==="other")).toEqual(other) + await expect(cards.last().getByRole("textbox",{name:"Шлюз",exact:true})).toHaveValue("192.0.2.99%edited-last") + await page.setViewportSize({width:320,height:1000});await cards.first().scrollIntoViewIfNeeded();expect(await sheet.evaluate(el=>el.scrollWidth<=el.clientWidth)).toBe(true) + await page.addStyleTag({content:"nextjs-portal{display:none!important}"});await page.screenshot({path:info.outputPath("recursive-form-320.png"),animations:"disabled"}) + fail=false;await save.click();await expect(sheet).toHaveCount(0);expect(attempts).toBe(2);expect(submitted).toHaveLength(1001) +})