chore(ux): измерить матрицу WAN и расчёт назначения проб

This commit is contained in:
Denozordec committed 2026-10-04 21:52:05 +07:00
1 parent 28e2d38ee6
commit b3da4216e6
6 files changed
+65 -2

No files matched your search

+7
View File
@@ -117,3 +117,10 @@ Statistics pivot в backend/src/services/statistics-aggregate.ts огранич
buildLiveOptimizerData/assignProbesForWanJh делает DFS, когда WAN<=7 и WAN<=pool.length. Лимита pool нет. scoreProbeForWanIface допускает srcInterface="" для любого WAN; при7WAN/20 подходящих разных probes полное дерево содержит20P7=390700800 полных назначений (вывод из кода, не измеренный benchmark). Не запускать этот случай через Next/UI: сначала отдельный чистый CPU-профиль в дочернем процессе с жёстким timeout, без DB/RouterOS. Стратегию назначения/оптимальность нельзя менять без доказательства эквивалентности на малых наборах; простое обрезание pool может скрыть лучшее назначение.
commRecs строятся через filter(sourceServerId).slice(0,24), а HomeCard подписывает BGP community только длиной среза. Нужны полный source count/явное сообщение24 изN; нельзя выдавать24 за весь каталог или менять правила применения. Локальная вкладка карточки сбрасывается при pagination, native OSPF select1000 options остаётся. Общая main/context после delta и реальная внешняя12.5 ещё не закрыты. Этот блок — карта приёмки, не доказательство нагрузочной готовности. Product build a16bfae уже passed; для документации повтор не нужен. Следующий12.3.59 — указанные bounded profiles/CPU timeout. Локальный коммит без push.
## 12.3.59 — отдельные профили матрицы и CPU
2026-10-04. Live API подменён строгими DTO, без Exit и API mutations. Два браузерных профиля прошли: WAN1000×JH1 — 1000 строк,4 столбца,28019 DOM таблицы,28712 DOM страницы,4977мс dev-ready,108917байт; WAN10×JH100 — 10 строк,103 столбца,9803 DOM таблицы,10595 DOM страницы,2258мс dev-ready,16593байт. Время dev измеряет также загрузку/компиляцию и не является production benchmark.
Чистый расчёт7WAN×20 wildcard probes воспроизводится scripts/performance/optimizer-probes-profile.mjs, в отдельном child process с жёстким timeout10000мс. Расчёт не завершился за timeout; тяжёлый DFS нельзя воспроизводить в UI. Нет DB/API/сетевых операций. Следующий12.3.60 — страницы50WAN и ограниченное окно JH, поиск по полному каталогу, проверка обновления/сжатия и320px. Семантику назначения probes этим блоком не меняем.
+1 -1
View File
@@ -1,6 +1,6 @@
# План реализации UX/UI по пятичасовым окнам
Актуальное продолжение 2026-10-04: 12.3.58 сохранён — статический аудит WAN matrix/CPU назначения probes/среза24 commRecs. Следующий12.3.59: bounded profile1000 WAN×1JH и10WAN×100JH без Exit, отдельно ограниченный по времени CPU-профиль7WAN×20 wildcard probes. Не запускать тяжёлый DFS через UI. Последний UI-коммит a16bfae; общая регрессия после delta/внешняя12.5 остаются.
Актуальное продолжение 2026-10-04: 12.3.59 завершён: отдельные профили WAN1000×JH1, WAN10×JH100 и CPU7WAN×20 probes. Следующий12.3.60: ограничить отображение WAN/JH без среза исходных данных; тяжёлый DFS пока остаётся отдельным риском. Общая регрессия после delta и внешняя12.5 остаются.
Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-04. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат.
+1 -1
View File
@@ -1,6 +1,6 @@
# UX/UI: журнал реализации
Актуальное продолжение 2026-10-04: 12.3.58 сохранён — статический аудит WAN matrix/CPU назначения probes/среза24 commRecs. Следующий12.3.59: bounded profile1000 WAN×1JH и10WAN×100JH без Exit, отдельно ограниченный по времени CPU-профиль7WAN×20 wildcard probes. Не запускать тяжёлый DFS через UI. Последний UI-коммит a16bfae; общая регрессия после delta/внешняя12.5 остаются.
Актуальное продолжение 2026-10-04: 12.3.59 завершён: отдельные профили WAN1000×JH1, WAN10×JH100 и CPU7WAN×20 probes. Следующий12.3.60: ограничить отображение WAN/JH без среза исходных данных; тяжёлый DFS пока остаётся отдельным риском. Общая регрессия после delta и внешняя12.5 остаются.
Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения.
+7
View File
@@ -669,3 +669,10 @@ ReUI client pagination50 только раскрытого списка, autoRes
buildLiveOptimizerData/assignProbesForWanJh делает DFS, когда WAN<=7 и WAN<=pool.length. Лимита pool нет. scoreProbeForWanIface допускает srcInterface="" для любого WAN; при7WAN/20 подходящих разных probes полное дерево содержит20P7=390700800 полных назначений (вывод из кода, не измеренный benchmark). Не запускать этот случай через Next/UI: сначала отдельный чистый CPU-профиль в дочернем процессе с жёстким timeout, без DB/RouterOS. Стратегию назначения/оптимальность нельзя менять без доказательства эквивалентности на малых наборах; простое обрезание pool может скрыть лучшее назначение.
commRecs строятся через filter(sourceServerId).slice(0,24), а HomeCard подписывает BGP community только длиной среза. Нужны полный source count/явное сообщение24 изN; нельзя выдавать24 за весь каталог или менять правила применения. Локальная вкладка карточки сбрасывается при pagination, native OSPF select1000 options остаётся. Общая main/context после delta и реальная внешняя12.5 ещё не закрыты. Этот блок — карта приёмки, не доказательство нагрузочной готовности. Product build a16bfae уже passed; для документации повтор не нужен. Следующий12.3.59 — указанные bounded profiles/CPU timeout. Локальный коммит без push.
## 12.3.59 — отдельные профили матрицы и CPU
2026-10-04. Live API подменён строгими DTO, без Exit и API mutations. Два браузерных профиля прошли: WAN1000×JH1 — 1000 строк,4 столбца,28019 DOM таблицы,28712 DOM страницы,4977мс dev-ready,108917байт; WAN10×JH100 — 10 строк,103 столбца,9803 DOM таблицы,10595 DOM страницы,2258мс dev-ready,16593байт. Время dev измеряет также загрузку/компиляцию и не является production benchmark.
Чистый расчёт7WAN×20 wildcard probes воспроизводится scripts/performance/optimizer-probes-profile.mjs, в отдельном child process с жёстким timeout10000мс. Расчёт не завершился за timeout; тяжёлый DFS нельзя воспроизводить в UI. Нет DB/API/сетевых операций. Следующий12.3.60 — страницы50WAN и ограниченное окно JH, поиск по полному каталогу, проверка обновления/сжатия и320px. Семантику назначения probes этим блоком не меняем.
@@ -0,0 +1,19 @@
// Pure calculation in a child process: no API, database, browser or network operations.
import { spawnSync } from 'node:child_process'
const code = `
const fs=require('node:fs'),ts=require('typescript');
const source=ts.transpileModule(fs.readFileSync('lib/route-optimizer-data.ts','utf8'),{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022}}).outputText;
const mod={exports:{}};new Function('exports','require','module',source)(mod.exports,require,mod);
const {buildLiveOptimizerData,DEFAULT_ROUTE_AI_OPTIMIZER_SETTINGS}=mod.exports;
const common={host:'192.0.2.1',enabled:true,status:'online',latency:10,country:'RU',site:'TEST',model:'CHR'};
const rows=[{...common,id:1,name:'Home',type:'home-router',wanUplinks:Array.from({length:7},(_,i)=>({id:'wan'+i,name:'WAN'+i,isp:'ISP',iface:'ether'+i,ip:'198.51.100.1',maxDl:100,maxUl:100}))},{...common,id:2,name:'Jump',type:'jump-host'}];
const probes=Array.from({length:20},(_,i)=>({id:'p'+i,srcServerId:'1',dstServerId:'2',srcInterface:'',dstInterface:'',enabled:true,lastPingRttMs:10,lastPingLossPct:0,lastTxAvgMbps:100,lastRxAvgMbps:100,lastPingAt:null}));
const start=performance.now();const result=buildLiveOptimizerData(rows,[],DEFAULT_ROUTE_AI_OPTIMIZER_SETTINGS,probes);
console.log(JSON.stringify({elapsedMs:performance.now()-start,legs:result.homes[0].wanJhLegs.length}));
`;
const start=performance.now();
const result=spawnSync(process.execPath,['-e',code],{cwd:process.cwd(),timeout:10000,encoding:'utf8',windowsHide:true});
const profile={wanCount:7,probeCount:20,timeoutMs:10000,elapsedMs:Math.round(performance.now()-start),status:result.status,timedOut:result.error?.code==='ETIMEDOUT',result:result.stdout.trim(),error:result.stderr.trim()};
console.log(JSON.stringify(profile,null,2));
if(result.error && !profile.timedOut) throw result.error;
if(result.status !== 0 && !profile.timedOut) process.exitCode=1;
@@ -0,0 +1,30 @@
import { test, expect } from "@playwright/test"
import { writeFileSync } from "node:fs"
import { optimizerServersSchema } from "../../lib/route-optimizer-response"
for (const [wanCount,jhCount] of [[1000,1],[10,100]]) {
test(`Матрица WAN${wanCount} × JH${jhCount} без Exit`, async ({page},info) => {
test.setTimeout(90000)
const common = { host: "192.0.2.1", enabled: true, status: "online", latency: 10, country: "RU", site: "TEST", model: "CHR" }
const servers = optimizerServersSchema.parse([
{ ...common, id: 1, name: "Домашний роутер", type: "home-router", wanUplinks: Array.from({ length: wanCount }, (_, i) => ({ id: `wan${i+1}`, name: `WAN${i+1}`, isp: "ISP", iface: `ether${i+1}`, ip: `198.51.100.${i+1}`, maxDl: 100, maxUl: 100 })) },
...Array.from({ length: jhCount }, (_, i) => ({ ...common, id: i+2, name: `Jump ${i+1}`, type: "jump-host" })),
])
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 (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: "Домашний роутер", pingWeight: 60, interfacesTotal: 0, changedCount: 0, unchangedCount: 0, interfaces: [], changes: [] } : {} })
})
const started=Date.now(); await page.goto("/route-optimizer?server=1&keep=1")
const table=page.getByRole("table").filter({hasText:"WAN-аплинк"})
await expect(table.locator("tr[data-row-id]")).toHaveCount(wanCount)
const profile={wanCount,jhCount,bytes:Buffer.byteLength(JSON.stringify(servers)),rows:await table.locator("tr[data-row-id]").count(),columns:await table.getByRole("columnheader").count(),tableNodes:await table.locator("*").count(),pageNodes:await page.locator("*").count(),readyMs:Date.now()-started}
console.log("WAN matrix",JSON.stringify(profile)); writeFileSync(info.outputPath("matrix-profile.json"),JSON.stringify(profile,null,2))
expect(writes).toEqual([])
})
}