diff --git a/backend/src/services/internet-path-collector.ts b/backend/src/services/internet-path-collector.ts index 55c11bbb..cae3cb34 100644 --- a/backend/src/services/internet-path-collector.ts +++ b/backend/src/services/internet-path-collector.ts @@ -70,7 +70,7 @@ async function readRouteLookup(serverId: number): Promise<{ gateway: string | nu const row = (await db.select().from(servers).where(eq(servers.id, serverId)).limit(1))[0] if (!row) throw new Error(`Internet path: устройство ${serverId} не найдено`) const client = MikrotikClient.fromServer(row) - const routes = await client.get("/ip/route").then(raw => readInternetPathRows(raw, "routes " + serverId)) + const routes = await client.get("/ip/route").then(raw => readInternetPathRows(raw, "routes " + serverId, ["dst-address"])) const best = routes .filter((r) => String(r["dst-address"] ?? "").trim() === "0.0.0.0/0") .filter((r) => isTrue(r.active)) @@ -93,9 +93,9 @@ async function readWanRuntime(serverId: number) { if (!server || !row) throw new Error(`Internet path: устройство ${serverId} не найдено`) const client = MikrotikClient.fromServer(row) const [dhcpRaw, ipAddrs, routes] = await Promise.all([ - client.get("/ip/dhcp-client").then(raw => readInternetPathRows(raw, "DHCP " + serverId)), - client.get("/ip/address").then(raw => readInternetPathRows(raw, "addresses " + serverId)), - client.get("/ip/route").then(raw => readInternetPathRows(raw, "routes " + serverId)), + client.get("/ip/dhcp-client").then(raw => readInternetPathRows(raw, "DHCP " + serverId, ["interface"])), + client.get("/ip/address").then(raw => readInternetPathRows(raw, "addresses " + serverId, ["address", "interface"])), + client.get("/ip/route").then(raw => readInternetPathRows(raw, "routes " + serverId, ["dst-address"])), ]) const defaultRoute = routes .filter((r) => String(r["dst-address"] ?? "").trim() === "0.0.0.0/0") @@ -176,7 +176,7 @@ async function collectGreTunnels() { const all = await Promise.all(enabled.map(async (srv) => { try { const client = MikrotikClient.fromServer(srv) - const rows = await client.get("/interface/gre").then(raw => readInternetPathRows(raw, "GRE " + srv.id)) + const rows = await client.get("/interface/gre").then(raw => readInternetPathRows(raw, "GRE " + srv.id, [".id", "name", "local-address", "remote-address"])) return rows.map((g, idx) => { const keepalive = String(g.keepalive ?? "0,0").split(",") const inner = parseInnerIps(String(g.comment ?? "")) diff --git a/backend/src/services/internet-path-snapshot.test.ts b/backend/src/services/internet-path-snapshot.test.ts index 22ee7afc..ed10595b 100644 --- a/backend/src/services/internet-path-snapshot.test.ts +++ b/backend/src/services/internet-path-snapshot.test.ts @@ -20,3 +20,9 @@ console.log("internet path snapshot tests ok: complete, disabled home, six faile assert.deepEqual(readStoredInternetPathPayload('{"collectionComplete":true}'),{collectionComplete:true}) assert.deepEqual(readStoredInternetPathPayload(good),good) for(const raw of [null,"null","[1]","bad",[],1])assert.throws(()=>readStoredInternetPathPayload(raw)) + +// A syntactically valid string record may still be an API error, not an entity. +for(const [fields,row] of [[ ["dst-address"],{"message":"denied"} ],[["interface"],{}],[["address","interface"],{address:"192.0.2.1/24"}],[[".id","name","local-address","remote-address"],{".id":"*1",name:"gre", "local-address":"0.0.0.0"}] ] as Array<[string[],Record]>)assert.throws(()=>readInternetPathRows([row],"test",fields),/повреждён/) +assert.deepEqual(readInternetPathRows([],"routes",["dst-address"]),[]) +assert.equal(readInternetPathRows([{ "dst-address":"0.0.0.0/0",active:"true" }],"routes",["dst-address"]).length,1) +assert.equal(readInternetPathRows([{ ".id":"*1",name:"gre", "local-address":"0.0.0.0","remote-address":"192.0.2.2" }],"gre",[".id","name","local-address","remote-address"]).length,1) diff --git a/backend/src/services/internet-path-snapshot.ts b/backend/src/services/internet-path-snapshot.ts index b6b45b86..d7893323 100644 --- a/backend/src/services/internet-path-snapshot.ts +++ b/backend/src/services/internet-path-snapshot.ts @@ -18,8 +18,8 @@ export async function collectConfirmedInternetPath> { - if (!Array.isArray(raw) || raw.some(row => !row || typeof row !== "object" || Array.isArray(row) || Object.values(row).some(value => typeof value !== "string"))) { +export function readInternetPathRows(raw: unknown, source: string, requiredFields: string[] = []): Array> { + if (!Array.isArray(raw) || raw.some(row => !row || typeof row !== "object" || Array.isArray(row) || Object.values(row).some(value => typeof value !== "string") || requiredFields.some(field => typeof row[field] !== "string" || !row[field].trim()))) { throw new Error("Internet path: повреждён ответ " + source) } return raw as Array> diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index e03a1559..a393c649 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.1 принят локально:1000 серверов/штатная пагинация. 12.3.2 принят локально: пагинация CompactDataGrid/1000 строк статистики. 12.3.3 принят локально: порционный DOM очереди проблем. 12.3.4 принят локально: большой BGP каталог/страница при обновлении. Production build и123 профильных browser приняты локально на3978976. Следующий блок12.3.5: сохранение страницы CompactDataGrid при повторном опросе, затем uptime ресурсы/speed/schedule и остальные grids;12.4 общая регрессия ещё не завершена. См. 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/1000 строк статистики. 12.3.3 принят локально: порционный DOM очереди проблем. 12.3.4 принят локально: большой BGP каталог/страница при обновлении. Production build и123 профильных browser приняты локально на3978976. 12.2.10 принят локально: обязательные RouterOS поля. Завершить12.3.5: production build после сохранения страницы CompactDataGrid при повторном опросе, затем uptime ресурсы/speed/schedule и остальные grids;12.4 общая регрессия ещё не завершена. См. 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 4fab9527..67ead6b4 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -36,6 +36,12 @@ +## 12.2.10 — обязательные поля объектов RouterOS (2026-10-04) + +Сборщик Internet path проверяет не только array/string record, но и обязательные поля конкретного источника: route dst-address, DHCP interface, IP address/interface, GRE .id/name/local-address/remote-address. Строковый error record или пустой объект больше не проходят как подтверждённая сущность/пустая маршрутизация/синтетический GRE. Настоящий пустой массив остаётся допустимым; auto local-address0.0.0.0 сохраняется. + +Pure/DI профиль дополнен malformed entity/error record/valid empty/valid route/valid GRE; профиль, ESLint и backend/contracts build прошли. Сетевые операции не изменены, живой RouterOS не проверен. Далее завершить12.3.5:89 browser уже прошли после сохранения страницы CompactDataGrid, требуется повторный frontend production build и локальная фиксация этого блока. + ## Промежуточная регрессия12.2–12.3 (2026-10-04) Frontend production/contracts build на3978976 прошёл:34 страницы.123 browser после build прошли:20 accessibility shell, GRE/WireGuard/VXLAN/uptime links и операции/права/ошибки,3 больших каталога. Штатные fixtures теперь содержат sessions/failures, BGP catalog, collectionComplete и реальные ping observations; speed fixtures имеют полный nullable metadata. Не ослаблялась проверка DTO ради тестов. Regenerated monitor screenshots восстановлены до прежних проверенных bytes, случайные визуальные diff не включены. diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index dbca4abe..3c36ab25 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -155,3 +155,9 @@ Frontend production/contracts build на3978976 прошёл:34 страницы Измерения и точный список27 оставшихся manualPagination grids сохранены в [ux-ui-performance.md](ux-ui-performance.md); это карта последующей приёмки, не объявление всех таблиц исправными. Typecheck/ESLint fixtures прошли.20 dashboard unit и backend pure/DI подтверждены предыдущими блоками; реальные PG/RouterOS не запускались.12.3/12.4/12.5 ещё в работе. Далее12.3.5: проверка сохранения страницы общего CompactDataGrid при повторном dashboard polling (100 проб), затем uptime ресурсы/speed/schedule большие fixtures. Build должен быть повторён после новых изменений общей основы. + +## 12.2.10 — обязательные поля объектов RouterOS (2026-10-04) + +Сборщик Internet path проверяет не только array/string record, но и обязательные поля конкретного источника: route dst-address, DHCP interface, IP address/interface, GRE .id/name/local-address/remote-address. Строковый error record или пустой объект больше не проходят как подтверждённая сущность/пустая маршрутизация/синтетический GRE. Настоящий пустой массив остаётся допустимым; auto local-address0.0.0.0 сохраняется. + +Pure/DI профиль дополнен malformed entity/error record/valid empty/valid route/valid GRE; профиль, ESLint и backend/contracts build прошли. Сетевые операции не изменены, живой RouterOS не проверен. Далее завершить12.3.5:89 browser уже прошли после сохранения страницы CompactDataGrid, требуется повторный frontend production build и локальная фиксация этого блока.