diff --git a/components/data-grids/servers-data-grid.tsx b/components/data-grids/servers-data-grid.tsx index 79832a3b..edc80d42 100644 --- a/components/data-grids/servers-data-grid.tsx +++ b/components/data-grids/servers-data-grid.tsx @@ -378,7 +378,8 @@ function ServersDataGrid({ features: dataGridFeatures, data: servers, columns, - manualPagination: true, + manualPagination: false, + initialState: { pagination: { pageIndex: 0, pageSize: 50 } }, getRowId: (row) => row.id, getRowCanExpand: () => true, }) @@ -398,6 +399,7 @@ function ServersDataGrid({ 50 || table.state.pagination.pageSize !== 50} onRowClick={(row) => table.getRow(row.id).toggleExpanded()} /> ) diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index 89b72693..e64a706f 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -4,7 +4,7 @@ ## Актуальная точка продолжения — 2026-10-03 -Этап 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: производительность на фиксированных больших fixtures; затем12.4 сводная регрессия и production build. См. 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/статистика и ограничения специализированных таблиц; затем12.4 сводная регрессия и production build. См. 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 b89d672c..d29fec08 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -36,6 +36,14 @@ +## 12.3.1 — ограничение DOM таблицы серверов (2026-10-04) + +На фиксированном каталоге1000 ServerRead (501462 JSON bytes) прежняя таблица выводила1000 строк/41569 DOM nodes. Штатная client pagination ReUI/TanStack теперь выводит50 строк (initial page size50),2644 DOM nodes; поиск/сортировка выполняются по всему каталогу. При выбранном меньшем page size навигация остаётся доступной даже если фильтр даёт менее50 результатов. Малые каталоги при default50 сохраняют прежний вид. + +Проверки:36 browser large-servers+servers (права, формы, операции, URL/filters), затем1 расширенный large-case (page size5/фильтр9 результатов/вторая страница/возврат50), typecheck/ESLint прошли. Дев-замеры одного локального Edge прогона: доready3345ms, после1058ms и1543ms; это наблюдение с разной прогретостью dev compiler, не production SLA/статистическая оценка. Полный JSON всё ещё загружается/валидируется на клиенте. ReUI MCP DataGridPagination API и checklist прочитаны; primitives/shared theme unchanged. + +Следующий блок12.3.2: большие списки CompactDataGrid/статистика и явная карта оставшихся специализированных таблиц. Этап12.3 целиком ещё не принят;12.4 требует сводной browser/context проверки и production build. + ## 12.2.9 — метрики отдельных участков Internet path (2026-10-04) Configured WAN maxDl/maxUl, общий RTT сервера и GRE эвристики больше не выдаются за наблюдения Home→JH/JH→Exit. Метрики берутся из профильных проб: ping требует дату и отсутствие ошибки, speed — дату/lastStatus:done/отсутствие ошибки. Legacy без metadata и неудачная проба дают неизвестное; ноль остаётся нулём; RX/TX не подменяют друг друга, при обратной пробе направления меняются. Primary и Current используют одинаковую границу подтверждения. diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index 247e748b..233dde21 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -102,3 +102,11 @@ Configured WAN maxDl/maxUl, общий RTT сервера и GRE эвристи Active WAN выбирается только по наблюдаемому default interface/route; первый configured uplink и просто bound DHCP не доказывают выбор. Summary unknown больше не маркирует Оффлайн. Подпись поясняет, что JH/Exit — кандидаты, а default route подтверждает WAN; карта различает рекомендацию оптимизатора и наблюдаемый default route. Алгоритм сетевых операций/оптимизатора не изменён. Проверки:61 dashboard/permissions browser, включая4 новых hop-состояния и320px,6 path unit (success/error/undated/zero/directions/unmatched WAN), typecheck и ESLint без ошибок/предупреждений в изменённых frontend файлах. Удалены прежние unused helpers/imports; ReUI Frame/Badge/StatusBadge сохранены. Далее12.3: фиксированные большие fixtures, измерения и ограничения; сводная регрессия12.4 должна включить12.2 и production build. Реальный traffic path/PG/RouterOS не проверен. + +## 12.3.1 — ограничение DOM таблицы серверов (2026-10-04) + +На фиксированном каталоге1000 ServerRead (501462 JSON bytes) прежняя таблица выводила1000 строк/41569 DOM nodes. Штатная client pagination ReUI/TanStack теперь выводит50 строк (initial page size50),2644 DOM nodes; поиск/сортировка выполняются по всему каталогу. При выбранном меньшем page size навигация остаётся доступной даже если фильтр даёт менее50 результатов. Малые каталоги при default50 сохраняют прежний вид. + +Проверки:36 browser large-servers+servers (права, формы, операции, URL/filters), затем1 расширенный large-case (page size5/фильтр9 результатов/вторая страница/возврат50), typecheck/ESLint прошли. Дев-замеры одного локального Edge прогона: доready3345ms, после1058ms и1543ms; это наблюдение с разной прогретостью dev compiler, не production SLA/статистическая оценка. Полный JSON всё ещё загружается/валидируется на клиенте. ReUI MCP DataGridPagination API и checklist прочитаны; primitives/shared theme unchanged. + +Следующий блок12.3.2: большие списки CompactDataGrid/статистика и явная карта оставшихся специализированных таблиц. Этап12.3 целиком ещё не принят;12.4 требует сводной browser/context проверки и production build. diff --git a/tests/e2e/large-servers.spec.ts b/tests/e2e/large-servers.spec.ts new file mode 100644 index 00000000..c098e6c5 --- /dev/null +++ b/tests/e2e/large-servers.spec.ts @@ -0,0 +1,9 @@ +import {test,expect} from "@playwright/test" +import {serverReadSchema} from "../../packages/contracts/src/servers" +const rows=Array.from({length:1000},(_,i)=>serverReadSchema.parse({id:i+1,name:"Load node "+String(i+1).padStart(4,"0"),host:"192.0."+Math.floor(i/250)+"."+(i%250+1),port:443,username:"fixture",password:"fixture-only",useSsl:true,verifySsl:true,type:"exit-node",site:"TEST",country:"RU",asn:"",comment:"",enabled:true,lanSubnet:"",wanUplinks:[],createdAt:"2026-10-04T00:00:00Z",updatedAt:"2026-10-04T00:00:00Z",status:"online",latency:0,os:"7.20",model:"CHR",uptime:null,cpuLoad:null,freeMemory:null,totalMemory:null,identityName:null,sessions:0,polledAt:null})) +test("large server pagination bounds DOM and searches the whole catalog",async({page},info)=>{ + test.setTimeout(90000) + await page.route("**/health",r=>r.fulfill({json:{ok:true}}));await page.route("**/api/**",r=>r.fulfill({json:new URL(r.request().url()).pathname==="/api/auth/config"?{required:false}:new URL(r.request().url()).pathname==="/api/servers"?rows:{}})) + const start=Date.now();await page.goto("/servers");await expect(page.getByText("Load node 0001",{exact:true})).toBeVisible({timeout:60000});const sample={fixtureRows:rows.length,fixtureBytes:Buffer.byteLength(JSON.stringify(rows)),readyMs:Date.now()-start,renderedRows:await page.locator("tr[data-row-id]").count(),domNodes:await page.locator("*").count()};console.log(JSON.stringify(sample));await info.attach("large-servers-metrics",{body:JSON.stringify(sample),contentType:"application/json"});expect(sample.renderedRows).toBe(50);expect(sample.domNodes).toBeLessThan(5000) + await expect(page.getByText("1–50 из 1000",{exact:true})).toBeVisible();await page.getByRole("button",{name:"Следующая страница",exact:true}).click();await expect(page.getByText("Load node 0051",{exact:true})).toBeVisible();await expect(page.locator("tr[data-row-id]")).toHaveCount(50);await page.getByPlaceholder("Поиск по имени, хосту…").fill("Load node 1000");await expect(page.locator("tr[data-row-id]")).toHaveCount(1);await expect(page.getByText("Load node 1000",{exact:true})).toBeVisible();await page.getByPlaceholder("Поиск по имени, хосту…").fill("");await expect(page.locator("tr[data-row-id]")).toHaveCount(50);await expect(page.getByText("1–50 из 1000",{exact:true})).toBeVisible();await page.getByRole("button",{name:"Имя / Хост",exact:true}).click();await page.getByRole("button",{name:"Имя / Хост",exact:true}).click();await expect(page.locator("tr[data-row-id]").first()).toContainText("Load node 1000");await page.getByRole("combobox",{name:"Строк на странице",exact:true}).click();await page.getByRole("option",{name:"5",exact:true}).click();await expect(page.locator("tr[data-row-id]")).toHaveCount(5);await page.getByPlaceholder("Поиск по имени, хосту…").fill("Load node 000");await expect(page.locator("tr[data-row-id]")).toHaveCount(5);await expect(page.getByRole("button",{name:"Следующая страница",exact:true})).toBeVisible();await page.getByRole("button",{name:"Следующая страница",exact:true}).click();await expect(page.locator("tr[data-row-id]")).toHaveCount(4);await page.getByPlaceholder("Поиск по имени, хосту…").fill("");await page.getByRole("combobox",{name:"Строк на странице",exact:true}).click();await page.getByRole("option",{name:"50",exact:true}).click();await page.getByRole("button",{name:"Раскрыть: Load node 1000",exact:true}).click();await expect(page.getByText("Модель: CHR",{exact:true})).toBeVisible();await page.setViewportSize({width:320,height:900});expect(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth)).toBe(true);await expect(page.getByRole("button",{name:"Следующая страница",exact:true})).toBeVisible() +})