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.
32 lines
1.4 KiB
Python
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}')>"
|