feat: wire ansible blueprint, startup interrupted-run marker, git-pull task, cleanup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-17 20:33:14 -04:00
parent 60e2040284
commit 763cbda3ac
4 changed files with 50 additions and 1 deletions
+43
View File
@@ -23,6 +23,7 @@ def create_app(
PLUGINS=cfg.get("plugins", {}),
SMTP=cfg.get("smtp", {}),
WEBHOOK=cfg.get("webhook", {}),
ANSIBLE=cfg.get("ansible", {}),
TESTING=testing,
_RAW_CFG=cfg,
)
@@ -43,11 +44,13 @@ def create_app(
from .dashboard.routes import dashboard_bp
from .hosts.routes import hosts_bp
from .alerts.routes import alerts_bp
from .ansible.routes import ansible_bp
app.register_blueprint(auth_bp)
app.register_blueprint(dashboard_bp)
app.register_blueprint(hosts_bp)
app.register_blueprint(alerts_bp)
app.register_blueprint(ansible_bp)
# Build task registry
app._task_registry = []
@@ -64,6 +67,7 @@ def create_app(
@app.before_serving
async def startup():
if not testing:
await _mark_interrupted_runs(app)
asyncio.create_task(_run_scheduler(app))
return app
@@ -130,6 +134,45 @@ def _register_core_tasks(app: Quart, cfg: dict) -> None:
),
])
# Register git-pull tasks for each git source
ansible_cfg = cfg.get("ansible", {})
from .ansible.sources import get_sources
for source in get_sources(ansible_cfg):
if source["type"] != "git":
continue
_source = source # capture for closure
async def run_git_pull(src=_source):
from .ansible.sources import git_pull
await git_pull(src)
app._task_registry.append(
ScheduledTask(
name=f"ansible_git_pull_{_source['name']}",
coro_factory=run_git_pull,
interval_seconds=_source["pull_interval_seconds"],
run_on_startup=True,
)
)
async def _mark_interrupted_runs(app: Quart) -> None:
"""Mark any runs still in 'running' state as 'interrupted' (process restart)."""
from sqlalchemy import update
from .models.ansible import AnsibleRun, AnsibleRunStatus
from datetime import datetime, timezone
async with app.db_sessionmaker() as session:
async with session.begin():
await session.execute(
update(AnsibleRun)
.where(AnsibleRun.status == AnsibleRunStatus.running)
.values(
status=AnsibleRunStatus.interrupted,
finished_at=datetime.now(timezone.utc),
)
)
async def _run_scheduler(app: Quart) -> None:
from .core.scheduler import start_scheduler
+2
View File
@@ -7,6 +7,7 @@ from sqlalchemy import delete
from fablednetmon.models.monitors import DnsResult, PingResult
from fablednetmon.models.metrics import PluginMetric
from fablednetmon.models.ansible import AnsibleRun
if TYPE_CHECKING:
from quart import Quart
@@ -25,6 +26,7 @@ async def run_cleanup(app: "Quart") -> None:
(PingResult, PingResult.probed_at),
(DnsResult, DnsResult.resolved_at),
(PluginMetric, PluginMetric.recorded_at),
(AnsibleRun, AnsibleRun.started_at),
]:
result = await session.execute(
delete(model).where(ts_col < cutoff)
+1
View File
@@ -36,6 +36,7 @@
<a href="/">Dashboard</a>
<a href="/hosts/">Hosts</a>
<a href="/alerts/">Alerts</a>
<a href="/ansible/">Ansible</a>
<a href="/logout">Logout ({{ session.username }})</a>
</nav>
{% endif %}
+4 -1
View File
@@ -23,7 +23,10 @@
</div>
<div class="card">
<h3 style="color:#8080ff;margin-bottom:0.75rem;">Ansible</h3>
<p style="color:#606080;font-size:0.9rem;">Coming in Plan 3.</p>
<p style="color:#606080;font-size:0.9rem;">
<a href="/ansible/browse" style="color:#a0a0ff;">Browse playbooks</a> or view
<a href="/ansible/" style="color:#a0a0ff;">run history</a>.
</p>
</div>
</div>
{% endblock %}