Files
vulncheck/app/models/sync_run.py
T
vulncheck 9ca0be24f0 feat(scans): a sync that dies at login is now a failed run, on screen and by mail
A Wazuh API that refused every login for a day was visible only in the
container log. Every scan-jobs row is per asset, and a sync that fails at
authentication never reaches an asset — so /scans showed a clean history of
COMPLETED runs, the manual button said "Sync Failed — check the logs", and
nobody was told.

Ledger: every sync execution (Wazuh, Nessus, Intune, vCenter, IGEL; button
or scheduler) is one sync_runs row with start, end, status, stats and the
error that stopped it, written from its own session so the record never
depends on the job's transaction. A run where every agent failed the same
way counts as failed, not as "0 triggered". Rows left running by a restart
are closed at startup.

Visible: Scan Jobs gets a Sync Health strip (per source: state, last run,
last success, error) and a Sync Runs table; the dashboard shows a banner
while a source is failed or stale (stale follows the schedule interval, a
weekly Nessus is not stale after six days); the Sync Failed modal shows the
real reason, and the sync endpoints return it instead of "check server
logs".

Mail: a failed run mails the sync-alert recipients (own setting, else the
notification defaults) with the error and the last successful sync, i.e.
how long the coverage gap already is — one mail per source per 24h while
it keeps failing, logged as SYNC_FAILURE in the notification log.
2026-09-03 18:11:24 +02:00

32 lines
1.4 KiB
Python

"""SyncRun — one row per sync job execution, whatever the source or trigger.
The `scans` table records per-asset results, so a sync that dies BEFORE it
reaches any asset (Wazuh authentication refused, Nessus unreachable, IGEL UMS
down) left no trace anywhere but the container log. This ledger is the
source-level record: started, finished, status, and the error that stopped it.
"""
from sqlalchemy import Column, Integer, String, DateTime, Text
from app.models.base import Base
class SyncRun(Base):
__tablename__ = "sync_runs"
id = Column(Integer, primary_key=True, index=True)
# wazuh | nessus | intune | vcenter | igel
source = Column(String(30), nullable=False, index=True)
# manual | scheduled
trigger = Column(String(20), nullable=False, default="manual")
# running | completed | failed (plain strings: no Postgres enum to migrate)
status = Column(String(20), nullable=False, default="running", index=True)
started_at = Column(DateTime, nullable=False)
finished_at = Column(DateTime, nullable=True)
error_message = Column(Text, nullable=True)
# JSON: the service's stats dict (agents synced, created, errors[] ...)
stats = Column(Text, nullable=True)
# When a failure mail went out for this run — the cooldown anchor.
alerted_at = Column(DateTime, nullable=True)
def __repr__(self):
return f"<SyncRun(source='{self.source}', status='{self.status}')>"