From 2762f073a064758203251d946875eae14f9949dd Mon Sep 17 00:00:00 2001 From: Denozordec Date: Sun, 4 Oct 2026 02:54:33 +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=81=D1=82=D1=80=D0=BE=D0=BA=D0=B8?= =?UTF-8?q?=20=D1=80=D0=B5=D1=81=D1=83=D1=80=D1=81=D0=BE=D0=B2=20=D0=BC?= =?UTF-8?q?=D0=BE=D0=BD=D0=B8=D1=82=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data-grids/shared/data-grid-shell.tsx | 14 +++-- .../data-grids/uptime-resources-data-grid.tsx | 13 ++++- docs/ux-ui-performance.md | 5 +- docs/ux-ui-plan.md | 2 +- docs/ux-ui-progress.md | 10 +++- docs/ux-ui-stage-12.md | 8 +++ tests/e2e/large-uptime-resources.spec.ts | 54 +++++++++++++++++++ 7 files changed, 93 insertions(+), 13 deletions(-) create mode 100644 tests/e2e/large-uptime-resources.spec.ts diff --git a/components/data-grids/shared/data-grid-shell.tsx b/components/data-grids/shared/data-grid-shell.tsx index b4a5f7bf..eed3f45a 100644 --- a/components/data-grids/shared/data-grid-shell.tsx +++ b/components/data-grids/shared/data-grid-shell.tsx @@ -56,14 +56,12 @@ function DataGridShell({ tableClassNames={tableClassNames} > {children ?? ( - <> - - - - {pagination && recordCount > 0 && ( - - )} - + + + + )} + {pagination && recordCount > 0 && ( + )} ) diff --git a/components/data-grids/uptime-resources-data-grid.tsx b/components/data-grids/uptime-resources-data-grid.tsx index d5a53f15..e701c810 100644 --- a/components/data-grids/uptime-resources-data-grid.tsx +++ b/components/data-grids/uptime-resources-data-grid.tsx @@ -2,7 +2,7 @@ import { DataGridContainer, DataGridScrollArea, DataGridTable, dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid" -import { useMemo } from "react" +import { useMemo, useEffect } from "react" import { type ColumnDef, useTable, @@ -349,12 +349,20 @@ function UptimeResourcesDataGrid({ rows }: UptimeResourcesDataGridProps) { const table = useTable({ features: dataGridFeatures, - manualPagination: true, + manualPagination: false, + autoResetPageIndex: false, + initialState: { pagination: { pageIndex: 0, pageSize: 50 } }, data: rows, columns, getRowId: (row) => row.serverId, }) + const { pageIndex, pageSize } = table.state.pagination + useEffect(() => { + const lastPage = Math.max(0, Math.ceil(rows.length / pageSize) - 1) + if (pageIndex > lastPage) table.setPageIndex(lastPage) + }, [rows.length, pageSize, pageIndex, table]) + if (rows.length === 0) { return ( 50 || table.state.pagination.pageSize !== 50} tableClassNames={{ headerRow: "border-b border-border bg-muted/30", bodyRow: cn( diff --git a/docs/ux-ui-performance.md b/docs/ux-ui-performance.md index 413bc231..ab66112f 100644 --- a/docs/ux-ui-performance.md +++ b/docs/ux-ui-performance.md @@ -8,6 +8,7 @@ | Статистика/CompactDataGrid |1000 users,88894 JSON bytes массива |1000 →50 |12585 →1210 |5195 →2928/1195ms | | BGP |1000 полных сессий |1000 →50 |25657 →1932 |2990 →898ms | | Speed-пробы |1000 probes/1001 devices |1000 →50 |23590 →11265 (секция739) |3741 →2310ms | +| Ресурсы uptime |1000 resources/CPU10% |1000 →50 |38655 →2580 |4328 →1578ms | | Очередь проблем |1000 offline servers |5 начальных,55 после раскрытия,105 после кнопки |831 начальный dashboard |1081ms; baseline до не замерялся | Профили: large-servers.spec.ts, large-statistics.spec.ts, large-bgp.spec.ts, dashboard-data.spec.ts. Измерения приложены к Playwright test results как JSON. Полные массивы всё ещё загружаются/валидируются/фильтруются на клиенте. Pagination ограничивает строки DOM, но не число pivot колонок; широкая pivot требует отдельной проверки. Порционное раскрытие очереди допускает постепенный полный список по запросу пользователя. @@ -46,8 +47,10 @@ | [wireguard-data-grid.tsx](../components/data-grids/wireguard-data-grid.tsx) | Потребитель API, контекст/детали и fixture1000 | | [wireguard-peers-grid.tsx](../components/data-grids/wireguard-peers-grid.tsx) | Потребитель API, контекст/детали и fixture1000 | -Осталось26 специализированных grids; ни один из оставшегося перечня не принят этим документом на больших данных. CompactDataGrid дополнительно проверен на повторном получении100 active probes: страница сохраняется; сокращение100→1 ограничивает индекс. Следующая группа12.3.6 — uptime-resources/probes-speed/probes-schedule, затем сетевые/admin grids по профилям. Общий журнал: [ux-ui-progress.md](ux-ui-progress.md). +Осталось25 специализированных grids; ни один из оставшегося перечня не принят этим документом на больших данных. CompactDataGrid дополнительно проверен на повторном получении100 active probes: страница сохраняется; сокращение100→1 ограничивает индекс. Следующая группа12.3.6 — uptime-resources/probes-speed/probes-schedule, затем сетевые/admin grids по профилям. Общий журнал: [ux-ui-progress.md](ux-ui-progress.md). 12.3.6:1000 speed probes baseline —1000 rendered rows/23590 DOM nodes/3741ms dev ready; профиль collector-probes4 cases и ESLint прошли. Далее12.3.7 ограничение DOM speed grid; этот baseline не означает принятую производительность. 12.3.7: speed grid принят на1000 строках в описанных локальных границах.50 строк/739 nodes секции; вся страница11265 nodes из-за полного каталога устройств. ServerTileRail требует отдельного ограничения DOM с проверкой поиска, выбора и клавиатуры. Следующий блок12.3.8 — uptime resources. + +12.3.8: ресурсы приняты на1000 CPU10% строках; глобальный поиск/URL/сортировка/страница20/refresh/1000→1/320px. Большой список1000 предупреждений ресурсов требует отдельной проверки12.3.9. diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index 02be2843..5a17c249 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:1000 uptime resources, затем каталог устройств/schedule entries и остальные grids;12.4/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 предупреждений ресурсов, затем каталог устройств/schedule entries и остальные grids;12.4/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 11d8ecb7..dfb16f90 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -29,13 +29,21 @@ | 10 | Маршрутизация, диагностика, карта и терминал | Завершён в границах локальной приёмки 2026-10-01; блоки 10.1–10.71, внешние ограничения в 10.71 | | 11А | Пользователи, контейнеры, сертификаты и резервные копии | Завершён в границах локальной приёмки 2026-10-01; 11А.1–11А.33, внешние ограничения в 11А.33 | | 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 | Итоговая доступность, адаптивность, производительность и регрессия | В работе;12.1/12.2.1–12.2.10 и12.3.1–12.3.5 и12.3.7–12.3.8 приняты в описанных локальных границах | +## 12.3.8 — большой список ресурсов мониторинга (2026-10-04) + +UptimeResourcesDataGrid использует ReUI client pagination по50 строк с сохранением страницы при обновлении и ограничением индекса при уменьшении набора. DataGridShell исправлен: pagination выводится также при собственных children/ScrollArea; раньше такой потребитель не получал панель страниц. Установленные primitives и тема сохранены. + +1000 ресурсов: baseline1000 строк/38655 DOM nodes/4328ms dev; после50 строк/2580 nodes/1578ms. Проверены глобальная сортировка, страница20, поиск записи1000/URL/reload, обновление страницы2,1000→1,320px и отсутствие POST при чтении по ссылке.8 browser больших таблиц/collector-probes прошли после исправления оболочки;52 прежних uptime links сценария прошли. Первый large-case воспроизвёл отсутствие pagination при children; финальный прогон прошёл. Typecheck/ESLint, frontend production/contracts build (34 страницы), ReUI validate_usage/checklist прошли. + +Полные ресурсы остаются в памяти. Этот fixture использует CPU10%, список1000 предупреждений ресурсов ещё не проверен; ServerTileRail остаётся следующим отдельным потребителем. Далее12.3.9: большой список предупреждений ресурсов, затем каталог устройств/расписания и специализированные grids.12.4/12.5 не завершены, реальная PG/RouterOS нагрузка не проверена. Локальный коммит, без push. + ## 12.3.7 — пагинация большого списка speed-проб (2026-10-04) ProbesSpeedProbesDataGrid использует штатную ReUI client pagination по50 строк; обновление сохраняет страницу, сокращение набора ограничивает индекс. Сортировка работает по всему набору. Проверено1000→1, последняя страница20 и320px; небольшой список12 записей и демо-расписание сохраняют поведение. diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index 1922c842..0cf1a903 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -181,3 +181,11 @@ Pure/DI профиль дополнен malformed entity/error record/valid empt ProbesSpeedProbesDataGrid использует штатную ReUI client pagination по50 строк; обновление сохраняет страницу, сокращение набора ограничивает индекс. Сортировка работает по всему набору. Проверено1000→1, последняя страница20 и320px; небольшой список12 записей и демо-расписание сохраняют поведение. 4 browser collector-probes прошли.1000 строк дают50 rendered rows/739 узлов внутри секции speed-проб,11265 DOM nodes всей страницы,2310ms ready в одном dev прогоне против baseline23590 nodes/3741ms. Каталог1001 устройства в ServerTileRail остаётся полностью в DOM: общая производительность страницы ещё не принята. Полные DTO остаются в памяти; API и правила сетевых операций не менялись. Следующий блок12.3.8:1000 uptime resources; затем каталог устройств/расписания и остальные grids.12.4/12.5 не завершены. + +## 12.3.8 — большой список ресурсов мониторинга (2026-10-04) + +UptimeResourcesDataGrid использует ReUI client pagination по50 строк с сохранением страницы при обновлении и ограничением индекса при уменьшении набора. DataGridShell исправлен: pagination выводится также при собственных children/ScrollArea; раньше такой потребитель не получал панель страниц. Установленные primitives и тема сохранены. + +1000 ресурсов: baseline1000 строк/38655 DOM nodes/4328ms dev; после50 строк/2580 nodes/1578ms. Проверены глобальная сортировка, страница20, поиск записи1000/URL/reload, обновление страницы2,1000→1,320px и отсутствие POST при чтении по ссылке.8 browser больших таблиц/collector-probes прошли после исправления оболочки;52 прежних uptime links сценария прошли. Первый large-case воспроизвёл отсутствие pagination при children; финальный прогон прошёл. Typecheck/ESLint, frontend production/contracts build (34 страницы), ReUI validate_usage/checklist прошли. + +Полные ресурсы остаются в памяти. Этот fixture использует CPU10%, список1000 предупреждений ресурсов ещё не проверен; ServerTileRail остаётся следующим отдельным потребителем. Далее12.3.9: большой список предупреждений ресурсов, затем каталог устройств/расписания и специализированные grids.12.4/12.5 не завершены, реальная PG/RouterOS нагрузка не проверена. Локальный коммит, без push. diff --git a/tests/e2e/large-uptime-resources.spec.ts b/tests/e2e/large-uptime-resources.spec.ts new file mode 100644 index 00000000..fdb6b591 --- /dev/null +++ b/tests/e2e/large-uptime-resources.spec.ts @@ -0,0 +1,54 @@ +import { test, expect } from "@playwright/test" + +test("1000 ресурсов: страницы, поиск, обновление и уменьшение списка", async ({ page }, info) => { + test.setTimeout(90000) + const servers = Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, name: "Ресурс " + String(i + 1).padStart(4, "0"), host: "192.0.2." + (i % 250 + 1), type: "exit-node", enabled: true, country: "RU", site: "TEST", asn: "64512", status: "online", latency: 1, os: "7", model: "CHR", sessions: 0, wanUplinks: [] })) + let resources = servers.map(s => ({ serverId: String(s.id), hasData: true, cpu: 10, cpuHistory: [10], ramUsed: 100, ramTotal: 1000, hddUsed: 10, hddTotal: 100, uptimeSeconds: 60, boardName: "CHR" })) + let calls = 0 + const writes: string[] = [] + await page.route("**/health", route => route.fulfill({ json: { ok: true } })) + await page.route("**/api/**", route => { + const path = new URL(route.request().url()).pathname + if (!["GET", "HEAD"].includes(route.request().method())) writes.push(path) + if (path === "/api/uptime/overview") calls++ + return route.fulfill({ json: path === "/api/auth/config" ? { required: false } : path === "/api/servers" ? servers : path === "/api/uptime/overview" ? { probes: [], resources } : path === "/api/uptime/speed-probes" ? { probes: [] } : path === "/api/uptime/speed-test/runs" ? { runs: [] } : {} }) + }) + const start = Date.now() + await page.goto("/uptime?tab=resources&probe=p1&keep=1") + const rows = page.locator("tr[data-row-id]") + await expect(rows).toHaveCount(50) + const sample = { fixtureRows: 1000, renderedRows: await rows.count(), domNodes: await page.locator("*").count(), readyMs: Date.now() - start } + console.log(JSON.stringify(sample)) + await info.attach("large-uptime-resources-metrics", { body: JSON.stringify(sample), contentType: "application/json" }) + expect(sample.domNodes).toBeLessThan(4000) + await page.getByRole("button", { name: "Следующая страница", exact: true }).click() + await expect(rows.first()).toHaveAttribute("data-row-id", "51") + const before = calls + await page.getByRole("button", { name: "Обновить данные", exact: true }).click() + await expect.poll(() => calls).toBeGreaterThan(before) + await expect(page.getByRole("button", { name: "Обновить данные", exact: true })).toBeEnabled() + await expect(page.getByText("51–100 из 1000", { exact: true })).toBeVisible() + const search = page.getByPlaceholder("Поиск по имени, площадке…") + await search.fill("Ресурс 1000") + await expect(rows).toHaveCount(1) + await expect(rows.first()).toHaveAttribute("data-row-id", "1000") + await expect(page).toHaveURL(url => url.searchParams.get("rq") === "Ресурс 1000" && url.searchParams.get("probe") === "p1" && url.searchParams.get("keep") === "1") + await page.reload() + await expect(search).toHaveValue("Ресурс 1000") + await expect(rows).toHaveCount(1) + await search.fill("") + await expect(rows).toHaveCount(50) + await page.getByRole("button", { name: "Сервер", exact: true }).click() + await page.getByRole("button", { name: "Сервер", exact: true }).click() + await expect(rows.first()).toHaveAttribute("data-row-id", "1000") + for (let i = 0; i < 3; i++) await page.getByRole("button", { name: "…", exact: true }).last().click() + await page.getByRole("button", { name: "Перейти на страницу 20", exact: true }).click() + await expect(page.getByText("951–1000 из 1000", { exact: true })).toBeVisible() + await page.setViewportSize({ width: 320, height: 900 }) + expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true) + resources = resources.slice(0, 1) + await page.getByRole("button", { name: "Обновить данные", exact: true }).click() + await expect(rows).toHaveCount(1) + await expect(rows.first()).toHaveAttribute("data-row-id", "1") + expect(writes).toEqual([]) +})