fix(api): проверять обязательные поля снимка RouterOS
This commit is contained in:
1 parent
b658f7127f
commit
160b39186a
6 files changed
+26
-8
No files matched your search
@@ -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]
|
const row = (await db.select().from(servers).where(eq(servers.id, serverId)).limit(1))[0]
|
||||||
if (!row) throw new Error(`Internet path: устройство ${serverId} не найдено`)
|
if (!row) throw new Error(`Internet path: устройство ${serverId} не найдено`)
|
||||||
const client = MikrotikClient.fromServer(row)
|
const client = MikrotikClient.fromServer(row)
|
||||||
const routes = await client.get<unknown>("/ip/route").then(raw => readInternetPathRows(raw, "routes " + serverId))
|
const routes = await client.get<unknown>("/ip/route").then(raw => readInternetPathRows(raw, "routes " + serverId, ["dst-address"]))
|
||||||
const best = routes
|
const best = routes
|
||||||
.filter((r) => String(r["dst-address"] ?? "").trim() === "0.0.0.0/0")
|
.filter((r) => String(r["dst-address"] ?? "").trim() === "0.0.0.0/0")
|
||||||
.filter((r) => isTrue(r.active))
|
.filter((r) => isTrue(r.active))
|
||||||
@@ -93,9 +93,9 @@ async function readWanRuntime(serverId: number) {
|
|||||||
if (!server || !row) throw new Error(`Internet path: устройство ${serverId} не найдено`)
|
if (!server || !row) throw new Error(`Internet path: устройство ${serverId} не найдено`)
|
||||||
const client = MikrotikClient.fromServer(row)
|
const client = MikrotikClient.fromServer(row)
|
||||||
const [dhcpRaw, ipAddrs, routes] = await Promise.all([
|
const [dhcpRaw, ipAddrs, routes] = await Promise.all([
|
||||||
client.get<unknown>("/ip/dhcp-client").then(raw => readInternetPathRows(raw, "DHCP " + serverId)),
|
client.get<unknown>("/ip/dhcp-client").then(raw => readInternetPathRows(raw, "DHCP " + serverId, ["interface"])),
|
||||||
client.get<unknown>("/ip/address").then(raw => readInternetPathRows(raw, "addresses " + serverId)),
|
client.get<unknown>("/ip/address").then(raw => readInternetPathRows(raw, "addresses " + serverId, ["address", "interface"])),
|
||||||
client.get<unknown>("/ip/route").then(raw => readInternetPathRows(raw, "routes " + serverId)),
|
client.get<unknown>("/ip/route").then(raw => readInternetPathRows(raw, "routes " + serverId, ["dst-address"])),
|
||||||
])
|
])
|
||||||
const defaultRoute = routes
|
const defaultRoute = routes
|
||||||
.filter((r) => String(r["dst-address"] ?? "").trim() === "0.0.0.0/0")
|
.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) => {
|
const all = await Promise.all(enabled.map(async (srv) => {
|
||||||
try {
|
try {
|
||||||
const client = MikrotikClient.fromServer(srv)
|
const client = MikrotikClient.fromServer(srv)
|
||||||
const rows = await client.get<unknown>("/interface/gre").then(raw => readInternetPathRows(raw, "GRE " + srv.id))
|
const rows = await client.get<unknown>("/interface/gre").then(raw => readInternetPathRows(raw, "GRE " + srv.id, [".id", "name", "local-address", "remote-address"]))
|
||||||
return rows.map((g, idx) => {
|
return rows.map((g, idx) => {
|
||||||
const keepalive = String(g.keepalive ?? "0,0").split(",")
|
const keepalive = String(g.keepalive ?? "0,0").split(",")
|
||||||
const inner = parseInnerIps(String(g.comment ?? ""))
|
const inner = parseInnerIps(String(g.comment ?? ""))
|
||||||
|
|||||||
@@ -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('{"collectionComplete":true}'),{collectionComplete:true})
|
||||||
assert.deepEqual(readStoredInternetPathPayload(good),good)
|
assert.deepEqual(readStoredInternetPathPayload(good),good)
|
||||||
for(const raw of [null,"null","[1]","bad",[],1])assert.throws(()=>readStoredInternetPathPayload(raw))
|
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<string,string>]>)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)
|
||||||
@@ -18,8 +18,8 @@ export async function collectConfirmedInternetPath<S extends PathServer, G, F, P
|
|||||||
return {collectionComplete: true as const, sampledAt, servers, greTunnels, filtersRulesets, speedProbes, routeLookupByServerId, wanRuntimeByHomeId}
|
return {collectionComplete: true as const, sampledAt, servers, greTunnels, filtersRulesets, speedProbes, routeLookupByServerId, wanRuntimeByHomeId}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readInternetPathRows(raw: unknown, source: string): Array<Record<string, string>> {
|
export function readInternetPathRows(raw: unknown, source: string, requiredFields: string[] = []): Array<Record<string, string>> {
|
||||||
if (!Array.isArray(raw) || raw.some(row => !row || typeof row !== "object" || Array.isArray(row) || Object.values(row).some(value => typeof value !== "string"))) {
|
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)
|
throw new Error("Internet path: повреждён ответ " + source)
|
||||||
}
|
}
|
||||||
return raw as Array<Record<string, string>>
|
return raw as Array<Record<string, string>>
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
## Актуальная точка продолжения — 2026-10-03
|
## Актуальная точка продолжения — 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).
|
Реальные pg_dump/pg_restore, PG rollback и остановка UDP worker требуют внешней приёмки. Датированные продолжения ниже — история; точный следующий шаг также указан в начале [журнала](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)
|
## Промежуточная регрессия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 не включены.
|
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 не включены.
|
||||||
|
|||||||
@@ -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 ещё в работе.
|
Измерения и точный список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.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 и локальная фиксация этого блока.
|
||||||
Reference in new issue
Block a user