From 6e6558bde945ccd3e46d91952de8e2e285eb33f2 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Thu, 1 Oct 2026 00:58:59 +0700 Subject: [PATCH] =?UTF-8?q?fix(ui):=20=D0=B8=D1=81=D0=BA=D0=BB=D1=8E=D1=87?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D0=BF=D0=BE=D0=B4=D1=81=D1=82=D0=B0=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D0=BA=D1=83=20=D0=BC=D0=BE=D0=BA=D0=BE=D0=B2=D1=8B?= =?UTF-8?q?=D1=85=20WAN=20=D0=B2=20live=20=D1=80=D0=B0=D1=81=D1=87=D1=91?= =?UTF-8?q?=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(main)/route-optimizer/page.tsx | 1 + .../route-optimizer-wan-matrix-data-grid.tsx | 2 +- docs/ux-ui-progress.md | 8 +++- docs/ux-ui-stage-10.md | 4 ++ lib/route-optimizer-data.ts | 41 +------------------ lib/route-optimizer-observed.test.ts | 11 +++++ tests/e2e/route-optimizer-observed.spec.ts | 9 ++++ 7 files changed, 35 insertions(+), 41 deletions(-) diff --git a/app/(main)/route-optimizer/page.tsx b/app/(main)/route-optimizer/page.tsx index d167f308..7869cbf9 100644 --- a/app/(main)/route-optimizer/page.tsx +++ b/app/(main)/route-optimizer/page.tsx @@ -346,6 +346,7 @@ function HomeRouterCard({ entry, jumpHosts, pinned, applied, applying, onPin, on ))} + {home.wans.length === 0 &&

Нет WAN-аплинков для расчёта. Настроить сервер

} {/* Tab content */} {tab === "wan-matrix" && ( diff --git a/components/data-grids/route-optimizer-wan-matrix-data-grid.tsx b/components/data-grids/route-optimizer-wan-matrix-data-grid.tsx index 1b15f9ad..de653568 100644 --- a/components/data-grids/route-optimizer-wan-matrix-data-grid.tsx +++ b/components/data-grids/route-optimizer-wan-matrix-data-grid.tsx @@ -78,7 +78,7 @@ function RouteOptimizerWanMatrixDataGrid({ id: "bandwidth", header: () => ( - Макс. полоса + Лимит модели ), cell: ({ row }) => { diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index 70831208..f26dd2b7 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -26,11 +26,17 @@ | 7 | Серверы и мониторинг: детали, формы, URL-контекст, права | Завершён в границах локальной приёмки 2026-09-23; внешние ограничения ниже | | 8 | Трафик и статистика | Завершён 2026-09-28 в границах локальной приёмки; ограничения в блоке 8.13 | | 9 | VPN, Firewall и Filters | Завершён в границах локальной приёмки 2026-09-30; блоки 9.1–9.64, ограничения в 9.64 | -| 10 | Маршрутизация, диагностика, карта и терминал | В работе: 10.1–10.41; Router ID проверяется без first-match, далее визуальная приёмка | +| 10 | Маршрутизация, диагностика, карта и терминал | В работе: 10.1–10.42; live WAN без моков/синтетики, далее подписи и локальная приёмка | | 11А | Пользователи, контейнеры, сертификаты и резервные копии | Не начат | | 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Не начат | | 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат | +## Блок 10.42 — WAN только из live каталога (2026-10-01) + +- Live topology не ищет WAN по совпадению name/host/id в lib/data и не создаёт WAN1/1000 автоматически. Нет uplinks — нет цепочек/лучшего маршрута; пояснение ведёт к /servers?server=. Для существующих WAN формула ранжирования прежняя; лимит модели явно назван расчётным. +- 6 unit (включая совпадение с mock inventory), 16 E2E measured/unknown/empty WAN, read failures/races и simulation, TS/lint прошли. +- Далее уточнить единицы порога/инертный гистерезис, scope Community по домашнему серверу, затем обновить screenshots и принять оптимизатор локально. + ## Блок 10.41 — однозначное устройство OSPF (2026-10-01) - Сопоставление Router ID больше не останавливается на первом совпадении. Проверяются все enabled устройства: нужен literal ID активного экземпляра, повтор на другом устройстве отклоняется; неизвестный disabled/ID либо отказ опроса сообщает ошибку до write. Повторы одного ID внутри одного устройства допустимы; known disabled исключены. Без соседских Router ID дополнительный сетевой опрос не нужен. diff --git a/docs/ux-ui-stage-10.md b/docs/ux-ui-stage-10.md index cbe0ed28..5c121673 100644 --- a/docs/ux-ui-stage-10.md +++ b/docs/ux-ui-stage-10.md @@ -216,3 +216,7 @@ Shared контракт и backend observed исходные значения/д ## 10.41. Однозначность Router ID Полный опрос enabled inventory без first-match; duplicate ownership/unknown/failed каталог отклоняет план до write. Known disabled/empty и одинаковый ID одного устройства допустимы. Optimizer suite/backend build прошли, lint два прежних warning. Реальный RouterOS каталог ещё внешняя приёмка; далее screenshots. + +## 10.42. Live WAN без подстановки + +Нет mock catalog/synthetic WAN в live topology; отсутствие uplinks объяснено с переходом к серверу. 6 unit, 16 E2E, TS/lint прошли. Далее подписи и Community scope перед визуальной приёмкой. diff --git a/lib/route-optimizer-data.ts b/lib/route-optimizer-data.ts index bb5728ee..d7272bde 100644 --- a/lib/route-optimizer-data.ts +++ b/lib/route-optimizer-data.ts @@ -3,7 +3,7 @@ * (см. блок `#route-ai` в `/settings` и страницу `/route-optimizer`). */ -import { servers, type FilterRule } from "@/lib/data" +import { type FilterRule } from "@/lib/data" // ── Совместимо с app/(main)/route-optimizer/page.tsx ───────────────────────── @@ -191,18 +191,6 @@ function confidenceFromProb(prob: number): "HIGH" | "MEDIUM" | "LOW" { return prob >= 55 ? "HIGH" : prob >= 30 ? "MEDIUM" : "LOW" } -function syntheticWans(home: OptimizerApiServer): WanUplink[] { - return [{ - id: `w-${home.id}-1`, - name: "WAN1", - isp: "—", - iface: "auto", - ip: home.host, - maxDl: 1000, - maxUl: 1000, - }] -} - function normalizeApiWanUplinks(list: OptimizerApiServer["wanUplinks"]): WanUplink[] { const src = Array.isArray(list) ? list : [] const out = src @@ -219,29 +207,6 @@ function normalizeApiWanUplinks(list: OptimizerApiServer["wanUplinks"]): WanUpli return out } -function catalogWansForHome(homeId: string, homeHost?: string, homeName?: string): WanUplink[] | null { - const hostNorm = (homeHost ?? "").trim().toLowerCase() - const nameNorm = (homeName ?? "").trim().toLowerCase() - const row = servers.find((s) => { - if (s.type !== "home-router") return false - if (String(s.id) === homeId) return true - if (hostNorm && String(s.host ?? "").trim().toLowerCase() === hostNorm) return true - if (nameNorm && String(s.name ?? "").trim().toLowerCase() === nameNorm) return true - return false - }) - const list = row?.wanUplinks ?? [] - if (!list.length) return null - return list.map((w, idx) => ({ - id: w.id || `w-${homeId}-${idx + 1}`, - name: w.name || `WAN${idx + 1}`, - isp: w.isp || "—", - iface: w.iface || "", - ip: w.ip || "", - maxDl: Math.max(1, Math.round(Number(w.maxDl) || 1000)), - maxUl: Math.max(1, Math.round(Number(w.maxUl) || 1000)), - })) -} - function legPing( a: number | null, b: number | null, @@ -285,9 +250,7 @@ export function mapApiServersToTopology(rows: OptimizerApiServer[]): { model: s.model ?? "—", ip: s.host, wans: - normalizeApiWanUplinks(s.wanUplinks).length > 0 - ? normalizeApiWanUplinks(s.wanUplinks) - : (catalogWansForHome(String(s.id), s.host, s.name) ?? syntheticWans(s)), + normalizeApiWanUplinks(s.wanUplinks), })) const jumpHosts: JumpHost[] = rows diff --git a/lib/route-optimizer-observed.test.ts b/lib/route-optimizer-observed.test.ts index c61fcc60..1f8e67fb 100644 --- a/lib/route-optimizer-observed.test.ts +++ b/lib/route-optimizer-observed.test.ts @@ -21,3 +21,14 @@ test("missing and disabled probe leave observed values unknown while model stays assert.equal(result.homes[0].fullRoutes[0].je.observed?.probeId,null) } }) + +import {servers} from "./data" +test("live topology never imports mock WAN or creates a synthetic uplink",()=>{ + const mock=servers.find(server=>server.type==="home-router")! + for(const wanUplinks of [undefined,[]]){ + const inventory=rows.map((row,i)=>i===0?{...row,name:mock.name,host:mock.host,wanUplinks}:row) + const result=buildLiveOptimizerData(inventory,[],DEFAULT_ROUTE_AI_OPTIMIZER_SETTINGS) + assert.equal(result.homes[0].home.wans.length,0);assert.equal(result.homes[0].fullRoutes.length,0) + assert.equal(result.homes[0].bestRoute,null) + } +}) diff --git a/tests/e2e/route-optimizer-observed.spec.ts b/tests/e2e/route-optimizer-observed.spec.ts index 12662ef2..7b3770da 100644 --- a/tests/e2e/route-optimizer-observed.spec.ts +++ b/tests/e2e/route-optimizer-observed.spec.ts @@ -27,3 +27,12 @@ test("Оптимизатор: OSPF исходный ноль и происхож await expect(grid).toContainText("Измерение: —") await expect(grid).toContainText("Трафик интерфейса: 0 Мбит/с") }) + +test("Оптимизатор: WAN не подставляется из демо",async({page})=>{ + await page.route("**/health",r=>r.fulfill({json:{ok:true}})) + await page.route("**/api/**",r=>{const p=new URL(r.request().url()).pathname;return r.fulfill({json:p==="/api/auth/config"?{required:false}:p==="/api/servers"?[{id:1,name:"Router",host:"192.0.2.1",type:"home-router",country:"RU",enabled:true,status:null}]:p==="/api/filters/rules"?{rulesets:[]}:p==="/api/uptime/speed-probes"?{probes:[]}:p==="/api/ospf/catalog"?{interfaces:[],instances:[],neighbors:[],bfdSessions:[],routes:[],failures:[]}:p.endsWith("/preview")?{serverId:1,serverName:"Router",pingWeight:60,interfacesTotal:0,changedCount:0,unchangedCount:0,interfaces:[],changes:[]}:{}})}) + await page.goto("/route-optimizer") + await expect(page.getByText("Нет WAN-аплинков для расчёта.",{exact:false})).toBeVisible() + await expect(page.getByRole("table").filter({hasText:"WAN-аплинк"}).locator("tr[data-row-id]")).toHaveCount(0) + await expect(page.getByRole("link",{name:"Настроить сервер",exact:true})).toHaveAttribute("href","/servers?server=1") +})