"""monitor — static status page builder. Reads the shared database and renders a self-contained HTML file (no external assets, no JS, everything escaped). Contains NO sensitive data: daemon health, proposal queue counts, and recent audit activity (actor/action/result only — no email bodies, no detail payloads, no URLs). """ from __future__ import annotations import html from datetime import UTC, datetime from sqlalchemy import func, select from cayceops.config import Config from cayceops.db import session_scope from cayceops.models import ( AuditEntry, ContentItem, DaemonRun, MailMessage, Proposal, ProposalStatus, ) DAEMONS = ("scanner", "executor", "maild", "publisher", "monitor") _STYLE = """""" def _fmt_ts(dt) -> str: return dt.strftime("%Y-%m-%d %H:%M:%SZ") if dt else "—" def _daemon_rows(sess) -> str: rows = [] for name in DAEMONS: last = ( sess.query(DaemonRun) .filter(DaemonRun.daemon == name) .order_by(DaemonRun.started_at.desc()) .first() ) if last is None: rows.append( "
| time (utc) | actor | action | result |
|---|
generated {generated} · static page, no sensitive data by design
audit entries: {sess.scalar(select(func.count(AuditEntry.id)))} total
""" import os os.makedirs(os.path.dirname(cfg.monitor_output) or ".", exist_ok=True) with open(cfg.monitor_output, "w", encoding="utf-8") as fh: fh.write(page) return cfg.monitor_output def build(cfg: Config | None = None) -> int: """CLI entry: render the status page inside a DaemonRun bookkeeping wrap.""" from cayceops.config import load from cayceops.db import engine cfg = cfg or load() try: with session_scope() as sess: run_row = DaemonRun(daemon="monitor") sess.add(run_row) sess.flush() try: out = _build(cfg, sess) except Exception as exc: # noqa: BLE001 — daemon boundary run_row.status = "error" run_row.detail = {"error": str(exc)[:300]} run_row.finished_at = datetime.now(UTC) return 1 run_row.status = "ok" run_row.detail = {"output": out} run_row.finished_at = datetime.now(UTC) print(out) return 0 finally: engine().dispose()