From 879596987173d01dcd646cc112ff106b8be7e154 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Fri, 9 Oct 2026 17:22:41 +0700 Subject: [PATCH] =?UTF-8?q?fix(routing):=20=D0=B8=D1=81=D0=BA=D0=BB=D1=8E?= =?UTF-8?q?=D1=87=D0=B8=D1=82=D1=8C=20=D0=BE=D0=BF=D1=80=D0=BE=D1=81=20?= =?UTF-8?q?=D0=B2=D1=8B=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D1=85=20=D1=83=D1=81=D1=82=D1=80=D0=BE=D0=B9=D1=81=D1=82=D0=B2?= =?UTF-8?q?=20BGP=20=D0=B8=20OSPF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/routes/bgp-catalog.test.ts | 12 + backend/src/routes/bgp.ts | 11 +- backend/src/routes/ospf-catalog.test.ts | 14 ++ backend/src/routes/ospf.ts | 8 +- docs/ux-ui-external-acceptance.md | 4 +- docs/ux-ui-plan.md | 2 +- docs/ux-ui-progress.md | 18 +- docs/ux-ui-stage-12.md | 14 ++ docs/ux-ui-test-server.md | 6 +- .../routing-bgp-disabled-2026-10-09.json | 223 ++++++++++++++++++ 10 files changed, 299 insertions(+), 13 deletions(-) create mode 100644 docs/ux-ui/acceptance/routing-bgp-disabled-2026-10-09.json diff --git a/backend/src/routes/bgp-catalog.test.ts b/backend/src/routes/bgp-catalog.test.ts index 7cdb0a36..b06065b6 100644 --- a/backend/src/routes/bgp-catalog.test.ts +++ b/backend/src/routes/bgp-catalog.test.ts @@ -50,3 +50,15 @@ assert.equal((await app.inject({ method: "GET", url: "/api/servers/0/bgp/session assert.equal(reads, before) await app.close() console.log("BGP catalog: partial/empty/failed/context/duplicates and HTTP compatibility/503 passed") +for (const type of ["home-router", "exit-node"]) { + let enabled = false, polled = 0 + const testApp = Fastify(); testApp.setValidatorCompiler(validatorCompiler); testApp.setSerializerCompiler(serializerCompiler) + await testApp.register(createBgpRoutes({listEnabledServers:async()=>[],findServer:async id=>id===1?({id:1,name:"test",host:"192.0.2.1",enabled,type} as typeof servers.$inferSelect):undefined,readSessions:async()=>{polled++;return []},readRawSessions:async()=>{polled++;return []}}),{prefix:"/api"}) + for (const url of ["/api/bgp/sessions/raw/1","/api/servers/1/bgp/sessions"]) {const r=await testApp.inject({method:"GET",url});assert.equal(r.statusCode,409);assert.match(r.json().error,/выключен/)} + assert.equal(polled,0) + for(const url of ["/api/bgp/sessions/raw/99","/api/servers/99/bgp/sessions"])assert.equal((await testApp.inject({method:"GET",url})).statusCode,404) + assert.equal((await testApp.inject({method:"GET",url:"/api/bgp/sessions/raw/1oops"})).statusCode,400);assert.equal(polled,0) + enabled=true;assert.equal((await testApp.inject({method:"GET",url:"/api/bgp/sessions/raw/1"})).statusCode,200);assert.equal(polled,1) + await testApp.close() +} +console.log("BGP selected/raw: disabled home-router/exit-node do not poll; unknown/invalid IDs and enabled read preserved") diff --git a/backend/src/routes/bgp.ts b/backend/src/routes/bgp.ts index a15eeaae..27b83615 100644 --- a/backend/src/routes/bgp.ts +++ b/backend/src/routes/bgp.ts @@ -15,6 +15,7 @@ interface BgpDependencies { findServer: (id: number) => Promise readCatalogSessions?: (server: ServerRow) => Promise readSessions: (server: ServerRow) => Promise + readRawSessions?: (server: ServerRow) => Promise } const defaults: BgpDependencies = { listEnabledServers: () => db.select().from(servers).where(eq(servers.enabled, true)), @@ -33,12 +34,13 @@ export function createBgpRoutes(dependencies: BgpDependencies = defaults): Fasti // GET /api/bgp/sessions/raw/:id — raw RouterOS response for a single server (debug) app.get("/bgp/sessions/raw/:id", { schema: { params: ServerIdParamSchema } }, async (req, reply) => { const params = req.params as ServerIdParams - const server = (await db.select().from(servers).where(eq(servers.id, params.id)).limit(1))[0] + const server = await dependencies.findServer(params.id) if (!server) return reply.status(404).send({ error: "Server not found" }) + if (server.enabled === false) return reply.status(409).send({ error: "Сервер выключен. Опрос BGP не выполнялся" }) try { - const client = MikrotikClient.fromServer(server) - const raw = await client.getBgpSessionsRaw() - app.log.info({ serverId: server.id, host: server.host, raw }, "BGP raw response") + const raw = await (dependencies.readRawSessions ?? (s => MikrotikClient.fromServer(s).getBgpSessionsRaw()))(server) + if (!Array.isArray(raw)) throw new Error("Некорректный список BGP sessions") + app.log.info({ serverId: server.id, sessionCount: raw.length }, "BGP raw response") return reply.send({ server: { id: server.id, host: server.host, name: server.name }, raw }) } catch (err) { return reply.status(500).send({ error: String(err) }) @@ -58,6 +60,7 @@ export function createBgpRoutes(dependencies: BgpDependencies = defaults): Fasti const server = await dependencies.findServer(params.id) if (!server) return reply.status(404).send({ error: "Server not found" }) + if (server.enabled === false) return reply.status(409).send({ error: "Сервер выключен. Опрос BGP не выполнялся" }) try { const catalog = await pollBgpCatalog([server], dependencies.readSessions) diff --git a/backend/src/routes/ospf-catalog.test.ts b/backend/src/routes/ospf-catalog.test.ts index 94a56ff7..96c5b938 100644 --- a/backend/src/routes/ospf-catalog.test.ts +++ b/backend/src/routes/ospf-catalog.test.ts @@ -71,3 +71,17 @@ assert.equal(parseBfdCatalogSessions(bfdServer, rawBfd("up"))[0].multihop, null) assert.equal(parseBfdCatalogSessions(bfdServer, [{ ...rawBfd("up")[0], multihop: "false" }])[0].multihop, false) assert.equal(parseCatalogRoutes(bfdServer, [rawRoute])[0].type, "Unknown") assert.equal(parseCatalogRoutes(bfdServer, [{ ...rawRoute, "ospf-type": "intra-area" }])[0].type, "O") +for (const type of ["home-router", "exit-node"]) { + const {MikrotikClient}=await import("../services/mikrotik.js");const original=MikrotikClient.fromServer;let reads=0 + MikrotikClient.fromServer=()=>{reads++;throw Error("must not poll")} + const testApp=Fastify();testApp.setValidatorCompiler(validatorCompiler);testApp.setSerializerCompiler(serializerCompiler) + try {await testApp.register(createOspfRoutes({listEnabledServers:async()=>[],readCatalog:async()=>{throw Error("must not poll")},findServer:async id=>id===1?({id:1,name:"test",enabled:false,type} as typeof servers.$inferSelect):undefined}),{prefix:"/api"});const r=await testApp.inject({method:"GET",url:"/api/servers/1/ospf"});assert.equal(r.statusCode,409);assert.match(r.json().error,/выключен/);assert.equal((await testApp.inject({method:"GET",url:"/api/servers/99/ospf"})).statusCode,404);assert.equal((await testApp.inject({method:"GET",url:"/api/servers/1oops/ospf"})).statusCode,400);assert.equal(reads,0)}finally{await testApp.close();MikrotikClient.fromServer=original} +} +console.log("OSPF selected: disabled home-router/exit-node do not poll; unknown/invalid IDs fail before RouterOS") +{ + const {MikrotikClient}=await import("../services/mikrotik.js"),original=MikrotikClient.fromServer + MikrotikClient.fromServer=()=>({getOspfNeighbors:async()=>[],getOspfAreas:async()=>[],getOspfInterfaceTemplates:async()=>[],getOspfInstances:async()=>[],getBfdSessions:async()=>{throw Error("BFD unavailable")},getIpRoutes:async()=>[]} as unknown as MikrotikClient) + const testApp=Fastify();testApp.setValidatorCompiler(validatorCompiler);testApp.setSerializerCompiler(serializerCompiler) + try{await testApp.register(createOspfRoutes({listEnabledServers:async()=>[],readCatalog:async()=>emptyOspfCatalog(),findServer:async()=>({id:1,name:"test",enabled:true} as typeof servers.$inferSelect)}),{prefix:"/api"});const r=await testApp.inject({method:"GET",url:"/api/servers/1/ospf"});assert.equal(r.statusCode,503);assert.match(r.json().error,/BFD unavailable/)}finally{await testApp.close();MikrotikClient.fromServer=original} +} +console.log("OSPF selected: failed BFD does not become successful empty data") diff --git a/backend/src/routes/ospf.ts b/backend/src/routes/ospf.ts index 8538eaab..39602654 100644 --- a/backend/src/routes/ospf.ts +++ b/backend/src/routes/ospf.ts @@ -553,6 +553,7 @@ async function buildOspfOptimizationPlan(server: ServerRow, pingWeight: number): interface OspfCatalogDependencies { listEnabledServers: () => Promise readCatalog: (server: ServerRow) => Promise + findServer?: (id: number) => Promise getOptimizationServer?: (id:number)=>Promise buildOptimizationPlan?: (server:ServerRow,weight:number)=>Promise setTemplateCost?: (server:ServerRow,id:string,cost:number)=>Promise @@ -676,15 +677,16 @@ export function createOspfRoutes(dependencies: OspfCatalogDependencies = catalog // GET /api/servers/:id/ospf — single server OSPF + BFD data app.get("/servers/:id/ospf", { schema: { params: ServerIdParamSchema } }, async (req, reply) => { const params = req.params as ServerIdParams - const server = (await db + const server = dependencies.findServer ? await dependencies.findServer(params.id) : (await db .select().from(servers) .where(eq(servers.id, params.id)) .limit(1))[0] if (!server) return reply.status(404).send({ error: "Server not found" }) + if (server.enabled === false) return reply.status(409).send({ error: "Сервер выключен. Опрос OSPF не выполнялся" }) try { - const { neighbors, areas, ifaceTemplates, instances, bfdSessions, ipRoutes } = await fetchServerOspf(server) + const { neighbors, areas, ifaceTemplates, instances, bfdSessions, ipRoutes } = await readOspfCatalogRaw(MikrotikClient.fromServer(server)) const areaMap = buildAreaMap(areas) return reply.send({ neighbors: parseNeighbors(server, neighbors, areaMap), @@ -695,7 +697,7 @@ export function createOspfRoutes(dependencies: OspfCatalogDependencies = catalog areas: areas.map(a => ({ name: a.name, areaId: a["area-id"] ?? "0.0.0.0", type: a.type, disabled: a.disabled === "true", inactive: a.inactive === "true", instance: a.instance })), }) } catch (err) { - return reply.status(500).send({ error: String(err) }) + return reply.status(503).send({ error: String(err) }) } }) diff --git a/docs/ux-ui-external-acceptance.md b/docs/ux-ui-external-acceptance.md index 84c81b75..0a1999a2 100644 --- a/docs/ux-ui-external-acceptance.md +++ b/docs/ux-ui-external-acceptance.md @@ -1,6 +1,6 @@ # UX/UI: протокол внешней приёмки 12.5 -Статус на2026-10-09: backend ux-ipsec82c/frontend ux-ipsec77, backend healthy; основной блок IPsec77 и VXLAN76 приняты в описанных границах. В12.5.67 исправленный Peer .conf принят как клиентский шаблон: API6/6, browser4/4, server endpoint и placeholders; настоящий RSC roundtrip через UI/native/handshake/Ping принят в12.5.68, .conf с явнымName/отказы400/409/сохранение формы приняты в70; поздний native rejection/подтверждённая компенсация/retry приняты в72; изолированный audit INSERT permission denial принят в74; остальные неизвестные исходы/DB faults открыты. Мобильная ширина WireGuard Sheets принята в12.5.64. Мобильная GRE форма и native сохранение на обоих CHR приняты в12.5.57. WireGuard handshake/короткий Ping/native API/read-only UI приняты в границах12.5.58; 12.5.59 воспроизвёл оставшиеся peers/IP; 12.5.60 развернул исправление и принял обычное удаление own WG интерфейса с peer/IPv4 адресом на обоих CHR. 12.5.62 принял полный WireGuard UI create/peer/disable/enable/Ping/delete на обоих CHR; устойчивое меню развёрнуто. Устойчивость меню GRE при live refresh на обоих CHR принята в12.5.55. GRE peer metadata и реальный lifecycle/API/UI/двусторонний Ping подтверждены в12.5.53. Browser retry/checkpoint на реальном HTTP/PG принят в границах12.5.43–45; Telegram sender responses подменены, реальная доставка не проверена. PG-1/2/3/4/5 приняты в описанных границах, PG-6 integrated для одного isolated instance, PG-7 real JWT/processcounts и browser restore passed. PG-8 real PG/native и browser unknown→history done приняты с fault injection после настоящего COMMIT. ROS-8/9 real API/SQL/native приняты в границах12.5.23; browser pair/duration change и delete приняты в12.5.24. ROS-2 authentication failure принят в12.5.25. PortalSSO, физический TCP disconnect, мультиэкземпляры и остальные ROS/Q/интеграции остаются открытыми. [Паспорт](ux-ui-test-server.md), [журнал](ux-ui-progress.md). VXLAN75: собственная native пара/Ping/catalog/URL/export приняты, create/delete API отсутствует; selected baseline восстановлен. +Статус на2026-10-09: backend ux-routing83/frontend ux-ipsec77, backend healthy; основной блок IPsec77 и VXLAN76 приняты в описанных границах. В12.5.67 исправленный Peer .conf принят как клиентский шаблон: API6/6, browser4/4, server endpoint и placeholders; настоящий RSC roundtrip через UI/native/handshake/Ping принят в12.5.68, .conf с явнымName/отказы400/409/сохранение формы приняты в70; поздний native rejection/подтверждённая компенсация/retry приняты в72; изолированный audit INSERT permission denial принят в74; остальные неизвестные исходы/DB faults открыты. Мобильная ширина WireGuard Sheets принята в12.5.64. Мобильная GRE форма и native сохранение на обоих CHR приняты в12.5.57. WireGuard handshake/короткий Ping/native API/read-only UI приняты в границах12.5.58; 12.5.59 воспроизвёл оставшиеся peers/IP; 12.5.60 развернул исправление и принял обычное удаление own WG интерфейса с peer/IPv4 адресом на обоих CHR. 12.5.62 принял полный WireGuard UI create/peer/disable/enable/Ping/delete на обоих CHR; устойчивое меню развёрнуто. Устойчивость меню GRE при live refresh на обоих CHR принята в12.5.55. GRE peer metadata и реальный lifecycle/API/UI/двусторонний Ping подтверждены в12.5.53. Browser retry/checkpoint на реальном HTTP/PG принят в границах12.5.43–45; Telegram sender responses подменены, реальная доставка не проверена. PG-1/2/3/4/5 приняты в описанных границах, PG-6 integrated для одного isolated instance, PG-7 real JWT/processcounts и browser restore passed. PG-8 real PG/native и browser unknown→history done приняты с fault injection после настоящего COMMIT. ROS-8/9 real API/SQL/native приняты в границах12.5.23; browser pair/duration change и delete приняты в12.5.24. ROS-2 authentication failure принят в12.5.25. PortalSSO, физический TCP disconnect, мультиэкземпляры и остальные ROS/Q/интеграции остаются открытыми. [Паспорт](ux-ui-test-server.md), [журнал](ux-ui-progress.md). VXLAN75: собственная native пара/Ping/catalog/URL/export приняты, create/delete API отсутствует; selected baseline восстановлен. IPsec78: реальный PSK responder established, mode-config198.18.78.10 и online=true подтверждены API; точные собственные объекты удалены. Сертификатный/DN online, late failure и history restore остаются открытыми. [Отчёт](ux-ui/acceptance/ipsec-mode-config-2026-10-09.json), [журнал](ux-ui-progress.md). @@ -12,6 +12,8 @@ IPsec81: partial/unknown native fault injection, preflight конфликтов, IPsec82: production certificate data plane/NAT Ping3/3, capped UDP3s, history group/egress и API server delete приняты. Baseline14 коллекций×2 восстановлен после route convergence; own/private fixtures удалены. [Отчёт](ux-ui/acceptance/ipsec-network-2026-10-09.json). +Routing83: disabled BGP/OSPF selected/raw409/probe0 и собственная eBGP Established пара с reject-all filters приняты; original routes/config preserved, own DB/native fixtures удалены. [Отчёт](ux-ui/acceptance/routing-bgp-disabled-2026-10-09.json). + ## Паспорт стенда Перед внешним запуском зафиксировать отдельные PostgreSQL и RouterOS тестовые устройства, версии server/client PostgreSQL и RouterOS, HEAD backend/frontend, роли пользователей, число экземпляров backend/планировщика/отправителей и способ их остановки. Для Telegram использовать тестовый чат, для остальных интеграций — тестовые endpoints и учётные записи. Зафиксировать ожидаемые изменения и способ восстановления стенда. diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index 605c1241..b2e03713 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -1,6 +1,6 @@ # План реализации UX/UI по пятичасовым окнам -Актуальное продолжение 2026-10-09: 12.5.82 исправил NAT без обязательного WAN, изолировал IPsec policy group и сохранил ограничения NAT/группы в истории. Production API init/client/restore/delete, сертификатный data plane, NAT Ping3/3 и capped bandwidth3s приняты; baseline восстановлен после ожидания динамических маршрутов. Backend ux-ipsec82c healthy, frontend ux-ipsec77. Следующий12.5.83 — BGP/OSPF selected/diagnostic paths: запрет опроса выключенных устройств и достоверность ошибок; затем реальные профильные сценарии маршрутизации. Этап12.5 открыт. +Актуальное продолжение 2026-10-09: 12.5.83 закрыл опрос выключенных устройств в BGP raw/selected и OSPF selected; выбранный OSPF сообщает отказ BFD/routes. Real disabled probe0, eBGP Established на двух CHR с reject-all input/output и baseline cleanup приняты. Backend ux-routing83 healthy, frontend ux-ipsec77. Следующий12.5.84 — реальная OSPF adjacency/маршрут через собственный GRE; этап12.5 открыт. Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-09. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат. diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index d2d893e9..d4708556 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -1,6 +1,6 @@ # UX/UI: журнал реализации -Актуальное продолжение 2026-10-09: 12.5.82 исправил NAT без обязательного WAN, изолировал IPsec policy group и сохранил ограничения NAT/группы в истории. Production API init/client/restore/delete, сертификатный data plane, NAT Ping3/3 и capped bandwidth3s приняты; baseline восстановлен после ожидания динамических маршрутов. Backend ux-ipsec82c healthy, frontend ux-ipsec77. Следующий12.5.83 — BGP/OSPF selected/diagnostic paths: запрет опроса выключенных устройств и достоверность ошибок; затем реальные профильные сценарии маршрутизации. Этап12.5 открыт. +Актуальное продолжение 2026-10-09: 12.5.83 закрыл опрос выключенных устройств в BGP raw/selected и OSPF selected; выбранный OSPF сообщает отказ BFD/routes. Real disabled probe0, eBGP Established на двух CHR с reject-all input/output и baseline cleanup приняты. Backend ux-routing83 healthy, frontend ux-ipsec77. Следующий12.5.84 — реальная OSPF adjacency/маршрут через собственный GRE; этап12.5 открыт. Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md). @@ -36,7 +36,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.3.83; внешняя12.5 в работе, выполненные блоки1–82 и ограничения ниже. Runtime backend ux-ipsec82c/frontend ux-ipsec77. | +| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–83 и ограничения ниже. Runtime backend ux-routing83/frontend ux-ipsec77. | + +## 12.5.83 — маршрутизация: выключенные устройства и реальная eBGP-пара (2026-10-09) + +**Изменения.** BGP raw debug использует общую lookup зависимость и отклоняет enabled=false до создания клиента. Selected BGP sessions и selected OSPF также возвращают409 без опроса, независимо от типа Home Router/Exit Node. BGP debug журнал содержит число сессий вместо дублирования полного raw payload. Selected OSPF использует strict readOspfCatalogRaw: отказ BFD/routes или неправильный контейнер больше не подменяется успешными пустыми массивами; dependency failure503. Legacy aggregate paths не менялись этим блоком. + +**Проверки.** BGP/OSPF catalog tests с новыми selected/raw disabled/unknown/invalid/enabled сценариями, OSPF BFD failure503; backend TypeScript, scoped ESLint/diff check passed. Сохраняются исходные OSPF unused and/measurePingByInterface warnings, новых ошибок нет. UI/контракты не менялись, frontend build и полная регрессия не повторялись. + +**Real API.** Две собственные disabled DB записи с HTTP probe на приватном Docker DNS: Home Router/Exit Node × BGP raw/selected/OSPF selected409, probeRequests0. Записи удалены по id/name/host. Selected OSPF двух настоящих enabled CHR возвращает корректные массивы; это чтение исходно пустой OSPF конфигурации, не приёмка adjacency. + +**eBGP.** Собственные RouterOS7.24 instances с private ASN65083/65084 и конкретными remote/local public IP, multihop eBGP connections и input/output reject-all chains. Native established=true на обоих CHR, production selected и catalog State Established/type eBGP/ASNs корректны; received prefixes0, catalog transmitted prefixes null как ненаблюдаемое значение. Инстансы/соединения/filter rules удалены по точным ID/name/chain/comment после полного preflight. Исходные ID connection/instance/filter/IPv4 route коллекций совпали на обоих CHR, default/management routes не участвовали в обмене. Это Establishment/read-only UI data contract, не route policy propagation, failover или maximum scale. [Отчёт](ux-ui/acceptance/routing-bgp-disabled-2026-10-09.json). + +**Runtime.** Одна backend Docker сборка, entry/node --check/pg_restore18.6 и compose --wait passed. mmapp-test/backend:ux-routing83/version1.0.0-test.routing83, image sha256:b68c326b8ee601eb2c490e7517c49614abd8cdfccc9d2af2ec44fb5d3dc5aded7, healthy. Frontend ux-ipsec77 сохранён. Context ipsec82c +четыре routing backend файла рабочего состояния до коммита, archive eaaca441a3eae1c95396abaca4a09fa4a38e4a769adaff276fc4bbcadac18244; label routing83, не итоговый Git SHA. .env backup приватный; миграций нет. Один локальный коммит без push. + +Следующий84 — OSPF Full adjacency и обучение только собственного частного prefix через собственный GRE; исходные public interfaces/default routes сохранить. BGP failover/route propagation/BFD optimizer/recursive diagnostics и внешние границы IPsec82 остаются открытыми. Версия RouterOS7.20+ с явными instance подтверждена [MikroTik BGP](https://help.mikrotik.com/docs/spaces/ROS/pages/328220/BGP) и настоящим API7.24.5; схема таблиц/деталей сохраняет референсы Data Grid Filtering2/List9 общего плана. ## 12.5.82 — IPsec: NAT, policy group, реальная передача и полный цикл (2026-10-09) diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index 9942284b..15859960 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -1968,3 +1968,17 @@ flowchart LR **Runtime/происхождение.** Backend mmapp-test/backend:ux-ipsec82c/version1.0.0-test.ipsec82c, image sha256:57fdd7dbbf66091fd0710154ed7c47f284047505977e7b06d0b15bfbfd5fa57d3, healthy; frontend ux-ipsec77. Три builds82/82b/82c по новым подтверждённым отказам: NAT/WAN, proposal group, empty list restore. Финальный context82b +11 backend файлов рабочего состояния, archive d7b0ad10c248ca6c1525266d67b4953f2752f5d046d46b63262e3dc93421e280, label ipsec82c не итоговый Git SHA. Entry/node --check/pg_restore18.6 и compose --wait passed; apt cached. Приватные .env backups, миграций нет. Один локальный коммит без push. Основной реальный цикл IPsec принят в перечисленных границах. Открыты сложные/экранированные DN, реальные клиентские ОС/IPv6, Portal SSO/browser auth, физические disconnect/durable retry/multi-instance, maximum throughput и полный PKI/policy/profile/proposal restore. Следующий83 — маршрутизация. Референсы интерфейса сохраняются: List9/Sheet8 из общего плана; настройки полей подтверждены [RouterOS IPsec](https://help.mikrotik.com/docs/spaces/ROS/pages/11993097/IPsec) и [Bandwidth Test](https://help.mikrotik.com/docs/spaces/ROS/pages/7962644/Bandwidth%2BTest). + +## 12.5.83 — маршрутизация: выключенные устройства и реальная eBGP-пара (2026-10-09) + +**Изменения.** BGP raw debug использует общую lookup зависимость и отклоняет enabled=false до создания клиента. Selected BGP sessions и selected OSPF также возвращают409 без опроса, независимо от типа Home Router/Exit Node. BGP debug журнал содержит число сессий вместо дублирования полного raw payload. Selected OSPF использует strict readOspfCatalogRaw: отказ BFD/routes или неправильный контейнер больше не подменяется успешными пустыми массивами; dependency failure503. Legacy aggregate paths не менялись этим блоком. + +**Проверки.** BGP/OSPF catalog tests с новыми selected/raw disabled/unknown/invalid/enabled сценариями, OSPF BFD failure503; backend TypeScript, scoped ESLint/diff check passed. Сохраняются исходные OSPF unused and/measurePingByInterface warnings, новых ошибок нет. UI/контракты не менялись, frontend build и полная регрессия не повторялись. + +**Real API.** Две собственные disabled DB записи с HTTP probe на приватном Docker DNS: Home Router/Exit Node × BGP raw/selected/OSPF selected409, probeRequests0. Записи удалены по id/name/host. Selected OSPF двух настоящих enabled CHR возвращает корректные массивы; это чтение исходно пустой OSPF конфигурации, не приёмка adjacency. + +**eBGP.** Собственные RouterOS7.24 instances с private ASN65083/65084 и конкретными remote/local public IP, multihop eBGP connections и input/output reject-all chains. Native established=true на обоих CHR, production selected и catalog State Established/type eBGP/ASNs корректны; received prefixes0, catalog transmitted prefixes null как ненаблюдаемое значение. Инстансы/соединения/filter rules удалены по точным ID/name/chain/comment после полного preflight. Исходные ID connection/instance/filter/IPv4 route коллекций совпали на обоих CHR, default/management routes не участвовали в обмене. Это Establishment/read-only UI data contract, не route policy propagation, failover или maximum scale. [Отчёт](ux-ui/acceptance/routing-bgp-disabled-2026-10-09.json). + +**Runtime.** Одна backend Docker сборка, entry/node --check/pg_restore18.6 и compose --wait passed. mmapp-test/backend:ux-routing83/version1.0.0-test.routing83, image sha256:b68c326b8ee601eb2c490e7517c49614abd8cdfccc9d2af2ec44fb5d3dc5aded7, healthy. Frontend ux-ipsec77 сохранён. Context ipsec82c +четыре routing backend файла рабочего состояния до коммита, archive eaaca441a3eae1c95396abaca4a09fa4a38e4a769adaff276fc4bbcadac18244; label routing83, не итоговый Git SHA. .env backup приватный; миграций нет. Один локальный коммит без push. + +Следующий84 — OSPF Full adjacency и обучение только собственного частного prefix через собственный GRE; исходные public interfaces/default routes сохранить. BGP failover/route propagation/BFD optimizer/recursive diagnostics и внешние границы IPsec82 остаются открытыми. Версия RouterOS7.20+ с явными instance подтверждена [MikroTik BGP](https://help.mikrotik.com/docs/spaces/ROS/pages/328220/BGP) и настоящим API7.24.5; схема таблиц/деталей сохраняет референсы Data Grid Filtering2/List9 общего плана. diff --git a/docs/ux-ui-test-server.md b/docs/ux-ui-test-server.md index f4fd92f7..98da7581 100644 --- a/docs/ux-ui-test-server.md +++ b/docs/ux-ui-test-server.md @@ -1,6 +1,6 @@ # Тестовый сервер UX/UI — 2026-10-09 -Сервер161.104.46.222 предоставлен для временного тестирования. Backend ux-ipsec82c/version1.0.0-test.ipsec82c, frontend ux-ipsec77/version1.0.0-test.ipsec77; backend/PG healthy, frontend HTTP/browser passed. Основной блок IPsec77 принят в границах журнала, own fixtures/fake DB rows удалены; каталог flash CHR2 сохранён из-за неподтверждённого ownership. SSH/оба CHR доступны, пароли прежние. +Сервер161.104.46.222 предоставлен для временного тестирования. Backend ux-routing83/version1.0.0-test.routing83, frontend ux-ipsec77/version1.0.0-test.ipsec77; backend/PG healthy, frontend HTTP/browser passed. Основной блок IPsec77 принят в границах журнала, own fixtures/fake DB rows удалены; каталог flash CHR2 сохранён из-за неподтверждённого ownership. SSH/оба CHR доступны, пароли прежние. | Параметр | Проверенное состояние | | --- | --- | @@ -14,7 +14,7 @@ | PostgreSQL | postgres:18-alpine,18.6;mmapp-test-postgres healthy;SELECT version() passed | | Данные | Отдельный Compose project mmapp-test,том mmapp-test_mmapp-pgdata и каталог ./data | | Доступ | PostgreSQL не публикуется;frontend3000/backend8000 привязаны к127.0.0.1 после запуска | -| Образы приложения | mmapp-test/backend:ux-ipsec82c и mmapp-test/frontend:ux-ipsec77; запущены | +| Образы приложения | mmapp-test/backend:ux-routing83 и mmapp-test/frontend:ux-ipsec77; запущены | Compose SHA256:3052bd6ab5d9865f97605cebfb5ff27a4410cd3357b81153a65a87fb7c92582f. OptionalBACKEND_IMAGE_TAG позволяетобновлятьbackendотдельно. Локальный и удалённый файлы совпали. Шаблоны: [Compose](../deploy/docker-compose.test.yml), [env без секретов](../deploy/env.test.example). @@ -28,6 +28,8 @@ IPsec81: partial/unknown native fault injection, preflight конфликтов, IPsec82: production certificate data plane/NAT Ping3/3, capped UDP3s, history group/egress и API server delete приняты. Baseline14 коллекций×2 восстановлен после route convergence; own/private fixtures удалены. [Отчёт](ux-ui/acceptance/ipsec-network-2026-10-09.json). +Routing83: disabled BGP/OSPF selected/raw409/probe0 и собственная eBGP Established пара с reject-all filters приняты; original routes/config preserved, own DB/native fixtures удалены. [Отчёт](ux-ui/acceptance/routing-bgp-disabled-2026-10-09.json). + ## Развёртывание приложения Чистые frontend/backend build contexts подготовлены по .gitea/workflows/docker.yml из закреплённого Git HEAD;образы собраны на сервере без push. Локально docker CLI сейчас отсутствует, на сервере доступен Docker. В корневом .dockerignore исключён backend, поэтому обычный backend build из корня не повторяет CI: использовать отдельный backend context, как main workflow. Не передавать .env.local,backend/.env,ключи и локальные node_modules/.next. diff --git a/docs/ux-ui/acceptance/routing-bgp-disabled-2026-10-09.json b/docs/ux-ui/acceptance/routing-bgp-disabled-2026-10-09.json new file mode 100644 index 00000000..2e6d913e --- /dev/null +++ b/docs/ux-ui/acceptance/routing-bgp-disabled-2026-10-09.json @@ -0,0 +1,223 @@ +{ + "date": "2026-10-09", + "name": "mm-routing83-1791540955934", + "disabled": [ + { + "type": "home-router", + "path": "/bgp/sessions/raw/:id", + "status": 409 + }, + { + "type": "home-router", + "path": "/servers/:id/bgp/sessions", + "status": 409 + }, + { + "type": "home-router", + "path": "/servers/:id/ospf", + "status": 409 + }, + { + "type": "exit-node", + "path": "/bgp/sessions/raw/:id", + "status": 409 + }, + { + "type": "exit-node", + "path": "/servers/:id/bgp/sessions", + "status": 409 + }, + { + "type": "exit-node", + "path": "/servers/:id/ospf", + "status": 409 + } + ], + "bgp": [ + { + "serverId": 1, + "native": [ + { + ".id": "*2800001", + "name": "mm-routing83-1791540955934-1", + "established": "true", + "remote.address": "104.165.244.7", + "remote.as": "65084", + "local.address": "194.226.121.150", + "local.as": "65083", + "uptime": "20ms", + "prefix-count": "0", + "input.filter": "mm-routing83-1791540955934", + "output.filter-chain": "mm-routing83-1791540955934" + } + ], + "selected": [ + { + "id": "*2800001", + "serverId": 1, + "serverName": "CHR", + "serverSite": "", + "serverCountry": "", + "name": "mm-routing83-1791540955934-1", + "peerIp": "104.165.244.7", + "remoteAs": 65084, + "localAs": 65083, + "localId": "194.226.121.150", + "remoteId": "104.165.244.7", + "state": "Established", + "type": "eBGP", + "uptime": "20ms", + "holdTime": 180, + "keepalive": 60, + "prefixesRx": 0, + "prefixesTx": 0, + "inputMessages": 1, + "outputMessages": 1, + "capabilities": [ + "MP-BGP", + "Route Refresh", + "Extended Next-Hop", + "Graceful Restart", + "4-byte-AS" + ], + "lastError": null + } + ], + "catalog": [ + { + "id": "*2800001", + "serverId": 1, + "serverName": "CHR", + "serverSite": "", + "serverCountry": "", + "name": "mm-routing83-1791540955934-1", + "peerIp": "104.165.244.7", + "remoteAs": 65084, + "localAs": 65083, + "localId": "194.226.121.150", + "remoteId": "104.165.244.7", + "state": "Established", + "type": "eBGP", + "uptime": "20ms", + "holdTime": 180, + "keepalive": 60, + "prefixesRx": 0, + "prefixesTx": null, + "inputMessages": 1, + "outputMessages": 1, + "capabilities": [ + "MP-BGP", + "Route Refresh", + "Extended Next-Hop", + "Graceful Restart", + "4-byte-AS" + ], + "lastError": null, + "afi": "ip" + } + ] + }, + { + "serverId": 2, + "native": [ + { + ".id": "*2800001", + "name": "mm-routing83-1791540955934-1", + "established": "true", + "remote.address": "194.226.121.150", + "remote.as": "65083", + "local.address": "104.165.244.7", + "local.as": "65084", + "uptime": "250ms", + "prefix-count": "0", + "input.filter": "mm-routing83-1791540955934", + "output.filter-chain": "mm-routing83-1791540955934" + } + ], + "selected": [ + { + "id": "*2800001", + "serverId": 2, + "serverName": "CHR", + "serverSite": "", + "serverCountry": "", + "name": "mm-routing83-1791540955934-1", + "peerIp": "194.226.121.150", + "remoteAs": 65083, + "localAs": 65084, + "localId": "104.165.244.7", + "remoteId": "194.226.121.150", + "state": "Established", + "type": "eBGP", + "uptime": "250ms", + "holdTime": 180, + "keepalive": 60, + "prefixesRx": 0, + "prefixesTx": 0, + "inputMessages": 1, + "outputMessages": 1, + "capabilities": [ + "MP-BGP", + "Route Refresh", + "Extended Next-Hop", + "Graceful Restart", + "4-byte-AS" + ], + "lastError": null + } + ], + "catalog": [ + { + "id": "*2800001", + "serverId": 2, + "serverName": "CHR", + "serverSite": "", + "serverCountry": "", + "name": "mm-routing83-1791540955934-1", + "peerIp": "194.226.121.150", + "remoteAs": 65083, + "localAs": 65084, + "localId": "104.165.244.7", + "remoteId": "194.226.121.150", + "state": "Established", + "type": "eBGP", + "uptime": "250ms", + "holdTime": 180, + "keepalive": 60, + "prefixesRx": 0, + "prefixesTx": null, + "inputMessages": 1, + "outputMessages": 1, + "capabilities": [ + "MP-BGP", + "Route Refresh", + "Extended Next-Hop", + "Graceful Restart", + "4-byte-AS" + ], + "lastError": null, + "afi": "ip" + } + ] + } + ], + "cleanup": [ + { + "serverId": 1, + "/routing/bgp/connection": true, + "/routing/bgp/instance": true, + "/routing/filter/rule": true, + "/ip/route": true + }, + { + "serverId": 2, + "/routing/bgp/connection": true, + "/routing/bgp/instance": true, + "/routing/filter/rule": true, + "/ip/route": true + } + ], + "disabledProbeRequests": 0, + "routingFilters": "input and output reject all; no routes advertised/accepted", + "dbFixtureRemoved": true +} \ No newline at end of file