From 555bd3159600802cc44b92de705d2a08cc6cdcdd Mon Sep 17 00:00:00 2001 From: Denozordec Date: Sun, 11 Oct 2026 18:27:41 +0700 Subject: [PATCH] =?UTF-8?q?fix(netflow):=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D1=8F=D1=82=D1=8C=20=D0=BC=D0=B5=D1=81=D1=8F=D1=86=20?= =?UTF-8?q?=D0=B8=20=D1=82=D0=BE=D1=87=D0=BD=D1=8B=D0=B5=20=D0=BA=D0=B0?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B4=D0=B0=D1=80=D0=BD=D1=8B=D0=B5=20=D0=B3?= =?UTF-8?q?=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/package.json | 2 +- backend/src/routes/traffic-flow-query.test.ts | 5 ++ backend/src/routes/traffic-flow.ts | 6 +- .../src/services/traffic-flow-analytics.ts | 18 ++-- .../src/services/traffic-flow-month.test.ts | 44 ++++++++++ docs/ux-ui-external-acceptance.md | 16 +++- docs/ux-ui-plan.md | 6 +- docs/ux-ui-progress.md | 16 +++- docs/ux-ui-remaining-acceptance.md | 18 +++- docs/ux-ui-stage-12.md | 14 +++- docs/ux-ui-test-server.md | 16 +++- .../flow-month-calendar-2026-10-11.json | 71 ++++++++++++++++ ...affic-month-calendar-smoke-2026-10-11.json | 84 +++++++++++++++++++ 13 files changed, 291 insertions(+), 25 deletions(-) create mode 100644 backend/src/services/traffic-flow-month.test.ts create mode 100644 docs/ux-ui/acceptance/flow-month-calendar-2026-10-11.json create mode 100644 docs/ux-ui/acceptance/traffic-month-calendar-smoke-2026-10-11.json diff --git a/backend/package.json b/backend/package.json index d0f08926..21d7de39 100644 --- a/backend/package.json +++ b/backend/package.json @@ -20,7 +20,7 @@ "test:auth": "tsx src/lib/permissions.test.ts && tsx src/plugins/auth.smoke.test.ts && tsx src/plugins/auth.ospf-optimizer.test.ts", "test:wireguard": "npx tsx src/services/wireguard-config.test.ts && npx tsx --test src/services/wireguard-import.test.ts src/services/wireguard-import-apply.test.ts src/services/wireguard-import-revision.test.ts && npx tsx src/routes/wireguard-import.test.ts", "test:traffic-rate": "tsx src/services/traffic-rate.test.ts", - "test:traffic-flow": "tsx src/routes/traffic-flow-query.test.ts && tsx src/services/traffic-flow-parse.test.ts && tsx src/services/traffic-flow-ipv6-pipeline.test.ts && tsx src/services/traffic-flow-map-exporter.test.ts && tsx src/services/traffic-flow-ifaces.test.ts && tsx src/services/traffic-flow-ifindex.test.ts && tsx src/services/traffic-flow-dedup.test.ts && tsx src/services/traffic-flow-planes.test.ts && tsx src/services/traffic-flow-ip.test.ts && tsx src/services/traffic-flow-dest.test.ts && tsx src/services/traffic-flow-classify.test.ts && tsx src/services/traffic-flow-ripe.test.ts && tsx src/services/traffic-flow-brands.test.ts && tsx src/services/traffic-flow-ingest.test.ts && tsx src/services/traffic-flow-analytics.test.ts && tsx src/services/traffic-flow-map-hops.test.ts && tsx src/services/traffic-flow-purge.test.ts && tsx src/services/traffic-flow-geoip.test.ts && tsx src/services/traffic-flow-facts.test.ts && tsx src/services/traffic-flow-facts-filter.test.ts && tsx src/services/traffic-flow-facts-rebuild.test.ts && tsx src/services/statistics-aggregate.test.ts", + "test:traffic-flow": "tsx src/routes/traffic-flow-query.test.ts && tsx src/services/traffic-flow-month.test.ts && tsx src/services/traffic-flow-parse.test.ts && tsx src/services/traffic-flow-ipv6-pipeline.test.ts && tsx src/services/traffic-flow-map-exporter.test.ts && tsx src/services/traffic-flow-ifaces.test.ts && tsx src/services/traffic-flow-ifindex.test.ts && tsx src/services/traffic-flow-dedup.test.ts && tsx src/services/traffic-flow-planes.test.ts && tsx src/services/traffic-flow-ip.test.ts && tsx src/services/traffic-flow-dest.test.ts && tsx src/services/traffic-flow-classify.test.ts && tsx src/services/traffic-flow-ripe.test.ts && tsx src/services/traffic-flow-brands.test.ts && tsx src/services/traffic-flow-ingest.test.ts && tsx src/services/traffic-flow-analytics.test.ts && tsx src/services/traffic-flow-map-hops.test.ts && tsx src/services/traffic-flow-purge.test.ts && tsx src/services/traffic-flow-geoip.test.ts && tsx src/services/traffic-flow-facts.test.ts && tsx src/services/traffic-flow-facts-filter.test.ts && tsx src/services/traffic-flow-facts-rebuild.test.ts && tsx src/services/statistics-aggregate.test.ts", "test:users": "tsx src/modules/users/iface-type.test.ts && tsx src/modules/users/bindings.test.ts && tsx src/modules/users/interface-inventory.test.ts && tsx src/routes/users-catalog.test.ts", "test:pg": "tsx src/db/sql-bind.test.ts && tsx src/db/sqlite-json.test.ts && tsx src/db/traffic-flags.test.ts && tsx src/db/pg-schema.test.ts && tsx src/services/config-revisions.test.ts", "test:recursive-routes": "tsx src/routes/recursive-routes.test.ts && tsx src/services/recursive-route-catalog.test.ts && tsx src/services/recursive-apply.test.ts", diff --git a/backend/src/routes/traffic-flow-query.test.ts b/backend/src/routes/traffic-flow-query.test.ts index 544b8203..5965a9fb 100644 --- a/backend/src/routes/traffic-flow-query.test.ts +++ b/backend/src/routes/traffic-flow-query.test.ts @@ -21,6 +21,11 @@ try { for (let i = 0; i < 4; i++) assert.equal(tryAcquireFlowLiveSlot(), true) assert.equal(tryAcquireFlowLiveSlot(), false) for (let i = 0; i < 4; i++) releaseFlowLiveSlot() + for (const month of ["bad", "2026-00", "2026-13", "0000-01", "2026-1", "2026-10junk"]) { + const response = await app.inject({ method: "GET", url: `/api/traffic/flow/monthly?serverId=1&month=${encodeURIComponent(month)}` }) + assert.equal(response.statusCode, 400, month) + assert.match(response.body, /Некорректный месяц отчёта/) + } console.log("traffic-flow-query.test.ts: 21 invalid scoped requests rejected; SSE slots retained") } finally { resetFlowLiveSlotsForTests() diff --git a/backend/src/routes/traffic-flow.ts b/backend/src/routes/traffic-flow.ts index 51e630bb..9a409ce2 100644 --- a/backend/src/routes/traffic-flow.ts +++ b/backend/src/routes/traffic-flow.ts @@ -231,9 +231,9 @@ const trafficFlowRoutes: FastifyPluginAsyncZod = async (app) => { app.get("/traffic/flow/monthly", async (req, reply) => { const q = req.query as { month?: string; serverId?: string } const now = new Date() - const month = /^\d{4}-\d{2}$/.test(q.month ?? "") - ? (q.month as string) - : `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, "0")}` + const month = q.month == null || q.month === "" + ? `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, "0")}` + : q.month return reply.send(await getFlowMonthly(month, parseId(q.serverId))) }) diff --git a/backend/src/services/traffic-flow-analytics.ts b/backend/src/services/traffic-flow-analytics.ts index 3e0dfc22..b5fd6497 100644 --- a/backend/src/services/traffic-flow-analytics.ts +++ b/backend/src/services/traffic-flow-analytics.ts @@ -4,7 +4,6 @@ import { appUsers, userInterfaceBindings } from "../db/schema.js" import type { FlowAnalyticsDto, FlowBreakdownRow, - FlowClientsDto, FlowEntityCard, FlowExportersDto, FlowMapEdge, @@ -645,15 +644,16 @@ export async function safeBuildLiveFlowSample(q: Omit 12) return null const start = `${month}-01` - const endDate = new Date(Date.UTC(year, monthIdx, 1)) - const end = endDate.toISOString().slice(0, 10) + // Date.UTC treats years0..99 as1900..1999. Calendar arithmetic also preserves + // the full ISO year at December9999 without truncating an extended date string. + const end = `${String(year + (monthIdx === 12 ? 1 : 0)).padStart(4, "0")}-${String(monthIdx === 12 ? 1 : monthIdx + 1).padStart(2, "0")}-01` return { start, end } } @@ -676,9 +676,9 @@ function toBreakdown( } export async function getFlowMonthly(month: string, serverId?: number): Promise { - const bounds = monthBounds(month) + const bounds = flowMonthBounds(month) if (!bounds) { - return { month, bytes: 0, countries: [], services: [], asns: [] } + throw Object.assign(new Error("Некорректный месяц отчёта"), { statusCode: 400 }) } const params: Array = [bounds.start, bounds.end] let where = "day >= ? AND day < ? AND dim IN ('country', 'service', 'asn')" @@ -705,7 +705,9 @@ export async function getFlowMonthly(month: string, serverId?: number): Promise< } else if (row.dim === "service") services.push(rec) else if (row.dim === "asn") asns.push(rec) } - const daysInMonth = Math.max(1, Math.round((Date.parse(`${bounds.end}T00:00:00Z`) - Date.parse(`${bounds.start}T00:00:00Z`)) / 86_400_000)) + const [year, monthIndex] = month.split("-").map(Number) + const leap = year! % 4 === 0 && (year! % 100 !== 0 || year! % 400 === 0) + const daysInMonth = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][monthIndex! - 1]! const windowSec = daysInMonth * 86_400 const countryTotal = countries.reduce((a, r) => a + r.bytes, 0) || bytes || 1 return { diff --git a/backend/src/services/traffic-flow-month.test.ts b/backend/src/services/traffic-flow-month.test.ts new file mode 100644 index 00000000..897c5ef3 --- /dev/null +++ b/backend/src/services/traffic-flow-month.test.ts @@ -0,0 +1,44 @@ +import assert from "node:assert/strict" +import { flowMonthBounds, getFlowMonthly } from "./traffic-flow-analytics.js" +import { pool, closePool } from "../db/index.js" +import { initDatabase } from "../db/bootstrap.js" + +for (const [month, end] of [ + ["2026-10", "2026-11-01"], ["2026-12", "2027-01-01"], + ["0001-01", "0001-02-01"], ["0099-12", "0100-01-01"], + ["2024-02", "2024-03-01"], ["9999-12", "10000-01-01"], +]) assert.deepEqual(flowMonthBounds(month!), { start: `${month}-01`, end }) +try { + for (const month of ["bad", "2026-00", "2026-13", "0000-01", "2026-1", "2026-10junk", "2026-10-01", " 2026-10"]) { + assert.equal(flowMonthBounds(month), null) + await assert.rejects(getFlowMonthly(month), (error: unknown) => error instanceof Error && (error as Error & { statusCode: number }).statusCode === 400) + } + const ownDb = process.env.MMAPP_OWN_TEST_DB + if (ownDb) { + assert(/^mm_accept_149_\d+$/.test(ownDb)) + assert.equal((await pool.query("SELECT current_database() AS name")).rows[0].name, ownDb) + await initDatabase() + const serverId = Number((await pool.query("INSERT INTO servers (name,host) VALUES ('month-boundary-fixture','127.0.0.1') RETURNING id")).rows[0].id) + const cases = [ + { month: "0001-01", start: "0001-01-01", end: "0001-03-01", inside: "0001-01-15", outside: "0001-02-01", days: 31 }, + { month: "0099-12", start: "0099-12-01", end: "0100-02-01", inside: "0099-12-15", outside: "0100-01-01", days: 31 }, + { month: "1900-02", start: "1900-02-01", end: "1900-04-01", inside: "1900-02-28", outside: "1900-03-01", days: 28 }, + { month: "2000-02", start: "2000-02-01", end: "2000-04-01", inside: "2000-02-29", outside: "2000-03-01", days: 29 }, + { month: "9999-12", start: "9999-12-01", end: "10000-02-01", inside: "9999-12-15", outside: "10000-01-01", days: 31 }, + ] + for (const [i, item] of cases.entries()) { + // Fixed fixture DDL only, guarded by an exact owned database before migrations. + await pool.query(`CREATE TABLE flow_daily_dims_mm149_${i} PARTITION OF flow_daily_dims FOR VALUES FROM ('${item.start}') TO ('${item.end}')`) + await pool.query("INSERT INTO flow_daily_dims (server_id,day,dim,key,bytes,packets) VALUES ($1,$2,'country','US',1200,12),($1,$3,'country','US',12000,120)", [serverId, item.inside, item.outside]) + const monthly = await getFlowMonthly(item.month, serverId) + assert.equal(monthly.month, item.month) + assert.equal(monthly.bytes, 1200, item.month) + assert.equal(monthly.countries[0]?.bytes, 1200) + assert.equal(monthly.countries[0]?.bps, 1200 * 8 / (item.days * 86400), item.month) + } + console.log("ISOLATED_MONTH_SQL_ACCEPTED: 5 exact intervals, outside rows excluded, leap-year rates verified") + } else console.log("Monthly SQL: skip (requires an owned isolated database)") + console.log("traffic-flow-month.test.ts: exact month bounds and invalid-before-SQL checks passed") +} finally { + await closePool() +} diff --git a/docs/ux-ui-external-acceptance.md b/docs/ux-ui-external-acceptance.md index e407398a..9bfc9878 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-11: завершён12.5.148 — scoped flow HTTP/SSE ждут confirmed catalog; invalid serverId получает400 без aggregate fallback/SSE slot leak.36 E2E,21 backend checks/source+candidate,984 outputs/types/ESLint/both builds,live API+PC/mobile passed. Backend/frontend ux-scope148. Далее149 — strict monthly calendar input/bounds, без подмены invalid month текущим/нулевым отчётом.12.5 открыт. +Актуальное продолжение 2026-10-11: завершён12.5.149 — invalid month400 без current/zero fallback; calendar bounds исправлены для годов1–99/9999 и leap rates.5 SQL intervals в own DB/removed,984 outputs/build/ESLint,6 frontend checks и live400/200+PC/mobile passed. Backend ux-month149/frontend ux-scope148. Далее150 — ясные названия колонок Sessions вместо App/Proto/Iface/JH/EN, без изменения данных/темы.12.5 открыт. 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). @@ -696,7 +696,7 @@ flowchart LR ## 12.5.77 — основной блок IPsec (2026-10-09) -Актуальное продолжение 2026-10-11: завершён12.5.148 — scoped flow HTTP/SSE ждут confirmed catalog; invalid serverId получает400 без aggregate fallback/SSE slot leak.36 E2E,21 backend checks/source+candidate,984 outputs/types/ESLint/both builds,live API+PC/mobile passed. Backend/frontend ux-scope148. Далее149 — strict monthly calendar input/bounds, без подмены invalid month текущим/нулевым отчётом.12.5 открыт. +Актуальное продолжение 2026-10-11: завершён12.5.149 — invalid month400 без current/zero fallback; calendar bounds исправлены для годов1–99/9999 и leap rates.5 SQL intervals в own DB/removed,984 outputs/build/ESLint,6 frontend checks и live400/200+PC/mobile passed. Backend ux-month149/frontend ux-scope148. Далее150 — ясные названия колонок Sessions вместо App/Proto/Iface/JH/EN, без изменения данных/темы.12.5 открыт. Подробный объём, протоколы, схема и ограничения: [12.5.77 в журнале](ux-ui-progress.md). @@ -1601,3 +1601,15 @@ Backend больше не извлекает numeric prefix из1suffix и не Backend/frontend ux-scope148 развёрнуты, health healthy. Real API9 malformed cases400 и2 valid scoped cases200; [receipt](ux-ui/acceptance/flow-scoped-query-2026-10-11.json), [PC/mobile readonly smoke](ux-ui/acceptance/traffic-flow-scope-smoke-2026-10-11.json). No native changes/password changes/schema/theme changes; rollback env/images сохранены. Populated native145 не повторялся без необходимости. Следующий149 — строгий month input и calendar bounds: invalid month не должен подменяться текущим или выдаваться за подтверждённый zero report.12.5 открыт. + +## 12.5.149 — календарный месяц без тихой подмены и ложных нулей (2026-10-11) + +Supplied malformed month раньше подменялся current UTC month на route; impossible month мог вернуться из service как zero report. Теперь непустой параметр передаётся для strict YYYY-MM/month1..12/year!=0000 validation, invalid получает400 Некорректный месяц отчёта до SQL. Missing/empty optional month сохраняет прежний current UTC default. Runtime non-string тоже отклоняется. + +Calendar bounds больше не используют Date.UTC дляyear1..99 (он превращает их в1901..1999) и не режут extended ISO date at year10000. Integer year/month arithmetic даёт exact next-month day1, average bps используют Gregorian month length/leap rule. Source tests проверяют нормальный month/year rollover,1/99/9999, invalid input rejection before SQL. Normalized labels/DB schema не менялись. + +В candidate Docker5 SQL intervals прошли в own temporary PostgreSQL DB:0001 January,0099 December,1900 February28days,2000 February29days,9999 December/end10000. In-month1200B retained, next-month12000B excluded, average country bps matches exact month length. Fixture DDL выполняется только после exact own DB/current_database check, DB удалена с owner check; working DB/legacy SQLite не использовались fixtures. Both month and scoped-query tests добавлены в normal traffic-flow series. ESLint/backend build/984 output comparison и6 frontend scoped/context tests passed; frontend не пересобирался. + +Backend ux-month149 развёрнут, frontend ux-scope148 сохранён, health healthy. Real API6 invalid months400 иcorrect2026-10→200/same month; initial smoke helper Fetch.status method typo исправлен только в harness, сохранён как setup error. [Full SQL/live receipt](ux-ui/acceptance/flow-month-calendar-2026-10-11.json), [PC/mobile smoke](ux-ui/acceptance/traffic-month-calendar-smoke-2026-10-11.json). Device/password changes нет, rollback retained. Это не расширяет UI на future month selection. + +Следующий небольшой150 — понятные подписи Sessions columns вместо App/Proto/Iface/JH/EN, сохраняя ReUI grid/Frame/base-nova/data semantics.12.5 открыт. diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index cf7995a1..34eda7b8 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -1,12 +1,12 @@ # План реализации UX/UI по пятичасовым окнам -Актуальное продолжение 2026-10-11: завершён12.5.148 — scoped flow HTTP/SSE ждут confirmed catalog; invalid serverId получает400 без aggregate fallback/SSE slot leak.36 E2E,21 backend checks/source+candidate,984 outputs/types/ESLint/both builds,live API+PC/mobile passed. Backend/frontend ux-scope148. Далее149 — strict monthly calendar input/bounds, без подмены invalid month текущим/нулевым отчётом.12.5 открыт. +Актуальное продолжение 2026-10-11: завершён12.5.149 — invalid month400 без current/zero fallback; calendar bounds исправлены для годов1–99/9999 и leap rates.5 SQL intervals в own DB/removed,984 outputs/build/ESLint,6 frontend checks и live400/200+PC/mobile passed. Backend ux-month149/frontend ux-scope148. Далее150 — ясные названия колонок Sessions вместо App/Proto/Iface/JH/EN, без изменения данных/темы.12.5 открыт. Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-10. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат. ## Актуальная точка продолжения — 2026-10-10 -Актуальное продолжение 2026-10-11: завершён12.5.148 — scoped flow HTTP/SSE ждут confirmed catalog; invalid serverId получает400 без aggregate fallback/SSE slot leak.36 E2E,21 backend checks/source+candidate,984 outputs/types/ESLint/both builds,live API+PC/mobile passed. Backend/frontend ux-scope148. Далее149 — strict monthly calendar input/bounds, без подмены invalid month текущим/нулевым отчётом.12.5 открыт. +Актуальное продолжение 2026-10-11: завершён12.5.149 — invalid month400 без current/zero fallback; calendar bounds исправлены для годов1–99/9999 и leap rates.5 SQL intervals в own DB/removed,984 outputs/build/ESLint,6 frontend checks и live400/200+PC/mobile passed. Backend ux-month149/frontend ux-scope148. Далее150 — ясные названия колонок Sessions вместо App/Proto/Iface/JH/EN, без изменения данных/темы.12.5 открыт. Этапы0–11 завершены в локальных границах журнала. История выполненных внешних блоков и доказательства сохранены в [журнале](ux-ui-stage-12.md) и [протоколе](ux-ui-external-acceptance.md). Реальная Telegram доставка, Portal SSO, multi-instance и оставшиеся профильные сетевые ограничения открыты. @@ -371,6 +371,6 @@ flowchart LR ## 12.5.77 — основной блок IPsec (2026-10-09) -Актуальное продолжение 2026-10-11: завершён12.5.148 — scoped flow HTTP/SSE ждут confirmed catalog; invalid serverId получает400 без aggregate fallback/SSE slot leak.36 E2E,21 backend checks/source+candidate,984 outputs/types/ESLint/both builds,live API+PC/mobile passed. Backend/frontend ux-scope148. Далее149 — strict monthly calendar input/bounds, без подмены invalid month текущим/нулевым отчётом.12.5 открыт. +Актуальное продолжение 2026-10-11: завершён12.5.149 — invalid month400 без current/zero fallback; calendar bounds исправлены для годов1–99/9999 и leap rates.5 SQL intervals в own DB/removed,984 outputs/build/ESLint,6 frontend checks и live400/200+PC/mobile passed. Backend ux-month149/frontend ux-scope148. Далее150 — ясные названия колонок Sessions вместо App/Proto/Iface/JH/EN, без изменения данных/темы.12.5 открыт. Подробный объём, протоколы, схема и ограничения: [12.5.77 в журнале](ux-ui-progress.md). diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index ad2f257b..05df9669 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -1,6 +1,6 @@ # UX/UI: журнал реализации -Актуальное продолжение 2026-10-11: завершён12.5.148 — scoped flow HTTP/SSE ждут confirmed catalog; invalid serverId получает400 без aggregate fallback/SSE slot leak.36 E2E,21 backend checks/source+candidate,984 outputs/types/ESLint/both builds,live API+PC/mobile passed. Backend/frontend ux-scope148. Далее149 — strict monthly calendar input/bounds, без подмены invalid month текущим/нулевым отчётом.12.5 открыт. +Актуальное продолжение 2026-10-11: завершён12.5.149 — invalid month400 без current/zero fallback; calendar bounds исправлены для годов1–99/9999 и leap rates.5 SQL intervals в own DB/removed,984 outputs/build/ESLint,6 frontend checks и live400/200+PC/mobile passed. Backend ux-month149/frontend ux-scope148. Далее150 — ясные названия колонок Sessions вместо App/Proto/Iface/JH/EN, без изменения данных/темы.12.5 открыт. Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md). @@ -36,7 +36,19 @@ | 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–148 и ограничения ниже. Runtime backend/frontend ux-scope148. | +| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–149 и ограничения ниже. Runtime backend ux-month149/frontend ux-scope148. | + +## 12.5.149 — календарный месяц без тихой подмены и ложных нулей (2026-10-11) + +Supplied malformed month раньше подменялся current UTC month на route; impossible month мог вернуться из service как zero report. Теперь непустой параметр передаётся для strict YYYY-MM/month1..12/year!=0000 validation, invalid получает400 Некорректный месяц отчёта до SQL. Missing/empty optional month сохраняет прежний current UTC default. Runtime non-string тоже отклоняется. + +Calendar bounds больше не используют Date.UTC дляyear1..99 (он превращает их в1901..1999) и не режут extended ISO date at year10000. Integer year/month arithmetic даёт exact next-month day1, average bps используют Gregorian month length/leap rule. Source tests проверяют нормальный month/year rollover,1/99/9999, invalid input rejection before SQL. Normalized labels/DB schema не менялись. + +В candidate Docker5 SQL intervals прошли в own temporary PostgreSQL DB:0001 January,0099 December,1900 February28days,2000 February29days,9999 December/end10000. In-month1200B retained, next-month12000B excluded, average country bps matches exact month length. Fixture DDL выполняется только после exact own DB/current_database check, DB удалена с owner check; working DB/legacy SQLite не использовались fixtures. Both month and scoped-query tests добавлены в normal traffic-flow series. ESLint/backend build/984 output comparison и6 frontend scoped/context tests passed; frontend не пересобирался. + +Backend ux-month149 развёрнут, frontend ux-scope148 сохранён, health healthy. Real API6 invalid months400 иcorrect2026-10→200/same month; initial smoke helper Fetch.status method typo исправлен только в harness, сохранён как setup error. [Full SQL/live receipt](ux-ui/acceptance/flow-month-calendar-2026-10-11.json), [PC/mobile smoke](ux-ui/acceptance/traffic-month-calendar-smoke-2026-10-11.json). Device/password changes нет, rollback retained. Это не расширяет UI на future month selection. + +Следующий небольшой150 — понятные подписи Sessions columns вместо App/Proto/Iface/JH/EN, сохраняя ReUI grid/Frame/base-nova/data semantics.12.5 открыт. ## 12.5.148 — подтверждённый срез потоков без агрегатной подмены (2026-10-11) diff --git a/docs/ux-ui-remaining-acceptance.md b/docs/ux-ui-remaining-acceptance.md index a173a9e3..a0844f8f 100644 --- a/docs/ux-ui-remaining-acceptance.md +++ b/docs/ux-ui-remaining-acceptance.md @@ -1,8 +1,8 @@ # Оставшаяся внешняя приёмка — 2026-10-11 -Это актуальная матрица после 12.5.148. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии. +Это актуальная матрица после 12.5.149. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии. -Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-scope148`, frontend `ux-scope148`; production lab использует AUTH_REQUIRED=false. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись. +Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-month149`, frontend `ux-scope148`; production lab использует AUTH_REQUIRED=false. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись. | Раздел | Подтверждено | Осталось / условие завершения | | --- | --- | --- | @@ -18,7 +18,7 @@ | Recursive routes | Production PUT/change/own history restore88, disabled guards, partial/unknown/applied outcomes, live формы/контекст | Автоматический rollback/retry, durable receipts и несколько экземпляров отсутствуют; PUT не является idempotent noop. Полная произвольная legacy catalog миграция не принята | | Diagnostics/terminal | Allowlist/native reads, Ping/trace/route/MTU/DNS, actual avg-rtt, disabled guards, JWT/live PC/mobile89; real default memory tail/log limits/API/UI133 | DNS использует backend resolver; MTU — ограниченная оценка, не гарантированный максимум. Stop ожидания не доказывает остановку RouterOS команды; arbitrary CLI и physical disconnect не приняты | | Portal/интеграции | Локальные и isolated JWT guards; UI/API error states в границах журналов | Настоящий Portal issuer/login/отзыв прав; RIPE SDK two-IP/no-fetch passed140, native IPv6 ULA flow pipeline passed145 (IPv4 transport), public IPv6 WAN/local licensed MaxMind/S3/container/certificate jobs и Gitea delivery требуют отдельных тестовых endpoints/аккаунтов. Не отправлять сообщения и не публиковать релиз без соответствующего поручения | -| Общая приёмка | Локальная12.3.83; UI1244/context221/compiled984 parity139; профильные build/E2E/live проверки последующих блоков | Traffic initial live mock-id gate passed141; IPv6 special ranges/cache passed142; unknown RIPE coordinates passed143; IPv6 synthetic UDP/worker/SQL passed144; native CHR ULA/IPFIX/API/UI passed145; ICMPv6/IPv6 endpoint labels passed146; HTTP DTO validation passed147; scoped flow catalog/serverId guards passed148; strict monthly calendar149; последующие изменения проверять отдельно; scoped axe не означает полную WCAG сертификацию | +| Общая приёмка | Локальная12.3.83; UI1244/context221/compiled984 parity139; профильные build/E2E/live проверки последующих блоков | Traffic initial live mock-id gate passed141; IPv6 special ranges/cache passed142; unknown RIPE coordinates passed143; IPv6 synthetic UDP/worker/SQL passed144; native CHR ULA/IPFIX/API/UI passed145; ICMPv6/IPv6 endpoint labels passed146; HTTP DTO validation passed147; scoped flow catalog/serverId guards passed148; strict monthly calendar passed149; Sessions labels150; последующие изменения проверять отдельно; scoped axe не означает полную WCAG сертификацию | Доказательства и точные ограничения: [полная история этапа12](ux-ui-stage-12.md), [внешний протокол](ux-ui-external-acceptance.md), [BGP93](ux-ui/acceptance/bgp-failover-2026-10-10.json), [паспорт стенда](ux-ui-test-server.md). Невыполненные пункты не являются все блокерами данного лабораторного сценария: для выпуска необходимо явно определить требуемые конфигурации и принять их либо согласовать ограничение. @@ -516,3 +516,15 @@ Backend больше не извлекает numeric prefix из1suffix и не Backend/frontend ux-scope148 развёрнуты, health healthy. Real API9 malformed cases400 и2 valid scoped cases200; [receipt](ux-ui/acceptance/flow-scoped-query-2026-10-11.json), [PC/mobile readonly smoke](ux-ui/acceptance/traffic-flow-scope-smoke-2026-10-11.json). No native changes/password changes/schema/theme changes; rollback env/images сохранены. Populated native145 не повторялся без необходимости. Следующий149 — строгий month input и calendar bounds: invalid month не должен подменяться текущим или выдаваться за подтверждённый zero report.12.5 открыт. + +## 12.5.149 — календарный месяц без тихой подмены и ложных нулей (2026-10-11) + +Supplied malformed month раньше подменялся current UTC month на route; impossible month мог вернуться из service как zero report. Теперь непустой параметр передаётся для strict YYYY-MM/month1..12/year!=0000 validation, invalid получает400 Некорректный месяц отчёта до SQL. Missing/empty optional month сохраняет прежний current UTC default. Runtime non-string тоже отклоняется. + +Calendar bounds больше не используют Date.UTC дляyear1..99 (он превращает их в1901..1999) и не режут extended ISO date at year10000. Integer year/month arithmetic даёт exact next-month day1, average bps используют Gregorian month length/leap rule. Source tests проверяют нормальный month/year rollover,1/99/9999, invalid input rejection before SQL. Normalized labels/DB schema не менялись. + +В candidate Docker5 SQL intervals прошли в own temporary PostgreSQL DB:0001 January,0099 December,1900 February28days,2000 February29days,9999 December/end10000. In-month1200B retained, next-month12000B excluded, average country bps matches exact month length. Fixture DDL выполняется только после exact own DB/current_database check, DB удалена с owner check; working DB/legacy SQLite не использовались fixtures. Both month and scoped-query tests добавлены в normal traffic-flow series. ESLint/backend build/984 output comparison и6 frontend scoped/context tests passed; frontend не пересобирался. + +Backend ux-month149 развёрнут, frontend ux-scope148 сохранён, health healthy. Real API6 invalid months400 иcorrect2026-10→200/same month; initial smoke helper Fetch.status method typo исправлен только в harness, сохранён как setup error. [Full SQL/live receipt](ux-ui/acceptance/flow-month-calendar-2026-10-11.json), [PC/mobile smoke](ux-ui/acceptance/traffic-month-calendar-smoke-2026-10-11.json). Device/password changes нет, rollback retained. Это не расширяет UI на future month selection. + +Следующий небольшой150 — понятные подписи Sessions columns вместо App/Proto/Iface/JH/EN, сохраняя ReUI grid/Frame/base-nova/data semantics.12.5 открыт. diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index 5410a476..14324572 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -1,6 +1,6 @@ # Этап 12: итоговая приёмка UX/UI -Актуальное продолжение 2026-10-11: завершён12.5.148 — scoped flow HTTP/SSE ждут confirmed catalog; invalid serverId получает400 без aggregate fallback/SSE slot leak.36 E2E,21 backend checks/source+candidate,984 outputs/types/ESLint/both builds,live API+PC/mobile passed. Backend/frontend ux-scope148. Далее149 — strict monthly calendar input/bounds, без подмены invalid month текущим/нулевым отчётом.12.5 открыт. +Актуальное продолжение 2026-10-11: завершён12.5.149 — invalid month400 без current/zero fallback; calendar bounds исправлены для годов1–99/9999 и leap rates.5 SQL intervals в own DB/removed,984 outputs/build/ESLint,6 frontend checks и live400/200+PC/mobile passed. Backend ux-month149/frontend ux-scope148. Далее150 — ясные названия колонок Sessions вместо App/Proto/Iface/JH/EN, без изменения данных/темы.12.5 открыт. План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push. @@ -2906,3 +2906,15 @@ Backend больше не извлекает numeric prefix из1suffix и не Backend/frontend ux-scope148 развёрнуты, health healthy. Real API9 malformed cases400 и2 valid scoped cases200; [receipt](ux-ui/acceptance/flow-scoped-query-2026-10-11.json), [PC/mobile readonly smoke](ux-ui/acceptance/traffic-flow-scope-smoke-2026-10-11.json). No native changes/password changes/schema/theme changes; rollback env/images сохранены. Populated native145 не повторялся без необходимости. Следующий149 — строгий month input и calendar bounds: invalid month не должен подменяться текущим или выдаваться за подтверждённый zero report.12.5 открыт. + +## 12.5.149 — календарный месяц без тихой подмены и ложных нулей (2026-10-11) + +Supplied malformed month раньше подменялся current UTC month на route; impossible month мог вернуться из service как zero report. Теперь непустой параметр передаётся для strict YYYY-MM/month1..12/year!=0000 validation, invalid получает400 Некорректный месяц отчёта до SQL. Missing/empty optional month сохраняет прежний current UTC default. Runtime non-string тоже отклоняется. + +Calendar bounds больше не используют Date.UTC дляyear1..99 (он превращает их в1901..1999) и не режут extended ISO date at year10000. Integer year/month arithmetic даёт exact next-month day1, average bps используют Gregorian month length/leap rule. Source tests проверяют нормальный month/year rollover,1/99/9999, invalid input rejection before SQL. Normalized labels/DB schema не менялись. + +В candidate Docker5 SQL intervals прошли в own temporary PostgreSQL DB:0001 January,0099 December,1900 February28days,2000 February29days,9999 December/end10000. In-month1200B retained, next-month12000B excluded, average country bps matches exact month length. Fixture DDL выполняется только после exact own DB/current_database check, DB удалена с owner check; working DB/legacy SQLite не использовались fixtures. Both month and scoped-query tests добавлены в normal traffic-flow series. ESLint/backend build/984 output comparison и6 frontend scoped/context tests passed; frontend не пересобирался. + +Backend ux-month149 развёрнут, frontend ux-scope148 сохранён, health healthy. Real API6 invalid months400 иcorrect2026-10→200/same month; initial smoke helper Fetch.status method typo исправлен только в harness, сохранён как setup error. [Full SQL/live receipt](ux-ui/acceptance/flow-month-calendar-2026-10-11.json), [PC/mobile smoke](ux-ui/acceptance/traffic-month-calendar-smoke-2026-10-11.json). Device/password changes нет, rollback retained. Это не расширяет UI на future month selection. + +Следующий небольшой150 — понятные подписи Sessions columns вместо App/Proto/Iface/JH/EN, сохраняя ReUI grid/Frame/base-nova/data semantics.12.5 открыт. diff --git a/docs/ux-ui-test-server.md b/docs/ux-ui-test-server.md index 91675548..17dfc225 100644 --- a/docs/ux-ui-test-server.md +++ b/docs/ux-ui-test-server.md @@ -1,6 +1,6 @@ # Тестовый сервер UX/UI — 2026-10-10 -Сервер161.104.46.222: завершён12.5.148 — scoped flow HTTP/SSE ждут confirmed catalog; invalid serverId получает400 без aggregate fallback/SSE slot leak.36 E2E,21 backend checks/source+candidate,984 outputs/types/ESLint/both builds,live API+PC/mobile passed. Backend/frontend ux-scope148. Далее149 — strict monthly calendar input/bounds, без подмены invalid month текущим/нулевым отчётом.12.5 открыт. +Сервер161.104.46.222: завершён12.5.149 — invalid month400 без current/zero fallback; calendar bounds исправлены для годов1–99/9999 и leap rates.5 SQL intervals в own DB/removed,984 outputs/build/ESLint,6 frontend checks и live400/200+PC/mobile passed. Backend ux-month149/frontend ux-scope148. Далее150 — ясные названия колонок Sessions вместо App/Proto/Iface/JH/EN, без изменения данных/темы.12.5 открыт. | Параметр | Проверенное состояние | | --- | --- | @@ -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-scope148 и mmapp-test/frontend:ux-scope148; запущены | +| Образы приложения | mmapp-test/backend:ux-month149 и mmapp-test/frontend:ux-scope148; запущены | Compose SHA256:3052bd6ab5d9865f97605cebfb5ff27a4410cd3357b81153a65a87fb7c92582f. OptionalBACKEND_IMAGE_TAG позволяетобновлятьbackendотдельно. Локальный и удалённый файлы совпали. Шаблоны: [Compose](../deploy/docker-compose.test.yml), [env без секретов](../deploy/env.test.example). @@ -1332,3 +1332,15 @@ Backend больше не извлекает numeric prefix из1suffix и не Backend/frontend ux-scope148 развёрнуты, health healthy. Real API9 malformed cases400 и2 valid scoped cases200; [receipt](ux-ui/acceptance/flow-scoped-query-2026-10-11.json), [PC/mobile readonly smoke](ux-ui/acceptance/traffic-flow-scope-smoke-2026-10-11.json). No native changes/password changes/schema/theme changes; rollback env/images сохранены. Populated native145 не повторялся без необходимости. Следующий149 — строгий month input и calendar bounds: invalid month не должен подменяться текущим или выдаваться за подтверждённый zero report.12.5 открыт. + +## 12.5.149 — календарный месяц без тихой подмены и ложных нулей (2026-10-11) + +Supplied malformed month раньше подменялся current UTC month на route; impossible month мог вернуться из service как zero report. Теперь непустой параметр передаётся для strict YYYY-MM/month1..12/year!=0000 validation, invalid получает400 Некорректный месяц отчёта до SQL. Missing/empty optional month сохраняет прежний current UTC default. Runtime non-string тоже отклоняется. + +Calendar bounds больше не используют Date.UTC дляyear1..99 (он превращает их в1901..1999) и не режут extended ISO date at year10000. Integer year/month arithmetic даёт exact next-month day1, average bps используют Gregorian month length/leap rule. Source tests проверяют нормальный month/year rollover,1/99/9999, invalid input rejection before SQL. Normalized labels/DB schema не менялись. + +В candidate Docker5 SQL intervals прошли в own temporary PostgreSQL DB:0001 January,0099 December,1900 February28days,2000 February29days,9999 December/end10000. In-month1200B retained, next-month12000B excluded, average country bps matches exact month length. Fixture DDL выполняется только после exact own DB/current_database check, DB удалена с owner check; working DB/legacy SQLite не использовались fixtures. Both month and scoped-query tests добавлены в normal traffic-flow series. ESLint/backend build/984 output comparison и6 frontend scoped/context tests passed; frontend не пересобирался. + +Backend ux-month149 развёрнут, frontend ux-scope148 сохранён, health healthy. Real API6 invalid months400 иcorrect2026-10→200/same month; initial smoke helper Fetch.status method typo исправлен только в harness, сохранён как setup error. [Full SQL/live receipt](ux-ui/acceptance/flow-month-calendar-2026-10-11.json), [PC/mobile smoke](ux-ui/acceptance/traffic-month-calendar-smoke-2026-10-11.json). Device/password changes нет, rollback retained. Это не расширяет UI на future month selection. + +Следующий небольшой150 — понятные подписи Sessions columns вместо App/Proto/Iface/JH/EN, сохраняя ReUI grid/Frame/base-nova/data semantics.12.5 открыт. diff --git a/docs/ux-ui/acceptance/flow-month-calendar-2026-10-11.json b/docs/ux-ui/acceptance/flow-month-calendar-2026-10-11.json new file mode 100644 index 00000000..9aa04f3c --- /dev/null +++ b/docs/ux-ui/acceptance/flow-month-calendar-2026-10-11.json @@ -0,0 +1,71 @@ +{ + "date": "2026-10-11", + "block": "12.5.149", + "runtime": { + "backend": "ux-month149", + "frontend": "ux-scope148", + "backendHealthy": true + }, + "checks": { + "sourcePureBoundsAndInvalidBeforeSqlPassed": true, + "sourceAndCandidateInvalidQueryChecksPassed": true, + "ownedSqlCalendarIntervalsPassed": 5, + "ownedDbRemoved": true, + "eslintPassed": true, + "backendProductionBuildPassed": true, + "compiledBackendOutputsMatched": 984, + "frontendScopedChecksPassed": 6, + "liveMonthApi": [ + { + "month": "bad", + "status": 400 + }, + { + "month": "2026-00", + "status": 400 + }, + { + "month": "2026-13", + "status": 400 + }, + { + "month": "0000-01", + "status": 400 + }, + { + "month": "2026-1", + "status": 400 + }, + { + "month": "2026-10junk", + "status": 400 + }, + { + "month": "2026-10", + "status": 200 + } + ] + }, + "sql": { + "date": "2026-10-11", + "name": "mm_accept_149_1791717813194", + "tests": [ + { + "module": "traffic-flow-month", + "passed": true, + "output": "ISOLATED_MONTH_SQL_ACCEPTED: 5 exact intervals, outside rows excluded, leap-year rates verified\ntraffic-flow-month.test.ts: exact month bounds and invalid-before-SQL checks passed\n" + } + ], + "databaseRemoved": true + }, + "bounds": [ + "Malformed supplied month now400, no fallback to current or fake zero report; omitted/empty month retains legacy current UTC default", + "Year0000 rejected consistently with UI; full YYYY-MM/month1..12 required, runtime non-string rejected", + "Integer calendar arithmetic fixes Date.UTC years1..99 offset and extended-year string truncation at December9999", + "Explicit Gregorian leap/month lengths compute average rates without Date.parse limitations for year10000", + "Five own SQL fixtures verify years0001/0099,1900 non-leap,2000 leap and9999 December; sibling next-month rows excluded", + "Own fixture DDL only after exact database name/current_database checks; parent owner-checked removal; main DB/legacy SQLite untouched", + "Initial live receipt helper incorrectly called Fetch response.status as a method; corrected harness only, product400/200 verified afterwards", + "Frontend unchanged; no device/password/schema/theme change, no UI/capability expansion for future month selection" + ] +} diff --git a/docs/ux-ui/acceptance/traffic-month-calendar-smoke-2026-10-11.json b/docs/ux-ui/acceptance/traffic-month-calendar-smoke-2026-10-11.json new file mode 100644 index 00000000..bd21899c --- /dev/null +++ b/docs/ux-ui/acceptance/traffic-month-calendar-smoke-2026-10-11.json @@ -0,0 +1,84 @@ +{ + "date": "2026-10-11", + "block": "12.5.149", + "runtime": { + "backend": "ux-month149", + "frontend": "ux-scope148" + }, + "checks": { + "readOnlyFrontendUnchanged": true, + "backendHealthy": true, + "backendProductionBuildPassed": true + }, + "cases": [ + { + "width": 1440, + "httpStatus": 200, + "api": [ + { + "path": "/api/traffic/bound-interfaces", + "status": 200 + }, + { + "path": "/api/traffic/users", + "status": 200 + }, + { + "path": "/api/traffic/servers", + "status": 200 + }, + { + "path": "/api/traffic/servers/1/live", + "status": 200 + }, + { + "path": "/api/traffic/servers/1/interfaces", + "status": 200 + }, + { + "path": "/api/traffic/servers/1", + "status": 200 + } + ], + "pageErrors": [], + "noOverflow": true + }, + { + "width": 320, + "httpStatus": 200, + "api": [ + { + "path": "/api/traffic/users", + "status": 200 + }, + { + "path": "/api/traffic/bound-interfaces", + "status": 200 + }, + { + "path": "/api/traffic/servers", + "status": 200 + }, + { + "path": "/api/traffic/servers/1/live", + "status": 200 + }, + { + "path": "/api/traffic/servers/1/interfaces", + "status": 200 + }, + { + "path": "/api/traffic/servers/1", + "status": 200 + } + ], + "pageErrors": [], + "noOverflow": true + } + ], + "bounds": [ + "Read-only initial loading with real current lab catalog; no full NetFlow ingestion or throughput claim", + "Catalog failure and pending loading checked by controlled E2E; numeric live server details/interfaces/SSE enabled only after confirmed catalog ownership", + "Interface filter remains selected on range changes; backend source switch invalidates prior catalog ownership" + ] +}