From 53e36f2a036881962cf6ebce98c01cb8063b7c9d Mon Sep 17 00:00:00 2001 From: Denozordec Date: Wed, 7 Oct 2026 12:42:20 +0700 Subject: [PATCH] =?UTF-8?q?fix(ui):=20=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D1=82?= =?UTF-8?q?=D1=8C=20=D1=81=D0=B2=D0=BE=D0=B4=D0=BA=D0=B8=20=D0=BF=D0=BB?= =?UTF-8?q?=D0=BE=D1=89=D0=B0=D0=B4=D0=BE=D0=BA=20=D0=BA=D1=80=D0=B0=D1=82?= =?UTF-8?q?=D0=BA=D0=B8=D0=BC=D0=B8=20=D0=B8=20=D0=B4=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=BE=D0=B2=D0=B5=D1=80=D0=BD=D1=8B=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(main)/route-optimizer/page.tsx | 5 ++- components/optimizer-site-summary.tsx | 25 ++++++++++++ docs/ux-ui-performance.md | 9 +++++ docs/ux-ui-plan.md | 2 +- docs/ux-ui-progress.md | 2 +- docs/ux-ui-stage-12.md | 9 +++++ .../screens/optimizer-sites-exit-node-320.png | Bin 0 -> 21903 bytes .../screens/optimizer-sites-jump-host-320.png | Bin 0 -> 21942 bytes tests/e2e/optimizer-site-summary.spec.ts | 38 ++++++++++++++++++ 9 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 components/optimizer-site-summary.tsx create mode 100644 docs/ux-ui/screens/optimizer-sites-exit-node-320.png create mode 100644 docs/ux-ui/screens/optimizer-sites-jump-host-320.png create mode 100644 tests/e2e/optimizer-site-summary.spec.ts diff --git a/app/(main)/route-optimizer/page.tsx b/app/(main)/route-optimizer/page.tsx index e0539509..8bcfd599 100644 --- a/app/(main)/route-optimizer/page.tsx +++ b/app/(main)/route-optimizer/page.tsx @@ -7,6 +7,7 @@ import {can} from "@/lib/auth" import {ospfCatalogSchema,ospfOptimizeSchema} from "@/lib/ospf-response" import {optimizerServersSchema,optimizerRulesSchema,optimizerProbesSchema,optimizerOspfPreviewSchema,type OptimizerOspfPreview} from "@/lib/route-optimizer-response" import { OptimizerHomesList } from "@/components/optimizer-homes-list" +import { OptimizerSiteSummary } from "@/components/optimizer-site-summary" import { PageHeader } from "@/components/page-header" import { RouteOptimizerWanMatrixDataGrid } from "@/components/data-grids/route-optimizer-wan-matrix-data-grid" import { RouteOptimizerFullRoutesDataGrid } from "@/components/data-grids/route-optimizer-full-routes-data-grid" @@ -588,8 +589,8 @@ function RouteOptimizerContent({requestedServer,onSelectServer}:{requestedServer : HOME_ROUTERS.reduce((s, h) => s + h.wans.length, 0) const jh = useLiveData ? liveJumpHosts : JUMPHOSTS const ex = useLiveData ? liveExitNodes : EXIT_NODES - const jhSub = jh.length ? jh.map((j) => j.site).join(" · ") : "—" - const exSub = ex.length ? ex.map((e) => e.site).join(" · ") : "—" + const jhSub = j.site)} /> + const exSub = e.site)} /> return [ { label: "Home", diff --git a/components/optimizer-site-summary.tsx b/components/optimizer-site-summary.tsx new file mode 100644 index 00000000..89cb6626 --- /dev/null +++ b/components/optimizer-site-summary.tsx @@ -0,0 +1,25 @@ +import { Badge } from "@/components/reui/badge" + +type OptimizerSiteSummaryProps = { + kind: "JumpHost" | "Exit Node" + sites?: readonly string[] +} + +export function OptimizerSiteSummary({kind,sites}:OptimizerSiteSummaryProps){ + const labels:string[]=[] + if(sites===undefined)labels.push("Площадки неизвестны") + else if(!sites.length)labels.push("Нет узлов в расчёте") + else { + const known=sites.map(site=>site.trim()).filter(site=>site && site!=="—") + const distinct=[...new Set(known)] + const missing=sites.length-known.length + if(distinct.length){ + const names=distinct.length<=3 ? distinct.join(" · ") : "" + labels.push(distinct.length<=3 && names.length<=24 ? names : `Площадки: ${distinct.length}`) + } + if(missing)labels.push(`Без площадки: ${missing}`) + } + return
+ {labels.map(label=>{label})} +
+} diff --git a/docs/ux-ui-performance.md b/docs/ux-ui-performance.md index 8cb3282a..77643b1f 100644 --- a/docs/ux-ui-performance.md +++ b/docs/ux-ui-performance.md @@ -181,3 +181,12 @@ Final1000WAN:134chars/86.5px высоты summary; WAN10:130chars/86.5px. В т Pure child CPU1000: equal1217.5→15.6ms,varied7967.5→14.5ms,freshness10с timeout→16.7ms; CPU20:11.5–11.7ms. Child startup отдельно~0.25s. Exact IDs проверены во всех6 профилях. Evidence docs/ux-ui/acceptance/optimizer-probes-cpu-2026-10-07.json содержит source SHA256, baseline HEAD и результаты. Браузерная live ветка7WAN/1000probes:219902bytes/7rows/222tableDOM/1018ms dev-ready; timestamps соответствуют p993..999, после refresh сохраняются,320px без overflow, API mutations отсутствуют. Это не production SLA/нагрузка PostgreSQL/RouterOS и не гарантия произвольного mixed pool: худшая экспоненциальная сложность DFS и full-route product остаются. Следующий12.3.67: KPI captions JH/Exit (повторы/пустая площадка/unknown), затем separate full-route CPU profile. Общая main/context регрессия12.4.6/7 относится к прежним HEAD; этот delta принят профильными51+8. Внешняя12.5 не выполнена. + + +## 12.3.67 — краткие и достоверные KPI-подписи площадок + +2026-10-07. В существующем ReUI Stats12/KpiStatGrid footer переиспользован ReUI Badge outline/sm. Короткие уникальные названия (≤3 и≤24символов) показываются без повторов; большой/длинный список заменяется точным количеством площадок. Отдельно виден count узлов без площадки: пустые/whitespace/нормализованный — не считаются реальной площадкой. Нет полного join большого каталога. Неизвестный каталог сообщает «Площадки неизвестны»; нулевой topology pool — «Нет узлов в расчёте», потому что live topology исключает offline/disabled, а устройства могут существовать в inventory. Права/выбор узлов/расчёт/источники не менялись. + +16 main browser +2 повторных новых профиля после copy-уточнения прошли:1000JH и1000Exit отдельно (без опасного cross product), repetitions/two sites/1000sites/long80chars/unknown/empty/offline/disabled/503 cache/recovery.320px без overflow, caption≤80px, scoped WCAG axe passed. Non-incremental TypeScript до dev, full page/helper/test lint и окончательный production build прошли. ReUI API/validate_usage проверены; primitive/theme не изменены. Historical9 optimizer PNG восстановлены, новые docs/ux-ui/screens/optimizer-sites-{jump-host,exit-node}-320.png просмотрены. + +Далее12.3.68 pure child CPU profile full-route product≤100000/timeout10s/memory cap; не грузить тяжёлый baseline в UI. Остаточные DFS worst-case/full-route memory/native selector и внешняя12.5 не объявляются принятыми. Общая main/context регрессия относится к ранее записанным HEAD; текущий footer delta проверен профильными16+2. diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index cc1c3120..02a8b997 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -1,6 +1,6 @@ # План реализации UX/UI по пятичасовым окнам -Актуальное продолжение 2026-10-07: 12.3.66 завершён: DFS score cache/one-time snapshot/lower-bound/child-bound, exact1230 fixtures,10pure+51main+8context/build passed. CPU1000 equal15.6ms,varied14.5ms,freshness16.7ms вместо10с timeout. Следующий12.3.67: краткие KPI captions JH/Exit с честными площадками/unknown. Затем отдельный CPU-профиль full-route product; внешняя12.5 остаётся. +Актуальное продолжение 2026-10-07: 12.3.67 завершён: компактные ReUI KPI captions, distinct площадки/unknown/пустой и исключённый pool,16+2browser/type/lint/build. Следующий12.3.68: child CPU/full-route product≤100000, hard timeout/memory cap, сохранить baseline перед map lookup оптимизацией. Внешняя12.5 отдельно. Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-04. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат. diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index 0f3d0c3f..1dcb0700 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -1,6 +1,6 @@ # UX/UI: журнал реализации -Актуальное продолжение 2026-10-07: 12.3.66 завершён: DFS score cache/one-time snapshot/lower-bound/child-bound, exact1230 fixtures,10pure+51main+8context/build passed. CPU1000 equal15.6ms,varied14.5ms,freshness16.7ms вместо10с timeout. Следующий12.3.67: краткие KPI captions JH/Exit с честными площадками/unknown. Затем отдельный CPU-профиль full-route product; внешняя12.5 остаётся. +Актуальное продолжение 2026-10-07: 12.3.67 завершён: компактные ReUI KPI captions, distinct площадки/unknown/пустой и исключённый pool,16+2browser/type/lint/build. Следующий12.3.68: child CPU/full-route product≤100000, hard timeout/memory cap, сохранить baseline перед map lookup оптимизацией. Внешняя12.5 отдельно. Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index da8bc2dd..808a714b 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -758,3 +758,12 @@ Final1000WAN:134chars/86.5px высоты summary; WAN10:130chars/86.5px. В т Pure child CPU1000: equal1217.5→15.6ms,varied7967.5→14.5ms,freshness10с timeout→16.7ms; CPU20:11.5–11.7ms. Child startup отдельно~0.25s. Exact IDs проверены во всех6 профилях. Evidence docs/ux-ui/acceptance/optimizer-probes-cpu-2026-10-07.json содержит source SHA256, baseline HEAD и результаты. Браузерная live ветка7WAN/1000probes:219902bytes/7rows/222tableDOM/1018ms dev-ready; timestamps соответствуют p993..999, после refresh сохраняются,320px без overflow, API mutations отсутствуют. Это не production SLA/нагрузка PostgreSQL/RouterOS и не гарантия произвольного mixed pool: худшая экспоненциальная сложность DFS и full-route product остаются. Следующий12.3.67: KPI captions JH/Exit (повторы/пустая площадка/unknown), затем separate full-route CPU profile. Общая main/context регрессия12.4.6/7 относится к прежним HEAD; этот delta принят профильными51+8. Внешняя12.5 не выполнена. + + +## 12.3.67 — краткие и достоверные KPI-подписи площадок + +2026-10-07. В существующем ReUI Stats12/KpiStatGrid footer переиспользован ReUI Badge outline/sm. Короткие уникальные названия (≤3 и≤24символов) показываются без повторов; большой/длинный список заменяется точным количеством площадок. Отдельно виден count узлов без площадки: пустые/whitespace/нормализованный — не считаются реальной площадкой. Нет полного join большого каталога. Неизвестный каталог сообщает «Площадки неизвестны»; нулевой topology pool — «Нет узлов в расчёте», потому что live topology исключает offline/disabled, а устройства могут существовать в inventory. Права/выбор узлов/расчёт/источники не менялись. + +16 main browser +2 повторных новых профиля после copy-уточнения прошли:1000JH и1000Exit отдельно (без опасного cross product), repetitions/two sites/1000sites/long80chars/unknown/empty/offline/disabled/503 cache/recovery.320px без overflow, caption≤80px, scoped WCAG axe passed. Non-incremental TypeScript до dev, full page/helper/test lint и окончательный production build прошли. ReUI API/validate_usage проверены; primitive/theme не изменены. Historical9 optimizer PNG восстановлены, новые docs/ux-ui/screens/optimizer-sites-{jump-host,exit-node}-320.png просмотрены. + +Далее12.3.68 pure child CPU profile full-route product≤100000/timeout10s/memory cap; не грузить тяжёлый baseline в UI. Остаточные DFS worst-case/full-route memory/native selector и внешняя12.5 не объявляются принятыми. Общая main/context регрессия относится к ранее записанным HEAD; текущий footer delta проверен профильными16+2. diff --git a/docs/ux-ui/screens/optimizer-sites-exit-node-320.png b/docs/ux-ui/screens/optimizer-sites-exit-node-320.png new file mode 100644 index 0000000000000000000000000000000000000000..1dd996d5b6e3e51b1cc24781fd6e9a5d9da2c113 GIT binary patch literal 21903 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 literal 0 HcmV?d00001 diff --git a/docs/ux-ui/screens/optimizer-sites-jump-host-320.png b/docs/ux-ui/screens/optimizer-sites-jump-host-320.png new file mode 100644 index 0000000000000000000000000000000000000000..8ce9d9485a6c25e0005b29dad190eb27374fd0ac GIT binary patch literal 21942 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 +tBYxm7xn7VWX^ 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 SK|S!~hB`<`xl++G^uGWkFHY3} literal 0 HcmV?d00001 diff --git a/tests/e2e/optimizer-site-summary.spec.ts b/tests/e2e/optimizer-site-summary.spec.ts new file mode 100644 index 00000000..b70f4e1f --- /dev/null +++ b/tests/e2e/optimizer-site-summary.spec.ts @@ -0,0 +1,38 @@ +import {test,expect} from "@playwright/test" +import axe from "axe-core" +import {optimizerServersSchema} from "../../lib/route-optimizer-response" + +for(const type of ["jump-host","exit-node"] as const)test(`Сводка оптимизатора1000: площадки ${type}`,async({page},info)=>{ + test.setTimeout(90000) + const kind=type==="jump-host"?"JumpHost":"Exit Node",other=type==="jump-host"?"Exit Node":"JumpHost" + const common={host:"192.0.2.1",enabled:true,status:"online",latency:10,country:"RU",model:"CHR"} + let servers=optimizerServersSchema.parse([{...common,id:1,name:"Home",site:"MSK",type:"home-router",wanUplinks:[]},...Array.from({length:1000},(_,i)=>({...common,id:i+2,name:`Node ${i+1}`,site:i<600?"MSK":i<999?"NSK":" ",type}))]) + let failed=true,reads=0;const writes:string[]=[] + await page.route("**/health",r=>r.fulfill({json:{ok:true}})) + await page.route("**/api/**",r=>{ + const path=new URL(r.request().url()).pathname + if(path==="/api/servers"){reads++;if(failed)return r.fulfill({status:503,json:{error:"Площадки не загружены"}})} + if(r.request().method()!=="GET"&&!path.endsWith("/preview"))writes.push(path) + return r.fulfill({json:path==="/api/auth/config"?{required:false}:path==="/api/servers"?servers:path==="/api/filters/rules"?{rulesets:[]}:path==="/api/uptime/speed-probes"?{probes:[]}:path==="/api/ospf/catalog"?{interfaces:[],instances:[],neighbors:[],bfdSessions:[],routes:[],failures:[]}:path.endsWith("/ospf/optimize/preview")?{serverId:1,serverName:"Home",pingWeight:60,interfacesTotal:0,changedCount:0,unchangedCount:0,interfaces:[],changes:[]}: {}}) + }) + await page.goto("/route-optimizer?server=1") + const root=page.locator('[aria-label="Сводка оптимизатора"]') + const summary=()=>root.getByRole("group",{name:`Площадки ${kind}`,exact:true}) + await expect(summary()).toHaveText("Площадки неизвестны") + failed=false;await page.getByRole("button",{name:"Обновить",exact:true}).click() + await expect(summary()).toContainText("MSK · NSK");await expect(summary()).toContainText("Без площадки: 1") + await expect(root.getByText("1000",{exact:true})).toBeVisible();await expect(root.getByRole("group",{name:`Площадки ${other}`,exact:true})).toHaveText("Нет узлов в расчёте") + const refresh=async()=>{const before=reads;await page.getByRole("button",{name:"Обновить",exact:true}).click();await expect.poll(()=>reads).toBeGreaterThan(before)} + servers=servers.map(row=>row.id===1?row:{...row,site:`SITE-${row.id}`});await refresh();await expect(summary()).toHaveText("Площадки: 1000") + servers=servers.map(row=>row.id===1?row:{...row,site:"W".repeat(80)});await refresh();await expect(summary()).toHaveText("Площадки: 1") + servers=servers.map(row=>row.id===1?row:{...row,site:""});await refresh();await expect(summary()).toHaveText("Без площадки: 1000") + failed=true;await refresh();await expect(page.getByRole("alert").filter({hasText:"Последний полный снимок"})).toBeVisible();await expect(summary()).toHaveText("Без площадки: 1000") + await page.setViewportSize({width:320,height:1000});expect(await page.evaluate(()=>document.documentElement.scrollWidth)).toBeLessThanOrEqual(320) + const box=await summary().boundingBox();expect(box!.x).toBeGreaterThanOrEqual(0);expect(box!.x+box!.width).toBeLessThanOrEqual(320);expect(box!.height).toBeLessThan(80) + await page.addStyleTag({content:"nextjs-portal{display:none!important}"});await root.screenshot({path:info.outputPath(`sites-${type}-320.png`),animations:"disabled"}) + await page.addScriptTag({content:axe.source});expect((await page.evaluate(async()=>(window as unknown as {axe:typeof axe}).axe.run(document.querySelector('[aria-label="Сводка оптимизатора"]')!,{runOnly:{type:"tag",values:["wcag2a","wcag2aa","wcag21a","wcag21aa"]}}))).violations.map(v=>({id:v.id,nodes:v.nodes.map(n=>n.target)}))).toEqual([]) + failed=false;servers=servers.map(row=>row.id===1?row:{...row,status:"offline" as const});await refresh();await expect(summary()).toHaveText("Нет узлов в расчёте") + servers=servers.map(row=>row.id===1?row:{...row,status:"online" as const,enabled:false});await refresh();await expect(summary()).toHaveText("Нет узлов в расчёте") + servers=servers.slice(0,1);await refresh();await expect(summary()).toHaveText("Нет узлов в расчёте") + expect(writes).toEqual([]) +})