fix(traffic): считать общий пик по совпадающим интервалам
This commit is contained in:
1 parent
dbc3b2c947
commit
7b88416415
21 files changed
+1390
-27
No files matched your search
@@ -19,7 +19,7 @@
|
||||
"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",
|
||||
"test:traffic-rate": "tsx src/services/traffic-rate.test.ts && tsx src/services/traffic-observation.test.ts && tsx src/services/traffic-freshness.test.ts && tsx src/services/traffic-live.test.ts && tsx src/services/traffic-observation-sql.test.ts && tsx src/services/traffic-history-sql.test.ts && tsx src/services/traffic-live-sql.test.ts && tsx src/services/traffic-volume-sql.test.ts && tsx src/services/traffic-binding-status.test.ts && tsx src/services/traffic-status-sql.test.ts && tsx src/services/traffic-gap.test.ts && tsx src/services/traffic-identity.test.ts && tsx src/services/traffic-membership.test.ts",
|
||||
"test:traffic-rate": "tsx src/services/traffic-rate.test.ts && tsx src/services/traffic-observation.test.ts && tsx src/services/traffic-freshness.test.ts && tsx src/services/traffic-live.test.ts && tsx src/services/traffic-observation-sql.test.ts && tsx src/services/traffic-history-sql.test.ts && tsx src/services/traffic-live-sql.test.ts && tsx src/services/traffic-volume-sql.test.ts && tsx src/services/traffic-binding-status.test.ts && tsx src/services/traffic-status-sql.test.ts && tsx src/services/traffic-gap.test.ts && tsx src/services/traffic-identity.test.ts && tsx src/services/traffic-membership.test.ts && tsx src/services/traffic-aggregate-peak.test.ts",
|
||||
"test:traffic-flow": "tsx src/routes/traffic-flow-query.test.ts && tsx src/services/traffic-flow-month.test.ts && tsx src/services/traffic-flow-parse.test.ts && tsx src/services/traffic-flow-ipv6-pipeline.test.ts && tsx src/services/traffic-flow-map-exporter.test.ts && tsx src/services/traffic-flow-ifaces.test.ts && tsx src/services/traffic-flow-ifindex.test.ts && tsx src/services/traffic-flow-dedup.test.ts && tsx src/services/traffic-flow-planes.test.ts && tsx src/services/traffic-flow-ip.test.ts && tsx src/services/traffic-flow-dest.test.ts && tsx src/services/traffic-flow-classify.test.ts && tsx src/services/traffic-flow-ripe.test.ts && tsx src/services/traffic-flow-brands.test.ts && tsx src/services/traffic-flow-ingest.test.ts && tsx src/services/traffic-flow-analytics.test.ts && tsx src/services/traffic-flow-map-hops.test.ts && tsx src/services/traffic-flow-purge.test.ts && tsx src/services/traffic-flow-geoip.test.ts && tsx src/services/traffic-flow-facts.test.ts && tsx src/services/traffic-flow-facts-filter.test.ts && tsx src/services/traffic-flow-facts-rebuild.test.ts && tsx src/services/statistics-aggregate.test.ts",
|
||||
"test:users": "tsx src/modules/users/iface-type.test.ts && tsx src/modules/users/bindings.test.ts && tsx src/modules/users/interface-inventory.test.ts && tsx src/routes/users-catalog.test.ts",
|
||||
"test:pg": "tsx src/db/pg-pool.test.ts && tsx src/db/sql-bind.test.ts && tsx src/db/sqlite-json.test.ts && tsx src/db/traffic-flags.test.ts && tsx src/db/pg-schema.test.ts && tsx src/services/config-revisions.test.ts",
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import {buildTrafficFromSamples, mergeBuiltTraffic} from './traffic-rate.js'
|
||||
|
||||
const at = Date.parse('2026-10-13T00:00:00Z')
|
||||
const rows = (name:string, bytes:number[], times=[0,30_000,60_000], known=[false,true,true]) => bytes.map((value,i)=>({
|
||||
interfaceName:name,sampledAt:new Date(at+times[i]).toISOString(),rxBytes:value,txBytes:value/2,
|
||||
rxBps:0,txBps:0,running:true,disabled:false,countersKnown:true,continuityKnown:known[i],nativeCounterIdentity:name,
|
||||
}))
|
||||
const build = (r:ReturnType<typeof rows>) => buildTrafficFromSamples(r,at,at+60_000,undefined,undefined,true)
|
||||
const a=build(rows('a',[0,375_000_000,375_000_000]))
|
||||
const b=build(rows('b',[0,0,375_000_000]))
|
||||
const separate=mergeBuiltTraffic([a,b])
|
||||
assert.equal(separate.rxPeak,100);assert.equal(separate.txPeak,50)
|
||||
assert.equal(separate.observations!.rxPeak,true)
|
||||
assert.equal(separate.rxTotalGiB,a.rxTotalGiB+b.rxTotalGiB)
|
||||
assert.equal(mergeBuiltTraffic([a,a]).rxPeak,200)
|
||||
assert.equal(mergeBuiltTraffic([separate,separate]).rxPeak,200,'nested merge preserves raw intervals')
|
||||
assert.equal(mergeBuiltTraffic([build(rows('z',[0,0,0])),build(rows('z2',[0,0,0]))]).observations!.rxPeak,true,'measured zero is known')
|
||||
const missing=build(rows('missing',[0,0,375_000_000],[0,30_000,60_000],[false,false,false]))
|
||||
assert.equal(mergeBuiltTraffic([a,missing]).observations!.rxPeak,false)
|
||||
const jitter=build(rows('jitter',[0,0,375_000_000],[1,30_001,59_999]))
|
||||
assert.equal(mergeBuiltTraffic([a,jitter]).observations!.rxPeak,false,'sharing a chart bucket does not establish a matching wave')
|
||||
const differentStart=build(rows('start',[0,375_000_000],[15_000,30_000]))
|
||||
assert.equal(mergeBuiltTraffic([a,differentStart]).observations!.rxPeak,false,'same end with different interval is not simultaneous')
|
||||
const recovery=build(rows('recovery',[0,0,375_000_000],[0,30_000,60_000],[false,false,true]))
|
||||
assert.equal(mergeBuiltTraffic([a,recovery]).rxPeak,100)
|
||||
assert.equal(mergeBuiltTraffic([a,recovery]).observations!.rxPeak,true)
|
||||
assert.equal(mergeBuiltTraffic([a,build([])]).observations!.rxPeak,false)
|
||||
assert.equal(mergeBuiltTraffic([a,{...a}]).observations!.rxPeak,false,'serialized legacy parts have no raw interval proof')
|
||||
assert(!JSON.stringify(separate).includes('rxKnown'),'internal points are not DTO fields')
|
||||
console.log('aggregate peak: separate/coincident/nested/zero/missing/jitter/interval/recovery/empty/legacy/serialization passed')
|
||||
@@ -25,6 +25,7 @@ export interface RatePoint {
|
||||
}
|
||||
|
||||
export interface TrafficObservations {rxNow:boolean;txNow:boolean;rxSeries:boolean[];txSeries:boolean[];sampledAt:string|null;expiresAt?:string|null;measurementIntervalMs?:number|null;
|
||||
rxPeak?:boolean;txPeak?:boolean;
|
||||
currentState?: import("./traffic-freshness.js").TrafficCurrentState;lastAttemptAt?:string|null;lastSucceededAt?:string|null;error?:string|null}
|
||||
|
||||
export interface BuiltTrafficSeries {
|
||||
@@ -40,6 +41,14 @@ export interface BuiltTrafficSeries {
|
||||
txSeries: number[]
|
||||
}
|
||||
|
||||
type PeakWave = { start: number; rx: number; tx: number; rxKnown: boolean; txKnown: boolean }
|
||||
// Raw intervals stay internal: DTO serialization must not expose or round these points.
|
||||
const peakWaves = new WeakMap<BuiltTrafficSeries, Map<number, PeakWave>>()
|
||||
function retainPeakWaves(built: BuiltTrafficSeries, waves: Map<number, PeakWave>): BuiltTrafficSeries {
|
||||
peakWaves.set(built, waves)
|
||||
return built
|
||||
}
|
||||
|
||||
export interface MonitorLiveSample {
|
||||
rxMbps: number
|
||||
txMbps: number
|
||||
@@ -129,7 +138,7 @@ export function buildTrafficFromSamples(
|
||||
txSeries: Array.from({ length: SERIES_POINTS }, () => 0),
|
||||
}
|
||||
const noObservations:TrafficObservations={rxNow:false,txNow:false,rxSeries:Array(SERIES_POINTS).fill(false),txSeries:Array(SERIES_POINTS).fill(false),sampledAt:null}
|
||||
if (rows.length === 0) return includeObservations?{...empty,observations:noObservations}:empty
|
||||
if (rows.length === 0) return retainPeakWaves(includeObservations?{...empty,observations:{...noObservations,rxPeak:false,txPeak:false}}:empty,new Map())
|
||||
|
||||
const bySeries = new Map<string, TrafficSampleLike[]>()
|
||||
for (const r of rows) {
|
||||
@@ -145,7 +154,7 @@ export function buildTrafficFromSamples(
|
||||
|
||||
const rxPoints: Array<{ t: number; v: number }> = []
|
||||
const txPoints: Array<{ t: number; v: number }> = []
|
||||
const byTs = new Map<number, { rx: number; tx: number;rxObserved:boolean;txObserved:boolean;count:number;intervalMs:number }>()
|
||||
const byTs = new Map<number, { rx: number; tx: number;rxObserved:boolean;txObserved:boolean;count:number;intervalMs:number;start:number;sameStart:boolean }>()
|
||||
let includedSeries=0,lastAttemptAt:number|null=null
|
||||
const membership = new Map<number, {count:number; declared:boolean; active:number}>()
|
||||
let latestMembershipAt = -Infinity
|
||||
@@ -205,7 +214,8 @@ export function buildTrafficFromSamples(
|
||||
if (rxBps == null && txBps == null) continue
|
||||
const rxMbps = bpsToMbps(rxBps ?? 0)
|
||||
const txMbps = bpsToMbps(txBps ?? 0)
|
||||
const acc = byTs.get(t1) ?? { rx: 0, tx: 0,rxObserved:true,txObserved:true,count:0,intervalMs:0 }
|
||||
const acc = byTs.get(t1) ?? { rx: 0, tx: 0,rxObserved:true,txObserved:true,count:0,intervalMs:0,start:t0,sameStart:true }
|
||||
acc.sameStart &&= acc.start === t0
|
||||
acc.intervalMs = Math.max(acc.intervalMs, t1 - t0)
|
||||
acc.count++;acc.rxObserved&&=rxBps!==null;acc.txObserved&&=txBps!==null
|
||||
acc.rx += rxMbps
|
||||
@@ -228,12 +238,18 @@ export function buildTrafficFromSamples(
|
||||
const last = lastTs != null ? byTs.get(lastTs) : undefined
|
||||
const peak = (points: Array<{t:number;v:number}>, side: "rxObserved"|"txObserved") => points.reduce((m,p) => {
|
||||
const wave = byTs.get(p.t)!
|
||||
return p.t >= rangeStartMs && p.t <= rangeEndMs && wave[side] && wave.count === expectedAt(p.t) ? Math.max(m,p.v) : m
|
||||
return p.t >= rangeStartMs && p.t <= rangeEndMs && wave.sameStart && wave[side] && wave.count === expectedAt(p.t) ? Math.max(m,p.v) : m
|
||||
},0)
|
||||
const rxPeak = peak(rxPoints,"rxObserved")
|
||||
const txPeak = peak(txPoints,"txObserved")
|
||||
|
||||
return {
|
||||
const waves = new Map<number, PeakWave>()
|
||||
for (const [t, wave] of byTs) {
|
||||
if (t < rangeStartMs || t > rangeEndMs) continue
|
||||
const complete = wave.sameStart && wave.count === expectedAt(t)
|
||||
waves.set(t,{start:wave.start,rx:wave.rx,tx:wave.tx,rxKnown:complete&&wave.rxObserved,txKnown:complete&&wave.txObserved})
|
||||
}
|
||||
return retainPeakWaves({
|
||||
rxNow: last?.rx ?? 0,
|
||||
txNow: last?.tx ?? 0,
|
||||
rxPeak,
|
||||
@@ -250,8 +266,10 @@ export function buildTrafficFromSamples(
|
||||
txSeries:bucketAvg([...byTs].map(([t,v])=>({t,v:Number(v.txObserved&&v.count===expectedAt(t)&&t>=rangeStartMs&&t<=rangeEndMs)})),rangeStartMs,rangeEndMs).map(v=>v===1),
|
||||
sampledAt:lastAttemptAt===null?null:new Date(lastAttemptAt).toISOString(),
|
||||
measurementIntervalMs:lastTs===lastAttemptAt?last?.intervalMs??null:null,
|
||||
rxPeak:[...waves.values()].some(w=>w.rxKnown),
|
||||
txPeak:[...waves.values()].some(w=>w.txKnown),
|
||||
}}:{}),
|
||||
}
|
||||
},waves)
|
||||
}
|
||||
|
||||
export function mergeBuiltTraffic(parts: BuiltTrafficSeries[]): BuiltTrafficSeries {
|
||||
@@ -281,8 +299,6 @@ export function mergeBuiltTraffic(parts: BuiltTrafficSeries[]): BuiltTrafficSeri
|
||||
for (const p of parts) {
|
||||
acc.rxNow += p.rxNow
|
||||
acc.txNow += p.txNow
|
||||
acc.rxPeak += p.rxPeak
|
||||
acc.txPeak += p.txPeak
|
||||
acc.rxTotalGiB += p.rxTotalGiB
|
||||
acc.txTotalGiB += p.txTotalGiB
|
||||
acc.sessions += p.sessions
|
||||
@@ -291,7 +307,22 @@ export function mergeBuiltTraffic(parts: BuiltTrafficSeries[]): BuiltTrafficSeri
|
||||
acc.txSeries[i] += p.txSeries[i] ?? 0
|
||||
}
|
||||
}
|
||||
return parts.every(p => p.observations) ? { ...acc, observations: mergeTrafficObservations(parts.map(p => p.observations!)) } : acc
|
||||
const waves = new Map<number, PeakWave>()
|
||||
const first = peakWaves.get(parts[0])
|
||||
for (const [t, wave] of first ?? []) {
|
||||
const matching = parts.map(p => peakWaves.get(p)?.get(t))
|
||||
if (!matching.every(w => w && w.start === wave.start)) continue
|
||||
const rows = matching as PeakWave[]
|
||||
const combined = {start:wave.start,rx:rows.reduce((n,w)=>n+w.rx,0),tx:rows.reduce((n,w)=>n+w.tx,0),
|
||||
rxKnown:rows.every(w=>w.rxKnown),txKnown:rows.every(w=>w.txKnown)}
|
||||
waves.set(t,combined)
|
||||
if (combined.rxKnown) acc.rxPeak = Math.max(acc.rxPeak,combined.rx)
|
||||
if (combined.txKnown) acc.txPeak = Math.max(acc.txPeak,combined.tx)
|
||||
}
|
||||
return retainPeakWaves(parts.every(p => p.observations) ? { ...acc, observations: {
|
||||
...mergeTrafficObservations(parts.map(p => p.observations!)),
|
||||
rxPeak:[...waves.values()].some(w=>w.rxKnown),txPeak:[...waves.values()].some(w=>w.txKnown),
|
||||
} } : acc,waves)
|
||||
}
|
||||
|
||||
export function parseMonitorTraffic(
|
||||
|
||||
@@ -59,6 +59,19 @@ else {
|
||||
for(const[name,offset,bytes]of[["ether1",-90_000,0],["ether1",-60_000,0],["ether1",-30_000,1024**2],["new",-30_000,0],["ether1",0,1024**2],["new",0,0]] as const)await put(name,offset,bytes);
|
||||
await pool.query("UPDATE traffic_samples SET flags=61,native_counter_identity=interface_name WHERE server_id=$1",[serverId]);await pool.query("UPDATE traffic_samples SET flags=45 WHERE server_id=$1 AND interface_name='new' AND sampled_at=$2",[serverId,new Date(Date.parse(at)-30_000).toISOString()]);
|
||||
s=(await get(`/traffic/servers/${serverId}?range=1h`)).server;assert.equal(s.observations.rxNow,true);assert.equal(s.rxPeak,0);assert.equal(s.txPeak,0);assert.equal(s.rxTotal,1/1024);assert(s.observations.rxSeries.some(Boolean));cases++;
|
||||
await pool.query("DELETE FROM user_interface_bindings WHERE user_id=$1",[userId])
|
||||
for(const name of ['peakA','peakB'])await pool.query("INSERT INTO user_interface_bindings(id,user_id,server_id,interface_name,interface_type) VALUES($1,$2,$3,$4,'ether')",[prefix+name,userId,serverId,name])
|
||||
for(const mode of ['separate','coincident','jitter']){
|
||||
await pool.query("DELETE FROM traffic_samples WHERE server_id=$1",[serverId])
|
||||
for(const [name,offset,bytes] of [['peakA',-60_000,0],['peakA',-30_000,375_000_000],['peakA',0,375_000_000],
|
||||
['peakB',-60_000,0],['peakB',-30_000,mode==='coincident'?375_000_000:0],['peakB',0,375_000_000]] as const)
|
||||
await put(name,offset+(mode==='jitter'&&name==='peakB'?-1:0),bytes)
|
||||
const aggregate=(await get('/traffic/users?range=1h')).users.find((u:{id:string})=>u.id===userId)
|
||||
assert.equal(aggregate.rxPeak,mode==='jitter'?0:mode==='coincident'?200:100)
|
||||
assert.equal(aggregate.observations.rxPeak,mode!=='jitter');assert.equal(aggregate.observations.txPeak,mode!=='jitter')
|
||||
assert.equal(aggregate.rxTotal,750_000_000/1024**3);assert(aggregate.observations.rxSeries.some(Boolean))
|
||||
assert.equal(aggregate.interfaces.length,2);assert(aggregate.interfaces.every((i:{rxPeak:number})=>i.rxPeak===100));cases++
|
||||
}
|
||||
console.log(`traffic volume owned SQL: ${cases} cases; exact bytes/binary unit/server/user/binding/100-part aggregate/reset passed`)
|
||||
}finally{await app.close();await pool.query("DELETE FROM app_users WHERE id=$1",[userId]);if(serverId!==undefined){await pool.query("DELETE FROM traffic_samples WHERE server_id=$1",[serverId]);await pool.query("DELETE FROM servers WHERE id=$1",[serverId])}await closePool()}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
# UX/UI: протокол внешней приёмки 12.5
|
||||
|
||||
Актуальное продолжение 2026-10-13: завершены185–190, backend ux-peak190/frontend ux-warning183 healthy; последний ownSQL68/38553ms/API/browser2 passed. Начат191 — baseline sum individual peaks200Mbps против complete simultaneous wave100Mbps; это не исправлено и не принято. Следующий шаг — temporal aggregate peak с confidence при несовпадающих timestamps.12.5 открыта.
|
||||
Актуальное продолжение 2026-10-13: завершён191 — общий пик привязок по совпадающим подтверждённым интервалам; backend/frontend ux-peak191 развёрнуты. ownSQL71/41228ms, E2E77/1.6m, source985/compiled1040/API/live UI2 passed. Начата192 — общая backend-регрессия в отдельной временной БД.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-13: завершены185–190, backend ux-peak190/frontend ux-warning183 healthy; последний ownSQL68/38553ms/API/browser2 passed. Начат191 — baseline sum individual peaks200Mbps против complete simultaneous wave100Mbps; это не исправлено и не принято. Следующий шаг — temporal aggregate peak с confidence при несовпадающих timestamps.12.5 открыта.
|
||||
Актуальное продолжение 2026-10-13: завершён191 — общий пик привязок по совпадающим подтверждённым интервалам; backend/frontend ux-peak191 развёрнуты. ownSQL71/41228ms, E2E77/1.6m, source985/compiled1040/API/live UI2 passed. Начата192 — общая backend-регрессия в отдельной временной БД.12.5 открыта.
|
||||
|
||||
Подробный объём, протоколы, схема и ограничения: [12.5.77 в журнале](ux-ui-progress.md).
|
||||
|
||||
@@ -2296,3 +2296,21 @@ Unit проверил partial high point → последующая complete0 wa
|
||||
Точный следующий шаг: общий peak вычислять из подтверждённых совпадающих временных срезов, сохраняя исходные timestamp/confidence внутренних parts; не складывать индивидуальные maxima. При отсутствии совместимого времени показывать неизвестное значение, сохраняя volumes/history/current metadata. Проверить разные времена peaks, совпадающий peak, missing contributor, jitter/неполные bins и recovery. Не подменять raw point peak максимумом bucket average без явной семантики. Нужны meaningful own SQL/API/UI cases перед production rollout.
|
||||
|
||||
Стенд остаётся backend ux-peak190/frontend ux-warning183. Source/runtime190 и real CHR/config/passwords unchanged; messages0. После191 — scoped control затронутых разделов и одна согласованная общая регрессия; внешние Portal/Telegram/client OS/multi-WAN/scale требуют доступов/сценариев из матрицы.12.5 открыта. Локальный commit без push сохраняет точное продолжение.
|
||||
|
||||
## 12.5.191 — подтверждённый общий пик привязок: реализация и приёмка (2026-10-13)
|
||||
|
||||
Исправлен baseline ниже: индивидуальные максимумы в разные моменты больше не складываются. A=100Mbps на первой волне и B=100Mbps на следующей дают общий пик100, совпадающие интервалы —200. Сумма byte deltas/учтённый объём сохранена. Пик является максимумом подтверждённых средних скоростей соседних счётчиков за совпадающие интервалы, а не мгновенным максимумом линии или максимумом chart bucket average.
|
||||
|
||||
Исходные интервалы и confidence parts хранятся во внутреннем WeakMap и не попадают в DTO/JSON. Merge, включая вложенный, требует совпадения начала и конца интервала и подтверждённых вкладов всех выбранных привязок для каждого направления. Jitter, отсутствие part, несовместимые интервалы или сериализованные parts без исходных точек не доказывают общий пик: observations.rxPeak/txPeak=false, UI показывает «—», а не ложный ноль. Подтверждённый ноль остаётся0. Для старого backend новый reader сохраняет прежнюю совместимость при отсутствии новых optional flags; interop проверен до переключения backend. История, объём и freshness metadata сохраняются. Данные разных серверов не интерполируются и не сдвигаются ради создания искусственного общего пика.
|
||||
|
||||
Изменены traffic-rate.ts, lib/traffic-observations.ts и профильные тесты. ReUI PRO/base-nova/Frame/KpiStatGrid переиспользуются, vendor/theme/layout не менялись. Проверены different/coincident peaks, nested merge, measured zero, missing/empty/legacy parts, jitter внутри одинаковых chart bins, одинаковый конец с разным началом и recovery. Реальный PostgreSQL/API fixture добавляет3 сценария пользователя с двумя привязками, включая неизменный объём750000000bytes. Все71 профильных SQL-проверок прошли41228ms без postgres-unavailable skip; собственные временная БД, роль и контейнер удалены.
|
||||
|
||||
Backend build и frontend TypeScript passed. Все77 уникальных E2E трафика прошли1.6m; начальные4 focused cases22.4s входят в77 и повторно не считаются. Production backend/frontend build passed; source manifests совпали461 backend+524 frontend файлов,1040 compiled backend artifacts совпали. Runtime backend/frontend ux-peak191, PostgreSQL healthy; frontend healthcheck отсутствует, работа подтверждена HTTP/browser. До backend rollout новый frontend принят с backend190. Actual API200 и fresh measurements обоих CHR, live UI1440/320 без page errors/overflow. Native конфигурация/пароли не менялись, messages0.
|
||||
|
||||
Протоколы: [SQL](ux-ui/acceptance/traffic-peak191-sql191-result.json), [source](ux-ui/acceptance/traffic-peak191-source191-result.json), [compiled](ux-ui/acceptance/traffic-peak191-compiled191-result.json), [runtime](ux-ui/acceptance/traffic-peak191-runtime191-result.json), [API](ux-ui/acceptance/traffic-peak191-peak191-api-result.json), [E2E](ux-ui/acceptance/traffic-peak-e2e-2026-10-13.json), [interop](ux-ui/acceptance/traffic-peak-interop-2026-10-13.json), [live UI](ux-ui/acceptance/traffic-peak-deployed-2026-10-13.json).
|
||||
|
||||
| Референс | Схема |
|
||||
| --- | --- |
|
||||
| [ReUI Stats12](https://reui.io/preview/base/stats-12?ref=mcp) | Совпадающие интервалы всех привязок → подтверждённая сумма → максимум периода; нет совпадения → «—», объём сохранён |
|
||||
|
||||
Следующий192: одна согласованная общая backend-регрессия; фиксировать реальные skipped/external ветки отдельно. Последующий объём определяется результатом этой проверки и матрицей внешней приёмки. Portal/Telegram/client OS/multi-WAN/scale не приняты по fixture-тестам.12.5 открыта.
|
||||
+3
-3
@@ -1,12 +1,12 @@
|
||||
# План реализации UX/UI по пятичасовым окнам
|
||||
|
||||
Актуальное продолжение 2026-10-13: завершены185–190, backend ux-peak190/frontend ux-warning183 healthy; последний ownSQL68/38553ms/API/browser2 passed. Начат191 — baseline sum individual peaks200Mbps против complete simultaneous wave100Mbps; это не исправлено и не принято. Следующий шаг — temporal aggregate peak с confidence при несовпадающих timestamps.12.5 открыта.
|
||||
Актуальное продолжение 2026-10-13: завершён191 — общий пик привязок по совпадающим подтверждённым интервалам; backend/frontend ux-peak191 развёрнуты. ownSQL71/41228ms, E2E77/1.6m, source985/compiled1040/API/live UI2 passed. Начата192 — общая backend-регрессия в отдельной временной БД.12.5 открыта.
|
||||
|
||||
Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-12. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат.
|
||||
|
||||
## Актуальная точка продолжения — 2026-10-12
|
||||
|
||||
Актуальное продолжение 2026-10-13: завершены185–190, backend ux-peak190/frontend ux-warning183 healthy; последний ownSQL68/38553ms/API/browser2 passed. Начат191 — baseline sum individual peaks200Mbps против complete simultaneous wave100Mbps; это не исправлено и не принято. Следующий шаг — temporal aggregate peak с confidence при несовпадающих timestamps.12.5 открыта.
|
||||
Актуальное продолжение 2026-10-13: завершён191 — общий пик привязок по совпадающим подтверждённым интервалам; backend/frontend ux-peak191 развёрнуты. ownSQL71/41228ms, E2E77/1.6m, source985/compiled1040/API/live UI2 passed. Начата192 — общая backend-регрессия в отдельной временной БД.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-13: завершены185–190, backend ux-peak190/frontend ux-warning183 healthy; последний ownSQL68/38553ms/API/browser2 passed. Начат191 — baseline sum individual peaks200Mbps против complete simultaneous wave100Mbps; это не исправлено и не принято. Следующий шаг — temporal aggregate peak с confidence при несовпадающих timestamps.12.5 открыта.
|
||||
Актуальное продолжение 2026-10-13: завершён191 — общий пик привязок по совпадающим подтверждённым интервалам; backend/frontend ux-peak191 развёрнуты. ownSQL71/41228ms, E2E77/1.6m, source985/compiled1040/API/live UI2 passed. Начата192 — общая backend-регрессия в отдельной временной БД.12.5 открыта.
|
||||
|
||||
Подробный объём, протоколы, схема и ограничения: [12.5.77 в журнале](ux-ui-progress.md).
|
||||
+20
-2
@@ -1,6 +1,6 @@
|
||||
# UX/UI: журнал реализации
|
||||
|
||||
Актуальное продолжение 2026-10-13: завершены185–190, backend ux-peak190/frontend ux-warning183 healthy; последний ownSQL68/38553ms/API/browser2 passed. Начат191 — baseline sum individual peaks200Mbps против complete simultaneous wave100Mbps; это не исправлено и не принято. Следующий шаг — temporal aggregate peak с confidence при несовпадающих timestamps.12.5 открыта.
|
||||
Актуальное продолжение 2026-10-13: завершён191 — общий пик привязок по совпадающим подтверждённым интервалам; backend/frontend ux-peak191 развёрнуты. ownSQL71/41228ms, E2E77/1.6m, source985/compiled1040/API/live UI2 passed. Начата192 — общая backend-регрессия в отдельной временной БД.12.5 открыта.
|
||||
|
||||
Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md).
|
||||
|
||||
@@ -36,7 +36,25 @@
|
||||
| 10 | Маршрутизация, диагностика, карта и терминал | Завершён в границах локальной приёмки 2026-10-01; блоки 10.1–10.71, внешние ограничения в 10.71 |
|
||||
| 11А | Пользователи, контейнеры, сертификаты и резервные копии | Завершён в границах локальной приёмки 2026-10-01; 11А.1–11А.33, внешние ограничения в 11А.33 |
|
||||
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 |
|
||||
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–168 и ограничения ниже. Runtime backend ux-month149/frontend ux-sessions150. |
|
||||
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–191 и ограничения ниже. Runtime backend/frontend ux-peak191. |
|
||||
|
||||
## 12.5.191 — подтверждённый общий пик привязок: реализация и приёмка (2026-10-13)
|
||||
|
||||
Исправлен baseline ниже: индивидуальные максимумы в разные моменты больше не складываются. A=100Mbps на первой волне и B=100Mbps на следующей дают общий пик100, совпадающие интервалы —200. Сумма byte deltas/учтённый объём сохранена. Пик является максимумом подтверждённых средних скоростей соседних счётчиков за совпадающие интервалы, а не мгновенным максимумом линии или максимумом chart bucket average.
|
||||
|
||||
Исходные интервалы и confidence parts хранятся во внутреннем WeakMap и не попадают в DTO/JSON. Merge, включая вложенный, требует совпадения начала и конца интервала и подтверждённых вкладов всех выбранных привязок для каждого направления. Jitter, отсутствие part, несовместимые интервалы или сериализованные parts без исходных точек не доказывают общий пик: observations.rxPeak/txPeak=false, UI показывает «—», а не ложный ноль. Подтверждённый ноль остаётся0. Для старого backend новый reader сохраняет прежнюю совместимость при отсутствии новых optional flags; interop проверен до переключения backend. История, объём и freshness metadata сохраняются. Данные разных серверов не интерполируются и не сдвигаются ради создания искусственного общего пика.
|
||||
|
||||
Изменены traffic-rate.ts, lib/traffic-observations.ts и профильные тесты. ReUI PRO/base-nova/Frame/KpiStatGrid переиспользуются, vendor/theme/layout не менялись. Проверены different/coincident peaks, nested merge, measured zero, missing/empty/legacy parts, jitter внутри одинаковых chart bins, одинаковый конец с разным началом и recovery. Реальный PostgreSQL/API fixture добавляет3 сценария пользователя с двумя привязками, включая неизменный объём750000000bytes. Все71 профильных SQL-проверок прошли41228ms без postgres-unavailable skip; собственные временная БД, роль и контейнер удалены.
|
||||
|
||||
Backend build и frontend TypeScript passed. Все77 уникальных E2E трафика прошли1.6m; начальные4 focused cases22.4s входят в77 и повторно не считаются. Production backend/frontend build passed; source manifests совпали461 backend+524 frontend файлов,1040 compiled backend artifacts совпали. Runtime backend/frontend ux-peak191, PostgreSQL healthy; frontend healthcheck отсутствует, работа подтверждена HTTP/browser. До backend rollout новый frontend принят с backend190. Actual API200 и fresh measurements обоих CHR, live UI1440/320 без page errors/overflow. Native конфигурация/пароли не менялись, messages0.
|
||||
|
||||
Протоколы: [SQL](ux-ui/acceptance/traffic-peak191-sql191-result.json), [source](ux-ui/acceptance/traffic-peak191-source191-result.json), [compiled](ux-ui/acceptance/traffic-peak191-compiled191-result.json), [runtime](ux-ui/acceptance/traffic-peak191-runtime191-result.json), [API](ux-ui/acceptance/traffic-peak191-peak191-api-result.json), [E2E](ux-ui/acceptance/traffic-peak-e2e-2026-10-13.json), [interop](ux-ui/acceptance/traffic-peak-interop-2026-10-13.json), [live UI](ux-ui/acceptance/traffic-peak-deployed-2026-10-13.json).
|
||||
|
||||
| Референс | Схема |
|
||||
| --- | --- |
|
||||
| [ReUI Stats12](https://reui.io/preview/base/stats-12?ref=mcp) | Совпадающие интервалы всех привязок → подтверждённая сумма → максимум периода; нет совпадения → «—», объём сохранён |
|
||||
|
||||
Следующий192: одна согласованная общая backend-регрессия; фиксировать реальные skipped/external ветки отдельно. Последующий объём определяется результатом этой проверки и матрицей внешней приёмки. Portal/Telegram/client OS/multi-WAN/scale не приняты по fixture-тестам.12.5 открыта.
|
||||
|
||||
## 12.5.191 — общий пик нескольких привязок: baseline (2026-10-13)
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Оставшаяся внешняя приёмка — 2026-10-13
|
||||
|
||||
Актуальное продолжение 2026-10-13: завершены185–190, backend ux-peak190/frontend ux-warning183 healthy; последний ownSQL68/38553ms/API/browser2 passed. Начат191 — baseline sum individual peaks200Mbps против complete simultaneous wave100Mbps; это не исправлено и не принято. Следующий шаг — temporal aggregate peak с confidence при несовпадающих timestamps.12.5 открыта.
|
||||
Актуальное продолжение 2026-10-13: завершён191 — общий пик привязок по совпадающим подтверждённым интервалам; backend/frontend ux-peak191 развёрнуты. ownSQL71/41228ms, E2E77/1.6m, source985/compiled1040/API/live UI2 passed. Начата192 — общая backend-регрессия в отдельной временной БД.12.5 открыта.
|
||||
|
||||
Это актуальная матрица после 12.5.191. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
|
||||
|
||||
Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-peak190`, frontend `ux-warning183`; production lab использует AUTH_REQUIRED=false. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись.
|
||||
Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend/frontend `ux-peak191`; production lab использует AUTH_REQUIRED=false. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись.
|
||||
|
||||
| Раздел | Подтверждено | Осталось / условие завершения |
|
||||
| --- | --- | --- |
|
||||
@@ -1212,3 +1212,21 @@ Unit проверил partial high point → последующая complete0 wa
|
||||
Точный следующий шаг: общий peak вычислять из подтверждённых совпадающих временных срезов, сохраняя исходные timestamp/confidence внутренних parts; не складывать индивидуальные maxima. При отсутствии совместимого времени показывать неизвестное значение, сохраняя volumes/history/current metadata. Проверить разные времена peaks, совпадающий peak, missing contributor, jitter/неполные bins и recovery. Не подменять raw point peak максимумом bucket average без явной семантики. Нужны meaningful own SQL/API/UI cases перед production rollout.
|
||||
|
||||
Стенд остаётся backend ux-peak190/frontend ux-warning183. Source/runtime190 и real CHR/config/passwords unchanged; messages0. После191 — scoped control затронутых разделов и одна согласованная общая регрессия; внешние Portal/Telegram/client OS/multi-WAN/scale требуют доступов/сценариев из матрицы.12.5 открыта. Локальный commit без push сохраняет точное продолжение.
|
||||
|
||||
## 12.5.191 — подтверждённый общий пик привязок: реализация и приёмка (2026-10-13)
|
||||
|
||||
Исправлен baseline ниже: индивидуальные максимумы в разные моменты больше не складываются. A=100Mbps на первой волне и B=100Mbps на следующей дают общий пик100, совпадающие интервалы —200. Сумма byte deltas/учтённый объём сохранена. Пик является максимумом подтверждённых средних скоростей соседних счётчиков за совпадающие интервалы, а не мгновенным максимумом линии или максимумом chart bucket average.
|
||||
|
||||
Исходные интервалы и confidence parts хранятся во внутреннем WeakMap и не попадают в DTO/JSON. Merge, включая вложенный, требует совпадения начала и конца интервала и подтверждённых вкладов всех выбранных привязок для каждого направления. Jitter, отсутствие part, несовместимые интервалы или сериализованные parts без исходных точек не доказывают общий пик: observations.rxPeak/txPeak=false, UI показывает «—», а не ложный ноль. Подтверждённый ноль остаётся0. Для старого backend новый reader сохраняет прежнюю совместимость при отсутствии новых optional flags; interop проверен до переключения backend. История, объём и freshness metadata сохраняются. Данные разных серверов не интерполируются и не сдвигаются ради создания искусственного общего пика.
|
||||
|
||||
Изменены traffic-rate.ts, lib/traffic-observations.ts и профильные тесты. ReUI PRO/base-nova/Frame/KpiStatGrid переиспользуются, vendor/theme/layout не менялись. Проверены different/coincident peaks, nested merge, measured zero, missing/empty/legacy parts, jitter внутри одинаковых chart bins, одинаковый конец с разным началом и recovery. Реальный PostgreSQL/API fixture добавляет3 сценария пользователя с двумя привязками, включая неизменный объём750000000bytes. Все71 профильных SQL-проверок прошли41228ms без postgres-unavailable skip; собственные временная БД, роль и контейнер удалены.
|
||||
|
||||
Backend build и frontend TypeScript passed. Все77 уникальных E2E трафика прошли1.6m; начальные4 focused cases22.4s входят в77 и повторно не считаются. Production backend/frontend build passed; source manifests совпали461 backend+524 frontend файлов,1040 compiled backend artifacts совпали. Runtime backend/frontend ux-peak191, PostgreSQL healthy; frontend healthcheck отсутствует, работа подтверждена HTTP/browser. До backend rollout новый frontend принят с backend190. Actual API200 и fresh measurements обоих CHR, live UI1440/320 без page errors/overflow. Native конфигурация/пароли не менялись, messages0.
|
||||
|
||||
Протоколы: [SQL](ux-ui/acceptance/traffic-peak191-sql191-result.json), [source](ux-ui/acceptance/traffic-peak191-source191-result.json), [compiled](ux-ui/acceptance/traffic-peak191-compiled191-result.json), [runtime](ux-ui/acceptance/traffic-peak191-runtime191-result.json), [API](ux-ui/acceptance/traffic-peak191-peak191-api-result.json), [E2E](ux-ui/acceptance/traffic-peak-e2e-2026-10-13.json), [interop](ux-ui/acceptance/traffic-peak-interop-2026-10-13.json), [live UI](ux-ui/acceptance/traffic-peak-deployed-2026-10-13.json).
|
||||
|
||||
| Референс | Схема |
|
||||
| --- | --- |
|
||||
| [ReUI Stats12](https://reui.io/preview/base/stats-12?ref=mcp) | Совпадающие интервалы всех привязок → подтверждённая сумма → максимум периода; нет совпадения → «—», объём сохранён |
|
||||
|
||||
Следующий192: одна согласованная общая backend-регрессия; фиксировать реальные skipped/external ветки отдельно. Последующий объём определяется результатом этой проверки и матрицей внешней приёмки. Portal/Telegram/client OS/multi-WAN/scale не приняты по fixture-тестам.12.5 открыта.
|
||||
+19
-1
@@ -1,6 +1,6 @@
|
||||
# Этап 12: итоговая приёмка UX/UI
|
||||
|
||||
Актуальное продолжение 2026-10-13: завершены185–190, backend ux-peak190/frontend ux-warning183 healthy; последний ownSQL68/38553ms/API/browser2 passed. Начат191 — baseline sum individual peaks200Mbps против complete simultaneous wave100Mbps; это не исправлено и не принято. Следующий шаг — temporal aggregate peak с confidence при несовпадающих timestamps.12.5 открыта.
|
||||
Актуальное продолжение 2026-10-13: завершён191 — общий пик привязок по совпадающим подтверждённым интервалам; backend/frontend ux-peak191 развёрнуты. ownSQL71/41228ms, E2E77/1.6m, source985/compiled1040/API/live UI2 passed. Начата192 — общая backend-регрессия в отдельной временной БД.12.5 открыта.
|
||||
|
||||
План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push.
|
||||
|
||||
@@ -3601,3 +3601,21 @@ Unit проверил partial high point → последующая complete0 wa
|
||||
Точный следующий шаг: общий peak вычислять из подтверждённых совпадающих временных срезов, сохраняя исходные timestamp/confidence внутренних parts; не складывать индивидуальные maxima. При отсутствии совместимого времени показывать неизвестное значение, сохраняя volumes/history/current metadata. Проверить разные времена peaks, совпадающий peak, missing contributor, jitter/неполные bins и recovery. Не подменять raw point peak максимумом bucket average без явной семантики. Нужны meaningful own SQL/API/UI cases перед production rollout.
|
||||
|
||||
Стенд остаётся backend ux-peak190/frontend ux-warning183. Source/runtime190 и real CHR/config/passwords unchanged; messages0. После191 — scoped control затронутых разделов и одна согласованная общая регрессия; внешние Portal/Telegram/client OS/multi-WAN/scale требуют доступов/сценариев из матрицы.12.5 открыта. Локальный commit без push сохраняет точное продолжение.
|
||||
|
||||
## 12.5.191 — подтверждённый общий пик привязок: реализация и приёмка (2026-10-13)
|
||||
|
||||
Исправлен baseline ниже: индивидуальные максимумы в разные моменты больше не складываются. A=100Mbps на первой волне и B=100Mbps на следующей дают общий пик100, совпадающие интервалы —200. Сумма byte deltas/учтённый объём сохранена. Пик является максимумом подтверждённых средних скоростей соседних счётчиков за совпадающие интервалы, а не мгновенным максимумом линии или максимумом chart bucket average.
|
||||
|
||||
Исходные интервалы и confidence parts хранятся во внутреннем WeakMap и не попадают в DTO/JSON. Merge, включая вложенный, требует совпадения начала и конца интервала и подтверждённых вкладов всех выбранных привязок для каждого направления. Jitter, отсутствие part, несовместимые интервалы или сериализованные parts без исходных точек не доказывают общий пик: observations.rxPeak/txPeak=false, UI показывает «—», а не ложный ноль. Подтверждённый ноль остаётся0. Для старого backend новый reader сохраняет прежнюю совместимость при отсутствии новых optional flags; interop проверен до переключения backend. История, объём и freshness metadata сохраняются. Данные разных серверов не интерполируются и не сдвигаются ради создания искусственного общего пика.
|
||||
|
||||
Изменены traffic-rate.ts, lib/traffic-observations.ts и профильные тесты. ReUI PRO/base-nova/Frame/KpiStatGrid переиспользуются, vendor/theme/layout не менялись. Проверены different/coincident peaks, nested merge, measured zero, missing/empty/legacy parts, jitter внутри одинаковых chart bins, одинаковый конец с разным началом и recovery. Реальный PostgreSQL/API fixture добавляет3 сценария пользователя с двумя привязками, включая неизменный объём750000000bytes. Все71 профильных SQL-проверок прошли41228ms без postgres-unavailable skip; собственные временная БД, роль и контейнер удалены.
|
||||
|
||||
Backend build и frontend TypeScript passed. Все77 уникальных E2E трафика прошли1.6m; начальные4 focused cases22.4s входят в77 и повторно не считаются. Production backend/frontend build passed; source manifests совпали461 backend+524 frontend файлов,1040 compiled backend artifacts совпали. Runtime backend/frontend ux-peak191, PostgreSQL healthy; frontend healthcheck отсутствует, работа подтверждена HTTP/browser. До backend rollout новый frontend принят с backend190. Actual API200 и fresh measurements обоих CHR, live UI1440/320 без page errors/overflow. Native конфигурация/пароли не менялись, messages0.
|
||||
|
||||
Протоколы: [SQL](ux-ui/acceptance/traffic-peak191-sql191-result.json), [source](ux-ui/acceptance/traffic-peak191-source191-result.json), [compiled](ux-ui/acceptance/traffic-peak191-compiled191-result.json), [runtime](ux-ui/acceptance/traffic-peak191-runtime191-result.json), [API](ux-ui/acceptance/traffic-peak191-peak191-api-result.json), [E2E](ux-ui/acceptance/traffic-peak-e2e-2026-10-13.json), [interop](ux-ui/acceptance/traffic-peak-interop-2026-10-13.json), [live UI](ux-ui/acceptance/traffic-peak-deployed-2026-10-13.json).
|
||||
|
||||
| Референс | Схема |
|
||||
| --- | --- |
|
||||
| [ReUI Stats12](https://reui.io/preview/base/stats-12?ref=mcp) | Совпадающие интервалы всех привязок → подтверждённая сумма → максимум периода; нет совпадения → «—», объём сохранён |
|
||||
|
||||
Следующий192: одна согласованная общая backend-регрессия; фиксировать реальные skipped/external ветки отдельно. Последующий объём определяется результатом этой проверки и матрицей внешней приёмки. Portal/Telegram/client OS/multi-WAN/scale не приняты по fixture-тестам.12.5 открыта.
|
||||
@@ -1,6 +1,6 @@
|
||||
# Тестовый сервер UX/UI — 2026-10-10
|
||||
|
||||
Актуальное продолжение 2026-10-13: завершены185–190, backend ux-peak190/frontend ux-warning183 healthy; последний ownSQL68/38553ms/API/browser2 passed. Начат191 — baseline sum individual peaks200Mbps против complete simultaneous wave100Mbps; это не исправлено и не принято. Следующий шаг — temporal aggregate peak с confidence при несовпадающих timestamps.12.5 открыта.
|
||||
Актуальное продолжение 2026-10-13: завершён191 — общий пик привязок по совпадающим подтверждённым интервалам; backend/frontend ux-peak191 развёрнуты. ownSQL71/41228ms, E2E77/1.6m, source985/compiled1040/API/live UI2 passed. Начата192 — общая backend-регрессия в отдельной временной БД.12.5 открыта.
|
||||
|
||||
| Параметр | Проверенное состояние |
|
||||
| --- | --- |
|
||||
@@ -708,7 +708,7 @@ flowchart LR
|
||||
|
||||
## 12.5.77 — основной блок IPsec (2026-10-09)
|
||||
|
||||
Актуальное продолжение 2026-10-13: завершены185–190, backend ux-peak190/frontend ux-warning183 healthy; последний ownSQL68/38553ms/API/browser2 passed. Начат191 — baseline sum individual peaks200Mbps против complete simultaneous wave100Mbps; это не исправлено и не принято. Следующий шаг — temporal aggregate peak с confidence при несовпадающих timestamps.12.5 открыта.
|
||||
Актуальное продолжение 2026-10-13: завершён191 — общий пик привязок по совпадающим подтверждённым интервалам; backend/frontend ux-peak191 развёрнуты. ownSQL71/41228ms, E2E77/1.6m, source985/compiled1040/API/live UI2 passed. Начата192 — общая backend-регрессия в отдельной временной БД.12.5 открыта.
|
||||
|
||||
Подробный объём, протоколы, схема и ограничения: [12.5.77 в журнале](ux-ui-progress.md).
|
||||
|
||||
@@ -2027,3 +2027,21 @@ Unit проверил partial high point → последующая complete0 wa
|
||||
Точный следующий шаг: общий peak вычислять из подтверждённых совпадающих временных срезов, сохраняя исходные timestamp/confidence внутренних parts; не складывать индивидуальные maxima. При отсутствии совместимого времени показывать неизвестное значение, сохраняя volumes/history/current metadata. Проверить разные времена peaks, совпадающий peak, missing contributor, jitter/неполные bins и recovery. Не подменять raw point peak максимумом bucket average без явной семантики. Нужны meaningful own SQL/API/UI cases перед production rollout.
|
||||
|
||||
Стенд остаётся backend ux-peak190/frontend ux-warning183. Source/runtime190 и real CHR/config/passwords unchanged; messages0. После191 — scoped control затронутых разделов и одна согласованная общая регрессия; внешние Portal/Telegram/client OS/multi-WAN/scale требуют доступов/сценариев из матрицы.12.5 открыта. Локальный commit без push сохраняет точное продолжение.
|
||||
|
||||
## 12.5.191 — подтверждённый общий пик привязок: реализация и приёмка (2026-10-13)
|
||||
|
||||
Исправлен baseline ниже: индивидуальные максимумы в разные моменты больше не складываются. A=100Mbps на первой волне и B=100Mbps на следующей дают общий пик100, совпадающие интервалы —200. Сумма byte deltas/учтённый объём сохранена. Пик является максимумом подтверждённых средних скоростей соседних счётчиков за совпадающие интервалы, а не мгновенным максимумом линии или максимумом chart bucket average.
|
||||
|
||||
Исходные интервалы и confidence parts хранятся во внутреннем WeakMap и не попадают в DTO/JSON. Merge, включая вложенный, требует совпадения начала и конца интервала и подтверждённых вкладов всех выбранных привязок для каждого направления. Jitter, отсутствие part, несовместимые интервалы или сериализованные parts без исходных точек не доказывают общий пик: observations.rxPeak/txPeak=false, UI показывает «—», а не ложный ноль. Подтверждённый ноль остаётся0. Для старого backend новый reader сохраняет прежнюю совместимость при отсутствии новых optional flags; interop проверен до переключения backend. История, объём и freshness metadata сохраняются. Данные разных серверов не интерполируются и не сдвигаются ради создания искусственного общего пика.
|
||||
|
||||
Изменены traffic-rate.ts, lib/traffic-observations.ts и профильные тесты. ReUI PRO/base-nova/Frame/KpiStatGrid переиспользуются, vendor/theme/layout не менялись. Проверены different/coincident peaks, nested merge, measured zero, missing/empty/legacy parts, jitter внутри одинаковых chart bins, одинаковый конец с разным началом и recovery. Реальный PostgreSQL/API fixture добавляет3 сценария пользователя с двумя привязками, включая неизменный объём750000000bytes. Все71 профильных SQL-проверок прошли41228ms без postgres-unavailable skip; собственные временная БД, роль и контейнер удалены.
|
||||
|
||||
Backend build и frontend TypeScript passed. Все77 уникальных E2E трафика прошли1.6m; начальные4 focused cases22.4s входят в77 и повторно не считаются. Production backend/frontend build passed; source manifests совпали461 backend+524 frontend файлов,1040 compiled backend artifacts совпали. Runtime backend/frontend ux-peak191, PostgreSQL healthy; frontend healthcheck отсутствует, работа подтверждена HTTP/browser. До backend rollout новый frontend принят с backend190. Actual API200 и fresh measurements обоих CHR, live UI1440/320 без page errors/overflow. Native конфигурация/пароли не менялись, messages0.
|
||||
|
||||
Протоколы: [SQL](ux-ui/acceptance/traffic-peak191-sql191-result.json), [source](ux-ui/acceptance/traffic-peak191-source191-result.json), [compiled](ux-ui/acceptance/traffic-peak191-compiled191-result.json), [runtime](ux-ui/acceptance/traffic-peak191-runtime191-result.json), [API](ux-ui/acceptance/traffic-peak191-peak191-api-result.json), [E2E](ux-ui/acceptance/traffic-peak-e2e-2026-10-13.json), [interop](ux-ui/acceptance/traffic-peak-interop-2026-10-13.json), [live UI](ux-ui/acceptance/traffic-peak-deployed-2026-10-13.json).
|
||||
|
||||
| Референс | Схема |
|
||||
| --- | --- |
|
||||
| [ReUI Stats12](https://reui.io/preview/base/stats-12?ref=mcp) | Совпадающие интервалы всех привязок → подтверждённая сумма → максимум периода; нет совпадения → «—», объём сохранён |
|
||||
|
||||
Следующий192: одна согласованная общая backend-регрессия; фиксировать реальные skipped/external ветки отдельно. Последующий объём определяется результатом этой проверки и матрицей внешней приёмки. Portal/Telegram/client OS/multi-WAN/scale не приняты по fixture-тестам.12.5 открыта.
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"date": "2026-10-13",
|
||||
"block": "12.5.190",
|
||||
"block": "12.5.191",
|
||||
"runtime": {
|
||||
"backend": "ux-peak190",
|
||||
"frontend": "ux-warning183"
|
||||
"backend": "ux-peak191",
|
||||
"frontend": "ux-peak191"
|
||||
},
|
||||
"apis": [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"date": "2026-10-13",
|
||||
"block": "12.5.191",
|
||||
"command": "playwright test traffic-.*\\.spec\\.ts",
|
||||
"uniqueCases": 77,
|
||||
"passed": 77,
|
||||
"duration": "1.6m",
|
||||
"focusedInitialRun": {
|
||||
"passed": 4,
|
||||
"durationSeconds": 22.4,
|
||||
"includedInUniqueCount": true
|
||||
},
|
||||
"scope": "Ordinary HTTP API fixture path, not built-in mock mode; separate deployed read-only smoke",
|
||||
"frontendTypeScript": true,
|
||||
"backendBuild": true
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"date": "2026-10-13",
|
||||
"block": "12.5.191",
|
||||
"newFrontend": "ux-peak191",
|
||||
"oldBackend": "ux-peak190",
|
||||
"readerAccepted": true,
|
||||
"serverCount": 2,
|
||||
"pageErrors": 0,
|
||||
"bound": "Additive observed DTO reader compatibility; legacy numeric-only paths verified separately as unknown"
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"date": "2026-10-13",
|
||||
"block": "12.5.191",
|
||||
"version": "1.0.0-test.peak191",
|
||||
"passed": true,
|
||||
"method": "Read-only actual application volume API; no new native RouterOS commands; exact edge values covered separately by owned SQL",
|
||||
"cases": [
|
||||
{
|
||||
"sourceId": "1",
|
||||
"volumeUnit": "GiB",
|
||||
"rxTotalGiB": 0.012759584002196789,
|
||||
"txTotalGiB": 0.0008463365957140923,
|
||||
"currentState": "current"
|
||||
},
|
||||
{
|
||||
"sourceId": "2",
|
||||
"volumeUnit": "GiB",
|
||||
"rxTotalGiB": 0.0011972729116678238,
|
||||
"txTotalGiB": 0.0014407914131879807,
|
||||
"currentState": "current"
|
||||
}
|
||||
],
|
||||
"newRouterOSCommands": 0,
|
||||
"passwordChanges": 0,
|
||||
"messages": 0
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"date": "2026-10-13",
|
||||
"block": "12.5.191",
|
||||
"secretEnvCopied": false,
|
||||
"rollbackTags": {
|
||||
"MMAPP_IMAGE_TAG": "ux-warning183",
|
||||
"BACKEND_IMAGE_TAG": "ux-peak190"
|
||||
},
|
||||
"services": {
|
||||
"backend": {
|
||||
"image": "mmapp-test/backend:ux-peak191",
|
||||
"imageId": "sha256:f6db37afa8263cf16ddb955938c7c20796b49570a08f8f706803b94e30812fb7",
|
||||
"state": "running",
|
||||
"health": "healthy",
|
||||
"matchedVerifiedImage": true
|
||||
},
|
||||
"frontend": {
|
||||
"image": "mmapp-test/frontend:ux-peak191",
|
||||
"imageId": "sha256:1d9f85dbb5c791f09dad2863041d0fae5a291b02f5e73a5e96185a7866c673c7",
|
||||
"state": "running",
|
||||
"health": null,
|
||||
"matchedVerifiedImage": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"date": "2026-10-13",
|
||||
"block": "12.5.191",
|
||||
"matched": {
|
||||
"backend": 461,
|
||||
"frontend": 524
|
||||
},
|
||||
"different": [],
|
||||
"missing": []
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"date": "2026-10-13",
|
||||
"block": "12.5.191",
|
||||
"passed": true,
|
||||
"profiles": [
|
||||
{
|
||||
"date": "2026-10-13",
|
||||
"block": "12.5.191",
|
||||
"database": "mm_accept_166_1791874191164",
|
||||
"scope": "ephemeral role and precreated owned database; no copied production credentials",
|
||||
"tests": [
|
||||
{
|
||||
"command": "npm --prefix backend run test:traffic-rate",
|
||||
"passed": true,
|
||||
"durationMs": 41228,
|
||||
"ownedTrafficSqlCases": 9,
|
||||
"ownedHistorySqlCases": 14,
|
||||
"ownedLiveSqlCases": 10,
|
||||
"ownedVolumeSqlCases": 17,
|
||||
"ownedStatusSqlCases": 21,
|
||||
"postgresUnavailableSkip": false
|
||||
}
|
||||
],
|
||||
"scopedRoleVerified": true,
|
||||
"passed": true,
|
||||
"databaseRemoved": true,
|
||||
"roleRemoved": true,
|
||||
"testContainerRemoved": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -12,3 +12,7 @@ const stale=projectTrafficObservations({...row,observations:obs},at+90_001);asse
|
||||
assert.equal(projectTrafficObservations({...row,observations:{...obs,currentState:"error"}},at).rxNow,null)
|
||||
for(const change of [{rxNow:-1},{observations:{...obs,sampledAt:"bad"}},{observations:{...obs,sampledAt:null}},{observations:{...obs,rxSeries:[]}}])assert.throws(()=>projectTrafficObservations({...row,...change},at))
|
||||
console.log("traffic observations nullable/zero/gaps/expiry guards tests ok")
|
||||
const noPeak=projectTrafficObservations({...row,volumeUnit:"GiB",observations:{...obs,rxPeak:false,txPeak:false}},at)
|
||||
assert.equal(noPeak.rxPeak,null);assert.equal(noPeak.txPeak,null);assert.equal(noPeak.rxTotal,0)
|
||||
assert.equal(projectTrafficObservations({...row,observations:{...obs,rxPeak:true,txPeak:true}},at).rxPeak,0)
|
||||
console.log('aggregate peak confidence overrides bucket confidence; measured zero and volumes retained')
|
||||
@@ -2,6 +2,7 @@ import { z } from "zod"
|
||||
const timestamp = z.string().refine(v => Number.isFinite(Date.parse(v))).nullable()
|
||||
const rate = z.number().finite().nonnegative().nullable()
|
||||
const meta = z.object({ rxNow: z.boolean(), txNow: z.boolean(), rxSeries: z.array(z.boolean()), txSeries: z.array(z.boolean()),
|
||||
rxPeak: z.boolean().optional(), txPeak: z.boolean().optional(),
|
||||
sampledAt: timestamp, expiresAt: timestamp.optional(), lastAttemptAt: timestamp.optional(), lastSucceededAt: timestamp.optional(), error: z.string().nullable().optional(),
|
||||
currentState: z.enum(["current", "unknown", "pending", "error", "stale", "disabled"]).optional() })
|
||||
export type TrafficObservationMeta = z.infer<typeof meta>
|
||||
@@ -19,7 +20,7 @@ export function projectTrafficObservations(raw: unknown, now = Date.now()) {
|
||||
const rxSeries = r.rxSeries.map((v, i) => o?.rxSeries[i] ? v : null)
|
||||
const txSeries = r.txSeries.map((v, i) => o?.txSeries[i] ? v : null)
|
||||
return { observations: o, volumeUnit: r.volumeUnit, rxNow: current && o?.rxNow ? r.rxNow : null, txNow: current && o?.txNow ? r.txNow : null,
|
||||
rxSeries, txSeries, rxPeak: rxSeries.some(v => v !== null) ? r.rxPeak : null,
|
||||
txPeak: txSeries.some(v => v !== null) ? r.txPeak : null,
|
||||
rxSeries, txSeries, rxPeak: (o?.rxPeak ?? rxSeries.some(v => v !== null)) ? r.rxPeak : null,
|
||||
txPeak: (o?.txPeak ?? txSeries.some(v => v !== null)) ? r.txPeak : null,
|
||||
rxTotal: r.volumeUnit && rxSeries.some(v => v !== null) ? r.rxTotal : null, txTotal: r.volumeUnit && txSeries.some(v => v !== null) ? r.txTotal : null }
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {test,expect} from '@playwright/test'
|
||||
for(const width of [1440,320])test(`Пик трафика: неизвестный интервал, подтверждённый пик и ноль ${width}`,async({page})=>{
|
||||
await page.setViewportSize({width,height:1000})
|
||||
let peakKnown=false,peak=100
|
||||
const server=()=>({id:'1',name:'Пик CHR',site:'TEST',country:'RU',status:'online',volumeUnit:'GiB',sessions:1,
|
||||
rxNow:0,txNow:0,rxPeak:peak,txPeak:peak,rxTotal:1,txTotal:1,rxSeries:[0,100],txSeries:[0,100],
|
||||
observations:{rxNow:true,txNow:true,rxPeak:peakKnown,txPeak:peakKnown,rxSeries:[false,true],txSeries:[false,true],sampledAt:'2026-10-13T00:00:00Z'}})
|
||||
await page.route('**/health',r=>r.fulfill({json:{ok:true}}))
|
||||
await page.route('**/api/**',r=>{
|
||||
const p=new URL(r.request().url()).pathname
|
||||
if(p.endsWith('/live'))return r.fulfill({status:503,json:{error:'Fixture без live'}})
|
||||
return r.fulfill({json:p==='/api/auth/config'?{required:false}:p==='/api/traffic/servers'?{servers:[server()]}:
|
||||
p==='/api/traffic/servers/1'?{server:server()}:p.endsWith('/interfaces')||p==='/api/traffic/bound-interfaces'?{interfaces:[]}:
|
||||
p==='/api/traffic/users'?{users:[]}:p==='/api/servers'?[]:{}})
|
||||
})
|
||||
await page.goto('/traffic')
|
||||
const peakCard=page.locator('[data-slot="frame-panel"]').filter({has:page.getByText('Пик RX',{exact:true})}).first()
|
||||
await expect(peakCard).toContainText('—')
|
||||
await expect(page.getByRole('group',{name:'Учтённый объём по измерениям'})).toContainText('1 ГиБ')
|
||||
peakKnown=true;await page.getByRole('button',{name:'15м',exact:true}).click()
|
||||
await expect(peakCard).toContainText('100')
|
||||
peak=0;await page.getByRole('button',{name:'24ч',exact:true}).click()
|
||||
await expect(peakCard).toContainText('0')
|
||||
expect(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth+1)).toBe(true)
|
||||
})
|
||||
Reference in new issue
Block a user