fix(alerts): объяснить отключённые и неоднозначные цели оповещений
This commit is contained in:
1 parent
db7105119d
commit
d3f9a120e6
23 files changed
+1429
-16
No files matched your search
@@ -10,6 +10,7 @@ import {useAlertsTargets} from "@/hooks/use-alerts-targets"
|
||||
import {AlertsOutboxPanel} from "@/components/alerts-outbox-panel"
|
||||
import {AlertsTelegramForm} from "@/components/alerts-telegram-form"
|
||||
import {AlertsTargetsStatus} from "@/components/alerts-targets-status"
|
||||
import {AlertsTargetIssues} from "@/components/alerts-target-issues"
|
||||
import { useDataSource } from "@/lib/data-source"
|
||||
import { PageHeader } from "@/components/page-header"
|
||||
import { FormToggle } from "@/components/form-kit"
|
||||
@@ -157,6 +158,7 @@ interface AlertsGreTunnelRow {
|
||||
}
|
||||
|
||||
interface AlertsMeta {
|
||||
targetIssues?: AlertsResponse["meta"]["targetIssues"]
|
||||
servers: string[]
|
||||
greClients: string[]
|
||||
probeTargets: string[]
|
||||
@@ -2531,6 +2533,7 @@ function AlertsContent() {
|
||||
{config.busy?<p role="status" className="text-xs">Ожидаем подтверждение правил и групп…</p>:null}
|
||||
{config.unknown&&!config.busy?<div className="space-y-2"><p role="status" className="text-xs">Результат сохранения правил неизвестен. Запрос мог выполниться; проверьте правила и группы в БД перед повтором. Обновление списка не разрешает повтор автоматически.</p><Button type="button" size="sm" variant="outline" className="h-auto whitespace-normal" disabled={!config.enabled||operationBusy} onClick={config.review}>Я проверил правила — разрешить новый запрос</Button></div>:null}
|
||||
{known?<>
|
||||
{isLive && <AlertsTargetIssues issues={meta?.targetIssues} />}
|
||||
{/* ── summary chip bar ── */}
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { pool, closePool } from "../../db/index.js"
|
||||
import { initDatabase } from "../../db/bootstrap.js"
|
||||
import { buildSignalSnapshotFromCollectors } from "./signals-from-collectors.js"
|
||||
import { evaluateRule } from "./evaluate-rule.js"
|
||||
import { getAlertsMeta } from "../alerts-service.js"
|
||||
import type { ApiAlertRule } from "../alerts-service.js"
|
||||
|
||||
const database = process.env.MMAPP_OWN_SQL_DB
|
||||
@@ -41,12 +42,14 @@ else {
|
||||
let signals = await build()
|
||||
assert.equal(signals.servers.length, 2); assert.equal(signals.servers[0].prevStatus, "online")
|
||||
assert.equal(signals.trafficByServer[0].rxMbps, 0); assert.equal(signals.probes.length, 2); cases++
|
||||
assert.deepEqual((await getAlertsMeta()).targetIssues, [])
|
||||
const offlineRule = { id: "owned", name: "owned", type: "server", targets: [server().name], conditions: ["offline"], enabled: true } as ApiAlertRule
|
||||
assert.ok(evaluateRule(offlineRule, signals))
|
||||
|
||||
await pool.query("UPDATE uptime_settings SET resources_enabled=false,ping_enabled=false WHERE id=1")
|
||||
await pool.query("UPDATE traffic_settings SET enabled=false WHERE id=1")
|
||||
signals = await build(); assert.deepEqual(signals.servers, []); assert.deepEqual(signals.probes, []); assert.deepEqual(signals.trafficByServer, []); cases++
|
||||
assert((await getAlertsMeta()).targetIssues?.every(issue => issue.reason === "disabled-source"))
|
||||
await pool.query("UPDATE uptime_settings SET resources_enabled=true,ping_enabled=true WHERE id=1")
|
||||
await pool.query("UPDATE traffic_settings SET enabled=true WHERE id=1")
|
||||
await pool.query("UPDATE servers SET enabled=false WHERE id=$1", [ids[0]])
|
||||
@@ -54,6 +57,7 @@ else {
|
||||
await pool.query("UPDATE servers SET enabled=true WHERE id=$1", [ids[0]])
|
||||
await pool.query("UPDATE servers SET name=$1 WHERE id=$2", [server().name, ids[1]])
|
||||
signals = await build(); assert.deepEqual(signals.servers, []); assert.deepEqual(signals.trafficByServer, []); cases++
|
||||
assert.deepEqual((await getAlertsMeta()).targetIssues?.filter(issue => issue.reason === "ambiguous").map(issue => issue.kind), ["server", "traffic"])
|
||||
await pool.query("UPDATE servers SET name=$1 WHERE id=$2", [server(1).name, ids[1]])
|
||||
|
||||
for (const broken of [null, "{", { ...snapshot("uptime_resources", []), servers: {} }, snapshot("uptime_resources", [])]) {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { AlertTargetIssue } from "@mmapp/contracts/alerts"
|
||||
import type { CollectorSourceConfig } from "./collector-source-signals.js"
|
||||
|
||||
/** Configuration diagnostics only. This does not claim fresh samples or Telegram delivery. */
|
||||
export function collectAlertTargetIssues(config: CollectorSourceConfig): AlertTargetIssue[] {
|
||||
const out: AlertTargetIssue[] = []
|
||||
const servers = new Map<string, typeof config.servers>()
|
||||
for (const server of config.servers) {
|
||||
const key = (server.name || server.host).trim().toLowerCase()
|
||||
if (key) servers.set(key, [...(servers.get(key) ?? []), server])
|
||||
}
|
||||
const serverOn = (config.uptime?.resourcesEnabled ?? config.uptime?.enabled) === true || config.rest?.enabled === true
|
||||
for (const group of servers.values()) {
|
||||
const active = group.filter(s => s.enabled === true), target = (group[0].name || group[0].host).trim()
|
||||
for (const [kind, sourceOn] of [["server", serverOn], ["traffic", config.traffic?.enabled === true]] as const) {
|
||||
const reason = !active.length ? "disabled-object" : active.length > 1 ? "ambiguous" : !sourceOn ? "disabled-source" : null
|
||||
if (reason) out.push({ kind, target, reason })
|
||||
}
|
||||
}
|
||||
const probes = new Map<string, typeof config.probes>()
|
||||
for (const probe of config.probes) {
|
||||
if (!probe.name.trim() || !probe.target.trim()) continue
|
||||
const key = `${probe.name.trim()} → ${probe.target.trim()}`
|
||||
probes.set(key, [...(probes.get(key) ?? []), probe])
|
||||
}
|
||||
for (const [target, group] of probes) {
|
||||
const active = group.filter(p => p.enabled && config.servers.some(s => s.enabled === true && Number(s.id) === p.srcServerId))
|
||||
const sourceOn = (config.uptime?.pingEnabled ?? config.uptime?.enabled) === true
|
||||
const reason = !active.length ? "disabled-object" : active.length > 1 ? "ambiguous" : !sourceOn ? "disabled-source" : null
|
||||
if (reason) out.push({ kind: "probe", target, reason })
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -3,6 +3,8 @@ import assert from "node:assert/strict"
|
||||
import {test} from "node:test"
|
||||
import type {AlertsResponse} from "@mmapp/contracts/alerts"
|
||||
import {readAlertsOverview,alertsOverviewTransaction,buildAlertsMeta} from "./alerts-overview.js"
|
||||
import {collectAlertTargetIssues} from "./alert-engine/target-issues.js"
|
||||
import type {CollectorSourceConfig} from "./alert-engine/collector-source-signals.js"
|
||||
const empty:AlertsResponse={revision:alertsConfigRevision({groups:[],rules:[]}),telegram:{chatId:"",tokenConfigured:false,connected:false,messageThreadId:null},groups:[],rules:[],history:[],meta:buildAlertsMeta([],[])}
|
||||
test("Alerts overview uses one context with repeatable read/read only and rejects partial snapshots",async()=>{
|
||||
const context={snapshot:structuredClone(empty)},calls:string[]=[];let transactions=0
|
||||
@@ -17,3 +19,21 @@ test("Alerts metadata exposes existing probe keys without synthesizing per-serve
|
||||
const withoutProbes=buildAlertsMeta(servers,[]);assert.deepEqual(withoutProbes.probeTargets,[]);assert.deepEqual(withoutProbes.rttLossTargets,[]);assert.equal(withoutProbes.serversDetail[0]?.type,"")
|
||||
const actual=buildAlertsMeta(servers,[{name:"probe-fixture",target:"1.1.1.1"}]);assert.deepEqual(actual.probeTargets,["probe-fixture → 1.1.1.1"]);assert.deepEqual(actual.rttLossTargets,actual.probeTargets);assert.equal(JSON.stringify(actual).includes("8.8.8.8"),false)
|
||||
})
|
||||
test("Target diagnostics explain ambiguous and disabled configuration without claiming live status",()=>{
|
||||
const config:CollectorSourceConfig={servers:[{id:1,name:"CHR",host:"one",enabled:true},{id:2,name:" chr ",host:"two",enabled:true}],probes:[],uptime:{enabled:true,resourcesEnabled:true,pingEnabled:true,intervalSec:15,probeIntervalSec:15},rest:{enabled:false,intervalSec:120},traffic:{enabled:true,intervalSec:30}}
|
||||
assert.deepEqual(collectAlertTargetIssues(config),[{kind:"server",target:"CHR",reason:"ambiguous"},{kind:"traffic",target:"CHR",reason:"ambiguous"}])
|
||||
const unique={...config,servers:config.servers.map((s,index)=>({...s,name:"server"+index}))}
|
||||
assert.deepEqual(collectAlertTargetIssues(unique),[])
|
||||
assert.deepEqual(collectAlertTargetIssues({...config,servers:[config.servers[0],{...config.servers[1],enabled:false}]}),[])
|
||||
const disabled=collectAlertTargetIssues({...unique,servers:unique.servers.map(s=>({...s,enabled:false}))});assert.equal(disabled.length,4);assert(disabled.every(i=>i.reason==="disabled-object"))
|
||||
const sourceOff={...unique,uptime:{...unique.uptime!,resourcesEnabled:false},traffic:{...unique.traffic!,enabled:false}}
|
||||
assert(collectAlertTargetIssues(sourceOff).every(i=>i.reason==="disabled-source"))
|
||||
assert.deepEqual(collectAlertTargetIssues({...sourceOff,rest:{...sourceOff.rest!,enabled:true}}).map(i=>i.kind),["traffic","traffic"])
|
||||
const probe={id:"probe",name:"ping",target:"target",srcServerId:1,srcInterface:"",enabled:true,intervalSec:15}
|
||||
const withProbe={...unique,probes:[probe,{...probe,id:"second"}]}
|
||||
assert.deepEqual(collectAlertTargetIssues(withProbe),[{kind:"probe",target:"ping → target",reason:"ambiguous"}])
|
||||
assert.deepEqual(collectAlertTargetIssues({...withProbe,probes:[probe,{...probe,id:"second",enabled:false}]}),[])
|
||||
assert.equal(collectAlertTargetIssues({...unique,probes:[probe],uptime:{...unique.uptime!,pingEnabled:false}})[0].reason,"disabled-source")
|
||||
const current=buildAlertsMeta(unique.servers.map(s=>({...s,site:null,country:null,type:null})),[probe],unique)
|
||||
assert.deepEqual(current.targetIssues,[]);assert.equal("password" in current,false)
|
||||
})
|
||||
@@ -1,5 +1,7 @@
|
||||
import {alertsConfigRevision} from "./alerts-revision.js"
|
||||
import {alertsResponseSchema,type AlertsResponse} from "@mmapp/contracts/alerts"
|
||||
import {collectAlertTargetIssues} from "./alert-engine/target-issues.js"
|
||||
import type {CollectorSourceConfig} from "./alert-engine/collector-source-signals.js"
|
||||
export const alertsOverviewTransaction={isolationLevel:"repeatable read",accessMode:"read only"} as const
|
||||
interface Dependencies<C>{transaction:(work:(context:C)=>Promise<AlertsResponse>,options:typeof alertsOverviewTransaction)=>Promise<AlertsResponse>;telegram:(context:C)=>Promise<unknown>;groups:(context:C)=>Promise<unknown>;rules:(context:C)=>Promise<unknown>;history:(context:C)=>Promise<unknown>;meta:(context:C)=>Promise<unknown>}
|
||||
export async function readAlertsOverview<C>(deps:Dependencies<C>):Promise<AlertsResponse>{
|
||||
@@ -14,8 +16,8 @@ export async function readAlertsOverview<C>(deps:Dependencies<C>):Promise<Alerts
|
||||
},alertsOverviewTransaction)
|
||||
}
|
||||
|
||||
export function buildAlertsMeta(servers:{name:string|null;host:string;site:string|null;country:string|null;type:string|null}[],probes:{name:string;target:string}[]){
|
||||
export function buildAlertsMeta(servers:{name:string|null;host:string;site:string|null;country:string|null;type:string|null}[],probes:{name:string;target:string}[],config?:CollectorSourceConfig){
|
||||
const labels=servers.map(server=>server.name?.trim()||server.host)
|
||||
const probeTargets=probes.map(probe=>probe.name+" → "+probe.target)
|
||||
return {servers:labels,greClients:labels,probeTargets,rttLossTargets:probeTargets,trafficServers:labels,serversDetail:servers.map(server=>({name:server.name?.trim()||"",host:server.host.trim(),site:server.site?.trim()||"",country:server.country?.trim()||"",type:server.type??""}))}
|
||||
return {servers:labels,greClients:labels,probeTargets,rttLossTargets:probeTargets,trafficServers:labels,serversDetail:servers.map(server=>({name:server.name?.trim()||"",host:server.host.trim(),site:server.site?.trim()||"",country:server.country?.trim()||"",type:server.type??""})),...(config?{targetIssues:collectAlertTargetIssues(config)}:{})}
|
||||
}
|
||||
@@ -15,6 +15,9 @@ import {
|
||||
alertTelegramSettings,
|
||||
servers,
|
||||
uptimeProbes,
|
||||
uptimeSettings,
|
||||
serversApiPingSettings,
|
||||
trafficSettings,
|
||||
} from "../db/schema.js"
|
||||
|
||||
export type ApiAlertGroup = {
|
||||
@@ -495,6 +498,8 @@ export async function listMergedHistory(persistLimit = 40, liveCap = 25,context:
|
||||
export async function getAlertsMeta(context:AlertsReadContext=db) {
|
||||
const srvRows = await context
|
||||
.select({
|
||||
id: servers.id,
|
||||
enabled: servers.enabled,
|
||||
name: servers.name,
|
||||
host: servers.host,
|
||||
site: servers.site,
|
||||
@@ -502,8 +507,12 @@ export async function getAlertsMeta(context:AlertsReadContext=db) {
|
||||
type: servers.type,
|
||||
})
|
||||
.from(servers)
|
||||
const probes=await context.select({name:uptimeProbes.name,target:uptimeProbes.target}).from(uptimeProbes)
|
||||
return buildAlertsMeta(srvRows,probes)
|
||||
const probes=await context.select({id:uptimeProbes.id,name:uptimeProbes.name,target:uptimeProbes.target,enabled:uptimeProbes.enabled,
|
||||
srcServerId:uptimeProbes.srcServerId,srcInterface:uptimeProbes.srcInterface,intervalSec:uptimeProbes.intervalSec}).from(uptimeProbes)
|
||||
const uptime=(await context.select().from(uptimeSettings).where(eq(uptimeSettings.id,1)).limit(1))[0]
|
||||
const rest=(await context.select().from(serversApiPingSettings).where(eq(serversApiPingSettings.id,1)).limit(1))[0]
|
||||
const traffic=(await context.select().from(trafficSettings).where(eq(trafficSettings.id,1)).limit(1))[0]
|
||||
return buildAlertsMeta(srvRows,probes,{servers:srvRows,probes,uptime,rest,traffic})
|
||||
}
|
||||
|
||||
export async function getAlertsOverview(){
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import Link from "next/link"
|
||||
import { TriangleAlertIcon } from "lucide-react"
|
||||
import type { AlertTargetIssue } from "@mmapp/contracts/alerts"
|
||||
import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert"
|
||||
|
||||
const kinds = { server: "Доступность сервера", traffic: "Трафик", probe: "Ping-проба" }
|
||||
const reasons = {
|
||||
ambiguous: "Несколько включённых объектов имеют эту подпись. События не подтверждаются.",
|
||||
"disabled-object": "Объект или его сервер выключен. Опрос не выполняется.",
|
||||
"disabled-source": "Сбор данных этого типа выключен.",
|
||||
}
|
||||
export function AlertsTargetIssues({ issues }: { issues: AlertTargetIssue[] | undefined }) {
|
||||
if (!issues?.length) return null
|
||||
const grouped = new Map<string, { target: string; reason: AlertTargetIssue["reason"]; kinds: string[] }>()
|
||||
for (const issue of issues) {
|
||||
const key = `${issue.reason}\0${issue.target}`
|
||||
const group = grouped.get(key) ?? { target: issue.target, reason: issue.reason, kinds: [] }
|
||||
if (!group.kinds.includes(kinds[issue.kind])) group.kinds.push(kinds[issue.kind])
|
||||
grouped.set(key, group)
|
||||
}
|
||||
return <Alert role="status" variant="warning" aria-label="Проблемы целей оповещений">
|
||||
<TriangleAlertIcon />
|
||||
<AlertTitle>Некоторые цели оповещений требуют проверки</AlertTitle>
|
||||
<AlertDescription>
|
||||
<details open={grouped.size <= 5}>
|
||||
<summary className="cursor-pointer">Проверить цели: {grouped.size}</summary>
|
||||
<ul className="mt-2 space-y-2">
|
||||
{[...grouped].map(([key, group]) => <li key={key} className="[overflow-wrap:anywhere]">
|
||||
<strong>{group.target}</strong> · {group.kinds.join(" / ")} — {reasons[group.reason]}
|
||||
</li>)}
|
||||
</ul>
|
||||
</details>
|
||||
{issues.some(i => i.reason === "ambiguous") && <p>Задайте разные имена объектов, затем обновите цели существующих правил.</p>}
|
||||
<p className="mt-2 flex flex-wrap gap-x-4 gap-y-1">
|
||||
<Link className="underline underline-offset-4" href="/servers">Проверить серверы</Link>
|
||||
{issues.some(i => i.kind === "probe") && <Link className="underline underline-offset-4" href="/uptime">Проверить пробы</Link>}
|
||||
{issues.some(i => i.reason === "disabled-source") && <Link className="underline underline-offset-4" href="/data-collection">Проверить сбор данных</Link>}
|
||||
</p>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
# UX/UI: протокол внешней приёмки 12.5
|
||||
|
||||
Актуальное продолжение 2026-10-12: завершён12.5.170 — source enabled/current/unknown/freshness/per-probe guards, включая169. Own PG16 source cases +GRE/BGP8/stability passed; temporary DB/role/container removed. Source433/compiled1008 parity; backend ux-sources170 healthy, frontend ux-bfd158 прежний;6 real UI/4 API reads passed, оба CHR доступны. Одноимённые server/traffic targets подавлены, имена/пароли не менялись. Следующий171 — показать неоднозначные/отключённые цели оповещений в UI и объяснить отсутствие подтверждённого сигнала;12.5 открыт.
|
||||
Актуальное продолжение 2026-10-12: завершён12.5.171 — объяснение неоднозначных/отключённых целей в оповещениях, ReUI Alert без vendor changes. Backend profile/42E2E/ownPG16+metadata/compiled1012/source434+518 passed;2 live warning screens/scoped axe passed. Frontend сначала проверен с old API, затем оба ux-targets171 развёрнуты;backend/PG healthy. DB/role/container удалены. Следующий172 — исправить синтезированный журнал monitoring: raw sample readers ещё игнорируют disabled/freshness и Date needs positive SQL verification;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.170 — source enabled/current/unknown/freshness/per-probe guards, включая169. Own PG16 source cases +GRE/BGP8/stability passed; temporary DB/role/container removed. Source433/compiled1008 parity; backend ux-sources170 healthy, frontend ux-bfd158 прежний;6 real UI/4 API reads passed, оба CHR доступны. Одноимённые server/traffic targets подавлены, имена/пароли не менялись. Следующий171 — показать неоднозначные/отключённые цели оповещений в UI и объяснить отсутствие подтверждённого сигнала;12.5 открыт.
|
||||
Актуальное продолжение 2026-10-12: завершён12.5.171 — объяснение неоднозначных/отключённых целей в оповещениях, ReUI Alert без vendor changes. Backend profile/42E2E/ownPG16+metadata/compiled1012/source434+518 passed;2 live warning screens/scoped axe passed. Frontend сначала проверен с old API, затем оба ux-targets171 развёрнуты;backend/PG healthy. DB/role/container удалены. Следующий172 — исправить синтезированный журнал monitoring: raw sample readers ещё игнорируют disabled/freshness и Date needs positive SQL verification;12.5 открыт.
|
||||
|
||||
Подробный объём, протоколы, схема и ограничения: [12.5.77 в журнале](ux-ui-progress.md).
|
||||
|
||||
@@ -1908,3 +1908,25 @@ Readonly production PG builder подтвердил1 группу одинако
|
||||
Протоколы: [SQL16/cleanup](ux-ui/acceptance/collector-sources-sql-2026-10-12.json), [source433](ux-ui/acceptance/collector-sources-source-2026-10-12.json), [compiled1008](ux-ui/acceptance/collector-sources-compiled-2026-10-12.json), [readonly production](ux-ui/acceptance/collector-sources-production-2026-10-12.json), [runtime](ux-ui/acceptance/collector-sources-runtime-2026-10-12.json), [API/UI](ux-ui/acceptance/collector-sources-deployed-2026-10-12.json).
|
||||
|
||||
Следующий171 — понятная диагностика неоднозначных/отключённых целей оповещений: silent suppression безопасен для engine, но пользователь должен понимать причину отсутствия события. Миграция name-based rules на stable ID, scale/multi-instance/physical WAN/внешние Telegram/Portal/client OS остаются отдельными условиями12.5. Локальный коммит без push.
|
||||
|
||||
## 12.5.171 — понятные причины недоступности целей оповещений (2026-10-12)
|
||||
|
||||
При conservative suppression170 пользователь должен понимать, почему событие не подтверждается. Добавлен чистый collectAlertTargetIssues и optional public meta.targetIssues: kind server/traffic/probe, подпись и причина ambiguous/disabled-object/disabled-source. Диагностика строится из текущих настроек внутри того же repeatable read/read only transaction, что основной overview; getters defaults не создают. Учитываются dedicated source switches, enabled REST как разрешённый availability source по конфигурации, enabled устройства/пробы, duplicate labels и fallback имени на host. Это диагностика конфигурации; она не заявляет свежесть измерений, online устройств или фактическую доставку Telegram. Поля credentials не выбираются и не публикуются. Engine правила, target representation и права не изменены.
|
||||
|
||||
На /alerts добавлен существующий ReUI/shadcn Alert warning в base-nova/Frame композиции без изменения vendor files/theme. Строки доступности и трафика одной цели объединены; причина и дальнейшее действие читаются вместе. Длинный список раскрывается native details/summary клавиатурой, большой список изначально свёрнут; длинные подписи переносятся на телефоне. Ссылки ведут к серверам, пробам и сбору данных. При устранении причины warning исчезает после подтверждённого обновления; legacy API без optional поля не подменяется догадкой. Имена двух CHR и targets действующих правил автоматически не переписывались.
|
||||
|
||||
| Эталон | Доработка | Проверка |
|
||||
| --- | --- | --- |
|
||||
| Существующие Alert и Frame проекта | Подпись → причина → понятный переход → обновление | [ПК1440](ux-ui/screens/alerts-target-issues-1440-2026-10-12.png), [телефон320](ux-ui/screens/alerts-target-issues-320-2026-10-12.png) |
|
||||
|
||||
Локально прошли12 скриптов test:alerts-telegram (unit/transaction/config/queue/HTTP/JWT), application-source TypeScript, backend build и ESLint. Полный root tsc сначала захватил ignored tmp с давними test/build artifacts и выдал их ошибки; source check с исключением tmp/backend/packages прошёл, contracts/backend проверены собственной сборкой. Root tsconfig не менялся.42 unique E2E:22 readers/targets/pickers/rules/new diagnostics за44.7s;19 history/outbox/Telegram за30.6s;1 новый keyboard/long-label mobile за6.2s. Реальная отправка Telegram отсутствовала: HTTP пути подменены тестовыми receipts. Последняя проверка хронологии старым check-progress168 ожидала прежний header168; сам сохранённый historical slice имел12 dated blocks/35 существующих evidence links. Это устаревшее ожидание helper, не удаление журнала; актуальная сверка обновлена отдельно.
|
||||
|
||||
Owned Node22/PostgreSQL18.6 profile passed19540ms:16 source SQL cases плюс проверки actual getAlertsMeta (known empty, disabled-source reasons, ambiguous server/traffic kinds),8 GRE/BGP SQL rows и actual stability;PG unavailable skips0. Временная own DB/ограниченная роль mm_accept_166_1791794042576 и test container удалены. Main fixtures/RouterOS writes/messages0. Обе production сборки прошли; source434 backend+518 frontend inputs и1012 emitted backend/contracts files совпали SHA256 (text CRLF→LF, binary unchanged). Полную UI1260/context221/backend24 regression без новой причины не повторяли.
|
||||
|
||||
Развёртывание staged: frontend ux-targets171/version1.0.0-test.targets171 сначала принял реальный прежний API ux-sources170 без targetIssues; затем обновлён backend ux-targets171. На старом cached frontend строгий прежний DTO не знает additive поле: после обновления backend следует перезагрузить страницу для получения совместимого frontend. Менялись только image tags в existing.env; rollback tags сохранены отдельно без копирования secret env. Backend/PG healthy; frontend running/HTTP/browser подтверждён, отдельного healthcheck нет. Запущенные imageIds совпадают с проверенными образами; после cleanup3 основных running containers.
|
||||
|
||||
2 real UI1440/320 показали настоящий CHR warning:1 сгруппированная подпись,2 affected kinds, ссылка /servers, overflow/pageerrors/API writes0. Scoped axe нового блока WCAG2A/AA violations0, full WCAG certification не заявляется. Снимки просмотрены; исходные пользовательские GRE/VXLAN images сохранены без staging/revert. Настройки/имена/пароли CHR и исходные правила не менялись.
|
||||
|
||||
Протоколы: [SQL/cleanup](ux-ui/acceptance/alert-target-issues-sql-2026-10-12.json), [source](ux-ui/acceptance/alert-target-issues-source-2026-10-12.json), [compiled](ux-ui/acceptance/alert-target-issues-compiled-2026-10-12.json), [interop](ux-ui/acceptance/alert-target-issues-interop-2026-10-12.json), [runtime](ux-ui/acceptance/alert-target-issues-runtime-2026-10-12.json), [live UI](ux-ui/acceptance/alert-target-issues-deployed-2026-10-12.json).
|
||||
|
||||
Следующий172 — отдельная обнаруженная ветка: синтезированный monitoring history в alerts-service читает raw latest samples, пока не применяет enabled/freshness/current-error guards170 и требует проверки реального PG timestamp→public string на positive sample. Source finding не является уже выполненным исправлением или доказанной runtime ошибкой. Stable-ID target migration, отсутствующие цели удалённых правил, physical WAN/scale/multi-instance и реальные Telegram/Portal/client OS остаются отдельными условиями12.5. Локальный коммит без push.
|
||||
+3
-3
@@ -1,12 +1,12 @@
|
||||
# План реализации UX/UI по пятичасовым окнам
|
||||
|
||||
Актуальное продолжение 2026-10-12: завершён12.5.170 — source enabled/current/unknown/freshness/per-probe guards, включая169. Own PG16 source cases +GRE/BGP8/stability passed; temporary DB/role/container removed. Source433/compiled1008 parity; backend ux-sources170 healthy, frontend ux-bfd158 прежний;6 real UI/4 API reads passed, оба CHR доступны. Одноимённые server/traffic targets подавлены, имена/пароли не менялись. Следующий171 — показать неоднозначные/отключённые цели оповещений в UI и объяснить отсутствие подтверждённого сигнала;12.5 открыт.
|
||||
Актуальное продолжение 2026-10-12: завершён12.5.171 — объяснение неоднозначных/отключённых целей в оповещениях, ReUI Alert без vendor changes. Backend profile/42E2E/ownPG16+metadata/compiled1012/source434+518 passed;2 live warning screens/scoped axe passed. Frontend сначала проверен с old API, затем оба ux-targets171 развёрнуты;backend/PG healthy. DB/role/container удалены. Следующий172 — исправить синтезированный журнал monitoring: raw sample readers ещё игнорируют disabled/freshness и Date needs positive SQL verification;12.5 открыт.
|
||||
|
||||
Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-12. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат.
|
||||
|
||||
## Актуальная точка продолжения — 2026-10-12
|
||||
|
||||
Актуальное продолжение 2026-10-12: завершён12.5.170 — source enabled/current/unknown/freshness/per-probe guards, включая169. Own PG16 source cases +GRE/BGP8/stability passed; temporary DB/role/container removed. Source433/compiled1008 parity; backend ux-sources170 healthy, frontend ux-bfd158 прежний;6 real UI/4 API reads passed, оба CHR доступны. Одноимённые server/traffic targets подавлены, имена/пароли не менялись. Следующий171 — показать неоднозначные/отключённые цели оповещений в UI и объяснить отсутствие подтверждённого сигнала;12.5 открыт.
|
||||
Актуальное продолжение 2026-10-12: завершён12.5.171 — объяснение неоднозначных/отключённых целей в оповещениях, ReUI Alert без vendor changes. Backend profile/42E2E/ownPG16+metadata/compiled1012/source434+518 passed;2 live warning screens/scoped axe passed. Frontend сначала проверен с old API, затем оба ux-targets171 развёрнуты;backend/PG healthy. DB/role/container удалены. Следующий172 — исправить синтезированный журнал monitoring: raw sample readers ещё игнорируют disabled/freshness и Date needs positive SQL verification;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.170 — source enabled/current/unknown/freshness/per-probe guards, включая169. Own PG16 source cases +GRE/BGP8/stability passed; temporary DB/role/container removed. Source433/compiled1008 parity; backend ux-sources170 healthy, frontend ux-bfd158 прежний;6 real UI/4 API reads passed, оба CHR доступны. Одноимённые server/traffic targets подавлены, имена/пароли не менялись. Следующий171 — показать неоднозначные/отключённые цели оповещений в UI и объяснить отсутствие подтверждённого сигнала;12.5 открыт.
|
||||
Актуальное продолжение 2026-10-12: завершён12.5.171 — объяснение неоднозначных/отключённых целей в оповещениях, ReUI Alert без vendor changes. Backend profile/42E2E/ownPG16+metadata/compiled1012/source434+518 passed;2 live warning screens/scoped axe passed. Frontend сначала проверен с old API, затем оба ux-targets171 развёрнуты;backend/PG healthy. DB/role/container удалены. Следующий172 — исправить синтезированный журнал monitoring: raw sample readers ещё игнорируют disabled/freshness и Date needs positive SQL verification;12.5 открыт.
|
||||
|
||||
Подробный объём, протоколы, схема и ограничения: [12.5.77 в журнале](ux-ui-progress.md).
|
||||
+23
-1
@@ -1,6 +1,6 @@
|
||||
# UX/UI: журнал реализации
|
||||
|
||||
Актуальное продолжение 2026-10-12: завершён12.5.170 — source enabled/current/unknown/freshness/per-probe guards, включая169. Own PG16 source cases +GRE/BGP8/stability passed; temporary DB/role/container removed. Source433/compiled1008 parity; backend ux-sources170 healthy, frontend ux-bfd158 прежний;6 real UI/4 API reads passed, оба CHR доступны. Одноимённые server/traffic targets подавлены, имена/пароли не менялись. Следующий171 — показать неоднозначные/отключённые цели оповещений в UI и объяснить отсутствие подтверждённого сигнала;12.5 открыт.
|
||||
Актуальное продолжение 2026-10-12: завершён12.5.171 — объяснение неоднозначных/отключённых целей в оповещениях, ReUI Alert без vendor changes. Backend profile/42E2E/ownPG16+metadata/compiled1012/source434+518 passed;2 live warning screens/scoped axe passed. Frontend сначала проверен с old API, затем оба ux-targets171 развёрнуты;backend/PG healthy. DB/role/container удалены. Следующий172 — исправить синтезированный журнал monitoring: raw sample readers ещё игнорируют disabled/freshness и Date needs positive SQL verification;12.5 открыт.
|
||||
|
||||
Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md).
|
||||
|
||||
@@ -38,6 +38,28 @@
|
||||
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 |
|
||||
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–168 и ограничения ниже. Runtime backend ux-month149/frontend ux-sessions150. |
|
||||
|
||||
## 12.5.171 — понятные причины недоступности целей оповещений (2026-10-12)
|
||||
|
||||
При conservative suppression170 пользователь должен понимать, почему событие не подтверждается. Добавлен чистый collectAlertTargetIssues и optional public meta.targetIssues: kind server/traffic/probe, подпись и причина ambiguous/disabled-object/disabled-source. Диагностика строится из текущих настроек внутри того же repeatable read/read only transaction, что основной overview; getters defaults не создают. Учитываются dedicated source switches, enabled REST как разрешённый availability source по конфигурации, enabled устройства/пробы, duplicate labels и fallback имени на host. Это диагностика конфигурации; она не заявляет свежесть измерений, online устройств или фактическую доставку Telegram. Поля credentials не выбираются и не публикуются. Engine правила, target representation и права не изменены.
|
||||
|
||||
На /alerts добавлен существующий ReUI/shadcn Alert warning в base-nova/Frame композиции без изменения vendor files/theme. Строки доступности и трафика одной цели объединены; причина и дальнейшее действие читаются вместе. Длинный список раскрывается native details/summary клавиатурой, большой список изначально свёрнут; длинные подписи переносятся на телефоне. Ссылки ведут к серверам, пробам и сбору данных. При устранении причины warning исчезает после подтверждённого обновления; legacy API без optional поля не подменяется догадкой. Имена двух CHR и targets действующих правил автоматически не переписывались.
|
||||
|
||||
| Эталон | Доработка | Проверка |
|
||||
| --- | --- | --- |
|
||||
| Существующие Alert и Frame проекта | Подпись → причина → понятный переход → обновление | [ПК1440](ux-ui/screens/alerts-target-issues-1440-2026-10-12.png), [телефон320](ux-ui/screens/alerts-target-issues-320-2026-10-12.png) |
|
||||
|
||||
Локально прошли12 скриптов test:alerts-telegram (unit/transaction/config/queue/HTTP/JWT), application-source TypeScript, backend build и ESLint. Полный root tsc сначала захватил ignored tmp с давними test/build artifacts и выдал их ошибки; source check с исключением tmp/backend/packages прошёл, contracts/backend проверены собственной сборкой. Root tsconfig не менялся.42 unique E2E:22 readers/targets/pickers/rules/new diagnostics за44.7s;19 history/outbox/Telegram за30.6s;1 новый keyboard/long-label mobile за6.2s. Реальная отправка Telegram отсутствовала: HTTP пути подменены тестовыми receipts. Последняя проверка хронологии старым check-progress168 ожидала прежний header168; сам сохранённый historical slice имел12 dated blocks/35 существующих evidence links. Это устаревшее ожидание helper, не удаление журнала; актуальная сверка обновлена отдельно.
|
||||
|
||||
Owned Node22/PostgreSQL18.6 profile passed19540ms:16 source SQL cases плюс проверки actual getAlertsMeta (known empty, disabled-source reasons, ambiguous server/traffic kinds),8 GRE/BGP SQL rows и actual stability;PG unavailable skips0. Временная own DB/ограниченная роль mm_accept_166_1791794042576 и test container удалены. Main fixtures/RouterOS writes/messages0. Обе production сборки прошли; source434 backend+518 frontend inputs и1012 emitted backend/contracts files совпали SHA256 (text CRLF→LF, binary unchanged). Полную UI1260/context221/backend24 regression без новой причины не повторяли.
|
||||
|
||||
Развёртывание staged: frontend ux-targets171/version1.0.0-test.targets171 сначала принял реальный прежний API ux-sources170 без targetIssues; затем обновлён backend ux-targets171. На старом cached frontend строгий прежний DTO не знает additive поле: после обновления backend следует перезагрузить страницу для получения совместимого frontend. Менялись только image tags в existing.env; rollback tags сохранены отдельно без копирования secret env. Backend/PG healthy; frontend running/HTTP/browser подтверждён, отдельного healthcheck нет. Запущенные imageIds совпадают с проверенными образами; после cleanup3 основных running containers.
|
||||
|
||||
2 real UI1440/320 показали настоящий CHR warning:1 сгруппированная подпись,2 affected kinds, ссылка /servers, overflow/pageerrors/API writes0. Scoped axe нового блока WCAG2A/AA violations0, full WCAG certification не заявляется. Снимки просмотрены; исходные пользовательские GRE/VXLAN images сохранены без staging/revert. Настройки/имена/пароли CHR и исходные правила не менялись.
|
||||
|
||||
Протоколы: [SQL/cleanup](ux-ui/acceptance/alert-target-issues-sql-2026-10-12.json), [source](ux-ui/acceptance/alert-target-issues-source-2026-10-12.json), [compiled](ux-ui/acceptance/alert-target-issues-compiled-2026-10-12.json), [interop](ux-ui/acceptance/alert-target-issues-interop-2026-10-12.json), [runtime](ux-ui/acceptance/alert-target-issues-runtime-2026-10-12.json), [live UI](ux-ui/acceptance/alert-target-issues-deployed-2026-10-12.json).
|
||||
|
||||
Следующий172 — отдельная обнаруженная ветка: синтезированный monitoring history в alerts-service читает raw latest samples, пока не применяет enabled/freshness/current-error guards170 и требует проверки реального PG timestamp→public string на positive sample. Source finding не является уже выполненным исправлением или доказанной runtime ошибкой. Stable-ID target migration, отсутствующие цели удалённых правил, physical WAN/scale/multi-instance и реальные Telegram/Portal/client OS остаются отдельными условиями12.5. Локальный коммит без push.
|
||||
|
||||
## 12.5.170 — достоверность серверных, пробных и трафиковых сигналов (2026-10-12)
|
||||
|
||||
Завершён оставшийся объём169 в одном coherent backend блоке. Новый collector-source-signals отделяет чтение от чистого построения фактов. Builder читает текущие настройки и identity без seeding getters и без credentials: resourcesEnabled/pingEnabled имеют тот же fallback на legacy enabled, что scheduler; REST/traffic используют собственные enabled. Выбираются последние завершённые ok/error runs, включая NULL/error JSON, вместо only-ok history. Текущие server/traffic keys берутся только из newest run: empty/error/malformed/unknown не восстанавливают старую цель. Частичная ошибочная коллекция сохраняет известные строки других устройств. ServerID/name/host/enabled/duplicate guards169 сохранены; пустое имя может использовать host, как collector. producer sample и completion проверяются по3 effective periods, future tolerance1 period; минимумы5s resources/traffic и10s REST повторяют scheduler. Resource неизвестное значение не становится offline; Traffic требует ok=true и конечные неотрицательные RX/TX, отсутствующее/ошибка не становится0. Подтверждённый нулевой трафик остаётся0.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Оставшаяся внешняя приёмка — 2026-10-12
|
||||
|
||||
Это актуальная матрица после 12.5.170. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
|
||||
Это актуальная матрица после 12.5.171. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
|
||||
|
||||
Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-sources170`, frontend `ux-bfd158`; production lab использует AUTH_REQUIRED=false. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись.
|
||||
Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-targets171`, frontend `ux-targets171`; 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 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; synthesized monitoring history guards172 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). Невыполненные пункты не являются все блокерами данного лабораторного сценария: для выпуска необходимо явно определить требуемые конфигурации и принять их либо согласовать ограничение.
|
||||
|
||||
@@ -823,3 +823,25 @@ Readonly production PG builder подтвердил1 группу одинако
|
||||
Протоколы: [SQL16/cleanup](ux-ui/acceptance/collector-sources-sql-2026-10-12.json), [source433](ux-ui/acceptance/collector-sources-source-2026-10-12.json), [compiled1008](ux-ui/acceptance/collector-sources-compiled-2026-10-12.json), [readonly production](ux-ui/acceptance/collector-sources-production-2026-10-12.json), [runtime](ux-ui/acceptance/collector-sources-runtime-2026-10-12.json), [API/UI](ux-ui/acceptance/collector-sources-deployed-2026-10-12.json).
|
||||
|
||||
Следующий171 — понятная диагностика неоднозначных/отключённых целей оповещений: silent suppression безопасен для engine, но пользователь должен понимать причину отсутствия события. Миграция name-based rules на stable ID, scale/multi-instance/physical WAN/внешние Telegram/Portal/client OS остаются отдельными условиями12.5. Локальный коммит без push.
|
||||
|
||||
## 12.5.171 — понятные причины недоступности целей оповещений (2026-10-12)
|
||||
|
||||
При conservative suppression170 пользователь должен понимать, почему событие не подтверждается. Добавлен чистый collectAlertTargetIssues и optional public meta.targetIssues: kind server/traffic/probe, подпись и причина ambiguous/disabled-object/disabled-source. Диагностика строится из текущих настроек внутри того же repeatable read/read only transaction, что основной overview; getters defaults не создают. Учитываются dedicated source switches, enabled REST как разрешённый availability source по конфигурации, enabled устройства/пробы, duplicate labels и fallback имени на host. Это диагностика конфигурации; она не заявляет свежесть измерений, online устройств или фактическую доставку Telegram. Поля credentials не выбираются и не публикуются. Engine правила, target representation и права не изменены.
|
||||
|
||||
На /alerts добавлен существующий ReUI/shadcn Alert warning в base-nova/Frame композиции без изменения vendor files/theme. Строки доступности и трафика одной цели объединены; причина и дальнейшее действие читаются вместе. Длинный список раскрывается native details/summary клавиатурой, большой список изначально свёрнут; длинные подписи переносятся на телефоне. Ссылки ведут к серверам, пробам и сбору данных. При устранении причины warning исчезает после подтверждённого обновления; legacy API без optional поля не подменяется догадкой. Имена двух CHR и targets действующих правил автоматически не переписывались.
|
||||
|
||||
| Эталон | Доработка | Проверка |
|
||||
| --- | --- | --- |
|
||||
| Существующие Alert и Frame проекта | Подпись → причина → понятный переход → обновление | [ПК1440](ux-ui/screens/alerts-target-issues-1440-2026-10-12.png), [телефон320](ux-ui/screens/alerts-target-issues-320-2026-10-12.png) |
|
||||
|
||||
Локально прошли12 скриптов test:alerts-telegram (unit/transaction/config/queue/HTTP/JWT), application-source TypeScript, backend build и ESLint. Полный root tsc сначала захватил ignored tmp с давними test/build artifacts и выдал их ошибки; source check с исключением tmp/backend/packages прошёл, contracts/backend проверены собственной сборкой. Root tsconfig не менялся.42 unique E2E:22 readers/targets/pickers/rules/new diagnostics за44.7s;19 history/outbox/Telegram за30.6s;1 новый keyboard/long-label mobile за6.2s. Реальная отправка Telegram отсутствовала: HTTP пути подменены тестовыми receipts. Последняя проверка хронологии старым check-progress168 ожидала прежний header168; сам сохранённый historical slice имел12 dated blocks/35 существующих evidence links. Это устаревшее ожидание helper, не удаление журнала; актуальная сверка обновлена отдельно.
|
||||
|
||||
Owned Node22/PostgreSQL18.6 profile passed19540ms:16 source SQL cases плюс проверки actual getAlertsMeta (known empty, disabled-source reasons, ambiguous server/traffic kinds),8 GRE/BGP SQL rows и actual stability;PG unavailable skips0. Временная own DB/ограниченная роль mm_accept_166_1791794042576 и test container удалены. Main fixtures/RouterOS writes/messages0. Обе production сборки прошли; source434 backend+518 frontend inputs и1012 emitted backend/contracts files совпали SHA256 (text CRLF→LF, binary unchanged). Полную UI1260/context221/backend24 regression без новой причины не повторяли.
|
||||
|
||||
Развёртывание staged: frontend ux-targets171/version1.0.0-test.targets171 сначала принял реальный прежний API ux-sources170 без targetIssues; затем обновлён backend ux-targets171. На старом cached frontend строгий прежний DTO не знает additive поле: после обновления backend следует перезагрузить страницу для получения совместимого frontend. Менялись только image tags в existing.env; rollback tags сохранены отдельно без копирования secret env. Backend/PG healthy; frontend running/HTTP/browser подтверждён, отдельного healthcheck нет. Запущенные imageIds совпадают с проверенными образами; после cleanup3 основных running containers.
|
||||
|
||||
2 real UI1440/320 показали настоящий CHR warning:1 сгруппированная подпись,2 affected kinds, ссылка /servers, overflow/pageerrors/API writes0. Scoped axe нового блока WCAG2A/AA violations0, full WCAG certification не заявляется. Снимки просмотрены; исходные пользовательские GRE/VXLAN images сохранены без staging/revert. Настройки/имена/пароли CHR и исходные правила не менялись.
|
||||
|
||||
Протоколы: [SQL/cleanup](ux-ui/acceptance/alert-target-issues-sql-2026-10-12.json), [source](ux-ui/acceptance/alert-target-issues-source-2026-10-12.json), [compiled](ux-ui/acceptance/alert-target-issues-compiled-2026-10-12.json), [interop](ux-ui/acceptance/alert-target-issues-interop-2026-10-12.json), [runtime](ux-ui/acceptance/alert-target-issues-runtime-2026-10-12.json), [live UI](ux-ui/acceptance/alert-target-issues-deployed-2026-10-12.json).
|
||||
|
||||
Следующий172 — отдельная обнаруженная ветка: синтезированный monitoring history в alerts-service читает raw latest samples, пока не применяет enabled/freshness/current-error guards170 и требует проверки реального PG timestamp→public string на positive sample. Source finding не является уже выполненным исправлением или доказанной runtime ошибкой. Stable-ID target migration, отсутствующие цели удалённых правил, physical WAN/scale/multi-instance и реальные Telegram/Portal/client OS остаются отдельными условиями12.5. Локальный коммит без push.
|
||||
+23
-1
@@ -1,6 +1,6 @@
|
||||
# Этап 12: итоговая приёмка UX/UI
|
||||
|
||||
Актуальное продолжение 2026-10-12: завершён12.5.170 — source enabled/current/unknown/freshness/per-probe guards, включая169. Own PG16 source cases +GRE/BGP8/stability passed; temporary DB/role/container removed. Source433/compiled1008 parity; backend ux-sources170 healthy, frontend ux-bfd158 прежний;6 real UI/4 API reads passed, оба CHR доступны. Одноимённые server/traffic targets подавлены, имена/пароли не менялись. Следующий171 — показать неоднозначные/отключённые цели оповещений в UI и объяснить отсутствие подтверждённого сигнала;12.5 открыт.
|
||||
Актуальное продолжение 2026-10-12: завершён12.5.171 — объяснение неоднозначных/отключённых целей в оповещениях, ReUI Alert без vendor changes. Backend profile/42E2E/ownPG16+metadata/compiled1012/source434+518 passed;2 live warning screens/scoped axe passed. Frontend сначала проверен с old API, затем оба ux-targets171 развёрнуты;backend/PG healthy. DB/role/container удалены. Следующий172 — исправить синтезированный журнал monitoring: raw sample readers ещё игнорируют disabled/freshness и Date needs positive SQL verification;12.5 открыт.
|
||||
|
||||
План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push.
|
||||
|
||||
@@ -3213,3 +3213,25 @@ Readonly production PG builder подтвердил1 группу одинако
|
||||
Протоколы: [SQL16/cleanup](ux-ui/acceptance/collector-sources-sql-2026-10-12.json), [source433](ux-ui/acceptance/collector-sources-source-2026-10-12.json), [compiled1008](ux-ui/acceptance/collector-sources-compiled-2026-10-12.json), [readonly production](ux-ui/acceptance/collector-sources-production-2026-10-12.json), [runtime](ux-ui/acceptance/collector-sources-runtime-2026-10-12.json), [API/UI](ux-ui/acceptance/collector-sources-deployed-2026-10-12.json).
|
||||
|
||||
Следующий171 — понятная диагностика неоднозначных/отключённых целей оповещений: silent suppression безопасен для engine, но пользователь должен понимать причину отсутствия события. Миграция name-based rules на stable ID, scale/multi-instance/physical WAN/внешние Telegram/Portal/client OS остаются отдельными условиями12.5. Локальный коммит без push.
|
||||
|
||||
## 12.5.171 — понятные причины недоступности целей оповещений (2026-10-12)
|
||||
|
||||
При conservative suppression170 пользователь должен понимать, почему событие не подтверждается. Добавлен чистый collectAlertTargetIssues и optional public meta.targetIssues: kind server/traffic/probe, подпись и причина ambiguous/disabled-object/disabled-source. Диагностика строится из текущих настроек внутри того же repeatable read/read only transaction, что основной overview; getters defaults не создают. Учитываются dedicated source switches, enabled REST как разрешённый availability source по конфигурации, enabled устройства/пробы, duplicate labels и fallback имени на host. Это диагностика конфигурации; она не заявляет свежесть измерений, online устройств или фактическую доставку Telegram. Поля credentials не выбираются и не публикуются. Engine правила, target representation и права не изменены.
|
||||
|
||||
На /alerts добавлен существующий ReUI/shadcn Alert warning в base-nova/Frame композиции без изменения vendor files/theme. Строки доступности и трафика одной цели объединены; причина и дальнейшее действие читаются вместе. Длинный список раскрывается native details/summary клавиатурой, большой список изначально свёрнут; длинные подписи переносятся на телефоне. Ссылки ведут к серверам, пробам и сбору данных. При устранении причины warning исчезает после подтверждённого обновления; legacy API без optional поля не подменяется догадкой. Имена двух CHR и targets действующих правил автоматически не переписывались.
|
||||
|
||||
| Эталон | Доработка | Проверка |
|
||||
| --- | --- | --- |
|
||||
| Существующие Alert и Frame проекта | Подпись → причина → понятный переход → обновление | [ПК1440](ux-ui/screens/alerts-target-issues-1440-2026-10-12.png), [телефон320](ux-ui/screens/alerts-target-issues-320-2026-10-12.png) |
|
||||
|
||||
Локально прошли12 скриптов test:alerts-telegram (unit/transaction/config/queue/HTTP/JWT), application-source TypeScript, backend build и ESLint. Полный root tsc сначала захватил ignored tmp с давними test/build artifacts и выдал их ошибки; source check с исключением tmp/backend/packages прошёл, contracts/backend проверены собственной сборкой. Root tsconfig не менялся.42 unique E2E:22 readers/targets/pickers/rules/new diagnostics за44.7s;19 history/outbox/Telegram за30.6s;1 новый keyboard/long-label mobile за6.2s. Реальная отправка Telegram отсутствовала: HTTP пути подменены тестовыми receipts. Последняя проверка хронологии старым check-progress168 ожидала прежний header168; сам сохранённый historical slice имел12 dated blocks/35 существующих evidence links. Это устаревшее ожидание helper, не удаление журнала; актуальная сверка обновлена отдельно.
|
||||
|
||||
Owned Node22/PostgreSQL18.6 profile passed19540ms:16 source SQL cases плюс проверки actual getAlertsMeta (known empty, disabled-source reasons, ambiguous server/traffic kinds),8 GRE/BGP SQL rows и actual stability;PG unavailable skips0. Временная own DB/ограниченная роль mm_accept_166_1791794042576 и test container удалены. Main fixtures/RouterOS writes/messages0. Обе production сборки прошли; source434 backend+518 frontend inputs и1012 emitted backend/contracts files совпали SHA256 (text CRLF→LF, binary unchanged). Полную UI1260/context221/backend24 regression без новой причины не повторяли.
|
||||
|
||||
Развёртывание staged: frontend ux-targets171/version1.0.0-test.targets171 сначала принял реальный прежний API ux-sources170 без targetIssues; затем обновлён backend ux-targets171. На старом cached frontend строгий прежний DTO не знает additive поле: после обновления backend следует перезагрузить страницу для получения совместимого frontend. Менялись только image tags в existing.env; rollback tags сохранены отдельно без копирования secret env. Backend/PG healthy; frontend running/HTTP/browser подтверждён, отдельного healthcheck нет. Запущенные imageIds совпадают с проверенными образами; после cleanup3 основных running containers.
|
||||
|
||||
2 real UI1440/320 показали настоящий CHR warning:1 сгруппированная подпись,2 affected kinds, ссылка /servers, overflow/pageerrors/API writes0. Scoped axe нового блока WCAG2A/AA violations0, full WCAG certification не заявляется. Снимки просмотрены; исходные пользовательские GRE/VXLAN images сохранены без staging/revert. Настройки/имена/пароли CHR и исходные правила не менялись.
|
||||
|
||||
Протоколы: [SQL/cleanup](ux-ui/acceptance/alert-target-issues-sql-2026-10-12.json), [source](ux-ui/acceptance/alert-target-issues-source-2026-10-12.json), [compiled](ux-ui/acceptance/alert-target-issues-compiled-2026-10-12.json), [interop](ux-ui/acceptance/alert-target-issues-interop-2026-10-12.json), [runtime](ux-ui/acceptance/alert-target-issues-runtime-2026-10-12.json), [live UI](ux-ui/acceptance/alert-target-issues-deployed-2026-10-12.json).
|
||||
|
||||
Следующий172 — отдельная обнаруженная ветка: синтезированный monitoring history в alerts-service читает raw latest samples, пока не применяет enabled/freshness/current-error guards170 и требует проверки реального PG timestamp→public string на positive sample. Source finding не является уже выполненным исправлением или доказанной runtime ошибкой. Stable-ID target migration, отсутствующие цели удалённых правил, physical WAN/scale/multi-instance и реальные Telegram/Portal/client OS остаются отдельными условиями12.5. Локальный коммит без push.
|
||||
@@ -1,6 +1,6 @@
|
||||
# Тестовый сервер UX/UI — 2026-10-10
|
||||
|
||||
Сервер161.104.46.222: завершён12.5.170 — source enabled/current/unknown/freshness/per-probe guards, включая169. Own PG16 source cases +GRE/BGP8/stability passed; temporary DB/role/container removed. Source433/compiled1008 parity; backend ux-sources170 healthy, frontend ux-bfd158 прежний;6 real UI/4 API reads passed, оба CHR доступны. Одноимённые server/traffic targets подавлены, имена/пароли не менялись. Следующий171 — показать неоднозначные/отключённые цели оповещений в UI и объяснить отсутствие подтверждённого сигнала;12.5 открыт.
|
||||
Сервер161.104.46.222: завершён12.5.171 — объяснение неоднозначных/отключённых целей в оповещениях, ReUI Alert без vendor changes. Backend profile/42E2E/ownPG16+metadata/compiled1012/source434+518 passed;2 live warning screens/scoped axe passed. Frontend сначала проверен с old API, затем оба ux-targets171 развёрнуты;backend/PG healthy. DB/role/container удалены. Следующий172 — исправить синтезированный журнал monitoring: raw sample readers ещё игнорируют disabled/freshness и Date needs positive SQL verification;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-sources170 и mmapp-test/frontend:ux-bfd158; запущены |
|
||||
| Образы приложения | mmapp-test/backend:ux-targets171 и mmapp-test/frontend:ux-targets171; запущены |
|
||||
|
||||
Compose SHA256:3052bd6ab5d9865f97605cebfb5ff27a4410cd3357b81153a65a87fb7c92582f. OptionalBACKEND_IMAGE_TAG позволяетобновлятьbackendотдельно. Локальный и удалённый файлы совпали. Шаблоны: [Compose](../deploy/docker-compose.test.yml), [env без секретов](../deploy/env.test.example).
|
||||
|
||||
@@ -1639,3 +1639,25 @@ Readonly production PG builder подтвердил1 группу одинако
|
||||
Протоколы: [SQL16/cleanup](ux-ui/acceptance/collector-sources-sql-2026-10-12.json), [source433](ux-ui/acceptance/collector-sources-source-2026-10-12.json), [compiled1008](ux-ui/acceptance/collector-sources-compiled-2026-10-12.json), [readonly production](ux-ui/acceptance/collector-sources-production-2026-10-12.json), [runtime](ux-ui/acceptance/collector-sources-runtime-2026-10-12.json), [API/UI](ux-ui/acceptance/collector-sources-deployed-2026-10-12.json).
|
||||
|
||||
Следующий171 — понятная диагностика неоднозначных/отключённых целей оповещений: silent suppression безопасен для engine, но пользователь должен понимать причину отсутствия события. Миграция name-based rules на stable ID, scale/multi-instance/physical WAN/внешние Telegram/Portal/client OS остаются отдельными условиями12.5. Локальный коммит без push.
|
||||
|
||||
## 12.5.171 — понятные причины недоступности целей оповещений (2026-10-12)
|
||||
|
||||
При conservative suppression170 пользователь должен понимать, почему событие не подтверждается. Добавлен чистый collectAlertTargetIssues и optional public meta.targetIssues: kind server/traffic/probe, подпись и причина ambiguous/disabled-object/disabled-source. Диагностика строится из текущих настроек внутри того же repeatable read/read only transaction, что основной overview; getters defaults не создают. Учитываются dedicated source switches, enabled REST как разрешённый availability source по конфигурации, enabled устройства/пробы, duplicate labels и fallback имени на host. Это диагностика конфигурации; она не заявляет свежесть измерений, online устройств или фактическую доставку Telegram. Поля credentials не выбираются и не публикуются. Engine правила, target representation и права не изменены.
|
||||
|
||||
На /alerts добавлен существующий ReUI/shadcn Alert warning в base-nova/Frame композиции без изменения vendor files/theme. Строки доступности и трафика одной цели объединены; причина и дальнейшее действие читаются вместе. Длинный список раскрывается native details/summary клавиатурой, большой список изначально свёрнут; длинные подписи переносятся на телефоне. Ссылки ведут к серверам, пробам и сбору данных. При устранении причины warning исчезает после подтверждённого обновления; legacy API без optional поля не подменяется догадкой. Имена двух CHR и targets действующих правил автоматически не переписывались.
|
||||
|
||||
| Эталон | Доработка | Проверка |
|
||||
| --- | --- | --- |
|
||||
| Существующие Alert и Frame проекта | Подпись → причина → понятный переход → обновление | [ПК1440](ux-ui/screens/alerts-target-issues-1440-2026-10-12.png), [телефон320](ux-ui/screens/alerts-target-issues-320-2026-10-12.png) |
|
||||
|
||||
Локально прошли12 скриптов test:alerts-telegram (unit/transaction/config/queue/HTTP/JWT), application-source TypeScript, backend build и ESLint. Полный root tsc сначала захватил ignored tmp с давними test/build artifacts и выдал их ошибки; source check с исключением tmp/backend/packages прошёл, contracts/backend проверены собственной сборкой. Root tsconfig не менялся.42 unique E2E:22 readers/targets/pickers/rules/new diagnostics за44.7s;19 history/outbox/Telegram за30.6s;1 новый keyboard/long-label mobile за6.2s. Реальная отправка Telegram отсутствовала: HTTP пути подменены тестовыми receipts. Последняя проверка хронологии старым check-progress168 ожидала прежний header168; сам сохранённый historical slice имел12 dated blocks/35 существующих evidence links. Это устаревшее ожидание helper, не удаление журнала; актуальная сверка обновлена отдельно.
|
||||
|
||||
Owned Node22/PostgreSQL18.6 profile passed19540ms:16 source SQL cases плюс проверки actual getAlertsMeta (known empty, disabled-source reasons, ambiguous server/traffic kinds),8 GRE/BGP SQL rows и actual stability;PG unavailable skips0. Временная own DB/ограниченная роль mm_accept_166_1791794042576 и test container удалены. Main fixtures/RouterOS writes/messages0. Обе production сборки прошли; source434 backend+518 frontend inputs и1012 emitted backend/contracts files совпали SHA256 (text CRLF→LF, binary unchanged). Полную UI1260/context221/backend24 regression без новой причины не повторяли.
|
||||
|
||||
Развёртывание staged: frontend ux-targets171/version1.0.0-test.targets171 сначала принял реальный прежний API ux-sources170 без targetIssues; затем обновлён backend ux-targets171. На старом cached frontend строгий прежний DTO не знает additive поле: после обновления backend следует перезагрузить страницу для получения совместимого frontend. Менялись только image tags в existing.env; rollback tags сохранены отдельно без копирования secret env. Backend/PG healthy; frontend running/HTTP/browser подтверждён, отдельного healthcheck нет. Запущенные imageIds совпадают с проверенными образами; после cleanup3 основных running containers.
|
||||
|
||||
2 real UI1440/320 показали настоящий CHR warning:1 сгруппированная подпись,2 affected kinds, ссылка /servers, overflow/pageerrors/API writes0. Scoped axe нового блока WCAG2A/AA violations0, full WCAG certification не заявляется. Снимки просмотрены; исходные пользовательские GRE/VXLAN images сохранены без staging/revert. Настройки/имена/пароли CHR и исходные правила не менялись.
|
||||
|
||||
Протоколы: [SQL/cleanup](ux-ui/acceptance/alert-target-issues-sql-2026-10-12.json), [source](ux-ui/acceptance/alert-target-issues-source-2026-10-12.json), [compiled](ux-ui/acceptance/alert-target-issues-compiled-2026-10-12.json), [interop](ux-ui/acceptance/alert-target-issues-interop-2026-10-12.json), [runtime](ux-ui/acceptance/alert-target-issues-runtime-2026-10-12.json), [live UI](ux-ui/acceptance/alert-target-issues-deployed-2026-10-12.json).
|
||||
|
||||
Следующий172 — отдельная обнаруженная ветка: синтезированный monitoring history в alerts-service читает raw latest samples, пока не применяет enabled/freshness/current-error guards170 и требует проверки реального PG timestamp→public string на positive sample. Source finding не является уже выполненным исправлением или доказанной runtime ошибкой. Stable-ID target migration, отсутствующие цели удалённых правил, physical WAN/scale/multi-instance и реальные Telegram/Portal/client OS остаются отдельными условиями12.5. Локальный коммит без push.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"date": "2026-10-12",
|
||||
"block": "12.5.171",
|
||||
"runtime": {
|
||||
"backend": "ux-targets171",
|
||||
"frontend": "ux-targets171"
|
||||
},
|
||||
"api200": true,
|
||||
"ambiguousKindCount": 2,
|
||||
"cases": [
|
||||
{
|
||||
"width": 1440,
|
||||
"warningVisible": true,
|
||||
"duplicateGrouped": true,
|
||||
"serverLink": true,
|
||||
"noOverflow": true,
|
||||
"scopedAxeViolations": [],
|
||||
"pageErrors": [],
|
||||
"apiWrites": [],
|
||||
"screenshot": "docs/ux-ui/screens/alerts-target-issues-1440-2026-10-12.png"
|
||||
},
|
||||
{
|
||||
"width": 320,
|
||||
"warningVisible": true,
|
||||
"duplicateGrouped": true,
|
||||
"serverLink": true,
|
||||
"noOverflow": true,
|
||||
"scopedAxeViolations": [],
|
||||
"pageErrors": [],
|
||||
"apiWrites": [],
|
||||
"screenshot": "docs/ux-ui/screens/alerts-target-issues-320-2026-10-12.png"
|
||||
}
|
||||
],
|
||||
"bounds": [
|
||||
"Read-only real duplicate CHR metadata; no rename/native mutation/password change/message",
|
||||
"Scoped axe of new warning is not full WCAG certification",
|
||||
"Disabled configuration covered by owned SQL and API-path UI fixtures; live configuration preserved"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"date": "2026-10-12",
|
||||
"block": "12.5.171",
|
||||
"newFrontend": "ux-targets171",
|
||||
"oldBackend": "ux-sources170",
|
||||
"optionalDiagnosticMissing": true,
|
||||
"readerAccepted": true,
|
||||
"unknownDiagnosticNotInvented": true
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"date": "2026-10-12",
|
||||
"block": "12.5.171",
|
||||
"services": {
|
||||
"backend": {
|
||||
"image": "mmapp-test/backend:ux-targets171",
|
||||
"imageId": "sha256:754b7420ca35657fb566cec97cf37322320787363e1787bb4ce89aa0bfb256ed",
|
||||
"matchedVerifiedImage": true,
|
||||
"state": "running",
|
||||
"health": "healthy"
|
||||
},
|
||||
"frontend": {
|
||||
"image": "mmapp-test/frontend:ux-targets171",
|
||||
"imageId": "sha256:050987e82c1ab5571f4c3dc8692a22f8ba703881f699c802f0168d90ab74652e",
|
||||
"matchedVerifiedImage": true,
|
||||
"state": "running",
|
||||
"health": null
|
||||
}
|
||||
},
|
||||
"rollbackTags": {
|
||||
"MMAPP_IMAGE_TAG": "ux-bfd158",
|
||||
"BACKEND_IMAGE_TAG": "ux-sources170"
|
||||
},
|
||||
"secretEnvCopied": false,
|
||||
"runningContainers": [
|
||||
"mmapp-test-backend",
|
||||
"mmapp-test-frontend",
|
||||
"mmapp-test-postgres"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"date": "2026-10-12",
|
||||
"block": "12.5.171",
|
||||
"matched": {
|
||||
"backend": 434,
|
||||
"frontend": 518
|
||||
},
|
||||
"different": [],
|
||||
"missing": [],
|
||||
"sourceBase": "db7105119d0c9a3b1dee254d8e3d6ac45c97bba0"
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"date": "2026-10-12",
|
||||
"block": "12.5.171",
|
||||
"database": "mm_accept_166_1791794042576",
|
||||
"scope": "ephemeral role and precreated owned database; no copied production credentials",
|
||||
"tests": [
|
||||
{
|
||||
"command": "npm --prefix backend run test:alert-engine",
|
||||
"passed": true,
|
||||
"durationMs": 19540,
|
||||
"ownedHistorySqlRows": 8,
|
||||
"ownedSourceSqlCases": 16,
|
||||
"sqlConfirmStabilityPassed": true,
|
||||
"postgresUnavailableSkip": false
|
||||
}
|
||||
],
|
||||
"scopedRoleVerified": true,
|
||||
"passed": true,
|
||||
"databaseRemoved": true,
|
||||
"roleRemoved": true,
|
||||
"testContainerRemoved": true,
|
||||
"metadataSqlAssertionsPassed": true
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 193 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
@@ -93,9 +93,14 @@ export const alertRuleResponseSchema = z.object({
|
||||
confirmStabilitySec:z.number().int().min(0).max(86400).nullable(),recoveryMode:recoveryModeSchema,recoveryStabilitySec:z.number().int().min(0).max(86400).nullable(),chatId:z.string(),lastFiredAt:alertTimestampSchema.nullable(),
|
||||
}).strict()
|
||||
export const alertHistoryResponseSchema = z.object({id:z.string().min(1),ruleName:z.string(),severity:alertSeveritySchema,message:z.string(),sent:z.boolean(),firedAt:alertTimestampSchema,source:z.enum(["db","live"])}).strict()
|
||||
export const alertTargetIssueSchema = z.object({
|
||||
kind:z.enum(["server","traffic","probe"]),target:z.string().min(1),reason:z.enum(["ambiguous","disabled-object","disabled-source"]),
|
||||
}).strict()
|
||||
export type AlertTargetIssue=z.output<typeof alertTargetIssueSchema>
|
||||
export const alertsMetaResponseSchema = z.object({
|
||||
servers:z.array(z.string()),greClients:z.array(z.string()),probeTargets:z.array(z.string()),rttLossTargets:z.array(z.string()),trafficServers:z.array(z.string()),
|
||||
serversDetail:z.array(z.object({name:z.string(),host:z.string(),site:z.string(),country:z.string(),type:z.string()}).strict()),
|
||||
targetIssues:z.array(alertTargetIssueSchema).optional(),
|
||||
}).strict()
|
||||
const alertsConfigResponseShape = {groups:z.array(alertGroupSchema.strict()),rules:z.array(alertRuleResponseSchema)}
|
||||
function validConfig(value:{groups:{id:string}[];rules:{id:string;groupId:string|null}[]}):boolean {
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import {test,expect,type Page} from "@playwright/test"
|
||||
import {emptyAlerts,emptyOutbox} from "./fixtures/alerts"
|
||||
import type {AlertTargetIssue} from "@mmapp/contracts/alerts"
|
||||
async function setup(page:Page,reply:()=>unknown){
|
||||
await page.route("**/health",r=>r.fulfill({json:{status:"ok"}}))
|
||||
await page.route("**/api/**",r=>{const path=new URL(r.request().url()).pathname
|
||||
if(path==="/api/auth/config")return r.fulfill({json:{required:false}})
|
||||
if(path==="/api/alerts")return r.fulfill({json:reply()})
|
||||
if(path==="/api/alerts/outbox")return r.fulfill({json:emptyOutbox})
|
||||
if(path==="/api/servers"||path==="/api/bgp/sessions")return r.fulfill({json:[]})
|
||||
if(path==="/api/filters/gre-tunnels")return r.fulfill({json:{tunnels:[]}})
|
||||
return r.fulfill({status:503,json:{error:"outside diagnostic fixture"}})
|
||||
})
|
||||
await page.goto("/alerts")
|
||||
}
|
||||
for(const width of[1440,320])test(`Alerts targets: ambiguity is explained and grouped at ${width}`,async({page})=>{
|
||||
await page.setViewportSize({width,height:1000})
|
||||
const issues:AlertTargetIssue[]=[{kind:"server",target:"CHR",reason:"ambiguous"},{kind:"traffic",target:"CHR",reason:"ambiguous"}]
|
||||
let current=issues
|
||||
await setup(page,()=>({...emptyAlerts,meta:{...emptyAlerts.meta,targetIssues:current}}))
|
||||
const warning=page.getByRole("status",{name:"Проблемы целей оповещений"})
|
||||
await expect(warning).toBeVisible();await expect(warning.getByText("CHR",{exact:true})).toHaveCount(1)
|
||||
await expect(warning).toContainText("Несколько включённых объектов")
|
||||
await expect(warning).toContainText("обновите цели существующих правил")
|
||||
await expect(warning.getByRole("link",{name:"Проверить серверы"})).toHaveAttribute("href","/servers")
|
||||
expect(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth)).toBe(true)
|
||||
current=[];await page.getByRole("button",{name:"Обновить",exact:true}).click();await expect(warning).toHaveCount(0)
|
||||
})
|
||||
test("Alerts targets: disabled source and probe explain the next action",async({page})=>{
|
||||
await setup(page,()=>({...emptyAlerts,meta:{...emptyAlerts.meta,targetIssues:[{kind:"traffic",target:"router",reason:"disabled-source"},{kind:"probe",target:"probe → target",reason:"disabled-object"}]}}))
|
||||
const warning=page.getByRole("status",{name:"Проблемы целей оповещений"})
|
||||
await expect(warning).toContainText("Сбор данных этого типа выключен")
|
||||
await expect(warning).toContainText("Объект или его сервер выключен")
|
||||
await expect(warning.getByRole("link",{name:"Проверить сбор данных"})).toHaveAttribute("href","/data-collection")
|
||||
await expect(warning.getByRole("link",{name:"Проверить пробы"})).toHaveAttribute("href","/uptime")
|
||||
})
|
||||
test("Alerts targets: older valid API does not invent diagnostics",async({page})=>{
|
||||
await setup(page,()=>emptyAlerts)
|
||||
await expect(page.getByText("Всего: 0",{exact:true})).toBeVisible()
|
||||
await expect(page.getByRole("status",{name:"Проблемы целей оповещений"})).toHaveCount(0)
|
||||
})
|
||||
test("Alerts targets: many long labels remain compact and keyboard accessible on phone",async({page})=>{
|
||||
await page.setViewportSize({width:320,height:1000})
|
||||
const targets=Array.from({length:6},(_,index)=>`target-${index}-${"x".repeat(180)}`)
|
||||
await setup(page,()=>({...emptyAlerts,meta:{...emptyAlerts.meta,targetIssues:targets.map(target=>({kind:"server",target,reason:"ambiguous"}))}}))
|
||||
const warning=page.getByRole("status",{name:"Проблемы целей оповещений"})
|
||||
await expect(warning.getByText(targets[0],{exact:true})).toBeHidden()
|
||||
const summary=warning.locator("summary");await summary.focus();await page.keyboard.press("Enter")
|
||||
await expect(warning.getByText(targets[0],{exact:true})).toBeVisible()
|
||||
expect(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth)).toBe(true)
|
||||
await page.keyboard.press("Enter");await expect(warning.getByText(targets[0],{exact:true})).toBeHidden()
|
||||
})
|
||||
Reference in new issue
Block a user