Files
MikrotikManager/lib/scheduler-run-snapshot.ts
T

257 lines
7.0 KiB
TypeScript

/**
* Снимок прогона планировщика (JSON из `scheduler_runs.result_json`).
* Дублирует контракт `backend/src/types/scheduler-run-snapshot.ts` для UI.
*/
export interface ServerRestPingSnapshot {
serverId: number
name: string
host: string
ok: boolean
latencyMs?: number | null
error?: string
}
export interface ServersRestPingRunSnapshot {
v: number
job: "servers_rest_ping"
sampledAt: string
skipped?: boolean
fatalError?: string
servers: ServerRestPingSnapshot[]
}
export type AlertEngineRuleDiagSnapshot = {
ruleId: string
inGroup: boolean
evalHit: boolean
hitTransition?: "problem" | "recovery" | "neutral"
hitMessage?: string
stabilityOk: boolean
cooldownOk: boolean
telegramOk: boolean
blocked?:
| "no_hit"
| "stability"
| "cooldown"
| "no_telegram"
| "dedupe_positive"
| "in_group"
}
/** Сводка прогона движка оповещений (JSON из `scheduler_runs` для job `alert_engine`). */
export interface AlertEngineRunSnapshot {
v: number
job: "alert_engine"
sampledAt: string
rulesChecked: number
standaloneFires: number
groupFires: number
skippedNoTelegram: boolean
errors?: string[]
ruleDiag?: AlertEngineRuleDiagSnapshot[]
}
export interface GreBgpSnapshotRunSnapshot {
v: number
job: "gre_bgp"
sampledAt: string
skipped?: boolean
fatalError?: string
greWritten: number
bgpWritten: number
errors?: string[]
}
export interface InternetPathRunSnapshot {
v: number
job: "internet_path"
sampledAt: string
homes: number
snapshotSaved: boolean
fatalError?: string
}
export interface CertificatesRenewRunSnapshot {
v: number
job: "certificates_renew"
sampledAt: string
skipped?: boolean
fatalError?: string
checked: number
renewed: number
skippedTargets: number
errors: string[]
}
export interface BackupsRunSnapshot {
v: number
job: "backups"
sampledAt: string
skipped?: boolean
due: boolean
created: number
failures: number
pruned: number
uploadFailures?: number
pruneFailures?: number
metadataError?: string
eventErrors?: string[]
errors?: string[]
fatalError?: string
}
export interface GeoipUpdateRunSnapshot {
v: number
job: "geoip_update"
sampledAt: string
skipped?: boolean
fatalError?: string
checked: number
downloaded: number
skippedUnchanged: number
bytes: number
errors: string[]
}
export type SchedulerRunSnapshot =
| TrafficRunSnapshot
| ResourcesRunSnapshot
| PingRunSnapshot
| SpeedScheduledRunSnapshot
| ServersRestPingRunSnapshot
| GreBgpSnapshotRunSnapshot
| InternetPathRunSnapshot
| CertificatesRenewRunSnapshot
| BackupsRunSnapshot
| GeoipUpdateRunSnapshot
| AlertEngineRunSnapshot
export interface TrafficServerSnapshot {
serverId: number
name: string
host: string
ok: boolean
error?: string
interfaces?: number
sumRxMbps?: number
sumTxMbps?: number
}
export interface TrafficRunSnapshot {
v: number
job: "traffic"
sampledAt: string
skipped?: boolean
fatalError?: string
servers: TrafficServerSnapshot[]
}
export interface ResourceServerSnapshot {
serverId: number
name: string
host: string
status: "online" | "offline"
error?: string
cpuLoadPct?: number
memUsedMb?: number
memTotalMb?: number
memUsedPct?: number
diskFreeMb?: number
diskTotalMb?: number
uptimeSeconds?: number
boardName?: string
rosVersion?: string
}
export interface ResourcesRunSnapshot {
v: number
job: "uptime_resources"
sampledAt: string
skipped?: boolean
fatalError?: string
servers: ResourceServerSnapshot[]
}
export interface PingProbeSnapshot {
probeId: string
name: string
target: string
srcServerId: number
srcServerName: string
srcInterface: string
ok: boolean
rttMs: number | null
lossPct: number | null
status: "up" | "warn" | "down" | "unknown"
error?: string
}
export interface PingRunSnapshot {
v: number
job: "uptime_ping"
sampledAt: string
skipped?: boolean
fatalError?: string
probes: PingProbeSnapshot[]
skippedByInterval?: number
}
export interface SpeedRunSnapshot {
probeId: string
srcServerId: number
srcServerName: string
dstServerId: number
dstServerName: string
srcInterface: string
dstInterface: string
protocol: string
direction: string
durationSec: number
ok: boolean
txAvgMbps?: number | null
rxAvgMbps?: number | null
pingRttMs?: number | null
pingLossPct?: number | null
pingError?: string | null
error?: string
}
export interface SpeedScheduledRunSnapshot {
v: number
job: "uptime_speed"
sampledAt: string
runs: SpeedRunSnapshot[]
}
/** PostgreSQL JSONB returns an object; older records may still contain JSON text. */
export function parseSchedulerRunSnapshot(raw: unknown): SchedulerRunSnapshot | null {
try {
const value: unknown = typeof raw === "string" ? (raw.trim() ? JSON.parse(raw) : null) : raw
if (!value || typeof value !== "object" || Array.isArray(value)) return null
const o = value as Record<string, unknown>
if (o.v !== 1 || typeof o.job !== "string" || typeof o.sampledAt !== "string" || !Number.isFinite(Date.parse(o.sampledAt))) return null
if (o.skipped !== undefined && typeof o.skipped !== "boolean") return null
if (o.fatalError !== undefined && typeof o.fatalError !== "string") return null
for (const field of ["errors", "eventErrors"]) if (o[field] !== undefined && (!Array.isArray(o[field]) || !(o[field] as unknown[]).every(error => typeof error === "string"))) return null
const arrays: Record<string, string> = { traffic: "servers", uptime_resources: "servers", servers_rest_ping: "servers", uptime_ping: "probes", uptime_speed: "runs" }
if (Object.hasOwn(arrays, o.job)) {
if (!Array.isArray(o[arrays[o.job]])) return null
} else {
const fields: Record<string, string[]> = { gre_bgp: ["greWritten", "bgpWritten"], internet_path: ["homes"], certificates_renew: ["checked", "renewed", "skippedTargets"], backups: ["created", "failures", "pruned"], geoip_update: ["checked", "downloaded", "skippedUnchanged", "bytes"], alert_engine: ["rulesChecked", "standaloneFires", "groupFires"] }
if (!Object.hasOwn(fields, o.job) || !fields[o.job].every(field => typeof o[field] === "number" && Number.isSafeInteger(o[field]) && (o[field] as number) >= 0)) return null
if (["certificates_renew", "geoip_update"].includes(o.job) && !Array.isArray(o.errors)) return null
if (o.job === "backups") {
if (typeof o.due !== "boolean") return null
for (const field of ["uploadFailures", "pruneFailures"]) if (o[field] !== undefined && (typeof o[field] !== "number" || !Number.isSafeInteger(o[field]) || (o[field] as number) < 0)) return null
if (o.metadataError !== undefined && typeof o.metadataError !== "string") return null
}
if (o.job === "internet_path" && typeof o.snapshotSaved !== "boolean") return null
if (o.job === "alert_engine" && typeof o.skippedNoTelegram !== "boolean") return null
}
return o as unknown as SchedulerRunSnapshot
} catch {
return null
}
}