fix(api): нормализовать дату сохранённого снимка Internet path
This commit is contained in:
1 parent
4d88b97d49
commit
673e5ba444
7 files changed
+276
-8
No files matched your search
@@ -1,5 +1,5 @@
|
||||
import type { FastifyPluginAsyncZod } from "@fastify/type-provider-zod"
|
||||
import { readStoredInternetPathPayload } from "../services/internet-path-snapshot.js"
|
||||
import { mapStoredInternetPathResponse } from "../services/internet-path-snapshot.js"
|
||||
import { refreshScheduler } from "../services/scheduler.js"
|
||||
import {
|
||||
getInternetPathSettings,
|
||||
@@ -48,16 +48,11 @@ const internetPathRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
app.get("/internet-path/latest", async (_req, reply) => {
|
||||
const row = await getLatestInternetPathSnapshot()
|
||||
if (!row) return reply.send({ snapshot: null })
|
||||
let payload: object
|
||||
try {
|
||||
payload = readStoredInternetPathPayload(row.payloadJson)
|
||||
return reply.send(mapStoredInternetPathResponse(row))
|
||||
} catch {
|
||||
return reply.code(502).send({error: "Сохранённый снимок Internet path повреждён"})
|
||||
}
|
||||
return reply.send({
|
||||
snapshot: payload,
|
||||
sampledAt: row.sampledAt,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import assert from "node:assert/strict"
|
||||
import {collectConfirmedInternetPath, readInternetPathRows, readStoredInternetPathPayload} from "./internet-path-snapshot.js"
|
||||
import {collectConfirmedInternetPath, readInternetPathRows, readStoredInternetPathPayload, mapStoredInternetPathResponse} from "./internet-path-snapshot.js"
|
||||
const servers=[{id:1,type:"home-router",enabled:true},{id:2,type:"home-router",enabled:false},{id:3,type:"exit-node",enabled:true}]
|
||||
const sampledAt="2026-10-04T00:00:00Z"
|
||||
const defaults={servers:async()=>servers,gre:async()=>[],filters:async()=>[],speed:async()=>[],route:async(id:number)=>{assert.equal(id,1);return {gateway:null,routingMark:null}},wan:async(id:number)=>{assert.equal(id,1);return {defaultGateway:null,defaultInterface:null,uplinks:[]}}}
|
||||
@@ -26,3 +26,12 @@ for(const [fields,row] of [[ ["dst-address"],{"message":"denied"} ],[["interface
|
||||
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)
|
||||
|
||||
for (const stored of ["2026-10-04 07:00:00+07", "2026-10-04T00:00:00Z"]) {
|
||||
const response=mapStoredInternetPathResponse({payloadJson:good,sampledAt:stored})
|
||||
assert.equal(response.sampledAt,"2026-10-04T00:00:00.000Z")
|
||||
assert.deepEqual(response.snapshot,good)
|
||||
}
|
||||
for (const invalid of ["", "bad", null]) assert.throws(()=>mapStoredInternetPathResponse({payloadJson:good,sampledAt:invalid as string}))
|
||||
assert.throws(()=>mapStoredInternetPathResponse({payloadJson:"bad",sampledAt}))
|
||||
console.log("internet path HTTP timestamp: PostgreSQL/ISO instant preserved; invalid metadata rejected")
|
||||
@@ -30,3 +30,9 @@ export function readStoredInternetPathPayload(raw: unknown): object {
|
||||
if (!payload || typeof payload !== "object" || Array.isArray(payload)) throw new Error("Сохранённый снимок Internet path повреждён")
|
||||
return payload
|
||||
}
|
||||
|
||||
/** PostgreSQL string-mode timestamps must be normalized at the HTTP boundary. */
|
||||
export function mapStoredInternetPathResponse(row: { payloadJson: unknown; sampledAt: string }) {
|
||||
if (typeof row.sampledAt !== "string" || !row.sampledAt.trim()) throw new Error("Сохранённая дата Internet path повреждена")
|
||||
return { snapshot: readStoredInternetPathPayload(row.payloadJson), sampledAt: new Date(row.sampledAt).toISOString() }
|
||||
}
|
||||
@@ -34,6 +34,18 @@
|
||||
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 |
|
||||
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в описанных границах:12.3.83,main1186/1186,context221/221. Этап остаётся в работе: стенд12.5.1 подготовлен,bootstrap/сценарии12.5 не выполнены. |
|
||||
|
||||
## 12.5.3 — нормализация даты Internet path на границе API (2026-10-07)
|
||||
|
||||
Выявленный live-smoke дефект исправлен в mapper/GET latest: внешний PostgreSQL sampledAt преобразуется вISO без смены instant;payload не изменяется. Неверная дата/payload отклоняются502. Строгийfrontend DTO не ослаблен. Pure collector/mapper tests прошли,включаяPostgreSQL+07/ISO/invalid/null/blank,contracts/backend build passed. На сервере пока работает прежний образ;блок в работе до пересборки и live проверки.
|
||||
|
||||
## 12.5.2 — сборка Docker и запуск приложения на тестовом сервере (2026-10-07)
|
||||
|
||||
Собраны backend/frontend из закреплённого коммита4d88b97d490b2342aa4de81d093a0b8d4902e8d4,через чистые build contexts по CI main. Архив8MiB не содержит локальных.env/лицензии/ключей/node_modules. Образы mmapp-test/backend:ux-4d88b97 и mmapp-test/frontend:ux-4d88b97 созданы на сервере без push;version1.0.0-test.4d88b97,revision label сохранён. Контейнеры запущены наloopback;backend/PostgreSQL healthy,health напрямую/черезfrontend status:ok. SSH-туннель3000/8000 открыт для проверки.
|
||||
|
||||
Реальный PostgreSQL применил9миграций,включая0008/0009;CHECK разрешает5статусов outbox и resolution_version>=0. После restartbackend совпали контрольный uptime_settings(id1/retention14) и список миграций;health восстановился. Пустые servers/probes/history прочитаны через API. [Первый live browser smoke](ux-ui/acceptance/test-server-smoke-2026-10-07.json):3экрана200,безJSerrors/API>=400,320px fits,безHTTP-моков. [Снимок320px](ux-ui/screens/test-server-speed-320-2026-10-07.png) просмотрен.
|
||||
|
||||
Smoke обнаружил ошибку Internet path приHTTP200: внешний sampledAt пришёл строкой PostgreSQL вместоISO;корректный snapshot отклонён строгимfrontend. Общий smoke не является полной приёмкой: UI-source error сохранён в отчёте. Следующий12.5.3 — нормализация producer timestamp,пересборка и повтор live-smoke. PG backup/restore/concurrency/native/integrations не проверены. Внешний PG-1 проверен bootstrap/restart/schema/control-row в указанной границе.
|
||||
|
||||
## 12.5.1 — подготовка удалённого тестового Docker-стенда (2026-10-07)
|
||||
|
||||
По прямому поручению пользователя подготовлен временный сервер161.104.46.222,root,Debian13.7 trixie/x86_64. До установки Docker отсутствовал;1CPU/1973MiB RAM,23GiB свободно. Установлены Docker Engine29.8.2,Compose5.6.0,Buildx0.37.1 из официального apt repository,git/python3 и4GiB swap. Docker systemd enabled/active,hello-world passed. SSH и сетевые настройки подключения сохранены.
|
||||
|
||||
@@ -960,3 +960,15 @@ Main1186/1186 на9680a64 и эта context матрица теперь вклю
|
||||
Создан /opt/mmapp-test (0700),Compose по main с отдельными именами/project/data,ограничением logs,loopback3000/8000 и фиксированными тегами. Пароль SSH не сохранён в репозитории; PG/JWT secrets сгенерированы на сервере в.env0600 без вывода. Compose config --quiet passed; SHA256 совпал с локальным шаблоном. Реальный PostgreSQL18.6 запущен,healthy и SELECT version() passed; порт БД не опубликован. [Паспорт и дальнейшие команды](ux-ui-test-server.md), [Compose](../deploy/docker-compose.test.yml), [env-шаблон](../deploy/env.test.example).
|
||||
|
||||
Это подготовка инфраструктуры, не приёмка PG-1/restore/native: backend/frontend ещё не собраны и не запущены, bootstrap не выполнен. Следующий12.5.2 — чистые CI build contexts,закреплённые образы без push,bootstrap/health и начало PG-протокола. Локально docker CLI отсутствует; серверный Docker готов. Публикация Gitea/CI не выполнялась; локальный коммит без push. Ресурсы стенда не представляют production performance.
|
||||
|
||||
## 12.5.2 — сборка Docker и запуск приложения на тестовом сервере (2026-10-07)
|
||||
|
||||
Собраны backend/frontend из закреплённого коммита4d88b97d490b2342aa4de81d093a0b8d4902e8d4,через чистые build contexts по CI main. Архив8MiB не содержит локальных.env/лицензии/ключей/node_modules. Образы mmapp-test/backend:ux-4d88b97 и mmapp-test/frontend:ux-4d88b97 созданы на сервере без push;version1.0.0-test.4d88b97,revision label сохранён. Контейнеры запущены наloopback;backend/PostgreSQL healthy,health напрямую/черезfrontend status:ok. SSH-туннель3000/8000 открыт для проверки.
|
||||
|
||||
Реальный PostgreSQL применил9миграций,включая0008/0009;CHECK разрешает5статусов outbox и resolution_version>=0. После restartbackend совпали контрольный uptime_settings(id1/retention14) и список миграций;health восстановился. Пустые servers/probes/history прочитаны через API. [Первый live browser smoke](ux-ui/acceptance/test-server-smoke-2026-10-07.json):3экрана200,безJSerrors/API>=400,320px fits,безHTTP-моков. [Снимок320px](ux-ui/screens/test-server-speed-320-2026-10-07.png) просмотрен.
|
||||
|
||||
Smoke обнаружил ошибку Internet path приHTTP200: внешний sampledAt пришёл строкой PostgreSQL вместоISO;корректный snapshot отклонён строгимfrontend. Общий smoke не является полной приёмкой: UI-source error сохранён в отчёте. Следующий12.5.3 — нормализация producer timestamp,пересборка и повтор live-smoke. PG backup/restore/concurrency/native/integrations не проверены. Внешний PG-1 проверен bootstrap/restart/schema/control-row в указанной границе.
|
||||
|
||||
## 12.5.3 — нормализация даты Internet path на границе API (2026-10-07)
|
||||
|
||||
Выявленный live-smoke дефект исправлен в mapper/GET latest: внешний PostgreSQL sampledAt преобразуется вISO без смены instant;payload не изменяется. Неверная дата/payload отклоняются502. Строгийfrontend DTO не ослаблен. Pure collector/mapper tests прошли,включаяPostgreSQL+07/ISO/invalid/null/blank,contracts/backend build passed. На сервере пока работает прежний образ;блок в работе до пересборки и live проверки.
|
||||
@@ -0,0 +1,234 @@
|
||||
{
|
||||
"sourceHead": "4d88b97d490b2342aa4de81d093a0b8d4902e8d4",
|
||||
"server": "161.104.46.222",
|
||||
"url": "http://localhost:3000 via SSH tunnel",
|
||||
"mockedResponses": false,
|
||||
"results": [
|
||||
{
|
||||
"route": "/dashboard",
|
||||
"status": 200,
|
||||
"mainVisible": true,
|
||||
"text": "Боковое меню\nОбзор\nДашборд\nСистема\nAPI доступен\nНе обновлены источники: 1\nПоследние полученные данные сохранены. Если источник ещё не загрузился, его показатели неизвестны.\nИсточники и время получения\nСерверы: Получено 07.10.2026, 21:19:46\nПробы: Получено 07.10.2026, 21:19:46\nBGP: Получено 07.10.2026, 21:19:46\nGRE: Получено 07.10.2026, 21:19:46\nWir"
|
||||
},
|
||||
{
|
||||
"route": "/uptime?tab=speed",
|
||||
"status": 200,
|
||||
"mainVisible": true,
|
||||
"text": "Боковое меню\nОбзор\nМониторинг\nПауза\nОбновить\nНовая speed-проба\nСистема\nAPI доступен\nLive (backend)\nПинг-пробы\n0\nРесурсы\n0\nСкорость\n0\n\nSpeed-пробы: получено 07.10.2026, 21:19:55\n\nИстория тестов: получено 07.10.2026, 21:19:55\n\nSpeed-пробы\n0 шт\nТестов выполнено\n0 run\nМакс TX\n—\nМакс RX\n—\n\nНет speed-проб\n\nСоздайте пробу через кнопку «Новая speed-проба» "
|
||||
},
|
||||
{
|
||||
"route": "/servers",
|
||||
"status": 200,
|
||||
"mainVisible": true,
|
||||
"text": "Боковое меню\nУправление\nСерверы\nПроверить все\nДобавить сервер\nСистема\nAPI доступен\nВсего серверов\n0\nОнлайн\n0\nJH + Exit Node\n0\nHome Router\n0\nВсе\n0\nJumpHost\n0\nExit Node\n0\nHome Router\n0\nФильтры\n0 серверов\nНет серверов\nДобавьте первый MikroTik-сервер для мониторинга"
|
||||
}
|
||||
],
|
||||
"mobileFits": true,
|
||||
"pageErrors": [],
|
||||
"apiReads": [
|
||||
{
|
||||
"path": "/api/auth/config",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/filters/gre-tunnels",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/health",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/events",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/health",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/sidebar-counts",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/servers",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/filters/rules",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/bgp/catalog",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/sidebar-counts",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/uptime/overview",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/internet-path/latest",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/filters/gre-tunnels",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/traffic/servers",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/vxlan",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/wireguard",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/evobgp/settings",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/auth/config",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/filters/gre-tunnels",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/sidebar-counts",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/health",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/evobgp/settings",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/health",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/servers",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/health",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/uptime/overview",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/uptime/speed-probes",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/sidebar-counts",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/uptime/speed-test/runs",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/evobgp/settings",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/auth/config",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/servers",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/health",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/filters/gre-tunnels",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/sidebar-counts",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/health",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/sidebar-counts",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/evobgp/settings",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/auth/config",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/filters/gre-tunnels",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/health",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/sidebar-counts",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/evobgp/settings",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/health",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/servers",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/uptime/overview",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/uptime/speed-probes",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/health",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/sidebar-counts",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/uptime/speed-test/runs",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/evobgp/settings",
|
||||
"status": 200
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
Reference in new issue
Block a user