From 8fb46b15524fe6f09f21d345985e7a832d5f778e Mon Sep 17 00:00:00 2001 From: Denozordec Date: Mon, 12 Oct 2026 17:55:16 +0700 Subject: [PATCH] =?UTF-8?q?fix(uptime):=20=D0=BE=D1=82=D0=BB=D0=B8=D1=87?= =?UTF-8?q?=D0=B0=D1=82=D1=8C=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=BE=D0=BF=D1=80=D0=BE=D1=81=D0=B0=20=D0=BE=D1=82=20=D0=B8?= =?UTF-8?q?=D0=B7=D0=BC=D0=B5=D1=80=D0=B5=D0=BD=D0=BD=D1=8B=D1=85=20=D0=BF?= =?UTF-8?q?=D0=BE=D1=82=D0=B5=D1=80=D1=8C=20Ping?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(main)/uptime/page.tsx | 66 +- .../drizzle/0010_ping_measurement_known.sql | 3 + backend/package.json | 2 +- backend/src/db/schema.ts | 2 + backend/src/routes/uptime.ts | 15 +- .../collector-source-signals.test.ts | 4 +- .../alert-engine/collector-source-signals.ts | 4 +- .../src/services/ping-measurement-sql.test.ts | 55 + backend/src/services/ping-measurement.test.ts | 13 + backend/src/services/uptime-collector.ts | 41 +- .../uptime-probe-observations.test.ts | 3 +- .../src/services/uptime-probe-observations.ts | 6 +- backend/src/services/uptime-speed-service.ts | 1 + backend/src/types/scheduler-run-snapshot.ts | 4 +- .../dashboard-active-probes-data-grid.tsx | 4 +- docs/ux-ui-external-acceptance.md | 29 +- docs/ux-ui-plan.md | 6 +- docs/ux-ui-progress.md | 27 +- docs/ux-ui-remaining-acceptance.md | 31 +- docs/ux-ui-stage-12.md | 27 +- docs/ux-ui-test-server.md | 29 +- .../ping-measurement-compiled-2026-10-12.json | 1030 +++++++++++++++++ .../ping-measurement-deployed-2026-10-12.json | 29 + .../ping-measurement-interop-2026-10-12.json | 9 + .../ping-measurement-native-2026-10-12.json | 107 ++ .../ping-measurement-runtime-2026-10-12.json | 30 + .../ping-measurement-source-2026-10-12.json | 11 + .../ping-measurement-sql-2026-10-12.json | 23 + ...ng-measurement-unknown-1440-2026-10-12.png | Bin 0 -> 118824 bytes ...ing-measurement-unknown-320-2026-10-12.png | Bin 0 -> 52320 bytes lib/dashboard-probes.test.ts | 6 + lib/dashboard-probes.ts | 8 +- lib/data-filters/uptime-url-state.ts | 6 +- lib/scheduler-run-snapshot.ts | 4 +- tests/e2e/uptime-ping-measurement.spec.ts | 25 + 35 files changed, 1582 insertions(+), 78 deletions(-) create mode 100644 backend/drizzle/0010_ping_measurement_known.sql create mode 100644 backend/src/services/ping-measurement-sql.test.ts create mode 100644 backend/src/services/ping-measurement.test.ts create mode 100644 docs/ux-ui/acceptance/ping-measurement-compiled-2026-10-12.json create mode 100644 docs/ux-ui/acceptance/ping-measurement-deployed-2026-10-12.json create mode 100644 docs/ux-ui/acceptance/ping-measurement-interop-2026-10-12.json create mode 100644 docs/ux-ui/acceptance/ping-measurement-native-2026-10-12.json create mode 100644 docs/ux-ui/acceptance/ping-measurement-runtime-2026-10-12.json create mode 100644 docs/ux-ui/acceptance/ping-measurement-source-2026-10-12.json create mode 100644 docs/ux-ui/acceptance/ping-measurement-sql-2026-10-12.json create mode 100644 docs/ux-ui/screens/ping-measurement-unknown-1440-2026-10-12.png create mode 100644 docs/ux-ui/screens/ping-measurement-unknown-320-2026-10-12.png create mode 100644 tests/e2e/uptime-ping-measurement.spec.ts diff --git a/app/(main)/uptime/page.tsx b/app/(main)/uptime/page.tsx index f49c782f..e323d3ea 100644 --- a/app/(main)/uptime/page.tsx +++ b/app/(main)/uptime/page.tsx @@ -4,7 +4,7 @@ import { publishSpeedProbeSnapshots } from "@/lib/speed-probe-snapshot" import { parseSpeedRunResponse } from "@/lib/speed-run-response" import { isSpeedPersistenceUnknown } from "@/lib/speed-persistence-outcome" -import { dashboardProbeOverviewSchema } from "@/lib/dashboard-probes" +import { dashboardProbeOverviewSchema, parseDashboardProbes, type DashboardProbe as PingProbe } from "@/lib/dashboard-probes" import { sameSpeedMeasurement, speedHistoryResponseSchema, speedProbesResponseSchema, speedProbesWriteSchema, speedProbesSavedSchema, pingProbesWriteSchema, probeCatalogSavedSchema } from "@mmapp/contracts" @@ -38,7 +38,6 @@ import { UptimeSpeedHistoryDataGrid } from "@/components/data-grids/uptime-speed import { PING_PROBE_WARN_RTT_MS } from "@/lib/ping-probe" import { cn } from "@/lib/utils" import { servers as mockServers, pingProbes as INIT_PROBES, filters, type Server, type Filter } from "@/lib/data" -import type { PingProbe } from "@/lib/data" import { useDataSource } from "@/lib/data-source" import { requestJson } from "@/shared/api/http-client" import { @@ -101,13 +100,15 @@ function jitter(base: number, pct: number) { return Math.round(Math.max(1, base + (Math.random() - 0.5) * base * pct * 2)) } -function rttColor(rtt: number | null, loss: number): string { +function rttColor(rtt: number | null, loss: number | null): string { + if (loss === null) return "text-muted-foreground" if (rtt === null || loss >= 100) return "text-[var(--status-offline-fg)]" if (loss > 1 || rtt > PING_PROBE_WARN_RTT_MS) return "text-[var(--status-degraded-fg)]" return "text-[var(--status-online-fg)]" } function probeSparkColor(status: PingProbe["status"]): string { + if (status === "unknown") return "var(--muted-foreground)" return status === "down" ? "var(--status-offline)" : status === "warn" ? "var(--status-degraded)" : "var(--status-online)" } @@ -118,14 +119,14 @@ function ProbePingRttDetailChart({ probeName, target, }: { - series: number[] + series: Array status: PingProbe["status"] probeName: string target: string }) { const stroke = probeSparkColor(status) - const data = series.map((v) => (v != null && Number.isFinite(v) ? Math.max(0, v) : 0)) - const valid = data.filter((v) => Number.isFinite(v)) + const data = series.map((v) => (v != null && Number.isFinite(v) ? Math.max(0, v) : null)) + const valid = data.filter((v): v is number => v !== null) if (valid.length === 0) { return (

@@ -133,22 +134,25 @@ function ProbePingRttDetailChart({

) } - const chartPts = valid.length >= 2 ? data : [valid[0] ?? 0, valid[0] ?? 0] + const chartPts = data const W = 720 const H = 168 const pad = { l: 48, r: 14, t: 14, b: 36 } const iw = W - pad.l - pad.r const ih = H - pad.t - pad.b - const maxVal = Math.max(...chartPts, 1) - const minVal = Math.min(...chartPts) + const maxVal = Math.max(...valid, 1) + const minVal = Math.min(...valid) const span = Math.max(1, maxVal - minVal) * 1.08 const y0 = minVal - (span - (maxVal - minVal)) / 2 const xAt = (i: number) => pad.l + (chartPts.length <= 1 ? iw / 2 : (i / (chartPts.length - 1)) * iw) const yAt = (v: number) => pad.t + (1 - (v - y0) / span) * ih - const lineD = chartPts - .map((v, i) => `${i === 0 ? "M" : "L"}${xAt(i).toFixed(1)},${yAt(v).toFixed(1)}`) - .join(" ") - const areaD = `${lineD} L ${xAt(chartPts.length - 1).toFixed(1)},${pad.t + ih} L ${pad.l},${pad.t + ih} Z` + const segments: Array> = [] + let segment: Array<{ value: number; index: number }> = [] + chartPts.forEach((value, index) => { + if (value === null) { segment = []; return } + if (!segment.length) segments.push(segment) + segment.push({ value, index }) + }) const gridVals = [0, 0.25, 0.5, 0.75, 1] const fmt = (v: number) => `${Math.round(v)} мс` return ( @@ -191,15 +195,9 @@ function ProbePingRttDetailChart({ ) })} - - + {segments.map((points, index) => points.length === 1 + ? + : `${i ? "L" : "M"}${xAt(point.index).toFixed(1)},${yAt(point.value).toFixed(1)}`).join(" ")} fill="none" stroke={stroke} strokeWidth="2" strokeLinejoin="round" strokeLinecap="round" />)} {[0, Math.floor((chartPts.length - 1) / 2), chartPts.length - 1] .filter((i, idx, a) => a.indexOf(i) === idx) .map((i) => ( @@ -224,7 +222,7 @@ function ProbePingRttDetailChart({ max {Math.round(maxVal)} мс {valid.length < 2 && ( - В ряду одна точка — линия для наглядности продублирована. + В ряду одно измерение. )} @@ -408,7 +406,7 @@ function ServerPickerCards({ {!server.enabled && (

- В инвентаре выключен — для ping всё равно можно выбрать, если бекенд достигает REST API. + Сервер выключен. Включите его перед опросом.

)} @@ -1132,7 +1130,7 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId if (myReq !== overviewReqRef.current || context !== saveContext.current || (savingRef.current && writeContext !== context)) return const overview = dashboardProbeOverviewSchema.safeParse(rawData) if (!overview.success || !pingProbesWriteSchema.safeParse({ probes: overview.data.probes }).success || !rawData || typeof rawData !== "object" || !Array.isArray((rawData as { resources?: unknown }).resources)) throw new Error("Ответ сервера содержит некорректные Ping-пробы.") - const data = { probes: overview.data.probes, resources: (rawData as { resources: ServerResource[] }).resources } + const data = { probes: parseDashboardProbes(rawData).probes, resources: (rawData as { resources: ServerResource[] }).resources } setAllServers(mapBackendServersToServers(serverRows)) setProbes(data.probes) setPingReceivedAt(Date.now()) @@ -1394,6 +1392,7 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId up: probes.filter(p => p.status === "up").length, warn: probes.filter(p => p.status === "warn").length, down: probes.filter(p => p.status === "down").length, + unknown: probes.filter(p => p.status === "unknown").length, }), [probes]) const alertCount = useMemo(() => @@ -1606,9 +1605,9 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId target: newTarget.trim(), filter: newFilter || "—", rtt: null, - loss: 0, - status: "up", - series: Array(40).fill(0), + loss: null, + status: "unknown", + series: [], enabled: true, showOnDashboard: false, } @@ -2382,6 +2381,7 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId cycleStatusFilter("up")} /> cycleStatusFilter("warn")} /> cycleStatusFilter("down")} /> + cycleStatusFilter("unknown")} />
@@ -2548,11 +2548,11 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId >
{!p.enabled ? "Отключена" : p.status === "up" ? "Доступна" : p.status === "warn" ? "Предупреждение" : "Недоступна"}
+ />{!p.enabled ? "Отключена" : p.status === "unknown" ? "Нет данных" : p.status === "up" ? "Доступна" : p.status === "warn" ? "Предупреждение" : "Недоступна"}
Интерфейс{p.srcInterface || "auto"}
-
{p.name}
+
{p.name}{p.observationError &&

Ошибка измерения: {p.observationError}

}
Фильтр{p.filter === "—" ? — : {p.filter} @@ -2561,12 +2561,12 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId {p.rtt === null ? "—" : `${p.rtt}мс`}
Потери= 100 ? "text-red-600 dark:text-red-400" : p.loss > 5 ? "text-red-600 dark:text-red-400" : "text-amber-600 dark:text-amber-400", )}> - {p.loss}% + {p.loss === null ? "—" : `${p.loss}%`}
BT · ping {!linkedSp?.lastRunAt ? ( diff --git a/backend/drizzle/0010_ping_measurement_known.sql b/backend/drizzle/0010_ping_measurement_known.sql new file mode 100644 index 00000000..239552e7 --- /dev/null +++ b/backend/drizzle/0010_ping_measurement_known.sql @@ -0,0 +1,3 @@ +-- A failed command is an attempt, not a measured packet-loss percentage. +ALTER TABLE uptime_probe_samples ADD COLUMN IF NOT EXISTS measurement_known BOOLEAN NOT NULL DEFAULT true; +ALTER TABLE uptime_probe_samples ADD COLUMN IF NOT EXISTS observation_error TEXT; diff --git a/backend/package.json b/backend/package.json index 5ed5a43f..b1f7ddc7 100644 --- a/backend/package.json +++ b/backend/package.json @@ -15,7 +15,7 @@ "db:studio": "drizzle-kit studio", "db:migrate-from-sqlite": "tsx src/scripts/migrate-sqlite-to-pg.ts", "facts:rebuild": "tsx src/scripts/rebuild-flow-facts.ts", - "test:probes": "tsx src/routes/probes.test.ts && tsx src/routes/probes.cancel.test.ts", + "test:probes": "tsx src/routes/probes.test.ts && tsx src/routes/probes.cancel.test.ts && tsx src/services/ping-measurement.test.ts && tsx src/services/uptime-probe-observations.test.ts && tsx src/services/ping-measurement-sql.test.ts", "test:exec": "tsx src/services/mikrotik-logs.test.ts && tsx src/routes/exec.test.ts && tsx src/plugins/auth.exec.test.ts", "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 src/services/wireguard-ipv6.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 src/services/wireguard-delete.test.ts && npx tsx src/routes/wireguard-import.test.ts", diff --git a/backend/src/db/schema.ts b/backend/src/db/schema.ts index 9b7f86fa..22807bd7 100644 --- a/backend/src/db/schema.ts +++ b/backend/src/db/schema.ts @@ -365,6 +365,8 @@ export const uptimeProbeSamples = pgTable("uptime_probe_samples", { rttMs: integer("rtt_ms"), lossPct: integer("loss_pct").notNull().default(0), status: text("status", { enum: ["up", "warn", "down"] }).notNull().default("down"), + measurementKnown: boolean("measurement_known").notNull().default(true), + observationError: text("observation_error"), }, (t) => [ primaryKey({ columns: [t.probeId, t.sampledAt] }), ]) diff --git a/backend/src/routes/uptime.ts b/backend/src/routes/uptime.ts index 683bfe00..61169894 100644 --- a/backend/src/routes/uptime.ts +++ b/backend/src/routes/uptime.ts @@ -241,6 +241,7 @@ const uptimeRoutes: FastifyPluginAsyncZod = async (app) => { if (!Number.isFinite(serverId)) return reply.status(400).send({ error: "Invalid server id" }) const srv = (await db.select().from(servers).where(eq(servers.id, serverId)).limit(1))[0] if (!srv) return reply.status(404).send({ error: "Server not found" }) + if (!srv.enabled) return reply.status(409).send({ error: "Сервер отключён: включите его перед опросом интерфейсов" }) try { const rows = await MikrotikClient.fromServer(srv).getInterfaces() const interfaces = rows @@ -271,6 +272,7 @@ const uptimeRoutes: FastifyPluginAsyncZod = async (app) => { if (!Number.isFinite(serverId)) return reply.status(400).send({ error: "Invalid server id" }) const srv = (await db.select().from(servers).where(eq(servers.id, serverId)).limit(1))[0] if (!srv) return reply.status(404).send({ error: "Server not found" }) + if (!srv.enabled) return reply.status(409).send({ error: "Сервер отключён: включите его перед опросом интерфейсов" }) try { const client = MikrotikClient.fromServer(srv) const [ifaces, ips] = await Promise.all([ @@ -322,8 +324,9 @@ const uptimeRoutes: FastifyPluginAsyncZod = async (app) => { const probes = probeRows.map((p) => { const rows = probeSamples.filter((r) => r.probeId === p.id) - const last = rows[rows.length - 1] - const series = toSeries(rows.map((r) => r.rttMs ?? 0)) + const observations=buildUptimeProbeObservations(rows.filter(r=>Date.parse(r.sampledAt)<=Date.parse(rangeEndAt))) + const last = observations.samples.at(-1) + const series = observations.samples.map(r => r.rtt) return { id: p.id, srcServerId: String(p.srcServerId), @@ -331,13 +334,13 @@ const uptimeRoutes: FastifyPluginAsyncZod = async (app) => { name: p.name, target: p.target, filter: p.probeFilter || "—", - rtt: last?.rttMs ?? null, - loss: last?.lossPct ?? 100, - status: (last?.status ?? "down") as "up" | "warn" | "down", + rtt: last?.rtt ?? null, + loss: last?.loss ?? null, + status: last?.status ?? "unknown", series, enabled: p.enabled, showOnDashboard: p.showOnDashboard, - observations:buildUptimeProbeObservations(rows.filter(r=>Date.parse(r.sampledAt)<=Date.parse(rangeEndAt))), + observations, } }) diff --git a/backend/src/services/alert-engine/collector-source-signals.test.ts b/backend/src/services/alert-engine/collector-source-signals.test.ts index 2505f912..cc8ce80b 100644 --- a/backend/src/services/alert-engine/collector-source-signals.test.ts +++ b/backend/src/services/alert-engine/collector-source-signals.test.ts @@ -67,6 +67,7 @@ assert.equal(run({ resources: [row("uptime_resources", [resource("online")]), ro const due = [row("uptime_ping", [probe("fast")]), row("uptime_ping", [], -15_000), row("uptime_ping", [probe()], -90_000)] assert.deepEqual(run({ ping: due }).probes.map(p => p.key).sort(), ["fast → target", "slow → target"]) assert.deepEqual(run({ ping: [{ finishedAt: iso(), resultJson: null }, ...due] }).probes, []) +for (const malformed of [null, {}, {probeId:""}]) assert.deepEqual(run({ ping: [row("uptime_ping", [malformed]), ...due] }).probes, []) assert.deepEqual(run({ ping: [row("uptime_ping", [probe()], -360_001)] }).probes, []) assert.equal(run({ ping: [row("uptime_ping", [probe()], -360_000)] }).probes.length, 1) assert.deepEqual(run({ ping: [row("uptime_ping", [probe()], 15_001)] }).probes, []) @@ -77,7 +78,8 @@ assert.deepEqual(run({ ping: [row("uptime_ping", [probe(), probe()])] }).probes, assert.deepEqual(run({ ping: [row("uptime_ping", [probe()])] }, { ...config, probes: [{ ...config.probes[0], enabled: false }] }).probes, []) assert.deepEqual(run({ ping: [row("uptime_ping", [probe()])] }, { ...config, probes: [config.probes[0], { ...config.probes[0], id: "duplicate" }] }).probes, []) assert.deepEqual(run({ ping: [row("uptime_ping", [probe()], -1_000)] }, { ...config, servers: [{ ...config.servers[0], updatedAt: iso() }] }).probes, []) -assert.equal(run({ ping: [row("uptime_ping", [{ ...probe(), ok: false, status: "down", rttMs: null, lossPct: 100 }])] }).probes[0].status, "down") +assert.deepEqual(run({ ping: [row("uptime_ping", [{ ...probe(), ok: false, status: "down", rttMs: null, lossPct: 100 }]), row("uptime_ping", [probe()], -15_000)] }).probes, []) +assert.equal(run({ ping: [row("uptime_ping", [{ ...probe(), ok: true, status: "down", rttMs: null, lossPct: 100 }])] }).probes[0].status, "down") assert.equal(probePeriodMs(config, { ...config.probes[0], intervalSec: 1 }), 15_000) // Suppressed measurements do not fire real evaluation paths (offline, loss, low traffic). diff --git a/backend/src/services/alert-engine/collector-source-signals.ts b/backend/src/services/alert-engine/collector-source-signals.ts index 71cd2815..bc65f537 100644 --- a/backend/src/services/alert-engine/collector-source-signals.ts +++ b/backend/src/services/alert-engine/collector-source-signals.ts @@ -31,6 +31,7 @@ function parseRun(row: CollectorRunRow, job: string, field: "servers" | "probes" if (typeof value === "string") { try { value = JSON.parse(value) } catch { return null } } if (!record(value) || value.v !== 1 || value.job !== job || value.skipped === true || value.fatalError || typeof value.sampledAt !== "string" || !Array.isArray(value[field])) return null + if (field === "probes" && value[field].some(row => !record(row) || typeof row.probeId !== "string" || !row.probeId.trim())) return null const sampledMs = Date.parse(value.sampledAt), finishedMs = new Date(row.finishedAt).getTime() if (!Number.isFinite(sampledMs) || !Number.isFinite(finishedMs)) return null return { sampledAt: value.sampledAt, sampledMs, finishedMs, rows: value[field].filter(record) } @@ -104,9 +105,8 @@ function probeSignals(config: CollectorSourceConfig, rows: CollectorRunRow[], no const row = matches[0] if (row.name !== probe.name || row.target !== probe.target || row.srcServerId !== probe.srcServerId || row.srcInterface !== probe.srcInterface || row.srcServerName !== (source.name || source.host) - || typeof row.status !== "string" || !["up", "warn", "down"].includes(row.status) || typeof row.ok !== "boolean" + || typeof row.status !== "string" || !["up", "warn", "down"].includes(row.status) || row.ok !== true || !(row.rttMs === null || finiteNonnegative(row.rttMs)) || !finiteNonnegative(row.lossPct) || row.lossPct > 100) break - if (row.ok === false && (row.status !== "down" || row.rttMs !== null || row.lossPct !== 100)) break out.push({ key: label(probe), status: row.status as ProbeSignal["status"], rttMs: row.rttMs as number | null, lossPct: row.lossPct, sampledAt: run.sampledAt }) break } diff --git a/backend/src/services/ping-measurement-sql.test.ts b/backend/src/services/ping-measurement-sql.test.ts new file mode 100644 index 00000000..494d6ffc --- /dev/null +++ b/backend/src/services/ping-measurement-sql.test.ts @@ -0,0 +1,55 @@ +import assert from "node:assert/strict" +import { createServer } from "node:http" +import Fastify from "fastify" +import { validatorCompiler, serializerCompiler } from "@fastify/type-provider-zod" +import { pool, closePool } from "../db/index.js" +import { initDatabase } from "../db/bootstrap.js" +import { collectPingProbesOnce, collectPingForProbeIds, readProbeSamplesSince } from "./uptime-collector.js" +import { buildUptimeProbeObservations } from "./uptime-probe-observations.js" +import { buildSignalSnapshotFromCollectors } from "./alert-engine/signals-from-collectors.js" +import uptimeRoutes from "../routes/uptime.js" + +const database = process.env.MMAPP_OWN_SQL_DB +if (!database) console.log("ping measurement SQL: skipped; explicit owned database required") +else { + assert(/^mm_accept_166_\d+$/.test(database));assert.equal(new URL(process.env.DATABASE_URL!).pathname,"/"+database) + assert.equal((await pool.query("SELECT current_database() AS name")).rows[0].name,database) + const prefix="own-measure173-"+Date.now()+"-",probeId=prefix+"probe" + let mode:"deny"|"empty"|"loss"|"zero"="deny",requests=0,serverId:number|undefined,cases=0 + const http=createServer((req,res)=>{requests++;res.setHeader("Content-Type","application/json");if(mode==="deny"){res.statusCode=401;res.end(JSON.stringify({error:401,message:"Unauthorized"}))}else res.end(JSON.stringify(mode==="empty"?[]:[{sent:"4",received:mode==="loss"?"0":"4","packet-loss":mode==="loss"?"100":"0","avg-rtt":"0ms"}]))}) + await new Promise(resolve=>http.listen(0,"127.0.0.1",resolve)) + const address=http.address();assert(address&&typeof address==="object") + const app=Fastify();app.setValidatorCompiler(validatorCompiler);app.setSerializerCompiler(serializerCompiler);await app.register(uptimeRoutes) + try { + await initDatabase() + serverId=Number((await pool.query("INSERT INTO servers(name,host,port,use_ssl,enabled,username,password,updated_at) VALUES($1,'127.0.0.1',$2,false,true,'fixture','fixture',$3) RETURNING id",[prefix+"server",address.port,new Date(Date.now()-3_600_000).toISOString()])).rows[0].id) + await pool.query("INSERT INTO uptime_settings(id,enabled,resources_enabled,ping_enabled,interval_sec,probe_interval_sec) VALUES(1,true,false,true,15,15) ON CONFLICT(id) DO UPDATE SET enabled=true,resources_enabled=false,ping_enabled=true,interval_sec=15,probe_interval_sec=15") + await pool.query("INSERT INTO uptime_probes(id,src_server_id,name,target,interval_sec,updated_at) VALUES($1,$2,$3,'192.0.2.1',1,$4)",[probeId,serverId,prefix+"probe",new Date(Date.now()-3_600_000).toISOString()]) + const since=new Date(Date.now()-3_600_000).toISOString() + const latest=async()=>{const rows=await readProbeSamplesSince(since);return buildUptimeProbeObservations(rows.filter(r=>r.probeId===probeId)).samples.at(-1)!} + const snapshot=await collectPingProbesOnce();assert.equal(snapshot.probes[0].ok,false);assert.equal(snapshot.probes[0].status,"unknown");assert.equal(snapshot.probes[0].lossPct,null) + let sample=await latest();assert.equal(sample.status,"unknown");assert.equal(sample.loss,null);assert("error" in sample);cases++ + await pool.query("INSERT INTO scheduler_runs(id,job_key,started_at,finished_at,status,result_json) VALUES($1,'uptime_ping',$2,$3,'error',$4::jsonb)",[prefix+"run",snapshot.sampledAt,new Date().toISOString(),JSON.stringify(snapshot)]) + assert.deepEqual((await buildSignalSnapshotFromCollectors()).probes,[]) + let response=await app.inject({method:"GET",url:"/uptime/overview?range=1h"});assert.equal(response.statusCode,200);let dto=response.json().probes.find((p:{id:string})=>p.id===probeId);assert.equal(dto.status,"unknown");assert.equal(dto.loss,null);cases++ + mode="empty";assert.equal((await collectPingForProbeIds([probeId])).polled,1);sample=await latest();assert.equal(sample.status,"unknown");assert.equal(sample.loss,null);cases++ + mode="loss";await collectPingForProbeIds([probeId]);sample=await latest();assert.equal(sample.status,"down");assert.equal(sample.loss,100);assert.equal(sample.rtt,null);cases++ + mode="zero";await collectPingForProbeIds([probeId]);sample=await latest();assert.equal(sample.status,"up");assert.equal(sample.loss,0);assert.equal(sample.rtt,0);cases++ + response=await app.inject({method:"GET",url:"/uptime/overview?range=1h"});dto=response.json().probes.find((p:{id:string})=>p.id===probeId);assert.equal(dto.status,"up");assert.equal(dto.loss,0);cases++ + // Reclassify exact legacy scheduled failures through retained provenance, without guessing real loss. + const oldTime=new Date(Date.now()-120_000).toISOString() + await pool.query("INSERT INTO uptime_probe_samples(probe_id,sampled_at,status,loss_pct) VALUES($1,$2,'down',100)",[probeId,oldTime]) + await pool.query("INSERT INTO scheduler_runs(id,job_key,started_at,finished_at,status,result_json) VALUES($1,'uptime_ping',$2,$2,'error',$3::jsonb)",[prefix+"legacy",oldTime,JSON.stringify({v:1,job:"uptime_ping",sampledAt:oldTime,probes:[{probeId,ok:false,error:"legacy API denial"}]})]) + const old=(await readProbeSamplesSince(since)).find(r=>r.probeId===probeId&&Date.parse(r.sampledAt)===Date.parse(oldTime))!;assert.equal(old.measurementKnown,false);cases++ + await pool.query("UPDATE servers SET enabled=false WHERE id=$1",[serverId]);const before=requests + for(const url of[`/uptime/sources/${serverId}/interfaces`,`/uptime/speed-test/endpoints/${serverId}/interfaces`]){response=await app.inject({method:"GET",url});assert.equal(response.statusCode,409)} + assert.equal((await collectPingForProbeIds([probeId])).polled,0);assert.equal(requests,before);cases++ + console.log(`ping measurement owned SQL: ${cases} cases; real HTTP client/401/empty/manual/loss/zero/API/provenance/disabled guards passed`) + }finally{ + await app.close();await new Promise(resolve=>http.close(()=>resolve())) + await pool.query("DELETE FROM scheduler_runs WHERE id LIKE $1",[prefix+"%"]) + await pool.query("DELETE FROM uptime_probe_samples WHERE probe_id=$1",[probeId]);await pool.query("DELETE FROM uptime_probes WHERE id=$1",[probeId]) + if(serverId!==undefined)await pool.query("DELETE FROM servers WHERE id=$1",[serverId]) + await closePool() + } +} diff --git a/backend/src/services/ping-measurement.test.ts b/backend/src/services/ping-measurement.test.ts new file mode 100644 index 00000000..3066d1b0 --- /dev/null +++ b/backend/src/services/ping-measurement.test.ts @@ -0,0 +1,13 @@ +import assert from "node:assert/strict" +import { parsePing } from "./uptime-collector.js" +for (const rows of [[], [{}], [{status:"not enough permissions"}], [{sent:"0",received:"0","packet-loss":"0","avg-rtt":"0ms"}], [{sent:"4",received:"5","packet-loss":"0"}], [{sent:"4bad",received:"4","packet-loss":"0"}]]) { + const result=parsePing(rows) + assert.equal(result.measured,false);assert.equal(result.avgRtt,null) +} +const down=parsePing([{sent:"4",received:"0","packet-loss":"100","avg-rtt":"0ms"}]) +assert.equal(down.measured,true);assert.equal(down.status,"down");assert.equal(down.loss,100);assert.equal(down.avgRtt,null) +const up=parsePing([{sent:"4",received:"4","packet-loss":"0","avg-rtt":"0ms"}]) +assert.equal(up.measured,true);assert.equal(up.status,"up");assert.equal(up.loss,0);assert.equal(up.avgRtt,0) +const partial=parsePing([{time:"500us"},{status:"timeout"}]) +assert.equal(partial.measured,true);assert.equal(partial.loss,50) +console.log("ping measurement: empty/unsupported is not a measurement; zero and real 100 percent loss preserved") diff --git a/backend/src/services/uptime-collector.ts b/backend/src/services/uptime-collector.ts index 70a46124..ffea809d 100644 --- a/backend/src/services/uptime-collector.ts +++ b/backend/src/services/uptime-collector.ts @@ -2,7 +2,7 @@ import { replacePingProbes } from "./ping-probe-replacement.js" import type { PingProbeWrite } from "@mmapp/contracts" import { and, asc, eq, gte, inArray, max, sql } from "drizzle-orm" import { listEnabledMonitoringServers } from "./monitoring-server-catalog.js" -import { db } from "../db/index.js" +import { db, dbAll } from "../db/index.js" import { uptimeProbeSamples, uptimeProbes, @@ -76,7 +76,7 @@ export function parsePing(results: Array<{ time?: string; status?: string; sent? .filter((v): v is number => v != null && Number.isFinite(v)) const avgFromReplies = rtts.length ? Math.round(rtts.reduce((a, b) => a + b, 0) / rtts.length) : null const avgFromSummary = parsePingTimeMs(sum?.time) ?? parsePingTimeMs(sum?.["avg-rtt"]) - const avgRtt = avgFromReplies ?? (avgFromSummary != null ? Math.round(avgFromSummary) : null) + let avgRtt = avgFromReplies ?? (avgFromSummary != null ? Math.round(avgFromSummary) : null) const sent = Number.parseInt(String(sum?.sent ?? ""), 10) const received = Number.parseInt(String(sum?.received ?? ""), 10) const sentFallback = okReplies.length + timedOutReplies.length @@ -86,13 +86,17 @@ export function parsePing(results: Array<{ time?: string; status?: string; sent? const lossRaw = String(sum?.["packet-loss"] ?? "") const parsedLoss = Number.parseInt(lossRaw.replace("%", ""), 10) const lossSource = Number.isFinite(parsedLoss) ? parsedLoss : computedLoss + const validCounts = (sum?.sent === undefined || (/^\d+$/.test(String(sum.sent).trim()) && Number.isSafeInteger(sent) && sent > 0)) + && (sum?.received === undefined || (/^\d+$/.test(String(sum.received).trim()) && Number.isSafeInteger(received) && received >= 0 && (sum?.sent === undefined || received <= sent))) + const measured = validCounts && typeof lossSource === "number" && Number.isFinite(lossSource) && lossSource >= 0 && lossSource <= 100 const loss = typeof lossSource === "number" && Number.isFinite(lossSource) ? Math.max(0, Math.min(100, lossSource)) : 100 const status: "up" | "warn" | "down" = loss >= 100 ? "down" : (loss > 1 || (avgRtt ?? 0) > PING_PROBE_WARN_RTT_MS ? "warn" : "up") - return { avgRtt, loss, status } + if (loss === 100 || !measured) avgRtt = null + return { avgRtt, loss, status, measured } } /** Disabled excludes every server type from resource and ping collection. */ @@ -269,6 +273,7 @@ export async function collectPingProbesOnce(): Promise { const client = MikrotikClient.fromServer(src) const results = await client.ping(p.target, 4, p.srcInterface || undefined) const parsed = parsePing(results) + if (!parsed.measured) throw new Error("RouterOS не вернул подтверждённые результаты Ping") await db.insert(uptimeProbeSamples).values({ probeId: p.id, sampledAt: now, @@ -295,6 +300,8 @@ export async function collectPingProbesOnce(): Promise { rttMs: null, lossPct: 100, status: "down", + measurementKnown: false, + observationError: err instanceof Error ? err.message : String(err), }) snapshot.probes.push({ probeId: p.id, @@ -305,8 +312,8 @@ export async function collectPingProbesOnce(): Promise { srcInterface: p.srcInterface || "", ok: false, rttMs: null, - lossPct: 100, - status: "down", + lossPct: null, + status: "unknown", error: err instanceof Error ? err.message : String(err), }) } @@ -367,6 +374,7 @@ export async function collectPingForProbeIds(probeIds: string[]): Promise<{ poll const client = MikrotikClient.fromServer(src) const results = await client.ping(p.target, 4, p.srcInterface || undefined) const parsed = parsePing(results) + if (!parsed.measured) throw new Error("RouterOS не вернул подтверждённые результаты Ping") await db.insert(uptimeProbeSamples).values({ probeId: p.id, sampledAt: now, @@ -374,13 +382,15 @@ export async function collectPingForProbeIds(probeIds: string[]): Promise<{ poll lossPct: parsed.loss, status: parsed.status, }) - } catch { + } catch (err) { await db.insert(uptimeProbeSamples).values({ probeId: p.id, sampledAt: now, rttMs: null, lossPct: 100, status: "down", + measurementKnown: false, + observationError: err instanceof Error ? err.message : String(err), }) } polled += 1 @@ -482,9 +492,26 @@ export async function replaceProbes(rows: PingProbeWrite[]) { } export async function readProbeSamplesSince(sinceIso: string) { - return await db.select().from(uptimeProbeSamples) + const rows = await db.select().from(uptimeProbeSamples) .where(gte(uptimeProbeSamples.sampledAt, sinceIso)) .orderBy(asc(uptimeProbeSamples.sampledAt)) + if (!rows.length) return rows + // Old scheduled failures can be classified only when their exact snapshot provenance still exists. + const failures = await dbAll<{probeId: string | null; sampledAt: string | null; error: string | null}>(` + SELECT probe->>'probeId' AS "probeId", result_json->>'sampledAt' AS "sampledAt", probe->>'error' AS error + FROM scheduler_runs CROSS JOIN LATERAL jsonb_array_elements( + CASE WHEN jsonb_typeof(result_json->'probes')='array' THEN result_json->'probes' ELSE '[]'::jsonb END) probe + WHERE job_key='uptime_ping' AND status IN ('ok','error') AND finished_at >= ? + AND result_json->'v'='1'::jsonb AND result_json->>'job'='uptime_ping' AND probe->'ok'='false'::jsonb`, [sinceIso]) + const bySample = new Map() + for (const failure of failures) { + if (!failure.probeId || !failure.sampledAt || !Number.isFinite(Date.parse(failure.sampledAt))) continue + bySample.set(`${failure.probeId}\0${Date.parse(failure.sampledAt)}`, failure.error ?? "Результат измерения не подтверждён") + } + return rows.map(row => { + const error = bySample.get(`${row.probeId}\0${Date.parse(row.sampledAt)}`) + return error === undefined ? row : {...row,measurementKnown:false,observationError:error} + }) } export async function readResourceSamplesSince(sinceIso: string, serverId: number) { diff --git a/backend/src/services/uptime-probe-observations.test.ts b/backend/src/services/uptime-probe-observations.test.ts index 343c01db..85827729 100644 --- a/backend/src/services/uptime-probe-observations.test.ts +++ b/backend/src/services/uptime-probe-observations.test.ts @@ -2,5 +2,6 @@ import assert from "node:assert/strict" import {buildUptimeProbeObservations} from "./uptime-probe-observations.js" assert.deepEqual(buildUptimeProbeObservations([]),{samples:[]}) const good={sampledAt:"2026-10-03T00:00:00Z",rttMs:0,lossPct:0,status:"up" as const},failed={sampledAt:"2026-10-03T00:01:00Z",rttMs:null,lossPct:100,status:"down" as const} -assert.deepEqual(buildUptimeProbeObservations([failed,good]),{samples:[{sampledAt:good.sampledAt,rtt:0,loss:0,status:"up"},{sampledAt:failed.sampledAt,rtt:null,loss:100,status:"down"}]});assert.equal(good.rttMs,0) +assert.deepEqual(buildUptimeProbeObservations([failed,good]),{samples:[{sampledAt:new Date(good.sampledAt).toISOString(),rtt:0,loss:0,status:"up"},{sampledAt:new Date(failed.sampledAt).toISOString(),rtt:null,loss:100,status:"down"}]});assert.equal(good.rttMs,0) +assert.deepEqual(buildUptimeProbeObservations([{...failed,measurementKnown:false,observationError:"API denied"}]).samples,[{sampledAt:new Date(failed.sampledAt).toISOString(),rtt:null,loss:null,status:"unknown",error:"API denied"}]) console.log("uptime probe observations tests ok") diff --git a/backend/src/services/uptime-probe-observations.ts b/backend/src/services/uptime-probe-observations.ts index b40e7c3b..a428fc63 100644 --- a/backend/src/services/uptime-probe-observations.ts +++ b/backend/src/services/uptime-probe-observations.ts @@ -1,4 +1,6 @@ import type {UptimeProbeSampleRow} from "../db/schema.js" -type Sample=Pick +type Sample=Pick&Partial> /** Additive metadata; no samples means no proof of loss or failure. */ -export function buildUptimeProbeObservations(rows:readonly Sample[]){return {samples:rows.map(row=>({sampledAt:row.sampledAt,rtt:row.rttMs,loss:row.lossPct,status:row.status})).sort((a,b)=>Date.parse(a.sampledAt)-Date.parse(b.sampledAt))}} +export function buildUptimeProbeObservations(rows:readonly Sample[]){return {samples:rows.map(row=>row.measurementKnown===false + ? {sampledAt:new Date(row.sampledAt).toISOString(),rtt:null,loss:null,status:"unknown" as const,error:row.observationError??"Результат измерения не подтверждён"} + : {sampledAt:new Date(row.sampledAt).toISOString(),rtt:row.rttMs,loss:row.lossPct,status:row.status}).sort((a,b)=>Date.parse(a.sampledAt)-Date.parse(b.sampledAt))}} diff --git a/backend/src/services/uptime-speed-service.ts b/backend/src/services/uptime-speed-service.ts index 89f5e1fa..7725ea11 100644 --- a/backend/src/services/uptime-speed-service.ts +++ b/backend/src/services/uptime-speed-service.ts @@ -128,6 +128,7 @@ export async function executeBandwidthSpeedTestInner(params: SpeedTestRunParams) try { const pingRows = await srcClient.ping(initialAddress, 4, srcInterface || undefined, { interval: "1s" }) const pr = parsePing(pingRows as Parameters[0]) + if (!pr.measured) throw new Error("RouterOS не вернул подтверждённые результаты контрольного Ping") afterBtPing = { rttMs: pr.avgRtt, lossPct: pr.loss, error: null } } catch (pe) { afterBtPing = { diff --git a/backend/src/types/scheduler-run-snapshot.ts b/backend/src/types/scheduler-run-snapshot.ts index e6936b4a..09d4c53e 100644 --- a/backend/src/types/scheduler-run-snapshot.ts +++ b/backend/src/types/scheduler-run-snapshot.ts @@ -65,8 +65,8 @@ export interface PingProbeSnapshot { srcInterface: string ok: boolean rttMs: number | null - lossPct: number - status: "up" | "warn" | "down" + lossPct: number | null + status: "up" | "warn" | "down" | "unknown" error?: string } diff --git a/components/data-grids/dashboard-active-probes-data-grid.tsx b/components/data-grids/dashboard-active-probes-data-grid.tsx index 5329b9f3..d6581810 100644 --- a/components/data-grids/dashboard-active-probes-data-grid.tsx +++ b/components/data-grids/dashboard-active-probes-data-grid.tsx @@ -168,9 +168,9 @@ function DashboardActiveProbesDataGrid({ header: "Статус", enableSorting: false, cell: (p) => ( - + /> ), }, ], diff --git a/docs/ux-ui-external-acceptance.md b/docs/ux-ui-external-acceptance.md index a584cba2..222ef230 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-12: завершён12.5.172 — monitoring history использует общие confirmed collector facts внутри readonly transaction; disabled/stale/error/unknown не поднимают raw samples. Старый backend реально показывал disabled/hour-old sample; Date schema failure не воспроизведён. OwnSQL26+GRE8/stability/source437/compiled1020/build/4E2E/2live UI passed; own DB/roles removed. Backend ux-history172 healthy, frontend ux-targets171 прежний, оба CHR selected API200. Следующий173 — различить ошибку выполнения Ping и измеренные потери: producer catch пишет100%, guard пока принимает ok=false down;12.5 открыт. +Актуальное продолжение 2026-10-12: завершён12.5.173 — Ping execution error/empty payload не считается100% loss: measurement_known/error provenance, nullable unknown API/UI, scheduled/manual/after-BT, malformed/zero/inconsistent count guards.85 unique E2E initial83+delta3, ownSQL26+Ping8+GRE8/stability, source440+518/compiled1020/build passed. Migration0010 applied; оба ux-ping173 healthy/running; real CHR Ping4/4 both/0%/53ms, config/password unchanged. Own DB/role removed. Следующий174 — audit first-wave/invalid traffic rate confidence; legacy unclassified manual samples и source unconfirmed diagnostics остаются;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-12: завершён12.5.172 — monitoring history использует общие confirmed collector facts внутри readonly transaction; disabled/stale/error/unknown не поднимают raw samples. Старый backend реально показывал disabled/hour-old sample; Date schema failure не воспроизведён. OwnSQL26+GRE8/stability/source437/compiled1020/build/4E2E/2live UI passed; own DB/roles removed. Backend ux-history172 healthy, frontend ux-targets171 прежний, оба CHR selected API200. Следующий173 — различить ошибку выполнения Ping и измеренные потери: producer catch пишет100%, guard пока принимает ok=false down;12.5 открыт. +Актуальное продолжение 2026-10-12: завершён12.5.173 — Ping execution error/empty payload не считается100% loss: measurement_known/error provenance, nullable unknown API/UI, scheduled/manual/after-BT, malformed/zero/inconsistent count guards.85 unique E2E initial83+delta3, ownSQL26+Ping8+GRE8/stability, source440+518/compiled1020/build passed. Migration0010 applied; оба ux-ping173 healthy/running; real CHR Ping4/4 both/0%/53ms, config/password unchanged. Own DB/role removed. Следующий174 — audit first-wave/invalid traffic rate confidence; legacy unclassified manual samples и source unconfirmed diagnostics остаются;12.5 открыт. Подробный объём, протоколы, схема и ограничения: [12.5.77 в журнале](ux-ui-progress.md). @@ -1948,3 +1948,28 @@ Source/build/unit/ESLint прошли. Локальные alert-engine и12 ск Протоколы: [first baseline hypothesis](ux-ui/acceptance/monitoring-history-baseline-attempt-2026-10-12.json), [real baseline](ux-ui/acceptance/monitoring-history-baseline-2026-10-12.json), [SQL26/cleanup](ux-ui/acceptance/monitoring-history-sql-2026-10-12.json), [source437](ux-ui/acceptance/monitoring-history-source-2026-10-12.json), [compiled1020](ux-ui/acceptance/monitoring-history-compiled-2026-10-12.json), [runtime](ux-ui/acceptance/monitoring-history-runtime-2026-10-12.json), [API/UI](ux-ui/acceptance/monitoring-history-deployed-2026-10-12.json). Следующий173 — source finding: collectPingProbesOnce/pingProbesNow catch превращает ошибку REST-команды в lossPct100/down; decoder пока разрешает ok=false/down/null/100. Это не независимое доказательство потери пакетов. Проверить и исправить подтверждение измерений/empty payload; не объявлять legacy raw UI stats и manual provenance уже исправленными. Также остаются unconfirmed/missing target diagnostics, stable-ID migration, scale/multi-instance/physical WAN и внешние Telegram/Portal/client OS.12.5 открыт. Локальный коммит без push. + +## 12.5.173 — ошибка Ping не подменяет измеренные потери (2026-10-12) + +Завершён целый раздел producer→SQL→API→UI. parsePing теперь возвращает measured: empty/unsupported replies, zero sent и противоречивые/malformed counts не подтверждают процент потерь. Подтверждённые received0/sent4 остаются100%/down с RTTnull; успешный настоящий RTT0/loss0 сохранён. Scheduled и manual collectors различают успешное измерение и ошибку REST/недостаточный ответ; after-bandwidth Ping при неизвестном результате возвращает null metrics/error, а не выдуманный outage. Alert decoder принимает только ok=true и известное состояние; retained legacy ok=false/down/100 не срабатывает как измеренная потеря. Malformed due-probe rows без ID — barrier, не разрешение восстановить старое измерение. + +Миграция0010 добавляет uptime_probe_samples.measurement_known boolean defaulttrue и observation_error text; CHECK/legacy storage status и integer loss не ломаются. Raw100/down в failed rows — прежние storage sentinel с явным measurement_known=false, не публичное измерение. Snapshot/public observations/API loss становятсяnull/statusunknown, есть причина ошибки. Новые flags пишутся при scheduled и manual error/empty response. Старые scheduled failure samples переклассифицируются readonly по точному probeId+sampledAt retained v1 JSON provenance; genuine100% без failure flag не меняется. Старые manual/retention-lost failures без provenance надёжно восстановить нельзя: они остаются legacy ambiguity, полного historical cleanup не заявляем. Migration additive, прошлые данные не удаляет; source JSON internal v1 теперь допускает nullable loss/unknown. Для rollback старый код не интерпретирует measurement_known, поэтому совместимость хранения не означает корректное чтение новых unknown attempts старой версией. + +Overview выдаёт unknown/null без fallback100/down и без padded historical0. Observations timestamps канонизированы; parser проверяет согласованность unknown/null. /uptime использует тот же observed decoder, что dashboard: «Нет данных», — для RTT/loss, причина execution error, отдельный filter/URLstatus=unknown. Новая draft probe тоже не объявляется заранее up/0. Мини-Sparkline сохраняет gaps; expanded RTT chart больше не превращаетnull в0 и не дублирует одиночную точку для выдуманной линии. Dashboard status tooltip получает причину. Source/speed-interface read endpoints теперь409 до RouterOS при disabled server; ошибочная подпись «для ping всё равно можно выбрать выключенный» исправлена. Это соответствует прямому правилу пользователя не опрашивать выключенные устройства любых типов. UI/ReUI primitives/theme не заменены. + +| Состояние | Эталон | Результат | +| --- | --- | --- | +| Ошибка REST/нет измерения | Существующие probe rows/StatusDot/Sparkline | [ПК1440](ux-ui/screens/ping-measurement-unknown-1440-2026-10-12.png), [телефон320](ux-ui/screens/ping-measurement-unknown-320-2026-10-12.png) — API-fixture, не production positive sample | +| Подтверждённые100% | Тот же public observed path | Down и100% сохранены отдельным positive control | + +Unit parser/observations/frontend dashboard-probes/URL guards прошли; backend build, application-source TypeScript и ESLint прошли (в uptime-collector два прежних unused settings warnings, functional scope не расширен). Первый npm --prefix exec взял неверный cwd, повтор direct tsx корректен; новый тест сначала использовал неверное имя Zod package, исправлен на установленный @fastify/type-provider-zod. Local cancellation test получил sandbox EACCES, разрешённый сетевой профиль прошёл; local own SQL skipped без выделенной базы.85 unique E2E Uptime/Ping/Speed: initial83passed/2failed были новые ambiguous test selectors (имя есть в sidebar и строке); selector исправлен на конкретную причину ошибки, delta3passed9.5s. Existing83 scenarios повторно не запускались;2 screenshot captures passed8.5s отдельно. Это не85 clean single-run и не87 новых сценариев. Два перезаписанных тестами прежних speed images восстановлены SHA256;8 user dirty GRE/VXLAN images не тронуты. Новые снимки просмотрены, nullable состояние читаемо при320px. + +Owned Node22/PG18.6 профили alert-engine+probes прошли42380ms:26 source/history SQL cases, GRE/BGP8/actual stability и8 новых Ping cases. Последние проходят через реальный MikrotikClient HTTP к loopback simulator, persisted flags, GET overview Fastify path:401,empty,manual,real loss/zero,API,legacy exact provenance и disabled no-outbound requests. Simulator не является физическим RouterOS/network failure. Финальные unit guards zero/inconsistent counts/malformed probe проверены перед scoped rerun; temporary DB/role/container удалены. Production credentials/JWT не копировались. Type/producer tests подключены к стандартному test:probes; default profile count не увеличен. + +Source440 backend+518 frontend inputs/1020 emitted parity совпали SHA256. Both production builds passed, backend image включает новый drizzle0010. Staged deploy: frontend ux-ping173 сначала принял старый API ux-history172, затем backend ux-ping173/version1.0.0-test.ping173. Migration0010 и2 новых columns подтверждены readonly в main PG. Backend/PG healthy, frontend running/HTTP/UI, imageIds verified, running containers3. В.env менялись толькоimage tags; rollback tags сохранились отдельно без secret env копии. Cached прежний frontend следует перезагрузить: новые nullable/unknown DTO требуют совместимого reader. + +Независимо выполнен настоящий native Ping4 packets194.226.121.150→104.165.244.7 иобратно через running compiled173: обе команды приняты, обе4/4,0%loss, parsed RTT53ms. Это короткое наблюдение, не WAN SLA/max throughput. Main DB read-only, native конфигурация/пароли не менялись;2 operational Ping commands, routerConfigWrites0/messages0. На production configured probes0:2live UI1440/320 подтверждают actual empty catalog/new filter/reader/overflow/pageerrors0; nonempty/error UI доказан fixtures, не выдан за main positive measurement. Collector row/error flags проверены ownSQL, real parser отдельно native payloads. + +Протоколы: [SQL/HTTP8+cleanup](ux-ui/acceptance/ping-measurement-sql-2026-10-12.json), [native4/4](ux-ui/acceptance/ping-measurement-native-2026-10-12.json), [source](ux-ui/acceptance/ping-measurement-source-2026-10-12.json), [compiled](ux-ui/acceptance/ping-measurement-compiled-2026-10-12.json), [interop](ux-ui/acceptance/ping-measurement-interop-2026-10-12.json), [runtime](ux-ui/acceptance/ping-measurement-runtime-2026-10-12.json), [live UI](ux-ui/acceptance/ping-measurement-deployed-2026-10-12.json). + +Следующий174 — audit first-wave/invalid traffic rate confidence: ok request и numeric sum не всегда доказывают измеренный rate без previous wave. Не заявлять эту гипотезу как уже выполненное исправление. Source unconfirmed/missing target diagnostics, manual history provenance, unsupported/compound time formats, legacy ambiguous samples, scale/multi-instance/physical WAN/external integrations остаются отдельными условиями12.5. Полная acceptance открыта. Локальный коммит без push. diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index ca552497..c65cdde3 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -1,12 +1,12 @@ # План реализации UX/UI по пятичасовым окнам -Актуальное продолжение 2026-10-12: завершён12.5.172 — monitoring history использует общие confirmed collector facts внутри readonly transaction; disabled/stale/error/unknown не поднимают raw samples. Старый backend реально показывал disabled/hour-old sample; Date schema failure не воспроизведён. OwnSQL26+GRE8/stability/source437/compiled1020/build/4E2E/2live UI passed; own DB/roles removed. Backend ux-history172 healthy, frontend ux-targets171 прежний, оба CHR selected API200. Следующий173 — различить ошибку выполнения Ping и измеренные потери: producer catch пишет100%, guard пока принимает ok=false down;12.5 открыт. +Актуальное продолжение 2026-10-12: завершён12.5.173 — Ping execution error/empty payload не считается100% loss: measurement_known/error provenance, nullable unknown API/UI, scheduled/manual/after-BT, malformed/zero/inconsistent count guards.85 unique E2E initial83+delta3, ownSQL26+Ping8+GRE8/stability, source440+518/compiled1020/build passed. Migration0010 applied; оба ux-ping173 healthy/running; real CHR Ping4/4 both/0%/53ms, config/password unchanged. Own DB/role removed. Следующий174 — audit first-wave/invalid traffic rate confidence; legacy unclassified manual samples и source unconfirmed diagnostics остаются;12.5 открыт. Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-12. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат. ## Актуальная точка продолжения — 2026-10-12 -Актуальное продолжение 2026-10-12: завершён12.5.172 — monitoring history использует общие confirmed collector facts внутри readonly transaction; disabled/stale/error/unknown не поднимают raw samples. Старый backend реально показывал disabled/hour-old sample; Date schema failure не воспроизведён. OwnSQL26+GRE8/stability/source437/compiled1020/build/4E2E/2live UI passed; own DB/roles removed. Backend ux-history172 healthy, frontend ux-targets171 прежний, оба CHR selected API200. Следующий173 — различить ошибку выполнения Ping и измеренные потери: producer catch пишет100%, guard пока принимает ok=false down;12.5 открыт. +Актуальное продолжение 2026-10-12: завершён12.5.173 — Ping execution error/empty payload не считается100% loss: measurement_known/error provenance, nullable unknown API/UI, scheduled/manual/after-BT, malformed/zero/inconsistent count guards.85 unique E2E initial83+delta3, ownSQL26+Ping8+GRE8/stability, source440+518/compiled1020/build passed. Migration0010 applied; оба ux-ping173 healthy/running; real CHR Ping4/4 both/0%/53ms, config/password unchanged. Own DB/role removed. Следующий174 — audit first-wave/invalid traffic rate confidence; legacy unclassified manual samples и source unconfirmed diagnostics остаются;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-12: завершён12.5.172 — monitoring history использует общие confirmed collector facts внутри readonly transaction; disabled/stale/error/unknown не поднимают raw samples. Старый backend реально показывал disabled/hour-old sample; Date schema failure не воспроизведён. OwnSQL26+GRE8/stability/source437/compiled1020/build/4E2E/2live UI passed; own DB/roles removed. Backend ux-history172 healthy, frontend ux-targets171 прежний, оба CHR selected API200. Следующий173 — различить ошибку выполнения Ping и измеренные потери: producer catch пишет100%, guard пока принимает ok=false down;12.5 открыт. +Актуальное продолжение 2026-10-12: завершён12.5.173 — Ping execution error/empty payload не считается100% loss: measurement_known/error provenance, nullable unknown API/UI, scheduled/manual/after-BT, malformed/zero/inconsistent count guards.85 unique E2E initial83+delta3, ownSQL26+Ping8+GRE8/stability, source440+518/compiled1020/build passed. Migration0010 applied; оба ux-ping173 healthy/running; real CHR Ping4/4 both/0%/53ms, config/password unchanged. Own DB/role removed. Следующий174 — audit first-wave/invalid traffic rate confidence; legacy unclassified manual samples и source unconfirmed diagnostics остаются;12.5 открыт. Подробный объём, протоколы, схема и ограничения: [12.5.77 в журнале](ux-ui-progress.md). diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index 591ea7ae..8d5a2d7b 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -1,6 +1,6 @@ # UX/UI: журнал реализации -Актуальное продолжение 2026-10-12: завершён12.5.172 — monitoring history использует общие confirmed collector facts внутри readonly transaction; disabled/stale/error/unknown не поднимают raw samples. Старый backend реально показывал disabled/hour-old sample; Date schema failure не воспроизведён. OwnSQL26+GRE8/stability/source437/compiled1020/build/4E2E/2live UI passed; own DB/roles removed. Backend ux-history172 healthy, frontend ux-targets171 прежний, оба CHR selected API200. Следующий173 — различить ошибку выполнения Ping и измеренные потери: producer catch пишет100%, guard пока принимает ok=false down;12.5 открыт. +Актуальное продолжение 2026-10-12: завершён12.5.173 — Ping execution error/empty payload не считается100% loss: measurement_known/error provenance, nullable unknown API/UI, scheduled/manual/after-BT, malformed/zero/inconsistent count guards.85 unique E2E initial83+delta3, ownSQL26+Ping8+GRE8/stability, source440+518/compiled1020/build passed. Migration0010 applied; оба ux-ping173 healthy/running; real CHR Ping4/4 both/0%/53ms, config/password unchanged. Own DB/role removed. Следующий174 — audit first-wave/invalid traffic rate confidence; legacy unclassified manual samples и source unconfirmed diagnostics остаются;12.5 открыт. Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md). @@ -38,6 +38,31 @@ | 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 | | 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–168 и ограничения ниже. Runtime backend ux-month149/frontend ux-sessions150. | +## 12.5.173 — ошибка Ping не подменяет измеренные потери (2026-10-12) + +Завершён целый раздел producer→SQL→API→UI. parsePing теперь возвращает measured: empty/unsupported replies, zero sent и противоречивые/malformed counts не подтверждают процент потерь. Подтверждённые received0/sent4 остаются100%/down с RTTnull; успешный настоящий RTT0/loss0 сохранён. Scheduled и manual collectors различают успешное измерение и ошибку REST/недостаточный ответ; after-bandwidth Ping при неизвестном результате возвращает null metrics/error, а не выдуманный outage. Alert decoder принимает только ok=true и известное состояние; retained legacy ok=false/down/100 не срабатывает как измеренная потеря. Malformed due-probe rows без ID — barrier, не разрешение восстановить старое измерение. + +Миграция0010 добавляет uptime_probe_samples.measurement_known boolean defaulttrue и observation_error text; CHECK/legacy storage status и integer loss не ломаются. Raw100/down в failed rows — прежние storage sentinel с явным measurement_known=false, не публичное измерение. Snapshot/public observations/API loss становятсяnull/statusunknown, есть причина ошибки. Новые flags пишутся при scheduled и manual error/empty response. Старые scheduled failure samples переклассифицируются readonly по точному probeId+sampledAt retained v1 JSON provenance; genuine100% без failure flag не меняется. Старые manual/retention-lost failures без provenance надёжно восстановить нельзя: они остаются legacy ambiguity, полного historical cleanup не заявляем. Migration additive, прошлые данные не удаляет; source JSON internal v1 теперь допускает nullable loss/unknown. Для rollback старый код не интерпретирует measurement_known, поэтому совместимость хранения не означает корректное чтение новых unknown attempts старой версией. + +Overview выдаёт unknown/null без fallback100/down и без padded historical0. Observations timestamps канонизированы; parser проверяет согласованность unknown/null. /uptime использует тот же observed decoder, что dashboard: «Нет данных», — для RTT/loss, причина execution error, отдельный filter/URLstatus=unknown. Новая draft probe тоже не объявляется заранее up/0. Мини-Sparkline сохраняет gaps; expanded RTT chart больше не превращаетnull в0 и не дублирует одиночную точку для выдуманной линии. Dashboard status tooltip получает причину. Source/speed-interface read endpoints теперь409 до RouterOS при disabled server; ошибочная подпись «для ping всё равно можно выбрать выключенный» исправлена. Это соответствует прямому правилу пользователя не опрашивать выключенные устройства любых типов. UI/ReUI primitives/theme не заменены. + +| Состояние | Эталон | Результат | +| --- | --- | --- | +| Ошибка REST/нет измерения | Существующие probe rows/StatusDot/Sparkline | [ПК1440](ux-ui/screens/ping-measurement-unknown-1440-2026-10-12.png), [телефон320](ux-ui/screens/ping-measurement-unknown-320-2026-10-12.png) — API-fixture, не production positive sample | +| Подтверждённые100% | Тот же public observed path | Down и100% сохранены отдельным positive control | + +Unit parser/observations/frontend dashboard-probes/URL guards прошли; backend build, application-source TypeScript и ESLint прошли (в uptime-collector два прежних unused settings warnings, functional scope не расширен). Первый npm --prefix exec взял неверный cwd, повтор direct tsx корректен; новый тест сначала использовал неверное имя Zod package, исправлен на установленный @fastify/type-provider-zod. Local cancellation test получил sandbox EACCES, разрешённый сетевой профиль прошёл; local own SQL skipped без выделенной базы.85 unique E2E Uptime/Ping/Speed: initial83passed/2failed были новые ambiguous test selectors (имя есть в sidebar и строке); selector исправлен на конкретную причину ошибки, delta3passed9.5s. Existing83 scenarios повторно не запускались;2 screenshot captures passed8.5s отдельно. Это не85 clean single-run и не87 новых сценариев. Два перезаписанных тестами прежних speed images восстановлены SHA256;8 user dirty GRE/VXLAN images не тронуты. Новые снимки просмотрены, nullable состояние читаемо при320px. + +Owned Node22/PG18.6 профили alert-engine+probes прошли42380ms:26 source/history SQL cases, GRE/BGP8/actual stability и8 новых Ping cases. Последние проходят через реальный MikrotikClient HTTP к loopback simulator, persisted flags, GET overview Fastify path:401,empty,manual,real loss/zero,API,legacy exact provenance и disabled no-outbound requests. Simulator не является физическим RouterOS/network failure. Финальные unit guards zero/inconsistent counts/malformed probe проверены перед scoped rerun; temporary DB/role/container удалены. Production credentials/JWT не копировались. Type/producer tests подключены к стандартному test:probes; default profile count не увеличен. + +Source440 backend+518 frontend inputs/1020 emitted parity совпали SHA256. Both production builds passed, backend image включает новый drizzle0010. Staged deploy: frontend ux-ping173 сначала принял старый API ux-history172, затем backend ux-ping173/version1.0.0-test.ping173. Migration0010 и2 новых columns подтверждены readonly в main PG. Backend/PG healthy, frontend running/HTTP/UI, imageIds verified, running containers3. В.env менялись толькоimage tags; rollback tags сохранились отдельно без secret env копии. Cached прежний frontend следует перезагрузить: новые nullable/unknown DTO требуют совместимого reader. + +Независимо выполнен настоящий native Ping4 packets194.226.121.150→104.165.244.7 иобратно через running compiled173: обе команды приняты, обе4/4,0%loss, parsed RTT53ms. Это короткое наблюдение, не WAN SLA/max throughput. Main DB read-only, native конфигурация/пароли не менялись;2 operational Ping commands, routerConfigWrites0/messages0. На production configured probes0:2live UI1440/320 подтверждают actual empty catalog/new filter/reader/overflow/pageerrors0; nonempty/error UI доказан fixtures, не выдан за main positive measurement. Collector row/error flags проверены ownSQL, real parser отдельно native payloads. + +Протоколы: [SQL/HTTP8+cleanup](ux-ui/acceptance/ping-measurement-sql-2026-10-12.json), [native4/4](ux-ui/acceptance/ping-measurement-native-2026-10-12.json), [source](ux-ui/acceptance/ping-measurement-source-2026-10-12.json), [compiled](ux-ui/acceptance/ping-measurement-compiled-2026-10-12.json), [interop](ux-ui/acceptance/ping-measurement-interop-2026-10-12.json), [runtime](ux-ui/acceptance/ping-measurement-runtime-2026-10-12.json), [live UI](ux-ui/acceptance/ping-measurement-deployed-2026-10-12.json). + +Следующий174 — audit first-wave/invalid traffic rate confidence: ok request и numeric sum не всегда доказывают измеренный rate без previous wave. Не заявлять эту гипотезу как уже выполненное исправление. Source unconfirmed/missing target diagnostics, manual history provenance, unsupported/compound time formats, legacy ambiguous samples, scale/multi-instance/physical WAN/external integrations остаются отдельными условиями12.5. Полная acceptance открыта. Локальный коммит без push. + ## 12.5.172 — подтверждённые текущие проблемы в журнале мониторинга (2026-10-12) Внешнее воспроизведение выполнено на compiled ux-targets171 с own PostgreSQL/ограниченной ролью. Прежний overview принимал положительный raw sample и корректно выдавал timestamp string: гипотеза Date/schema failure НЕ подтвердилась. Первая baseline попытка ожидала ошибку и завершилась assertion failure; её результат сохранён, own fixture/DB/role очищены. Уточнённый baseline прошёл: один и тот же live offline оставался после disabled server, disabled resources source и замены sampledAt на часовой давности. Это положительное независимое подтверждение реального disabled/freshness дефекта прежней ветки, не вывод только из кода. diff --git a/docs/ux-ui-remaining-acceptance.md b/docs/ux-ui-remaining-acceptance.md index 997e38de..dd146d1f 100644 --- a/docs/ux-ui-remaining-acceptance.md +++ b/docs/ux-ui-remaining-acceptance.md @@ -1,8 +1,8 @@ # Оставшаяся внешняя приёмка — 2026-10-12 -Это актуальная матрица после 12.5.172. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии. +Это актуальная матрица после 12.5.173. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии. -Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-history172`, frontend `ux-targets171`; production lab использует AUTH_REQUIRED=false. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись. +Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-ping173`, frontend `ux-ping173`; 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 calendar passed149; Sessions labels passed150; application GRE static IPv6 lifecycle151 passed; WireGuard static IPv6 lifecycle152 и disabled SQL/API gates passed; OSPF/BFD boundary/warning153 passed; OSPFv3 routes154 accepted in recorded bounds; BGP IPv6 basic155 passed; bigint ID/SQL guards156 passed; общая UI1260/context221/backend24 regression157 passed; IPv6 BFD correlation158 profile/deployed passed; native OSPFv3/BFDv6 boundary/recovery159 passed; BGP IPv6/BFD withdrawal/recovery160 passed; selected BGP/alert unknown guards161 replay/profiles/deployed passed (SQL stability skipped); private4-byte ASN162 native/API/UI passed; duplicate BGP alert keys163 replay/profiles/deployed passed; nullable selected BGP164 replay/profiles/API/UI passed; collector165 parallel/current guards/read-only PG passed; positive partial/error SQL166/full24 with PG passed (optional network SQL not repeated); GRE/BGP freshness167/SQL8 passed; other source audit168 выявил identity/disabled/freshness gaps; локальный server/traffic identity guard169 passed без SQL/deploy, source/per-probe/freshness170/ownSQL16/compiled1008/deployed passed; source diagnostics171/42E2E/ownSQL metadata/live2 passed; monitoring history172/ownSQL26/compiled1020/deployed passed; Ping execution error vs measured loss173 pending; последующие изменения проверять отдельно; 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 labels passed150; application GRE static IPv6 lifecycle151 passed; WireGuard static IPv6 lifecycle152 и disabled SQL/API gates passed; OSPF/BFD boundary/warning153 passed; OSPFv3 routes154 accepted in recorded bounds; BGP IPv6 basic155 passed; bigint ID/SQL guards156 passed; общая UI1260/context221/backend24 regression157 passed; IPv6 BFD correlation158 profile/deployed passed; native OSPFv3/BFDv6 boundary/recovery159 passed; BGP IPv6/BFD withdrawal/recovery160 passed; selected BGP/alert unknown guards161 replay/profiles/deployed passed (SQL stability skipped); private4-byte ASN162 native/API/UI passed; duplicate BGP alert keys163 replay/profiles/deployed passed; nullable selected BGP164 replay/profiles/API/UI passed; collector165 parallel/current guards/read-only PG passed; positive partial/error SQL166/full24 with PG passed (optional network SQL not repeated); GRE/BGP freshness167/SQL8 passed; other source audit168 выявил identity/disabled/freshness gaps; локальный server/traffic identity guard169 passed без SQL/deploy, source/per-probe/freshness170/ownSQL16/compiled1008/deployed passed; source diagnostics171/42E2E/ownSQL metadata/live2 passed; monitoring history172/ownSQL26/compiled1020/deployed passed; Ping known/error173/ownHTTP-SQL8/85uniqueE2E/native4/4/deployed passed; traffic first-wave confidence174 audit pending; последующие изменения проверять отдельно; 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). Невыполненные пункты не являются все блокерами данного лабораторного сценария: для выпуска необходимо явно определить требуемые конфигурации и принять их либо согласовать ограничение. @@ -863,3 +863,28 @@ Source/build/unit/ESLint прошли. Локальные alert-engine и12 ск Протоколы: [first baseline hypothesis](ux-ui/acceptance/monitoring-history-baseline-attempt-2026-10-12.json), [real baseline](ux-ui/acceptance/monitoring-history-baseline-2026-10-12.json), [SQL26/cleanup](ux-ui/acceptance/monitoring-history-sql-2026-10-12.json), [source437](ux-ui/acceptance/monitoring-history-source-2026-10-12.json), [compiled1020](ux-ui/acceptance/monitoring-history-compiled-2026-10-12.json), [runtime](ux-ui/acceptance/monitoring-history-runtime-2026-10-12.json), [API/UI](ux-ui/acceptance/monitoring-history-deployed-2026-10-12.json). Следующий173 — source finding: collectPingProbesOnce/pingProbesNow catch превращает ошибку REST-команды в lossPct100/down; decoder пока разрешает ok=false/down/null/100. Это не независимое доказательство потери пакетов. Проверить и исправить подтверждение измерений/empty payload; не объявлять legacy raw UI stats и manual provenance уже исправленными. Также остаются unconfirmed/missing target diagnostics, stable-ID migration, scale/multi-instance/physical WAN и внешние Telegram/Portal/client OS.12.5 открыт. Локальный коммит без push. + +## 12.5.173 — ошибка Ping не подменяет измеренные потери (2026-10-12) + +Завершён целый раздел producer→SQL→API→UI. parsePing теперь возвращает measured: empty/unsupported replies, zero sent и противоречивые/malformed counts не подтверждают процент потерь. Подтверждённые received0/sent4 остаются100%/down с RTTnull; успешный настоящий RTT0/loss0 сохранён. Scheduled и manual collectors различают успешное измерение и ошибку REST/недостаточный ответ; after-bandwidth Ping при неизвестном результате возвращает null metrics/error, а не выдуманный outage. Alert decoder принимает только ok=true и известное состояние; retained legacy ok=false/down/100 не срабатывает как измеренная потеря. Malformed due-probe rows без ID — barrier, не разрешение восстановить старое измерение. + +Миграция0010 добавляет uptime_probe_samples.measurement_known boolean defaulttrue и observation_error text; CHECK/legacy storage status и integer loss не ломаются. Raw100/down в failed rows — прежние storage sentinel с явным measurement_known=false, не публичное измерение. Snapshot/public observations/API loss становятсяnull/statusunknown, есть причина ошибки. Новые flags пишутся при scheduled и manual error/empty response. Старые scheduled failure samples переклассифицируются readonly по точному probeId+sampledAt retained v1 JSON provenance; genuine100% без failure flag не меняется. Старые manual/retention-lost failures без provenance надёжно восстановить нельзя: они остаются legacy ambiguity, полного historical cleanup не заявляем. Migration additive, прошлые данные не удаляет; source JSON internal v1 теперь допускает nullable loss/unknown. Для rollback старый код не интерпретирует measurement_known, поэтому совместимость хранения не означает корректное чтение новых unknown attempts старой версией. + +Overview выдаёт unknown/null без fallback100/down и без padded historical0. Observations timestamps канонизированы; parser проверяет согласованность unknown/null. /uptime использует тот же observed decoder, что dashboard: «Нет данных», — для RTT/loss, причина execution error, отдельный filter/URLstatus=unknown. Новая draft probe тоже не объявляется заранее up/0. Мини-Sparkline сохраняет gaps; expanded RTT chart больше не превращаетnull в0 и не дублирует одиночную точку для выдуманной линии. Dashboard status tooltip получает причину. Source/speed-interface read endpoints теперь409 до RouterOS при disabled server; ошибочная подпись «для ping всё равно можно выбрать выключенный» исправлена. Это соответствует прямому правилу пользователя не опрашивать выключенные устройства любых типов. UI/ReUI primitives/theme не заменены. + +| Состояние | Эталон | Результат | +| --- | --- | --- | +| Ошибка REST/нет измерения | Существующие probe rows/StatusDot/Sparkline | [ПК1440](ux-ui/screens/ping-measurement-unknown-1440-2026-10-12.png), [телефон320](ux-ui/screens/ping-measurement-unknown-320-2026-10-12.png) — API-fixture, не production positive sample | +| Подтверждённые100% | Тот же public observed path | Down и100% сохранены отдельным positive control | + +Unit parser/observations/frontend dashboard-probes/URL guards прошли; backend build, application-source TypeScript и ESLint прошли (в uptime-collector два прежних unused settings warnings, functional scope не расширен). Первый npm --prefix exec взял неверный cwd, повтор direct tsx корректен; новый тест сначала использовал неверное имя Zod package, исправлен на установленный @fastify/type-provider-zod. Local cancellation test получил sandbox EACCES, разрешённый сетевой профиль прошёл; local own SQL skipped без выделенной базы.85 unique E2E Uptime/Ping/Speed: initial83passed/2failed были новые ambiguous test selectors (имя есть в sidebar и строке); selector исправлен на конкретную причину ошибки, delta3passed9.5s. Existing83 scenarios повторно не запускались;2 screenshot captures passed8.5s отдельно. Это не85 clean single-run и не87 новых сценариев. Два перезаписанных тестами прежних speed images восстановлены SHA256;8 user dirty GRE/VXLAN images не тронуты. Новые снимки просмотрены, nullable состояние читаемо при320px. + +Owned Node22/PG18.6 профили alert-engine+probes прошли42380ms:26 source/history SQL cases, GRE/BGP8/actual stability и8 новых Ping cases. Последние проходят через реальный MikrotikClient HTTP к loopback simulator, persisted flags, GET overview Fastify path:401,empty,manual,real loss/zero,API,legacy exact provenance и disabled no-outbound requests. Simulator не является физическим RouterOS/network failure. Финальные unit guards zero/inconsistent counts/malformed probe проверены перед scoped rerun; temporary DB/role/container удалены. Production credentials/JWT не копировались. Type/producer tests подключены к стандартному test:probes; default profile count не увеличен. + +Source440 backend+518 frontend inputs/1020 emitted parity совпали SHA256. Both production builds passed, backend image включает новый drizzle0010. Staged deploy: frontend ux-ping173 сначала принял старый API ux-history172, затем backend ux-ping173/version1.0.0-test.ping173. Migration0010 и2 новых columns подтверждены readonly в main PG. Backend/PG healthy, frontend running/HTTP/UI, imageIds verified, running containers3. В.env менялись толькоimage tags; rollback tags сохранились отдельно без secret env копии. Cached прежний frontend следует перезагрузить: новые nullable/unknown DTO требуют совместимого reader. + +Независимо выполнен настоящий native Ping4 packets194.226.121.150→104.165.244.7 иобратно через running compiled173: обе команды приняты, обе4/4,0%loss, parsed RTT53ms. Это короткое наблюдение, не WAN SLA/max throughput. Main DB read-only, native конфигурация/пароли не менялись;2 operational Ping commands, routerConfigWrites0/messages0. На production configured probes0:2live UI1440/320 подтверждают actual empty catalog/new filter/reader/overflow/pageerrors0; nonempty/error UI доказан fixtures, не выдан за main positive measurement. Collector row/error flags проверены ownSQL, real parser отдельно native payloads. + +Протоколы: [SQL/HTTP8+cleanup](ux-ui/acceptance/ping-measurement-sql-2026-10-12.json), [native4/4](ux-ui/acceptance/ping-measurement-native-2026-10-12.json), [source](ux-ui/acceptance/ping-measurement-source-2026-10-12.json), [compiled](ux-ui/acceptance/ping-measurement-compiled-2026-10-12.json), [interop](ux-ui/acceptance/ping-measurement-interop-2026-10-12.json), [runtime](ux-ui/acceptance/ping-measurement-runtime-2026-10-12.json), [live UI](ux-ui/acceptance/ping-measurement-deployed-2026-10-12.json). + +Следующий174 — audit first-wave/invalid traffic rate confidence: ok request и numeric sum не всегда доказывают измеренный rate без previous wave. Не заявлять эту гипотезу как уже выполненное исправление. Source unconfirmed/missing target diagnostics, manual history provenance, unsupported/compound time formats, legacy ambiguous samples, scale/multi-instance/physical WAN/external integrations остаются отдельными условиями12.5. Полная acceptance открыта. Локальный коммит без push. diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index 6036161e..cbbe9f63 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -1,6 +1,6 @@ # Этап 12: итоговая приёмка UX/UI -Актуальное продолжение 2026-10-12: завершён12.5.172 — monitoring history использует общие confirmed collector facts внутри readonly transaction; disabled/stale/error/unknown не поднимают raw samples. Старый backend реально показывал disabled/hour-old sample; Date schema failure не воспроизведён. OwnSQL26+GRE8/stability/source437/compiled1020/build/4E2E/2live UI passed; own DB/roles removed. Backend ux-history172 healthy, frontend ux-targets171 прежний, оба CHR selected API200. Следующий173 — различить ошибку выполнения Ping и измеренные потери: producer catch пишет100%, guard пока принимает ok=false down;12.5 открыт. +Актуальное продолжение 2026-10-12: завершён12.5.173 — Ping execution error/empty payload не считается100% loss: measurement_known/error provenance, nullable unknown API/UI, scheduled/manual/after-BT, malformed/zero/inconsistent count guards.85 unique E2E initial83+delta3, ownSQL26+Ping8+GRE8/stability, source440+518/compiled1020/build passed. Migration0010 applied; оба ux-ping173 healthy/running; real CHR Ping4/4 both/0%/53ms, config/password unchanged. Own DB/role removed. Следующий174 — audit first-wave/invalid traffic rate confidence; legacy unclassified manual samples и source unconfirmed diagnostics остаются;12.5 открыт. План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push. @@ -3253,3 +3253,28 @@ Source/build/unit/ESLint прошли. Локальные alert-engine и12 ск Протоколы: [first baseline hypothesis](ux-ui/acceptance/monitoring-history-baseline-attempt-2026-10-12.json), [real baseline](ux-ui/acceptance/monitoring-history-baseline-2026-10-12.json), [SQL26/cleanup](ux-ui/acceptance/monitoring-history-sql-2026-10-12.json), [source437](ux-ui/acceptance/monitoring-history-source-2026-10-12.json), [compiled1020](ux-ui/acceptance/monitoring-history-compiled-2026-10-12.json), [runtime](ux-ui/acceptance/monitoring-history-runtime-2026-10-12.json), [API/UI](ux-ui/acceptance/monitoring-history-deployed-2026-10-12.json). Следующий173 — source finding: collectPingProbesOnce/pingProbesNow catch превращает ошибку REST-команды в lossPct100/down; decoder пока разрешает ok=false/down/null/100. Это не независимое доказательство потери пакетов. Проверить и исправить подтверждение измерений/empty payload; не объявлять legacy raw UI stats и manual provenance уже исправленными. Также остаются unconfirmed/missing target diagnostics, stable-ID migration, scale/multi-instance/physical WAN и внешние Telegram/Portal/client OS.12.5 открыт. Локальный коммит без push. + +## 12.5.173 — ошибка Ping не подменяет измеренные потери (2026-10-12) + +Завершён целый раздел producer→SQL→API→UI. parsePing теперь возвращает measured: empty/unsupported replies, zero sent и противоречивые/malformed counts не подтверждают процент потерь. Подтверждённые received0/sent4 остаются100%/down с RTTnull; успешный настоящий RTT0/loss0 сохранён. Scheduled и manual collectors различают успешное измерение и ошибку REST/недостаточный ответ; after-bandwidth Ping при неизвестном результате возвращает null metrics/error, а не выдуманный outage. Alert decoder принимает только ok=true и известное состояние; retained legacy ok=false/down/100 не срабатывает как измеренная потеря. Malformed due-probe rows без ID — barrier, не разрешение восстановить старое измерение. + +Миграция0010 добавляет uptime_probe_samples.measurement_known boolean defaulttrue и observation_error text; CHECK/legacy storage status и integer loss не ломаются. Raw100/down в failed rows — прежние storage sentinel с явным measurement_known=false, не публичное измерение. Snapshot/public observations/API loss становятсяnull/statusunknown, есть причина ошибки. Новые flags пишутся при scheduled и manual error/empty response. Старые scheduled failure samples переклассифицируются readonly по точному probeId+sampledAt retained v1 JSON provenance; genuine100% без failure flag не меняется. Старые manual/retention-lost failures без provenance надёжно восстановить нельзя: они остаются legacy ambiguity, полного historical cleanup не заявляем. Migration additive, прошлые данные не удаляет; source JSON internal v1 теперь допускает nullable loss/unknown. Для rollback старый код не интерпретирует measurement_known, поэтому совместимость хранения не означает корректное чтение новых unknown attempts старой версией. + +Overview выдаёт unknown/null без fallback100/down и без padded historical0. Observations timestamps канонизированы; parser проверяет согласованность unknown/null. /uptime использует тот же observed decoder, что dashboard: «Нет данных», — для RTT/loss, причина execution error, отдельный filter/URLstatus=unknown. Новая draft probe тоже не объявляется заранее up/0. Мини-Sparkline сохраняет gaps; expanded RTT chart больше не превращаетnull в0 и не дублирует одиночную точку для выдуманной линии. Dashboard status tooltip получает причину. Source/speed-interface read endpoints теперь409 до RouterOS при disabled server; ошибочная подпись «для ping всё равно можно выбрать выключенный» исправлена. Это соответствует прямому правилу пользователя не опрашивать выключенные устройства любых типов. UI/ReUI primitives/theme не заменены. + +| Состояние | Эталон | Результат | +| --- | --- | --- | +| Ошибка REST/нет измерения | Существующие probe rows/StatusDot/Sparkline | [ПК1440](ux-ui/screens/ping-measurement-unknown-1440-2026-10-12.png), [телефон320](ux-ui/screens/ping-measurement-unknown-320-2026-10-12.png) — API-fixture, не production positive sample | +| Подтверждённые100% | Тот же public observed path | Down и100% сохранены отдельным positive control | + +Unit parser/observations/frontend dashboard-probes/URL guards прошли; backend build, application-source TypeScript и ESLint прошли (в uptime-collector два прежних unused settings warnings, functional scope не расширен). Первый npm --prefix exec взял неверный cwd, повтор direct tsx корректен; новый тест сначала использовал неверное имя Zod package, исправлен на установленный @fastify/type-provider-zod. Local cancellation test получил sandbox EACCES, разрешённый сетевой профиль прошёл; local own SQL skipped без выделенной базы.85 unique E2E Uptime/Ping/Speed: initial83passed/2failed были новые ambiguous test selectors (имя есть в sidebar и строке); selector исправлен на конкретную причину ошибки, delta3passed9.5s. Existing83 scenarios повторно не запускались;2 screenshot captures passed8.5s отдельно. Это не85 clean single-run и не87 новых сценариев. Два перезаписанных тестами прежних speed images восстановлены SHA256;8 user dirty GRE/VXLAN images не тронуты. Новые снимки просмотрены, nullable состояние читаемо при320px. + +Owned Node22/PG18.6 профили alert-engine+probes прошли42380ms:26 source/history SQL cases, GRE/BGP8/actual stability и8 новых Ping cases. Последние проходят через реальный MikrotikClient HTTP к loopback simulator, persisted flags, GET overview Fastify path:401,empty,manual,real loss/zero,API,legacy exact provenance и disabled no-outbound requests. Simulator не является физическим RouterOS/network failure. Финальные unit guards zero/inconsistent counts/malformed probe проверены перед scoped rerun; temporary DB/role/container удалены. Production credentials/JWT не копировались. Type/producer tests подключены к стандартному test:probes; default profile count не увеличен. + +Source440 backend+518 frontend inputs/1020 emitted parity совпали SHA256. Both production builds passed, backend image включает новый drizzle0010. Staged deploy: frontend ux-ping173 сначала принял старый API ux-history172, затем backend ux-ping173/version1.0.0-test.ping173. Migration0010 и2 новых columns подтверждены readonly в main PG. Backend/PG healthy, frontend running/HTTP/UI, imageIds verified, running containers3. В.env менялись толькоimage tags; rollback tags сохранились отдельно без secret env копии. Cached прежний frontend следует перезагрузить: новые nullable/unknown DTO требуют совместимого reader. + +Независимо выполнен настоящий native Ping4 packets194.226.121.150→104.165.244.7 иобратно через running compiled173: обе команды приняты, обе4/4,0%loss, parsed RTT53ms. Это короткое наблюдение, не WAN SLA/max throughput. Main DB read-only, native конфигурация/пароли не менялись;2 operational Ping commands, routerConfigWrites0/messages0. На production configured probes0:2live UI1440/320 подтверждают actual empty catalog/new filter/reader/overflow/pageerrors0; nonempty/error UI доказан fixtures, не выдан за main positive measurement. Collector row/error flags проверены ownSQL, real parser отдельно native payloads. + +Протоколы: [SQL/HTTP8+cleanup](ux-ui/acceptance/ping-measurement-sql-2026-10-12.json), [native4/4](ux-ui/acceptance/ping-measurement-native-2026-10-12.json), [source](ux-ui/acceptance/ping-measurement-source-2026-10-12.json), [compiled](ux-ui/acceptance/ping-measurement-compiled-2026-10-12.json), [interop](ux-ui/acceptance/ping-measurement-interop-2026-10-12.json), [runtime](ux-ui/acceptance/ping-measurement-runtime-2026-10-12.json), [live UI](ux-ui/acceptance/ping-measurement-deployed-2026-10-12.json). + +Следующий174 — audit first-wave/invalid traffic rate confidence: ok request и numeric sum не всегда доказывают измеренный rate без previous wave. Не заявлять эту гипотезу как уже выполненное исправление. Source unconfirmed/missing target diagnostics, manual history provenance, unsupported/compound time formats, legacy ambiguous samples, scale/multi-instance/physical WAN/external integrations остаются отдельными условиями12.5. Полная acceptance открыта. Локальный коммит без push. diff --git a/docs/ux-ui-test-server.md b/docs/ux-ui-test-server.md index 70e45ff9..aed342e0 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.172 — monitoring history использует общие confirmed collector facts внутри readonly transaction; disabled/stale/error/unknown не поднимают raw samples. Старый backend реально показывал disabled/hour-old sample; Date schema failure не воспроизведён. OwnSQL26+GRE8/stability/source437/compiled1020/build/4E2E/2live UI passed; own DB/roles removed. Backend ux-history172 healthy, frontend ux-targets171 прежний, оба CHR selected API200. Следующий173 — различить ошибку выполнения Ping и измеренные потери: producer catch пишет100%, guard пока принимает ok=false down;12.5 открыт. +Сервер161.104.46.222: завершён12.5.173 — Ping execution error/empty payload не считается100% loss: measurement_known/error provenance, nullable unknown API/UI, scheduled/manual/after-BT, malformed/zero/inconsistent count guards.85 unique E2E initial83+delta3, ownSQL26+Ping8+GRE8/stability, source440+518/compiled1020/build passed. Migration0010 applied; оба ux-ping173 healthy/running; real CHR Ping4/4 both/0%/53ms, config/password unchanged. Own DB/role removed. Следующий174 — audit first-wave/invalid traffic rate confidence; legacy unclassified manual samples и source unconfirmed diagnostics остаются;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-history172 и mmapp-test/frontend:ux-targets171; запущены | +| Образы приложения | mmapp-test/backend:ux-ping173 и mmapp-test/frontend:ux-ping173; запущены | Compose SHA256:3052bd6ab5d9865f97605cebfb5ff27a4410cd3357b81153a65a87fb7c92582f. OptionalBACKEND_IMAGE_TAG позволяетобновлятьbackendотдельно. Локальный и удалённый файлы совпали. Шаблоны: [Compose](../deploy/docker-compose.test.yml), [env без секретов](../deploy/env.test.example). @@ -1679,3 +1679,28 @@ Source/build/unit/ESLint прошли. Локальные alert-engine и12 ск Протоколы: [first baseline hypothesis](ux-ui/acceptance/monitoring-history-baseline-attempt-2026-10-12.json), [real baseline](ux-ui/acceptance/monitoring-history-baseline-2026-10-12.json), [SQL26/cleanup](ux-ui/acceptance/monitoring-history-sql-2026-10-12.json), [source437](ux-ui/acceptance/monitoring-history-source-2026-10-12.json), [compiled1020](ux-ui/acceptance/monitoring-history-compiled-2026-10-12.json), [runtime](ux-ui/acceptance/monitoring-history-runtime-2026-10-12.json), [API/UI](ux-ui/acceptance/monitoring-history-deployed-2026-10-12.json). Следующий173 — source finding: collectPingProbesOnce/pingProbesNow catch превращает ошибку REST-команды в lossPct100/down; decoder пока разрешает ok=false/down/null/100. Это не независимое доказательство потери пакетов. Проверить и исправить подтверждение измерений/empty payload; не объявлять legacy raw UI stats и manual provenance уже исправленными. Также остаются unconfirmed/missing target diagnostics, stable-ID migration, scale/multi-instance/physical WAN и внешние Telegram/Portal/client OS.12.5 открыт. Локальный коммит без push. + +## 12.5.173 — ошибка Ping не подменяет измеренные потери (2026-10-12) + +Завершён целый раздел producer→SQL→API→UI. parsePing теперь возвращает measured: empty/unsupported replies, zero sent и противоречивые/malformed counts не подтверждают процент потерь. Подтверждённые received0/sent4 остаются100%/down с RTTnull; успешный настоящий RTT0/loss0 сохранён. Scheduled и manual collectors различают успешное измерение и ошибку REST/недостаточный ответ; after-bandwidth Ping при неизвестном результате возвращает null metrics/error, а не выдуманный outage. Alert decoder принимает только ok=true и известное состояние; retained legacy ok=false/down/100 не срабатывает как измеренная потеря. Malformed due-probe rows без ID — barrier, не разрешение восстановить старое измерение. + +Миграция0010 добавляет uptime_probe_samples.measurement_known boolean defaulttrue и observation_error text; CHECK/legacy storage status и integer loss не ломаются. Raw100/down в failed rows — прежние storage sentinel с явным measurement_known=false, не публичное измерение. Snapshot/public observations/API loss становятсяnull/statusunknown, есть причина ошибки. Новые flags пишутся при scheduled и manual error/empty response. Старые scheduled failure samples переклассифицируются readonly по точному probeId+sampledAt retained v1 JSON provenance; genuine100% без failure flag не меняется. Старые manual/retention-lost failures без provenance надёжно восстановить нельзя: они остаются legacy ambiguity, полного historical cleanup не заявляем. Migration additive, прошлые данные не удаляет; source JSON internal v1 теперь допускает nullable loss/unknown. Для rollback старый код не интерпретирует measurement_known, поэтому совместимость хранения не означает корректное чтение новых unknown attempts старой версией. + +Overview выдаёт unknown/null без fallback100/down и без padded historical0. Observations timestamps канонизированы; parser проверяет согласованность unknown/null. /uptime использует тот же observed decoder, что dashboard: «Нет данных», — для RTT/loss, причина execution error, отдельный filter/URLstatus=unknown. Новая draft probe тоже не объявляется заранее up/0. Мини-Sparkline сохраняет gaps; expanded RTT chart больше не превращаетnull в0 и не дублирует одиночную точку для выдуманной линии. Dashboard status tooltip получает причину. Source/speed-interface read endpoints теперь409 до RouterOS при disabled server; ошибочная подпись «для ping всё равно можно выбрать выключенный» исправлена. Это соответствует прямому правилу пользователя не опрашивать выключенные устройства любых типов. UI/ReUI primitives/theme не заменены. + +| Состояние | Эталон | Результат | +| --- | --- | --- | +| Ошибка REST/нет измерения | Существующие probe rows/StatusDot/Sparkline | [ПК1440](ux-ui/screens/ping-measurement-unknown-1440-2026-10-12.png), [телефон320](ux-ui/screens/ping-measurement-unknown-320-2026-10-12.png) — API-fixture, не production positive sample | +| Подтверждённые100% | Тот же public observed path | Down и100% сохранены отдельным positive control | + +Unit parser/observations/frontend dashboard-probes/URL guards прошли; backend build, application-source TypeScript и ESLint прошли (в uptime-collector два прежних unused settings warnings, functional scope не расширен). Первый npm --prefix exec взял неверный cwd, повтор direct tsx корректен; новый тест сначала использовал неверное имя Zod package, исправлен на установленный @fastify/type-provider-zod. Local cancellation test получил sandbox EACCES, разрешённый сетевой профиль прошёл; local own SQL skipped без выделенной базы.85 unique E2E Uptime/Ping/Speed: initial83passed/2failed были новые ambiguous test selectors (имя есть в sidebar и строке); selector исправлен на конкретную причину ошибки, delta3passed9.5s. Existing83 scenarios повторно не запускались;2 screenshot captures passed8.5s отдельно. Это не85 clean single-run и не87 новых сценариев. Два перезаписанных тестами прежних speed images восстановлены SHA256;8 user dirty GRE/VXLAN images не тронуты. Новые снимки просмотрены, nullable состояние читаемо при320px. + +Owned Node22/PG18.6 профили alert-engine+probes прошли42380ms:26 source/history SQL cases, GRE/BGP8/actual stability и8 новых Ping cases. Последние проходят через реальный MikrotikClient HTTP к loopback simulator, persisted flags, GET overview Fastify path:401,empty,manual,real loss/zero,API,legacy exact provenance и disabled no-outbound requests. Simulator не является физическим RouterOS/network failure. Финальные unit guards zero/inconsistent counts/malformed probe проверены перед scoped rerun; temporary DB/role/container удалены. Production credentials/JWT не копировались. Type/producer tests подключены к стандартному test:probes; default profile count не увеличен. + +Source440 backend+518 frontend inputs/1020 emitted parity совпали SHA256. Both production builds passed, backend image включает новый drizzle0010. Staged deploy: frontend ux-ping173 сначала принял старый API ux-history172, затем backend ux-ping173/version1.0.0-test.ping173. Migration0010 и2 новых columns подтверждены readonly в main PG. Backend/PG healthy, frontend running/HTTP/UI, imageIds verified, running containers3. В.env менялись толькоimage tags; rollback tags сохранились отдельно без secret env копии. Cached прежний frontend следует перезагрузить: новые nullable/unknown DTO требуют совместимого reader. + +Независимо выполнен настоящий native Ping4 packets194.226.121.150→104.165.244.7 иобратно через running compiled173: обе команды приняты, обе4/4,0%loss, parsed RTT53ms. Это короткое наблюдение, не WAN SLA/max throughput. Main DB read-only, native конфигурация/пароли не менялись;2 operational Ping commands, routerConfigWrites0/messages0. На production configured probes0:2live UI1440/320 подтверждают actual empty catalog/new filter/reader/overflow/pageerrors0; nonempty/error UI доказан fixtures, не выдан за main positive measurement. Collector row/error flags проверены ownSQL, real parser отдельно native payloads. + +Протоколы: [SQL/HTTP8+cleanup](ux-ui/acceptance/ping-measurement-sql-2026-10-12.json), [native4/4](ux-ui/acceptance/ping-measurement-native-2026-10-12.json), [source](ux-ui/acceptance/ping-measurement-source-2026-10-12.json), [compiled](ux-ui/acceptance/ping-measurement-compiled-2026-10-12.json), [interop](ux-ui/acceptance/ping-measurement-interop-2026-10-12.json), [runtime](ux-ui/acceptance/ping-measurement-runtime-2026-10-12.json), [live UI](ux-ui/acceptance/ping-measurement-deployed-2026-10-12.json). + +Следующий174 — audit first-wave/invalid traffic rate confidence: ok request и numeric sum не всегда доказывают измеренный rate без previous wave. Не заявлять эту гипотезу как уже выполненное исправление. Source unconfirmed/missing target diagnostics, manual history provenance, unsupported/compound time formats, legacy ambiguous samples, scale/multi-instance/physical WAN/external integrations остаются отдельными условиями12.5. Полная acceptance открыта. Локальный коммит без push. diff --git a/docs/ux-ui/acceptance/ping-measurement-compiled-2026-10-12.json b/docs/ux-ui/acceptance/ping-measurement-compiled-2026-10-12.json new file mode 100644 index 00000000..bf53d444 --- /dev/null +++ b/docs/ux-ui/acceptance/ping-measurement-compiled-2026-10-12.json @@ -0,0 +1,1030 @@ +{ + "date": "2026-10-12", + "block": "12.5.173", + "backendVersion": "1.0.0-test.ping173", + "normalization": "CRLF to LF", + "matched": [ + "backend/dist/config.js", + "backend/dist/config.js.map", + "backend/dist/config.d.ts", + "backend/dist/config.d.ts.map", + "backend/dist/constants/ping-probe.js", + "backend/dist/constants/ping-probe.js.map", + "backend/dist/constants/ping-probe.d.ts", + "backend/dist/constants/ping-probe.d.ts.map", + "backend/dist/db/bootstrap.js", + "backend/dist/db/bootstrap.js.map", + "backend/dist/db/bootstrap.d.ts", + "backend/dist/db/bootstrap.d.ts.map", + "backend/dist/db/index.js", + "backend/dist/db/index.js.map", + "backend/dist/db/index.d.ts", + "backend/dist/db/index.d.ts.map", + "backend/dist/db/json.js", + "backend/dist/db/json.js.map", + "backend/dist/db/json.d.ts", + "backend/dist/db/json.d.ts.map", + "backend/dist/db/migrate.js", + "backend/dist/db/migrate.js.map", + "backend/dist/db/migrate.d.ts", + "backend/dist/db/migrate.d.ts.map", + "backend/dist/db/operation-lock.js", + "backend/dist/db/operation-lock.js.map", + "backend/dist/db/operation-lock.d.ts", + "backend/dist/db/operation-lock.d.ts.map", + "backend/dist/db/partitions.js", + "backend/dist/db/partitions.js.map", + "backend/dist/db/partitions.d.ts", + "backend/dist/db/partitions.d.ts.map", + "backend/dist/db/schema.js", + "backend/dist/db/schema.js.map", + "backend/dist/db/schema.d.ts", + "backend/dist/db/schema.d.ts.map", + "backend/dist/db/sql-bind.js", + "backend/dist/db/sql-bind.js.map", + "backend/dist/db/sql-bind.d.ts", + "backend/dist/db/sql-bind.d.ts.map", + "backend/dist/db/sqlite-import.js", + "backend/dist/db/sqlite-import.js.map", + "backend/dist/db/sqlite-import.d.ts", + "backend/dist/db/sqlite-import.d.ts.map", + "backend/dist/db/traffic-flags.js", + "backend/dist/db/traffic-flags.js.map", + "backend/dist/db/traffic-flags.d.ts", + "backend/dist/db/traffic-flags.d.ts.map", + "backend/dist/index.js", + "backend/dist/index.js.map", + "backend/dist/index.d.ts", + "backend/dist/index.d.ts.map", + "backend/dist/lib/permissions.js", + "backend/dist/lib/permissions.js.map", + "backend/dist/lib/permissions.d.ts", + "backend/dist/lib/permissions.d.ts.map", + "backend/dist/managed-markers.js", + "backend/dist/managed-markers.js.map", + "backend/dist/managed-markers.d.ts", + "backend/dist/managed-markers.d.ts.map", + "backend/dist/modules/events/repository/events-repository.js", + "backend/dist/modules/events/repository/events-repository.js.map", + "backend/dist/modules/events/repository/events-repository.d.ts", + "backend/dist/modules/events/repository/events-repository.d.ts.map", + "backend/dist/modules/events/service/events-service.js", + "backend/dist/modules/events/service/events-service.js.map", + "backend/dist/modules/events/service/events-service.d.ts", + "backend/dist/modules/events/service/events-service.d.ts.map", + "backend/dist/modules/servers/mapper/servers-mapper.js", + "backend/dist/modules/servers/mapper/servers-mapper.js.map", + "backend/dist/modules/servers/mapper/servers-mapper.d.ts", + "backend/dist/modules/servers/mapper/servers-mapper.d.ts.map", + "backend/dist/modules/servers/repository/servers-repository.js", + "backend/dist/modules/servers/repository/servers-repository.js.map", + "backend/dist/modules/servers/repository/servers-repository.d.ts", + "backend/dist/modules/servers/repository/servers-repository.d.ts.map", + "backend/dist/modules/servers/service/servers-service.js", + "backend/dist/modules/servers/service/servers-service.js.map", + "backend/dist/modules/servers/service/servers-service.d.ts", + "backend/dist/modules/servers/service/servers-service.d.ts.map", + "backend/dist/modules/users/iface-type.js", + "backend/dist/modules/users/iface-type.js.map", + "backend/dist/modules/users/iface-type.d.ts", + "backend/dist/modules/users/iface-type.d.ts.map", + "backend/dist/modules/users/interface-inventory.js", + "backend/dist/modules/users/interface-inventory.js.map", + "backend/dist/modules/users/interface-inventory.d.ts", + "backend/dist/modules/users/interface-inventory.d.ts.map", + "backend/dist/modules/users/peer-bind.js", + "backend/dist/modules/users/peer-bind.js.map", + "backend/dist/modules/users/peer-bind.d.ts", + "backend/dist/modules/users/peer-bind.d.ts.map", + "backend/dist/modules/users/repository/users-repository.js", + "backend/dist/modules/users/repository/users-repository.js.map", + "backend/dist/modules/users/repository/users-repository.d.ts", + "backend/dist/modules/users/repository/users-repository.d.ts.map", + "backend/dist/modules/users/service/users-service.js", + "backend/dist/modules/users/service/users-service.js.map", + "backend/dist/modules/users/service/users-service.d.ts", + "backend/dist/modules/users/service/users-service.d.ts.map", + "backend/dist/plugins/auth.js", + "backend/dist/plugins/auth.js.map", + "backend/dist/plugins/auth.d.ts", + "backend/dist/plugins/auth.d.ts.map", + "backend/dist/plugins/database-request-gate.js", + "backend/dist/plugins/database-request-gate.js.map", + "backend/dist/plugins/database-request-gate.d.ts", + "backend/dist/plugins/database-request-gate.d.ts.map", + "backend/dist/routes/alerts.js", + "backend/dist/routes/alerts.js.map", + "backend/dist/routes/alerts.d.ts", + "backend/dist/routes/alerts.d.ts.map", + "backend/dist/routes/backups.js", + "backend/dist/routes/backups.js.map", + "backend/dist/routes/backups.d.ts", + "backend/dist/routes/backups.d.ts.map", + "backend/dist/routes/bgp.js", + "backend/dist/routes/bgp.js.map", + "backend/dist/routes/bgp.d.ts", + "backend/dist/routes/bgp.d.ts.map", + "backend/dist/routes/certificates.js", + "backend/dist/routes/certificates.js.map", + "backend/dist/routes/certificates.d.ts", + "backend/dist/routes/certificates.d.ts.map", + "backend/dist/routes/containers.js", + "backend/dist/routes/containers.js.map", + "backend/dist/routes/containers.d.ts", + "backend/dist/routes/containers.d.ts.map", + "backend/dist/routes/events.js", + "backend/dist/routes/events.js.map", + "backend/dist/routes/events.d.ts", + "backend/dist/routes/events.d.ts.map", + "backend/dist/routes/evobgp-catalog.js", + "backend/dist/routes/evobgp-catalog.js.map", + "backend/dist/routes/evobgp-catalog.d.ts", + "backend/dist/routes/evobgp-catalog.d.ts.map", + "backend/dist/routes/evobgp-settings.js", + "backend/dist/routes/evobgp-settings.js.map", + "backend/dist/routes/evobgp-settings.d.ts", + "backend/dist/routes/evobgp-settings.d.ts.map", + "backend/dist/routes/evobgp.js", + "backend/dist/routes/evobgp.js.map", + "backend/dist/routes/evobgp.d.ts", + "backend/dist/routes/evobgp.d.ts.map", + "backend/dist/routes/exec.js", + "backend/dist/routes/exec.js.map", + "backend/dist/routes/exec.d.ts", + "backend/dist/routes/exec.d.ts.map", + "backend/dist/routes/filters.js", + "backend/dist/routes/filters.js.map", + "backend/dist/routes/filters.d.ts", + "backend/dist/routes/filters.d.ts.map", + "backend/dist/routes/firewall.js", + "backend/dist/routes/firewall.js.map", + "backend/dist/routes/firewall.d.ts", + "backend/dist/routes/firewall.d.ts.map", + "backend/dist/routes/geoip.js", + "backend/dist/routes/geoip.js.map", + "backend/dist/routes/geoip.d.ts", + "backend/dist/routes/geoip.d.ts.map", + "backend/dist/routes/gre.js", + "backend/dist/routes/gre.js.map", + "backend/dist/routes/gre.d.ts", + "backend/dist/routes/gre.d.ts.map", + "backend/dist/routes/internet-path.js", + "backend/dist/routes/internet-path.js.map", + "backend/dist/routes/internet-path.d.ts", + "backend/dist/routes/internet-path.d.ts.map", + "backend/dist/routes/ipsec.js", + "backend/dist/routes/ipsec.js.map", + "backend/dist/routes/ipsec.d.ts", + "backend/dist/routes/ipsec.d.ts.map", + "backend/dist/routes/network.js", + "backend/dist/routes/network.js.map", + "backend/dist/routes/network.d.ts", + "backend/dist/routes/network.d.ts.map", + "backend/dist/routes/ospf.js", + "backend/dist/routes/ospf.js.map", + "backend/dist/routes/ospf.d.ts", + "backend/dist/routes/ospf.d.ts.map", + "backend/dist/routes/ping-probes-write.js", + "backend/dist/routes/ping-probes-write.js.map", + "backend/dist/routes/ping-probes-write.d.ts", + "backend/dist/routes/ping-probes-write.d.ts.map", + "backend/dist/routes/probes.js", + "backend/dist/routes/probes.js.map", + "backend/dist/routes/probes.d.ts", + "backend/dist/routes/probes.d.ts.map", + "backend/dist/routes/recursive-routes.js", + "backend/dist/routes/recursive-routes.js.map", + "backend/dist/routes/recursive-routes.d.ts", + "backend/dist/routes/recursive-routes.d.ts.map", + "backend/dist/routes/scheduler.js", + "backend/dist/routes/scheduler.js.map", + "backend/dist/routes/scheduler.d.ts", + "backend/dist/routes/scheduler.d.ts.map", + "backend/dist/routes/servers-api-ping.js", + "backend/dist/routes/servers-api-ping.js.map", + "backend/dist/routes/servers-api-ping.d.ts", + "backend/dist/routes/servers-api-ping.d.ts.map", + "backend/dist/routes/servers.js", + "backend/dist/routes/servers.js.map", + "backend/dist/routes/servers.d.ts", + "backend/dist/routes/servers.d.ts.map", + "backend/dist/routes/sidebar-counts.js", + "backend/dist/routes/sidebar-counts.js.map", + "backend/dist/routes/sidebar-counts.d.ts", + "backend/dist/routes/sidebar-counts.d.ts.map", + "backend/dist/routes/speed-probes-write.js", + "backend/dist/routes/speed-probes-write.js.map", + "backend/dist/routes/speed-probes-write.d.ts", + "backend/dist/routes/speed-probes-write.d.ts.map", + "backend/dist/routes/speed-test.js", + "backend/dist/routes/speed-test.js.map", + "backend/dist/routes/speed-test.d.ts", + "backend/dist/routes/speed-test.d.ts.map", + "backend/dist/routes/statistics.js", + "backend/dist/routes/statistics.js.map", + "backend/dist/routes/statistics.d.ts", + "backend/dist/routes/statistics.d.ts.map", + "backend/dist/routes/system-database.js", + "backend/dist/routes/system-database.js.map", + "backend/dist/routes/system-database.d.ts", + "backend/dist/routes/system-database.d.ts.map", + "backend/dist/routes/traffic-flow-purge.js", + "backend/dist/routes/traffic-flow-purge.js.map", + "backend/dist/routes/traffic-flow-purge.d.ts", + "backend/dist/routes/traffic-flow-purge.d.ts.map", + "backend/dist/routes/traffic-flow-settings.js", + "backend/dist/routes/traffic-flow-settings.js.map", + "backend/dist/routes/traffic-flow-settings.d.ts", + "backend/dist/routes/traffic-flow-settings.d.ts.map", + "backend/dist/routes/traffic-flow.js", + "backend/dist/routes/traffic-flow.js.map", + "backend/dist/routes/traffic-flow.d.ts", + "backend/dist/routes/traffic-flow.d.ts.map", + "backend/dist/routes/traffic.js", + "backend/dist/routes/traffic.js.map", + "backend/dist/routes/traffic.d.ts", + "backend/dist/routes/traffic.d.ts.map", + "backend/dist/routes/uptime.js", + "backend/dist/routes/uptime.js.map", + "backend/dist/routes/uptime.d.ts", + "backend/dist/routes/uptime.d.ts.map", + "backend/dist/routes/users.js", + "backend/dist/routes/users.js.map", + "backend/dist/routes/users.d.ts", + "backend/dist/routes/users.d.ts.map", + "backend/dist/routes/vxlan.js", + "backend/dist/routes/vxlan.js.map", + "backend/dist/routes/vxlan.d.ts", + "backend/dist/routes/vxlan.d.ts.map", + "backend/dist/routes/wireguard.js", + "backend/dist/routes/wireguard.js.map", + "backend/dist/routes/wireguard.d.ts", + "backend/dist/routes/wireguard.d.ts.map", + "backend/dist/scripts/migrate-sqlite-to-pg.js", + "backend/dist/scripts/migrate-sqlite-to-pg.js.map", + "backend/dist/scripts/migrate-sqlite-to-pg.d.ts", + "backend/dist/scripts/migrate-sqlite-to-pg.d.ts.map", + "backend/dist/scripts/rebuild-flow-facts.js", + "backend/dist/scripts/rebuild-flow-facts.js.map", + "backend/dist/scripts/rebuild-flow-facts.d.ts", + "backend/dist/scripts/rebuild-flow-facts.d.ts.map", + "backend/dist/services/acme-cloudflare.js", + "backend/dist/services/acme-cloudflare.js.map", + "backend/dist/services/acme-cloudflare.d.ts", + "backend/dist/services/acme-cloudflare.d.ts.map", + "backend/dist/services/alert-collector-hooks.js", + "backend/dist/services/alert-collector-hooks.js.map", + "backend/dist/services/alert-collector-hooks.d.ts", + "backend/dist/services/alert-collector-hooks.d.ts.map", + "backend/dist/services/alert-engine/bgp-signals.js", + "backend/dist/services/alert-engine/bgp-signals.js.map", + "backend/dist/services/alert-engine/bgp-signals.d.ts", + "backend/dist/services/alert-engine/bgp-signals.d.ts.map", + "backend/dist/services/alert-engine/collector-context-reader.js", + "backend/dist/services/alert-engine/collector-context-reader.js.map", + "backend/dist/services/alert-engine/collector-context-reader.d.ts", + "backend/dist/services/alert-engine/collector-context-reader.d.ts.map", + "backend/dist/services/alert-engine/collector-source-signals.js", + "backend/dist/services/alert-engine/collector-source-signals.js.map", + "backend/dist/services/alert-engine/collector-source-signals.d.ts", + "backend/dist/services/alert-engine/collector-source-signals.d.ts.map", + "backend/dist/services/alert-engine/confirm-stability.js", + "backend/dist/services/alert-engine/confirm-stability.js.map", + "backend/dist/services/alert-engine/confirm-stability.d.ts", + "backend/dist/services/alert-engine/confirm-stability.d.ts.map", + "backend/dist/services/alert-engine/cooldown.js", + "backend/dist/services/alert-engine/cooldown.js.map", + "backend/dist/services/alert-engine/cooldown.d.ts", + "backend/dist/services/alert-engine/cooldown.d.ts.map", + "backend/dist/services/alert-engine/decision-engine.js", + "backend/dist/services/alert-engine/decision-engine.js.map", + "backend/dist/services/alert-engine/decision-engine.d.ts", + "backend/dist/services/alert-engine/decision-engine.d.ts.map", + "backend/dist/services/alert-engine/evaluate-rule.js", + "backend/dist/services/alert-engine/evaluate-rule.js.map", + "backend/dist/services/alert-engine/evaluate-rule.d.ts", + "backend/dist/services/alert-engine/evaluate-rule.d.ts.map", + "backend/dist/services/alert-engine/gre-signals.js", + "backend/dist/services/alert-engine/gre-signals.js.map", + "backend/dist/services/alert-engine/gre-signals.d.ts", + "backend/dist/services/alert-engine/gre-signals.d.ts.map", + "backend/dist/services/alert-engine/groups.js", + "backend/dist/services/alert-engine/groups.js.map", + "backend/dist/services/alert-engine/groups.d.ts", + "backend/dist/services/alert-engine/groups.d.ts.map", + "backend/dist/services/alert-engine/monitoring-history.js", + "backend/dist/services/alert-engine/monitoring-history.js.map", + "backend/dist/services/alert-engine/monitoring-history.d.ts", + "backend/dist/services/alert-engine/monitoring-history.d.ts.map", + "backend/dist/services/alert-engine/outbox-execution.js", + "backend/dist/services/alert-engine/outbox-execution.js.map", + "backend/dist/services/alert-engine/outbox-execution.d.ts", + "backend/dist/services/alert-engine/outbox-execution.d.ts.map", + "backend/dist/services/alert-engine/outbox.js", + "backend/dist/services/alert-engine/outbox.js.map", + "backend/dist/services/alert-engine/outbox.d.ts", + "backend/dist/services/alert-engine/outbox.d.ts.map", + "backend/dist/services/alert-engine/prev-live-store.js", + "backend/dist/services/alert-engine/prev-live-store.js.map", + "backend/dist/services/alert-engine/prev-live-store.d.ts", + "backend/dist/services/alert-engine/prev-live-store.d.ts.map", + "backend/dist/services/alert-engine/rule-matcher.js", + "backend/dist/services/alert-engine/rule-matcher.js.map", + "backend/dist/services/alert-engine/rule-matcher.d.ts", + "backend/dist/services/alert-engine/rule-matcher.d.ts.map", + "backend/dist/services/alert-engine/run-once.js", + "backend/dist/services/alert-engine/run-once.js.map", + "backend/dist/services/alert-engine/run-once.d.ts", + "backend/dist/services/alert-engine/run-once.d.ts.map", + "backend/dist/services/alert-engine/server-signal-identity.js", + "backend/dist/services/alert-engine/server-signal-identity.js.map", + "backend/dist/services/alert-engine/server-signal-identity.d.ts", + "backend/dist/services/alert-engine/server-signal-identity.d.ts.map", + "backend/dist/services/alert-engine/signal-ingestor.js", + "backend/dist/services/alert-engine/signal-ingestor.js.map", + "backend/dist/services/alert-engine/signal-ingestor.d.ts", + "backend/dist/services/alert-engine/signal-ingestor.d.ts.map", + "backend/dist/services/alert-engine/signals-from-collectors.js", + "backend/dist/services/alert-engine/signals-from-collectors.js.map", + "backend/dist/services/alert-engine/signals-from-collectors.d.ts", + "backend/dist/services/alert-engine/signals-from-collectors.d.ts.map", + "backend/dist/services/alert-engine/snapshot.js", + "backend/dist/services/alert-engine/snapshot.js.map", + "backend/dist/services/alert-engine/snapshot.d.ts", + "backend/dist/services/alert-engine/snapshot.d.ts.map", + "backend/dist/services/alert-engine/source-watermark.js", + "backend/dist/services/alert-engine/source-watermark.js.map", + "backend/dist/services/alert-engine/source-watermark.d.ts", + "backend/dist/services/alert-engine/source-watermark.d.ts.map", + "backend/dist/services/alert-engine/state-machine.js", + "backend/dist/services/alert-engine/state-machine.js.map", + "backend/dist/services/alert-engine/state-machine.d.ts", + "backend/dist/services/alert-engine/state-machine.d.ts.map", + "backend/dist/services/alert-engine/target-issues.js", + "backend/dist/services/alert-engine/target-issues.js.map", + "backend/dist/services/alert-engine/target-issues.d.ts", + "backend/dist/services/alert-engine/target-issues.d.ts.map", + "backend/dist/services/alert-engine/telegram-emoji.js", + "backend/dist/services/alert-engine/telegram-emoji.js.map", + "backend/dist/services/alert-engine/telegram-emoji.d.ts", + "backend/dist/services/alert-engine/telegram-emoji.d.ts.map", + "backend/dist/services/alert-engine/types.js", + "backend/dist/services/alert-engine/types.js.map", + "backend/dist/services/alert-engine/types.d.ts", + "backend/dist/services/alert-engine/types.d.ts.map", + "backend/dist/services/alerts-config-execution.js", + "backend/dist/services/alerts-config-execution.js.map", + "backend/dist/services/alerts-config-execution.d.ts", + "backend/dist/services/alerts-config-execution.d.ts.map", + "backend/dist/services/alerts-outbox-overview.js", + "backend/dist/services/alerts-outbox-overview.js.map", + "backend/dist/services/alerts-outbox-overview.d.ts", + "backend/dist/services/alerts-outbox-overview.d.ts.map", + "backend/dist/services/alerts-outbox-retry.js", + "backend/dist/services/alerts-outbox-retry.js.map", + "backend/dist/services/alerts-outbox-retry.d.ts", + "backend/dist/services/alerts-outbox-retry.d.ts.map", + "backend/dist/services/alerts-overview.js", + "backend/dist/services/alerts-overview.js.map", + "backend/dist/services/alerts-overview.d.ts", + "backend/dist/services/alerts-overview.d.ts.map", + "backend/dist/services/alerts-revision.js", + "backend/dist/services/alerts-revision.js.map", + "backend/dist/services/alerts-revision.d.ts", + "backend/dist/services/alerts-revision.d.ts.map", + "backend/dist/services/alerts-service.js", + "backend/dist/services/alerts-service.js.map", + "backend/dist/services/alerts-service.d.ts", + "backend/dist/services/alerts-service.d.ts.map", + "backend/dist/services/alerts-telegram-delivery.js", + "backend/dist/services/alerts-telegram-delivery.js.map", + "backend/dist/services/alerts-telegram-delivery.d.ts", + "backend/dist/services/alerts-telegram-delivery.d.ts.map", + "backend/dist/services/alerts-telegram-send.js", + "backend/dist/services/alerts-telegram-send.js.map", + "backend/dist/services/alerts-telegram-send.d.ts", + "backend/dist/services/alerts-telegram-send.d.ts.map", + "backend/dist/services/alerts-telegram-settings.js", + "backend/dist/services/alerts-telegram-settings.js.map", + "backend/dist/services/alerts-telegram-settings.d.ts", + "backend/dist/services/alerts-telegram-settings.d.ts.map", + "backend/dist/services/backup-deletion.js", + "backend/dist/services/backup-deletion.js.map", + "backend/dist/services/backup-deletion.d.ts", + "backend/dist/services/backup-deletion.d.ts.map", + "backend/dist/services/backup-dto.js", + "backend/dist/services/backup-dto.js.map", + "backend/dist/services/backup-dto.d.ts", + "backend/dist/services/backup-dto.d.ts.map", + "backend/dist/services/backup-job-execution.js", + "backend/dist/services/backup-job-execution.js.map", + "backend/dist/services/backup-job-execution.d.ts", + "backend/dist/services/backup-job-execution.d.ts.map", + "backend/dist/services/backup-prune.js", + "backend/dist/services/backup-prune.js.map", + "backend/dist/services/backup-prune.d.ts", + "backend/dist/services/backup-prune.d.ts.map", + "backend/dist/services/backup-restore.js", + "backend/dist/services/backup-restore.js.map", + "backend/dist/services/backup-restore.d.ts", + "backend/dist/services/backup-restore.d.ts.map", + "backend/dist/services/backup-scheduler-collector.js", + "backend/dist/services/backup-scheduler-collector.js.map", + "backend/dist/services/backup-scheduler-collector.d.ts", + "backend/dist/services/backup-scheduler-collector.d.ts.map", + "backend/dist/services/backup-scheduler-run.js", + "backend/dist/services/backup-scheduler-run.js.map", + "backend/dist/services/backup-scheduler-run.d.ts", + "backend/dist/services/backup-scheduler-run.d.ts.map", + "backend/dist/services/backup-service.js", + "backend/dist/services/backup-service.js.map", + "backend/dist/services/backup-service.d.ts", + "backend/dist/services/backup-service.d.ts.map", + "backend/dist/services/backup-storage-update.js", + "backend/dist/services/backup-storage-update.js.map", + "backend/dist/services/backup-storage-update.d.ts", + "backend/dist/services/backup-storage-update.d.ts.map", + "backend/dist/services/bandwidth-test-rates.js", + "backend/dist/services/bandwidth-test-rates.js.map", + "backend/dist/services/bandwidth-test-rates.d.ts", + "backend/dist/services/bandwidth-test-rates.d.ts.map", + "backend/dist/services/bandwidth-test-status.js", + "backend/dist/services/bandwidth-test-status.js.map", + "backend/dist/services/bandwidth-test-status.d.ts", + "backend/dist/services/bandwidth-test-status.d.ts.map", + "backend/dist/services/bgp-catalog-poll.js", + "backend/dist/services/bgp-catalog-poll.js.map", + "backend/dist/services/bgp-catalog-poll.d.ts", + "backend/dist/services/bgp-catalog-poll.d.ts.map", + "backend/dist/services/bgp-parse-sessions.js", + "backend/dist/services/bgp-parse-sessions.js.map", + "backend/dist/services/bgp-parse-sessions.d.ts", + "backend/dist/services/bgp-parse-sessions.d.ts.map", + "backend/dist/services/bgp-peers-live.js", + "backend/dist/services/bgp-peers-live.js.map", + "backend/dist/services/bgp-peers-live.d.ts", + "backend/dist/services/bgp-peers-live.d.ts.map", + "backend/dist/services/btest-node-locks.js", + "backend/dist/services/btest-node-locks.js.map", + "backend/dist/services/btest-node-locks.d.ts", + "backend/dist/services/btest-node-locks.d.ts.map", + "backend/dist/services/certificate-issue-dto.js", + "backend/dist/services/certificate-issue-dto.js.map", + "backend/dist/services/certificate-issue-dto.d.ts", + "backend/dist/services/certificate-issue-dto.d.ts.map", + "backend/dist/services/certificate-issue-execution.js", + "backend/dist/services/certificate-issue-execution.js.map", + "backend/dist/services/certificate-issue-execution.d.ts", + "backend/dist/services/certificate-issue-execution.d.ts.map", + "backend/dist/services/certificate-issue-request.js", + "backend/dist/services/certificate-issue-request.js.map", + "backend/dist/services/certificate-issue-request.d.ts", + "backend/dist/services/certificate-issue-request.d.ts.map", + "backend/dist/services/certificate-issue-runner.js", + "backend/dist/services/certificate-issue-runner.js.map", + "backend/dist/services/certificate-issue-runner.d.ts", + "backend/dist/services/certificate-issue-runner.d.ts.map", + "backend/dist/services/certificate-parse.js", + "backend/dist/services/certificate-parse.js.map", + "backend/dist/services/certificate-parse.d.ts", + "backend/dist/services/certificate-parse.d.ts.map", + "backend/dist/services/certificate-renew-collector.js", + "backend/dist/services/certificate-renew-collector.js.map", + "backend/dist/services/certificate-renew-collector.d.ts", + "backend/dist/services/certificate-renew-collector.d.ts.map", + "backend/dist/services/certificates-service.js", + "backend/dist/services/certificates-service.js.map", + "backend/dist/services/certificates-service.d.ts", + "backend/dist/services/certificates-service.d.ts.map", + "backend/dist/services/certificates-state.js", + "backend/dist/services/certificates-state.js.map", + "backend/dist/services/certificates-state.d.ts", + "backend/dist/services/certificates-state.d.ts.map", + "backend/dist/services/complete-count.js", + "backend/dist/services/complete-count.js.map", + "backend/dist/services/complete-count.d.ts", + "backend/dist/services/complete-count.d.ts.map", + "backend/dist/services/config-apply-plan.js", + "backend/dist/services/config-apply-plan.js.map", + "backend/dist/services/config-apply-plan.d.ts", + "backend/dist/services/config-apply-plan.d.ts.map", + "backend/dist/services/config-revisions.js", + "backend/dist/services/config-revisions.js.map", + "backend/dist/services/config-revisions.d.ts", + "backend/dist/services/config-revisions.d.ts.map", + "backend/dist/services/containers-live.js", + "backend/dist/services/containers-live.js.map", + "backend/dist/services/containers-live.d.ts", + "backend/dist/services/containers-live.d.ts.map", + "backend/dist/services/containers-state.js", + "backend/dist/services/containers-state.js.map", + "backend/dist/services/containers-state.d.ts", + "backend/dist/services/containers-state.d.ts.map", + "backend/dist/services/database-background-work.js", + "backend/dist/services/database-background-work.js.map", + "backend/dist/services/database-background-work.d.ts", + "backend/dist/services/database-background-work.d.ts.map", + "backend/dist/services/database-request-gate.js", + "backend/dist/services/database-request-gate.js.map", + "backend/dist/services/database-request-gate.d.ts", + "backend/dist/services/database-request-gate.d.ts.map", + "backend/dist/services/entity-snapshots.js", + "backend/dist/services/entity-snapshots.js.map", + "backend/dist/services/entity-snapshots.d.ts", + "backend/dist/services/entity-snapshots.d.ts.map", + "backend/dist/services/evobgp-catalog.js", + "backend/dist/services/evobgp-catalog.js.map", + "backend/dist/services/evobgp-catalog.d.ts", + "backend/dist/services/evobgp-catalog.d.ts.map", + "backend/dist/services/firewall-live.js", + "backend/dist/services/firewall-live.js.map", + "backend/dist/services/firewall-live.d.ts", + "backend/dist/services/firewall-live.d.ts.map", + "backend/dist/services/firewall-poll.js", + "backend/dist/services/firewall-poll.js.map", + "backend/dist/services/firewall-poll.d.ts", + "backend/dist/services/firewall-poll.d.ts.map", + "backend/dist/services/geoip-settings.js", + "backend/dist/services/geoip-settings.js.map", + "backend/dist/services/geoip-settings.d.ts", + "backend/dist/services/geoip-settings.d.ts.map", + "backend/dist/services/geoip-update-collector.js", + "backend/dist/services/geoip-update-collector.js.map", + "backend/dist/services/geoip-update-collector.d.ts", + "backend/dist/services/geoip-update-collector.d.ts.map", + "backend/dist/services/gre-bgp-snapshot-collector.js", + "backend/dist/services/gre-bgp-snapshot-collector.js.map", + "backend/dist/services/gre-bgp-snapshot-collector.d.ts", + "backend/dist/services/gre-bgp-snapshot-collector.d.ts.map", + "backend/dist/services/gre-bgp-snapshot-history.js", + "backend/dist/services/gre-bgp-snapshot-history.js.map", + "backend/dist/services/gre-bgp-snapshot-history.d.ts", + "backend/dist/services/gre-bgp-snapshot-history.d.ts.map", + "backend/dist/services/gre-bgp-timing.js", + "backend/dist/services/gre-bgp-timing.js.map", + "backend/dist/services/gre-bgp-timing.d.ts", + "backend/dist/services/gre-bgp-timing.d.ts.map", + "backend/dist/services/gre-catalog-poll.js", + "backend/dist/services/gre-catalog-poll.js.map", + "backend/dist/services/gre-catalog-poll.d.ts", + "backend/dist/services/gre-catalog-poll.d.ts.map", + "backend/dist/services/gre-comment.js", + "backend/dist/services/gre-comment.js.map", + "backend/dist/services/gre-comment.d.ts", + "backend/dist/services/gre-comment.d.ts.map", + "backend/dist/services/gre-live.js", + "backend/dist/services/gre-live.js.map", + "backend/dist/services/gre-live.d.ts", + "backend/dist/services/gre-live.d.ts.map", + "backend/dist/services/gre-tunnels-live.js", + "backend/dist/services/gre-tunnels-live.js.map", + "backend/dist/services/gre-tunnels-live.d.ts", + "backend/dist/services/gre-tunnels-live.d.ts.map", + "backend/dist/services/internet-path-collector.js", + "backend/dist/services/internet-path-collector.js.map", + "backend/dist/services/internet-path-collector.d.ts", + "backend/dist/services/internet-path-collector.d.ts.map", + "backend/dist/services/internet-path-snapshot.js", + "backend/dist/services/internet-path-snapshot.js.map", + "backend/dist/services/internet-path-snapshot.d.ts", + "backend/dist/services/internet-path-snapshot.d.ts.map", + "backend/dist/services/ipsec-ca.js", + "backend/dist/services/ipsec-ca.js.map", + "backend/dist/services/ipsec-ca.d.ts", + "backend/dist/services/ipsec-ca.d.ts.map", + "backend/dist/services/ipsec-certificate-dn.js", + "backend/dist/services/ipsec-certificate-dn.js.map", + "backend/dist/services/ipsec-certificate-dn.d.ts", + "backend/dist/services/ipsec-certificate-dn.d.ts.map", + "backend/dist/services/ipsec-cleanup.js", + "backend/dist/services/ipsec-cleanup.js.map", + "backend/dist/services/ipsec-cleanup.d.ts", + "backend/dist/services/ipsec-cleanup.d.ts.map", + "backend/dist/services/ipsec-config.js", + "backend/dist/services/ipsec-config.js.map", + "backend/dist/services/ipsec-config.d.ts", + "backend/dist/services/ipsec-config.d.ts.map", + "backend/dist/services/ipsec-live.js", + "backend/dist/services/ipsec-live.js.map", + "backend/dist/services/ipsec-live.d.ts", + "backend/dist/services/ipsec-live.d.ts.map", + "backend/dist/services/ipsec-operation.js", + "backend/dist/services/ipsec-operation.js.map", + "backend/dist/services/ipsec-operation.d.ts", + "backend/dist/services/ipsec-operation.d.ts.map", + "backend/dist/services/ipsec-ros.js", + "backend/dist/services/ipsec-ros.js.map", + "backend/dist/services/ipsec-ros.d.ts", + "backend/dist/services/ipsec-ros.d.ts.map", + "backend/dist/services/mikrotik.js", + "backend/dist/services/mikrotik.js.map", + "backend/dist/services/mikrotik.d.ts", + "backend/dist/services/mikrotik.d.ts.map", + "backend/dist/services/monitoring-server-catalog.js", + "backend/dist/services/monitoring-server-catalog.js.map", + "backend/dist/services/monitoring-server-catalog.d.ts", + "backend/dist/services/monitoring-server-catalog.d.ts.map", + "backend/dist/services/ospf-catalog-poll.js", + "backend/dist/services/ospf-catalog-poll.js.map", + "backend/dist/services/ospf-catalog-poll.d.ts", + "backend/dist/services/ospf-catalog-poll.d.ts.map", + "backend/dist/services/ospf-effective-disabled.js", + "backend/dist/services/ospf-effective-disabled.js.map", + "backend/dist/services/ospf-effective-disabled.d.ts", + "backend/dist/services/ospf-effective-disabled.d.ts.map", + "backend/dist/services/ospf-observed-values.js", + "backend/dist/services/ospf-observed-values.js.map", + "backend/dist/services/ospf-observed-values.d.ts", + "backend/dist/services/ospf-observed-values.d.ts.map", + "backend/dist/services/ospf-optimizer-identity.js", + "backend/dist/services/ospf-optimizer-identity.js.map", + "backend/dist/services/ospf-optimizer-identity.d.ts", + "backend/dist/services/ospf-optimizer-identity.d.ts.map", + "backend/dist/services/ospf-optimizer-metrics.js", + "backend/dist/services/ospf-optimizer-metrics.js.map", + "backend/dist/services/ospf-optimizer-metrics.d.ts", + "backend/dist/services/ospf-optimizer-metrics.d.ts.map", + "backend/dist/services/ospf-optimizer-plan.js", + "backend/dist/services/ospf-optimizer-plan.js.map", + "backend/dist/services/ospf-optimizer-plan.d.ts", + "backend/dist/services/ospf-optimizer-plan.d.ts.map", + "backend/dist/services/ospf-route-parse.js", + "backend/dist/services/ospf-route-parse.js.map", + "backend/dist/services/ospf-route-parse.d.ts", + "backend/dist/services/ospf-route-parse.d.ts.map", + "backend/dist/services/ping-probe-replacement.js", + "backend/dist/services/ping-probe-replacement.js.map", + "backend/dist/services/ping-probe-replacement.d.ts", + "backend/dist/services/ping-probe-replacement.d.ts.map", + "backend/dist/services/poller.js", + "backend/dist/services/poller.js.map", + "backend/dist/services/poller.d.ts", + "backend/dist/services/poller.d.ts.map", + "backend/dist/services/recursive-apply.js", + "backend/dist/services/recursive-apply.js.map", + "backend/dist/services/recursive-apply.d.ts", + "backend/dist/services/recursive-apply.d.ts.map", + "backend/dist/services/recursive-route-catalog.js", + "backend/dist/services/recursive-route-catalog.js.map", + "backend/dist/services/recursive-route-catalog.d.ts", + "backend/dist/services/recursive-route-catalog.d.ts.map", + "backend/dist/services/recursive-route-validation.js", + "backend/dist/services/recursive-route-validation.js.map", + "backend/dist/services/recursive-route-validation.d.ts", + "backend/dist/services/recursive-route-validation.d.ts.map", + "backend/dist/services/ros-metric-parse.js", + "backend/dist/services/ros-metric-parse.js.map", + "backend/dist/services/ros-metric-parse.d.ts", + "backend/dist/services/ros-metric-parse.d.ts.map", + "backend/dist/services/ros-ops.js", + "backend/dist/services/ros-ops.js.map", + "backend/dist/services/ros-ops.d.ts", + "backend/dist/services/ros-ops.d.ts.map", + "backend/dist/services/s3-backup-client.js", + "backend/dist/services/s3-backup-client.js.map", + "backend/dist/services/s3-backup-client.d.ts", + "backend/dist/services/s3-backup-client.d.ts.map", + "backend/dist/services/scheduler-recorded-run.js", + "backend/dist/services/scheduler-recorded-run.js.map", + "backend/dist/services/scheduler-recorded-run.d.ts", + "backend/dist/services/scheduler-recorded-run.d.ts.map", + "backend/dist/services/scheduler-running.js", + "backend/dist/services/scheduler-running.js.map", + "backend/dist/services/scheduler-running.d.ts", + "backend/dist/services/scheduler-running.d.ts.map", + "backend/dist/services/scheduler.js", + "backend/dist/services/scheduler.js.map", + "backend/dist/services/scheduler.d.ts", + "backend/dist/services/scheduler.d.ts.map", + "backend/dist/services/serial-operation.js", + "backend/dist/services/serial-operation.js.map", + "backend/dist/services/serial-operation.d.ts", + "backend/dist/services/serial-operation.d.ts.map", + "backend/dist/services/servers-rest-ping-collector.js", + "backend/dist/services/servers-rest-ping-collector.js.map", + "backend/dist/services/servers-rest-ping-collector.d.ts", + "backend/dist/services/servers-rest-ping-collector.d.ts.map", + "backend/dist/services/speed-history-dto.js", + "backend/dist/services/speed-history-dto.js.map", + "backend/dist/services/speed-history-dto.d.ts", + "backend/dist/services/speed-history-dto.d.ts.map", + "backend/dist/services/speed-probe-dto.js", + "backend/dist/services/speed-probe-dto.js.map", + "backend/dist/services/speed-probe-dto.d.ts", + "backend/dist/services/speed-probe-dto.d.ts.map", + "backend/dist/services/speed-probe-replacement.js", + "backend/dist/services/speed-probe-replacement.js.map", + "backend/dist/services/speed-probe-replacement.d.ts", + "backend/dist/services/speed-probe-replacement.d.ts.map", + "backend/dist/services/speed-run-error-message.js", + "backend/dist/services/speed-run-error-message.js.map", + "backend/dist/services/speed-run-error-message.d.ts", + "backend/dist/services/speed-run-error-message.d.ts.map", + "backend/dist/services/speed-run-persistence.js", + "backend/dist/services/speed-run-persistence.js.map", + "backend/dist/services/speed-run-persistence.d.ts", + "backend/dist/services/speed-run-persistence.d.ts.map", + "backend/dist/services/statistics-aggregate.js", + "backend/dist/services/statistics-aggregate.js.map", + "backend/dist/services/statistics-aggregate.d.ts", + "backend/dist/services/statistics-aggregate.d.ts.map", + "backend/dist/services/system-database-backup.js", + "backend/dist/services/system-database-backup.js.map", + "backend/dist/services/system-database-backup.d.ts", + "backend/dist/services/system-database-backup.d.ts.map", + "backend/dist/services/system-database-cache.js", + "backend/dist/services/system-database-cache.js.map", + "backend/dist/services/system-database-cache.d.ts", + "backend/dist/services/system-database-cache.d.ts.map", + "backend/dist/services/system-database-operation.js", + "backend/dist/services/system-database-operation.js.map", + "backend/dist/services/system-database-operation.d.ts", + "backend/dist/services/system-database-operation.d.ts.map", + "backend/dist/services/system-database-partition-restore.js", + "backend/dist/services/system-database-partition-restore.js.map", + "backend/dist/services/system-database-partition-restore.d.ts", + "backend/dist/services/system-database-partition-restore.d.ts.map", + "backend/dist/services/system-database-restore-execution.js", + "backend/dist/services/system-database-restore-execution.js.map", + "backend/dist/services/system-database-restore-execution.d.ts", + "backend/dist/services/system-database-restore-execution.d.ts.map", + "backend/dist/services/traffic-collector.js", + "backend/dist/services/traffic-collector.js.map", + "backend/dist/services/traffic-collector.d.ts", + "backend/dist/services/traffic-collector.d.ts.map", + "backend/dist/services/traffic-flow-analytics.js", + "backend/dist/services/traffic-flow-analytics.js.map", + "backend/dist/services/traffic-flow-analytics.d.ts", + "backend/dist/services/traffic-flow-analytics.d.ts.map", + "backend/dist/services/traffic-flow-apps.js", + "backend/dist/services/traffic-flow-apps.js.map", + "backend/dist/services/traffic-flow-apps.d.ts", + "backend/dist/services/traffic-flow-apps.d.ts.map", + "backend/dist/services/traffic-flow-brands.js", + "backend/dist/services/traffic-flow-brands.js.map", + "backend/dist/services/traffic-flow-brands.d.ts", + "backend/dist/services/traffic-flow-brands.d.ts.map", + "backend/dist/services/traffic-flow-classify.js", + "backend/dist/services/traffic-flow-classify.js.map", + "backend/dist/services/traffic-flow-classify.d.ts", + "backend/dist/services/traffic-flow-classify.d.ts.map", + "backend/dist/services/traffic-flow-collector-ipc.js", + "backend/dist/services/traffic-flow-collector-ipc.js.map", + "backend/dist/services/traffic-flow-collector-ipc.d.ts", + "backend/dist/services/traffic-flow-collector-ipc.d.ts.map", + "backend/dist/services/traffic-flow-collector-worker.js", + "backend/dist/services/traffic-flow-collector-worker.js.map", + "backend/dist/services/traffic-flow-collector-worker.d.ts", + "backend/dist/services/traffic-flow-collector-worker.d.ts.map", + "backend/dist/services/traffic-flow-dedup.js", + "backend/dist/services/traffic-flow-dedup.js.map", + "backend/dist/services/traffic-flow-dedup.d.ts", + "backend/dist/services/traffic-flow-dedup.d.ts.map", + "backend/dist/services/traffic-flow-dest.js", + "backend/dist/services/traffic-flow-dest.js.map", + "backend/dist/services/traffic-flow-dest.d.ts", + "backend/dist/services/traffic-flow-dest.d.ts.map", + "backend/dist/services/traffic-flow-engine.js", + "backend/dist/services/traffic-flow-engine.js.map", + "backend/dist/services/traffic-flow-engine.d.ts", + "backend/dist/services/traffic-flow-engine.d.ts.map", + "backend/dist/services/traffic-flow-facts-filter.js", + "backend/dist/services/traffic-flow-facts-filter.js.map", + "backend/dist/services/traffic-flow-facts-filter.d.ts", + "backend/dist/services/traffic-flow-facts-filter.d.ts.map", + "backend/dist/services/traffic-flow-facts-rebuild.js", + "backend/dist/services/traffic-flow-facts-rebuild.js.map", + "backend/dist/services/traffic-flow-facts-rebuild.d.ts", + "backend/dist/services/traffic-flow-facts-rebuild.d.ts.map", + "backend/dist/services/traffic-flow-facts.js", + "backend/dist/services/traffic-flow-facts.js.map", + "backend/dist/services/traffic-flow-facts.d.ts", + "backend/dist/services/traffic-flow-facts.d.ts.map", + "backend/dist/services/traffic-flow-geoip.js", + "backend/dist/services/traffic-flow-geoip.js.map", + "backend/dist/services/traffic-flow-geoip.d.ts", + "backend/dist/services/traffic-flow-geoip.d.ts.map", + "backend/dist/services/traffic-flow-host-file-builders.js", + "backend/dist/services/traffic-flow-host-file-builders.js.map", + "backend/dist/services/traffic-flow-host-file-builders.d.ts", + "backend/dist/services/traffic-flow-host-file-builders.d.ts.map", + "backend/dist/services/traffic-flow-host-files.js", + "backend/dist/services/traffic-flow-host-files.js.map", + "backend/dist/services/traffic-flow-host-files.d.ts", + "backend/dist/services/traffic-flow-host-files.d.ts.map", + "backend/dist/services/traffic-flow-ifaces.js", + "backend/dist/services/traffic-flow-ifaces.js.map", + "backend/dist/services/traffic-flow-ifaces.d.ts", + "backend/dist/services/traffic-flow-ifaces.d.ts.map", + "backend/dist/services/traffic-flow-ifindex.js", + "backend/dist/services/traffic-flow-ifindex.js.map", + "backend/dist/services/traffic-flow-ifindex.d.ts", + "backend/dist/services/traffic-flow-ifindex.d.ts.map", + "backend/dist/services/traffic-flow-ingest.js", + "backend/dist/services/traffic-flow-ingest.js.map", + "backend/dist/services/traffic-flow-ingest.d.ts", + "backend/dist/services/traffic-flow-ingest.d.ts.map", + "backend/dist/services/traffic-flow-ip.js", + "backend/dist/services/traffic-flow-ip.js.map", + "backend/dist/services/traffic-flow-ip.d.ts", + "backend/dist/services/traffic-flow-ip.d.ts.map", + "backend/dist/services/traffic-flow-key-execution.js", + "backend/dist/services/traffic-flow-key-execution.js.map", + "backend/dist/services/traffic-flow-key-execution.d.ts", + "backend/dist/services/traffic-flow-key-execution.d.ts.map", + "backend/dist/services/traffic-flow-map-exporter.js", + "backend/dist/services/traffic-flow-map-exporter.js.map", + "backend/dist/services/traffic-flow-map-exporter.d.ts", + "backend/dist/services/traffic-flow-map-exporter.d.ts.map", + "backend/dist/services/traffic-flow-map-hops.js", + "backend/dist/services/traffic-flow-map-hops.js.map", + "backend/dist/services/traffic-flow-map-hops.d.ts", + "backend/dist/services/traffic-flow-map-hops.d.ts.map", + "backend/dist/services/traffic-flow-overlay.js", + "backend/dist/services/traffic-flow-overlay.js.map", + "backend/dist/services/traffic-flow-overlay.d.ts", + "backend/dist/services/traffic-flow-overlay.d.ts.map", + "backend/dist/services/traffic-flow-parse.js", + "backend/dist/services/traffic-flow-parse.js.map", + "backend/dist/services/traffic-flow-parse.d.ts", + "backend/dist/services/traffic-flow-parse.d.ts.map", + "backend/dist/services/traffic-flow-planes.js", + "backend/dist/services/traffic-flow-planes.js.map", + "backend/dist/services/traffic-flow-planes.d.ts", + "backend/dist/services/traffic-flow-planes.d.ts.map", + "backend/dist/services/traffic-flow-purge-execution.js", + "backend/dist/services/traffic-flow-purge-execution.js.map", + "backend/dist/services/traffic-flow-purge-execution.d.ts", + "backend/dist/services/traffic-flow-purge-execution.d.ts.map", + "backend/dist/services/traffic-flow-ripe.js", + "backend/dist/services/traffic-flow-ripe.js.map", + "backend/dist/services/traffic-flow-ripe.d.ts", + "backend/dist/services/traffic-flow-ripe.d.ts.map", + "backend/dist/services/traffic-flow-settings.js", + "backend/dist/services/traffic-flow-settings.js.map", + "backend/dist/services/traffic-flow-settings.d.ts", + "backend/dist/services/traffic-flow-settings.d.ts.map", + "backend/dist/services/traffic-flow-topology.js", + "backend/dist/services/traffic-flow-topology.js.map", + "backend/dist/services/traffic-flow-topology.d.ts", + "backend/dist/services/traffic-flow-topology.d.ts.map", + "backend/dist/services/traffic-flow-worker-shutdown.js", + "backend/dist/services/traffic-flow-worker-shutdown.js.map", + "backend/dist/services/traffic-flow-worker-shutdown.d.ts", + "backend/dist/services/traffic-flow-worker-shutdown.d.ts.map", + "backend/dist/services/traffic-rate.js", + "backend/dist/services/traffic-rate.js.map", + "backend/dist/services/traffic-rate.d.ts", + "backend/dist/services/traffic-rate.d.ts.map", + "backend/dist/services/traffic-users.js", + "backend/dist/services/traffic-users.js.map", + "backend/dist/services/traffic-users.d.ts", + "backend/dist/services/traffic-users.d.ts.map", + "backend/dist/services/uptime-collector.js", + "backend/dist/services/uptime-collector.js.map", + "backend/dist/services/uptime-collector.d.ts", + "backend/dist/services/uptime-collector.d.ts.map", + "backend/dist/services/uptime-probe-observations.js", + "backend/dist/services/uptime-probe-observations.js.map", + "backend/dist/services/uptime-probe-observations.d.ts", + "backend/dist/services/uptime-probe-observations.d.ts.map", + "backend/dist/services/uptime-speed-service.js", + "backend/dist/services/uptime-speed-service.js.map", + "backend/dist/services/uptime-speed-service.d.ts", + "backend/dist/services/uptime-speed-service.d.ts.map", + "backend/dist/services/vxlan-live.js", + "backend/dist/services/vxlan-live.js.map", + "backend/dist/services/vxlan-live.d.ts", + "backend/dist/services/vxlan-live.d.ts.map", + "backend/dist/services/wg-keys.js", + "backend/dist/services/wg-keys.js.map", + "backend/dist/services/wg-keys.d.ts", + "backend/dist/services/wg-keys.d.ts.map", + "backend/dist/services/wireguard-config.js", + "backend/dist/services/wireguard-config.js.map", + "backend/dist/services/wireguard-config.d.ts", + "backend/dist/services/wireguard-config.d.ts.map", + "backend/dist/services/wireguard-delete.js", + "backend/dist/services/wireguard-delete.js.map", + "backend/dist/services/wireguard-delete.d.ts", + "backend/dist/services/wireguard-delete.d.ts.map", + "backend/dist/services/wireguard-import-apply.js", + "backend/dist/services/wireguard-import-apply.js.map", + "backend/dist/services/wireguard-import-apply.d.ts", + "backend/dist/services/wireguard-import-apply.d.ts.map", + "backend/dist/services/wireguard-import-revision.js", + "backend/dist/services/wireguard-import-revision.js.map", + "backend/dist/services/wireguard-import-revision.d.ts", + "backend/dist/services/wireguard-import-revision.d.ts.map", + "backend/dist/services/wireguard-import.js", + "backend/dist/services/wireguard-import.js.map", + "backend/dist/services/wireguard-import.d.ts", + "backend/dist/services/wireguard-import.d.ts.map", + "backend/dist/services/wireguard-live.js", + "backend/dist/services/wireguard-live.js.map", + "backend/dist/services/wireguard-live.d.ts", + "backend/dist/services/wireguard-live.d.ts.map", + "backend/dist/services/wireguard-observed-values.js", + "backend/dist/services/wireguard-observed-values.js.map", + "backend/dist/services/wireguard-observed-values.d.ts", + "backend/dist/services/wireguard-observed-values.d.ts.map", + "backend/dist/services/wireguard-ros.js", + "backend/dist/services/wireguard-ros.js.map", + "backend/dist/services/wireguard-ros.d.ts", + "backend/dist/services/wireguard-ros.d.ts.map", + "backend/dist/test/pg.js", + "backend/dist/test/pg.js.map", + "backend/dist/test/pg.d.ts", + "backend/dist/test/pg.d.ts.map", + "backend/dist/types/scheduler-run-snapshot.js", + "backend/dist/types/scheduler-run-snapshot.js.map", + "backend/dist/types/scheduler-run-snapshot.d.ts", + "backend/dist/types/scheduler-run-snapshot.d.ts.map", + "backend/dist/types/server.js", + "backend/dist/types/server.js.map", + "backend/dist/types/server.d.ts", + "backend/dist/types/server.d.ts.map", + "backend/dist/utils/abort-signals.js", + "backend/dist/utils/abort-signals.js.map", + "backend/dist/utils/abort-signals.d.ts", + "backend/dist/utils/abort-signals.d.ts.map", + "backend/dist/utils/ros-src-address.js", + "backend/dist/utils/ros-src-address.js.map", + "backend/dist/utils/ros-src-address.d.ts", + "backend/dist/utils/ros-src-address.d.ts.map", + "backend/dist/utils/server-id.js", + "backend/dist/utils/server-id.js.map", + "backend/dist/utils/server-id.d.ts", + "backend/dist/utils/server-id.d.ts.map", + "packages/contracts/dist/alerts.js", + "packages/contracts/dist/alerts.js.map", + "packages/contracts/dist/alerts.d.ts", + "packages/contracts/dist/alerts.d.ts.map", + "packages/contracts/dist/backups.js", + "packages/contracts/dist/backups.js.map", + "packages/contracts/dist/backups.d.ts", + "packages/contracts/dist/backups.d.ts.map", + "packages/contracts/dist/certificates.js", + "packages/contracts/dist/certificates.js.map", + "packages/contracts/dist/certificates.d.ts", + "packages/contracts/dist/certificates.d.ts.map", + "packages/contracts/dist/containers.js", + "packages/contracts/dist/containers.js.map", + "packages/contracts/dist/containers.d.ts", + "packages/contracts/dist/containers.d.ts.map", + "packages/contracts/dist/events.js", + "packages/contracts/dist/events.js.map", + "packages/contracts/dist/events.d.ts", + "packages/contracts/dist/events.d.ts.map", + "packages/contracts/dist/evobgp.js", + "packages/contracts/dist/evobgp.js.map", + "packages/contracts/dist/evobgp.d.ts", + "packages/contracts/dist/evobgp.d.ts.map", + "packages/contracts/dist/geoip.js", + "packages/contracts/dist/geoip.js.map", + "packages/contracts/dist/geoip.d.ts", + "packages/contracts/dist/geoip.d.ts.map", + "packages/contracts/dist/index.js", + "packages/contracts/dist/index.js.map", + "packages/contracts/dist/index.d.ts", + "packages/contracts/dist/index.d.ts.map", + "packages/contracts/dist/ipsec.js", + "packages/contracts/dist/ipsec.js.map", + "packages/contracts/dist/ipsec.d.ts", + "packages/contracts/dist/ipsec.d.ts.map", + "packages/contracts/dist/ospf-optimizer.js", + "packages/contracts/dist/ospf-optimizer.js.map", + "packages/contracts/dist/ospf-optimizer.d.ts", + "packages/contracts/dist/ospf-optimizer.d.ts.map", + "packages/contracts/dist/servers.js", + "packages/contracts/dist/servers.js.map", + "packages/contracts/dist/servers.d.ts", + "packages/contracts/dist/servers.d.ts.map", + "packages/contracts/dist/statistics.js", + "packages/contracts/dist/statistics.js.map", + "packages/contracts/dist/statistics.d.ts", + "packages/contracts/dist/statistics.d.ts.map", + "packages/contracts/dist/traffic-flow.js", + "packages/contracts/dist/traffic-flow.js.map", + "packages/contracts/dist/traffic-flow.d.ts", + "packages/contracts/dist/traffic-flow.d.ts.map", + "packages/contracts/dist/uptime-probes.js", + "packages/contracts/dist/uptime-probes.js.map", + "packages/contracts/dist/uptime-probes.d.ts", + "packages/contracts/dist/uptime-probes.d.ts.map", + "packages/contracts/dist/uptime-speed.js", + "packages/contracts/dist/uptime-speed.js.map", + "packages/contracts/dist/uptime-speed.d.ts", + "packages/contracts/dist/uptime-speed.d.ts.map", + "packages/contracts/dist/users.js", + "packages/contracts/dist/users.js.map", + "packages/contracts/dist/users.d.ts", + "packages/contracts/dist/users.d.ts.map", + "packages/contracts/dist/wireguard.js", + "packages/contracts/dist/wireguard.js.map", + "packages/contracts/dist/wireguard.d.ts", + "packages/contracts/dist/wireguard.d.ts.map" + ], + "missing": [], + "different": [] +} \ No newline at end of file diff --git a/docs/ux-ui/acceptance/ping-measurement-deployed-2026-10-12.json b/docs/ux-ui/acceptance/ping-measurement-deployed-2026-10-12.json new file mode 100644 index 00000000..597639da --- /dev/null +++ b/docs/ux-ui/acceptance/ping-measurement-deployed-2026-10-12.json @@ -0,0 +1,29 @@ +{ + "date": "2026-10-12", + "block": "12.5.173", + "runtime": { + "backend": "ux-ping173", + "frontend": "ux-ping173" + }, + "cases": [ + { + "width": 1440, + "overview200": true, + "unknownFilterPresent": true, + "pageErrors": 0, + "noOverflow": true + }, + { + "width": 320, + "overview200": true, + "unknownFilterPresent": true, + "pageErrors": 0, + "noOverflow": true + } + ], + "bounds": [ + "Actual empty configured probe catalog; positive/error UI proven by API-path fixtures and own HTTP/SQL", + "Real CHR parser checked separately by readonly native Ping, not by empty production catalog", + "No config/password/message writes during this read-only browser smoke" + ] +} diff --git a/docs/ux-ui/acceptance/ping-measurement-interop-2026-10-12.json b/docs/ux-ui/acceptance/ping-measurement-interop-2026-10-12.json new file mode 100644 index 00000000..e3fe10c3 --- /dev/null +++ b/docs/ux-ui/acceptance/ping-measurement-interop-2026-10-12.json @@ -0,0 +1,9 @@ +{ + "date": "2026-10-12", + "block": "12.5.173", + "newFrontend": "ux-ping173", + "oldBackend": "ux-history172", + "readerAccepted": true, + "probeCount": 0, + "bound": "Actual empty configured catalog; nonempty old valid payloads separately covered by unit/UI fixtures" +} \ No newline at end of file diff --git a/docs/ux-ui/acceptance/ping-measurement-native-2026-10-12.json b/docs/ux-ui/acceptance/ping-measurement-native-2026-10-12.json new file mode 100644 index 00000000..7654db2b --- /dev/null +++ b/docs/ux-ui/acceptance/ping-measurement-native-2026-10-12.json @@ -0,0 +1,107 @@ +{ + "date": "2026-10-12", + "block": "12.5.173", + "version": "1.0.0-test.ping173", + "readOnlyDatabase": true, + "cases": [ + { + "sourceId": 1, + "target": "104.165.244.7", + "requestAccepted": true, + "measured": true, + "status": "up", + "rttMs": 53, + "lossPct": 0, + "raw": [ + { + "avg-rtt": "53ms561us", + "host": "104.165.244.7", + "packet-loss": "0", + "received": "1", + "sent": "1", + "seq": "0", + "time": "53ms561us" + }, + { + "avg-rtt": "53ms376us", + "host": "104.165.244.7", + "packet-loss": "0", + "received": "2", + "sent": "2", + "seq": "1", + "time": "53ms191us" + }, + { + "avg-rtt": "53ms368us", + "host": "104.165.244.7", + "packet-loss": "0", + "received": "3", + "sent": "3", + "seq": "2", + "time": "53ms352us" + }, + { + "avg-rtt": "53ms333us", + "host": "104.165.244.7", + "packet-loss": "0", + "received": "4", + "sent": "4", + "seq": "3", + "time": "53ms231us" + } + ] + }, + { + "sourceId": 2, + "target": "194.226.121.150", + "requestAccepted": true, + "measured": true, + "status": "up", + "rttMs": 53, + "lossPct": 0, + "raw": [ + { + "avg-rtt": "53ms593us", + "host": "194.226.121.150", + "packet-loss": "0", + "received": "1", + "sent": "1", + "seq": "0", + "time": "53ms593us" + }, + { + "avg-rtt": "53ms373us", + "host": "194.226.121.150", + "packet-loss": "0", + "received": "2", + "sent": "2", + "seq": "1", + "time": "53ms153us" + }, + { + "avg-rtt": "53ms349us", + "host": "194.226.121.150", + "packet-loss": "0", + "received": "3", + "sent": "3", + "seq": "2", + "time": "53ms301us" + }, + { + "avg-rtt": "53ms364us", + "host": "194.226.121.150", + "packet-loss": "0", + "received": "4", + "sent": "4", + "seq": "3", + "time": "53ms409us" + } + ] + } + ], + "dbWrites": 0, + "routerConfigWrites": 0, + "messages": 0, + "migrationApplied": true, + "passed": true +} \ No newline at end of file diff --git a/docs/ux-ui/acceptance/ping-measurement-runtime-2026-10-12.json b/docs/ux-ui/acceptance/ping-measurement-runtime-2026-10-12.json new file mode 100644 index 00000000..cc36ad7b --- /dev/null +++ b/docs/ux-ui/acceptance/ping-measurement-runtime-2026-10-12.json @@ -0,0 +1,30 @@ +{ + "date": "2026-10-12", + "block": "12.5.173", + "services": { + "backend": { + "image": "mmapp-test/backend:ux-ping173", + "imageId": "sha256:8fe4ab84759f4d09a2dc775f91fd4266e503225ec3b6be8466d937f926ac97da", + "matchedVerifiedImage": true, + "state": "running", + "health": "healthy" + }, + "frontend": { + "image": "mmapp-test/frontend:ux-ping173", + "imageId": "sha256:b73d9cbd1e056839fe17b66f5f2b1324cd809fbc2699b4512fcd8040cc7501f3", + "matchedVerifiedImage": true, + "state": "running", + "health": null + } + }, + "rollbackTags": { + "MMAPP_IMAGE_TAG": "ux-targets171", + "BACKEND_IMAGE_TAG": "ux-history172" + }, + "secretEnvCopied": false, + "runningContainers": [ + "mmapp-test-backend", + "mmapp-test-frontend", + "mmapp-test-postgres" + ] +} \ No newline at end of file diff --git a/docs/ux-ui/acceptance/ping-measurement-source-2026-10-12.json b/docs/ux-ui/acceptance/ping-measurement-source-2026-10-12.json new file mode 100644 index 00000000..11487363 --- /dev/null +++ b/docs/ux-ui/acceptance/ping-measurement-source-2026-10-12.json @@ -0,0 +1,11 @@ +{ + "date": "2026-10-12", + "block": "12.5.173", + "matched": { + "backend": 440, + "frontend": 518 + }, + "different": [], + "missing": [], + "sourceBase": "ca23284486ab9cade3a5d59bb41c685d5b7c9dcc" +} \ No newline at end of file diff --git a/docs/ux-ui/acceptance/ping-measurement-sql-2026-10-12.json b/docs/ux-ui/acceptance/ping-measurement-sql-2026-10-12.json new file mode 100644 index 00000000..3ecffad3 --- /dev/null +++ b/docs/ux-ui/acceptance/ping-measurement-sql-2026-10-12.json @@ -0,0 +1,23 @@ +{ + "date": "2026-10-12", + "block": "12.5.173", + "database": "mm_accept_166_1791801498307", + "scope": "ephemeral role and precreated owned database; no copied production credentials", + "tests": [ + { + "command": "npm --prefix backend run test:alert-engine", + "passed": true, + "durationMs": 42380, + "ownedHistorySqlRows": 8, + "ownedSourceSqlCases": 26, + "pingMeasurementSqlCases": 8, + "sqlConfirmStabilityPassed": true, + "postgresUnavailableSkip": false + } + ], + "scopedRoleVerified": true, + "passed": true, + "databaseRemoved": true, + "roleRemoved": true, + "testContainerRemoved": true +} \ No newline at end of file diff --git a/docs/ux-ui/screens/ping-measurement-unknown-1440-2026-10-12.png b/docs/ux-ui/screens/ping-measurement-unknown-1440-2026-10-12.png new file mode 100644 index 0000000000000000000000000000000000000000..85d0c3484f3467b421cde6bef8e8e95d6f6d9e5d GIT binary patch literal 118824 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 literal 0 HcmV?d00001 diff --git a/docs/ux-ui/screens/ping-measurement-unknown-320-2026-10-12.png b/docs/ux-ui/screens/ping-measurement-unknown-320-2026-10-12.png new file mode 100644 index 0000000000000000000000000000000000000000..eed1f367ad8f6b1421f7f451fc1255be673f6eb2 GIT binary patch literal 52320 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 KMNUMnLSTaDpX~Jj literal 0 HcmV?d00001 diff --git a/lib/dashboard-probes.test.ts b/lib/dashboard-probes.test.ts index 0769b39e..bd47e6d0 100644 --- a/lib/dashboard-probes.test.ts +++ b/lib/dashboard-probes.test.ts @@ -19,3 +19,9 @@ test("ping observed zero and failure retain independent measurement values and s test("ping invalid time axis, duplicate or out of range samples cannot replace cache",()=>{ for(const raw of [payload([sampled,sampled]),payload([{...sampled,sampledAt:"invalid"}]),payload([{...sampled,sampledAt:"2026-10-02T00:00:00Z"}]),{...payload([sampled]),rangeEndAt:start},{...payload([sampled]),rangeStartAt:undefined},payload([{...sampled,loss:101}])])assert.throws(()=>parseDashboardProbes(raw)) }) +test("failed execution is an unknown observation, while measured 100 percent loss remains down",()=>{ + const failed={...sampled,sampledAt:"2026-10-03T00:31:00Z",rtt:null,loss:null,status:"unknown",error:"API denied"} + const p=parseDashboardProbes(payload([sampled,failed])).probes[0] + assert.equal(p.status,"unknown");assert.equal(p.rtt,null);assert.equal(p.loss,null);assert.equal(p.observationError,"API denied");assert.equal(p.series[31],null) + assert.throws(()=>parseDashboardProbes(payload([{...failed,loss:100}]))) +}) diff --git a/lib/dashboard-probes.ts b/lib/dashboard-probes.ts index e540726f..f5b95730 100644 --- a/lib/dashboard-probes.ts +++ b/lib/dashboard-probes.ts @@ -1,22 +1,22 @@ import {z} from "zod" import type {PingProbe} from "@/lib/data" const date=z.iso.datetime({offset:true}) -const sample=z.object({sampledAt:date,rtt:z.number().finite().nonnegative().nullable(),loss:z.number().finite().min(0).max(100),status:z.enum(["up","warn","down"])}) -const probe=z.object({id:z.string().trim().min(1),srcServerId:z.string().trim().min(1),srcInterface:z.string().optional(),name:z.string(),target:z.string(),filter:z.string(),rtt:z.number().finite().nonnegative().nullable(),loss:z.number().finite().min(0).max(100),status:z.enum(["up","warn","down"]),series:z.array(z.number().finite().nonnegative()),enabled:z.boolean(),showOnDashboard:z.boolean(),observations:z.object({samples:z.array(sample)}).optional()}) +const sample=z.object({sampledAt:date,rtt:z.number().finite().nonnegative().nullable(),loss:z.number().finite().min(0).max(100).nullable(),status:z.enum(["up","warn","down","unknown"]),error:z.string().optional()}).refine(s=>s.status==="unknown"?s.rtt===null&&s.loss===null:s.loss!==null) +const probe=z.object({id:z.string().trim().min(1),srcServerId:z.string().trim().min(1),srcInterface:z.string().optional(),name:z.string(),target:z.string(),filter:z.string(),rtt:z.number().finite().nonnegative().nullable(),loss:z.number().finite().min(0).max(100).nullable(),status:z.enum(["up","warn","down","unknown"]),series:z.array(z.number().finite().nonnegative().nullable()),enabled:z.boolean(),showOnDashboard:z.boolean(),observations:z.object({samples:z.array(sample)}).optional()}) const overview=z.object({probes:z.array(probe).refine(rows=>new Set(rows.map(r=>r.id)).size===rows.length),rangeStartAt:date.optional(),rangeEndAt:date.optional()}).refine(raw=>{ if(!raw.probes.some(p=>p.observations))return true if(!raw.rangeStartAt||!raw.rangeEndAt)return false const start=Date.parse(raw.rangeStartAt),end=Date.parse(raw.rangeEndAt) return end>start&&raw.probes.every(p=>{const samples=p.observations?.samples??[];return new Set(samples.map(s=>Date.parse(s.sampledAt))).size===samples.length&&samples.every((s,i)=>Date.parse(s.sampledAt)>=start&&Date.parse(s.sampledAt)<=end&&(i===0||Date.parse(s.sampledAt)>Date.parse(samples[i-1].sampledAt)))}) }) -export type DashboardProbe=Omit&{status:PingProbe["status"]|"unknown";loss:number|null;series:(number|null)[];sampledAt?:string|null;historyObserved?:boolean} +export type DashboardProbe=Omit&{status:PingProbe["status"]|"unknown";loss:number|null;series:(number|null)[];sampledAt?:string|null;historyObserved?:boolean;observationError?:string|null} function meanRtt(values:Array){const mean=values.reduce((sum,v)=>sum+(v??0),0)/values.length;if(!Number.isFinite(mean))throw Error("Средний RTT превышает допустимый диапазон");return Math.round(mean)} export function parseDashboardProbes(raw:unknown):{probes:DashboardProbe[];sampledAt:string|null}{ const parsed=overview.safeParse(raw);if(!parsed.success)throw Error("Некорректные данные проб") const data=parsed.data,start=data.rangeStartAt?Date.parse(data.rangeStartAt):0,end=data.rangeEndAt?Date.parse(data.rangeEndAt):0 const probes:DashboardProbe[]=data.probes.map(p=>{const samples=p.observations?.samples??[],last=samples.at(-1);const buckets:Array>=Array.from({length:60},()=>[]) for(const s of samples){const i=Math.min(59,Math.floor((Date.parse(s.sampledAt)-start)/(end-start)*60));buckets[i].push(s.rtt)} - return {...p,status:last?.status??"unknown",rtt:last?.rtt??null,loss:last?.loss??null,sampledAt:last?.sampledAt??null,historyObserved:!!p.observations,series:buckets.map(values=>!values.length||values.some(v=>v===null)?null:meanRtt(values))} + return {...p,status:last?.status??"unknown",rtt:last?.rtt??null,loss:last?.loss??null,sampledAt:last?.sampledAt??null,historyObserved:!!p.observations,observationError:last?.error??null,series:buckets.map(values=>!values.length||values.some(v=>v===null)?null:meanRtt(values))} }) const active=probes.filter(p=>p.enabled),times=active.map(p=>p.sampledAt).filter((v):v is string=>!!v) return {probes,sampledAt:active.length&×.length===active.length?new Date(times.reduce((oldest,v)=>Math.min(oldest,Date.parse(v)),Infinity)).toISOString():null} diff --git a/lib/data-filters/uptime-url-state.ts b/lib/data-filters/uptime-url-state.ts index c8a177a2..ccaa1ad4 100644 --- a/lib/data-filters/uptime-url-state.ts +++ b/lib/data-filters/uptime-url-state.ts @@ -1,17 +1,17 @@ import type { Dispatch, SetStateAction } from "react" export type UptimeTab = "probes" | "resources" | "speed" -export type ProbeStatusFilter = "all" | "up" | "warn" | "down" +export type ProbeStatusFilter = "all" | "up" | "warn" | "down" | "unknown" export function readUptimeUrl(params: URLSearchParams) { const rawTab = params.get("tab") const rawStatus = params.get("status") const search = params.get("q") ?? "" const tab: UptimeTab = rawTab === "resources" || rawTab === "speed" ? rawTab : "probes" - const status: ProbeStatusFilter = rawStatus === "up" || rawStatus === "warn" || rawStatus === "down" ? rawStatus : "all" + const status: ProbeStatusFilter = rawStatus === "up" || rawStatus === "warn" || rawStatus === "down" || rawStatus === "unknown" ? rawStatus : "all" let error: string | null = null let servers: string[] = [] if ((rawTab !== null && !["probes", "resources", "speed"].includes(rawTab)) || - (rawStatus !== null && !["up", "warn", "down"].includes(rawStatus)) || + (rawStatus !== null && !["up", "warn", "down", "unknown"].includes(rawStatus)) || ["tab", "q", "status", "servers"].some(key => params.getAll(key).length > 1)) error = "В ссылке указаны неверные или повторяющиеся параметры мониторинга." const rawServers = params.get("servers") if (rawServers !== null) { diff --git a/lib/scheduler-run-snapshot.ts b/lib/scheduler-run-snapshot.ts index ee7fc963..2054a886 100644 --- a/lib/scheduler-run-snapshot.ts +++ b/lib/scheduler-run-snapshot.ts @@ -182,8 +182,8 @@ export interface PingProbeSnapshot { srcInterface: string ok: boolean rttMs: number | null - lossPct: number - status: "up" | "warn" | "down" + lossPct: number | null + status: "up" | "warn" | "down" | "unknown" error?: string } diff --git a/tests/e2e/uptime-ping-measurement.spec.ts b/tests/e2e/uptime-ping-measurement.spec.ts new file mode 100644 index 00000000..5257e9c4 --- /dev/null +++ b/tests/e2e/uptime-ping-measurement.spec.ts @@ -0,0 +1,25 @@ +import {test,expect,type Page} from "@playwright/test" +async function setup(page:Page,known:boolean){ + const server={id:1,name:"Источник",host:"192.0.2.1",type:"exit-node",enabled:true,country:"RU",site:"TEST",asn:"64512",status:"online",latency:1,os:"7",model:"CHR",sessions:0,wanUplinks:[]} + const time="2026-10-12T10:30:00Z",point=known?{sampledAt:time,rtt:null,loss:100,status:"down"}:{sampledAt:time,rtt:null,loss:null,status:"unknown",error:"RouterOS API error 401: Unauthorized"} + const probe={id:"probe",srcServerId:"1",name:"Проверка",target:"192.0.2.10",filter:"—",srcInterface:"",rtt:null,loss:known?100:null,status:known?"down":"unknown",series:[],enabled:true,showOnDashboard:true,observations:{samples:[point]}} + await page.route("**/health",r=>r.fulfill({json:{status:"ok"}})) + await page.route("**/api/**",r=>{const path=new URL(r.request().url()).pathname + const json=path==="/api/auth/config"?{required:false}:path==="/api/servers"?[server]:path==="/api/uptime/overview"?{probes:[probe],resources:[],rangeStartAt:"2026-10-12T10:00:00Z",rangeEndAt:"2026-10-12T11:00:00Z"}:path==="/api/uptime/speed-probes"?{probes:[]}:path==="/api/uptime/speed-test/runs"?{runs:[]}:{} + return r.fulfill({json}) + }) + await page.goto("/uptime?tab=probes") +} +for(const width of[1440,320])test(`Ping ${width}: API failure is no data, not measured loss`,async({page})=>{ + await page.setViewportSize({width,height:1000});await setup(page,false) + await expect(page.getByText("Ошибка измерения: RouterOS API error 401: Unauthorized",{exact:true})).toBeVisible() + await expect(page.getByText("100%",{exact:true})).toHaveCount(0) + await page.getByRole("button",{name:/Нет данных/}).first().click();await expect(page).toHaveURL(/status=unknown/) + await expect(page.getByText("Ошибка измерения: RouterOS API error 401: Unauthorized",{exact:true})).toBeVisible() + expect(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth)).toBe(true) + await page.screenshot({path:`docs/ux-ui/screens/ping-measurement-unknown-${width}-2026-10-12.png`,fullPage:true}) +}) +test("Ping measured 100 percent loss remains a real down observation",async({page})=>{ + await setup(page,true);await expect(page.getByText("100%",{exact:true})).toBeVisible() + await expect(page.getByText(/Ошибка измерения:/)).toHaveCount(0) +})