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:
@@ -23,6 +23,7 @@ def create_app(
|
|||||||
PLUGINS=cfg.get("plugins", {}),
|
PLUGINS=cfg.get("plugins", {}),
|
||||||
SMTP=cfg.get("smtp", {}),
|
SMTP=cfg.get("smtp", {}),
|
||||||
WEBHOOK=cfg.get("webhook", {}),
|
WEBHOOK=cfg.get("webhook", {}),
|
||||||
|
ANSIBLE=cfg.get("ansible", {}),
|
||||||
TESTING=testing,
|
TESTING=testing,
|
||||||
_RAW_CFG=cfg,
|
_RAW_CFG=cfg,
|
||||||
)
|
)
|
||||||
@@ -43,11 +44,13 @@ def create_app(
|
|||||||
from .dashboard.routes import dashboard_bp
|
from .dashboard.routes import dashboard_bp
|
||||||
from .hosts.routes import hosts_bp
|
from .hosts.routes import hosts_bp
|
||||||
from .alerts.routes import alerts_bp
|
from .alerts.routes import alerts_bp
|
||||||
|
from .ansible.routes import ansible_bp
|
||||||
|
|
||||||
app.register_blueprint(auth_bp)
|
app.register_blueprint(auth_bp)
|
||||||
app.register_blueprint(dashboard_bp)
|
app.register_blueprint(dashboard_bp)
|
||||||
app.register_blueprint(hosts_bp)
|
app.register_blueprint(hosts_bp)
|
||||||
app.register_blueprint(alerts_bp)
|
app.register_blueprint(alerts_bp)
|
||||||
|
app.register_blueprint(ansible_bp)
|
||||||
|
|
||||||
# Build task registry
|
# Build task registry
|
||||||
app._task_registry = []
|
app._task_registry = []
|
||||||
@@ -64,6 +67,7 @@ def create_app(
|
|||||||
@app.before_serving
|
@app.before_serving
|
||||||
async def startup():
|
async def startup():
|
||||||
if not testing:
|
if not testing:
|
||||||
|
await _mark_interrupted_runs(app)
|
||||||
asyncio.create_task(_run_scheduler(app))
|
asyncio.create_task(_run_scheduler(app))
|
||||||
|
|
||||||
return 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:
|
async def _run_scheduler(app: Quart) -> None:
|
||||||
from .core.scheduler import start_scheduler
|
from .core.scheduler import start_scheduler
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from sqlalchemy import delete
|
|||||||
|
|
||||||
from fablednetmon.models.monitors import DnsResult, PingResult
|
from fablednetmon.models.monitors import DnsResult, PingResult
|
||||||
from fablednetmon.models.metrics import PluginMetric
|
from fablednetmon.models.metrics import PluginMetric
|
||||||
|
from fablednetmon.models.ansible import AnsibleRun
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from quart import Quart
|
from quart import Quart
|
||||||
@@ -25,6 +26,7 @@ async def run_cleanup(app: "Quart") -> None:
|
|||||||
(PingResult, PingResult.probed_at),
|
(PingResult, PingResult.probed_at),
|
||||||
(DnsResult, DnsResult.resolved_at),
|
(DnsResult, DnsResult.resolved_at),
|
||||||
(PluginMetric, PluginMetric.recorded_at),
|
(PluginMetric, PluginMetric.recorded_at),
|
||||||
|
(AnsibleRun, AnsibleRun.started_at),
|
||||||
]:
|
]:
|
||||||
result = await session.execute(
|
result = await session.execute(
|
||||||
delete(model).where(ts_col < cutoff)
|
delete(model).where(ts_col < cutoff)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
<a href="/">Dashboard</a>
|
<a href="/">Dashboard</a>
|
||||||
<a href="/hosts/">Hosts</a>
|
<a href="/hosts/">Hosts</a>
|
||||||
<a href="/alerts/">Alerts</a>
|
<a href="/alerts/">Alerts</a>
|
||||||
|
<a href="/ansible/">Ansible</a>
|
||||||
<a href="/logout">Logout ({{ session.username }})</a>
|
<a href="/logout">Logout ({{ session.username }})</a>
|
||||||
</nav>
|
</nav>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -23,7 +23,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h3 style="color:#8080ff;margin-bottom:0.75rem;">Ansible</h3>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user