Docker images / prepare-release (push) Successful in 12s
Docker images / backend-test (push) Successful in 4m9s
Docker images / frontend-image (push) Successful in 4m28s
Docker images / updater-image (push) Successful in 58s
Docker images / backend-image (push) Successful in 2m49s
Docker images / notify-webhook (push) Skipped
Docker images / publish-release (push) Successful in 11s
При старте backend накатывает схему PostgreSQL 18 и, если база пустая, один раз импортирует mikrotik.db с тома. Повторный старт не копирует данные. Бэкап в UI идёт через pg_dump. Co-authored-by: Cursor <[email protected]>
81 lines
2.7 KiB
TypeScript
81 lines
2.7 KiB
TypeScript
import { eq } from "drizzle-orm"
|
|
import { db } from "../../db/index.js"
|
|
import { alertEngineConfirmPending } from "../../db/schema.js"
|
|
import type { ApiAlertRule } from "../alerts-service.js"
|
|
import type { RuleEvalHit } from "./types.js"
|
|
|
|
export async function deleteConfirmPending(ruleId: string) {
|
|
await db.delete(alertEngineConfirmPending).where(eq(alertEngineConfirmPending.ruleId, ruleId))
|
|
}
|
|
|
|
/**
|
|
* Обновляет состояние ожидания стабильности и возвращает, можно ли отправлять уведомление в этом тике.
|
|
* При отсутствии срабатывания (`hit == null`) ожидание сбрасывается.
|
|
*/
|
|
export async function stabilityReadyForSend(
|
|
ruleId: string,
|
|
hit: RuleEvalHit | null,
|
|
delaySec: number | null | undefined,
|
|
): Promise<boolean> {
|
|
if (!hit) {
|
|
await deleteConfirmPending(ruleId)
|
|
return false
|
|
}
|
|
const sec = delaySec != null && delaySec > 0 ? Math.floor(delaySec) : 0
|
|
if (sec <= 0) {
|
|
await deleteConfirmPending(ruleId)
|
|
return true
|
|
}
|
|
|
|
const now = Date.now()
|
|
const nowIso = new Date().toISOString()
|
|
const existing = (await db
|
|
.select()
|
|
.from(alertEngineConfirmPending)
|
|
.where(eq(alertEngineConfirmPending.ruleId, ruleId))
|
|
.limit(1))[0]
|
|
|
|
if (!existing || existing.payloadHash !== hit.payloadHash) {
|
|
if (existing) {
|
|
await db.update(alertEngineConfirmPending)
|
|
.set({ payloadHash: hit.payloadHash, sinceAt: nowIso })
|
|
.where(eq(alertEngineConfirmPending.ruleId, ruleId))
|
|
} else {
|
|
await db.insert(alertEngineConfirmPending).values({ ruleId, payloadHash: hit.payloadHash, sinceAt: nowIso })
|
|
}
|
|
return false
|
|
}
|
|
|
|
const t = Date.parse(existing.sinceAt)
|
|
if (!Number.isFinite(t)) return false
|
|
return now - t >= sec * 1000
|
|
}
|
|
|
|
/** После успешной отправки — сбросить таймер стабильности для следующего цикла. */
|
|
export async function clearConfirmPendingAfterSend(ruleIds: string[]) {
|
|
for (const id of ruleIds) await deleteConfirmPending(id)
|
|
}
|
|
|
|
export async function computeStabilityReadyMap(
|
|
rules: ApiAlertRule[],
|
|
hitByRule: Map<string, RuleEvalHit | null>,
|
|
): Promise<Map<string, boolean>> {
|
|
const m = new Map<string, boolean>()
|
|
for (const rule of rules) {
|
|
if (!rule.enabled) {
|
|
await deleteConfirmPending(rule.id)
|
|
m.set(rule.id, false)
|
|
continue
|
|
}
|
|
const hit = hitByRule.get(rule.id) ?? null
|
|
const delaySec =
|
|
hit?.transition === "recovery"
|
|
? rule.recoveryMode === "conditional"
|
|
? rule.recoveryStabilitySec
|
|
: 0
|
|
: rule.confirmStabilitySec
|
|
m.set(rule.id, await stabilityReadyForSend(rule.id, hit, delaySec))
|
|
}
|
|
return m
|
|
}
|