fix: ansible routes validation, SSE dedup, scoped querySelector, status_color fallback

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-17 20:31:17 -04:00
parent 94e0f20f98
commit 60e2040284
5 changed files with 17 additions and 13 deletions
+10 -2
View File
@@ -1,5 +1,6 @@
# fablednetmon/ansible/routes.py
from __future__ import annotations
import asyncio
import uuid
from datetime import datetime, timezone
from pathlib import Path
@@ -77,6 +78,9 @@ async def create_run():
source_name = form.get("source_name", "").strip()
inventory_path = form.get("inventory_path", "").strip()
if not playbook_path or not inventory_path:
return "playbook_path and inventory_path are required", 400
all_sources = _get_sources()
source = next((s for s in all_sources if s["name"] == source_name), None)
if source is None:
@@ -98,8 +102,7 @@ async def create_run():
async with db.begin():
db.add(run)
import asyncio
asyncio.create_task(
task = asyncio.create_task(
executor.start_run(
current_app._get_current_object(), # type: ignore[attr-defined]
run_id,
@@ -108,6 +111,11 @@ async def create_run():
source["path"],
)
)
task.add_done_callback(
lambda t: t.exception() and current_app.logger.error(
"Ansible run %s raised: %s", run_id, t.exception()
)
)
return await render_template(
"ansible/run_started.html",
+1 -1
View File
@@ -87,7 +87,7 @@
<div style="display:flex;gap:0.75rem;align-items:center;">
<button type="button" class="btn"
onclick="var manual=document.getElementById('manual-inv-{{ sd.source.name }}').value.trim();
if(manual){document.querySelector('[name=inventory_path]').value=manual;}
if(manual){this.closest('form').querySelector('[name=inventory_path]').value=manual;}
htmx.trigger(this.closest('form'),'submit');">Execute</button>
<a href="#" onclick="document.getElementById('run-form-{{ sd.source.name }}').style.display='none';return false;"
style="color:#606080;font-size:0.9rem;">Cancel</a>
+1 -1
View File
@@ -23,7 +23,7 @@
</thead>
<tbody>
{% for run in runs %}
{% set status_color = {"running": "#a0a000", "success": "#40a040", "failed": "#a04040", "interrupted": "#806040"}[run.status.value] %}
{% set status_color = {"running": "#a0a000", "success": "#40a040", "failed": "#a04040", "interrupted": "#806040"}.get(run.status.value, "#808080") %}
<tr style="border-top:1px solid #2a2a4a;">
<td style="padding:0.75rem 1rem;color:#e0e0e0;font-size:0.9rem;">{{ run.playbook_path }}</td>
<td style="padding:0.75rem 1rem;color:#8080a0;font-size:0.9rem;">{{ run.source_name }}</td>
@@ -6,7 +6,7 @@
<h1 style="color:#c0c0ff;">Run Detail</h1>
</div>
{% set status_color = {"running": "#a0a000", "success": "#40a040", "failed": "#a04040", "interrupted": "#806040"}[run.status.value] %}
{% set status_color = {"running": "#a0a000", "success": "#40a040", "failed": "#a04040", "interrupted": "#806040"}.get(run.status.value, "#808080") %}
<div class="card">
<div style="display:grid;grid-template-columns:auto 1fr;gap:0.4rem 1rem;font-size:0.9rem;margin-bottom:1rem;">
<span style="color:#606080;">ID</span><span style="color:#a0a0c0;font-family:monospace;">{{ run.id }}</span>
@@ -4,14 +4,10 @@
<span style="color:#a0a0c0;font-size:0.9rem;">{{ run.playbook_path }}</span>
<a href="/ansible/runs/{{ run.id }}" style="color:#6060c0;font-size:0.82rem;margin-left:auto;">Full view →</a>
</div>
<div hx-ext="sse" sse-connect="/ansible/runs/{{ run.id }}/stream">
<pre id="sse-output-{{ run.id }}"
style="background:#080810;padding:0.75rem;border-radius:4px;font-family:monospace;font-size:0.78rem;color:#a0c0a0;max-height:400px;overflow-y:auto;white-space:pre-wrap;margin:0;"
sse-swap="output"
hx-swap="beforeend">
</pre>
</div>
<div id="sse-status-{{ run.id }}" style="margin-top:0.5rem;font-size:0.85rem;color:#606080;">
<pre id="sse-output-{{ run.id }}"
style="background:#080810;padding:0.75rem;border-radius:4px;font-family:monospace;font-size:0.78rem;color:#a0c0a0;max-height:400px;overflow-y:auto;white-space:pre-wrap;margin:0 0 0.5rem 0;">
</pre>
<div id="sse-status-{{ run.id }}" style="font-size:0.85rem;color:#606080;">
Streaming output…
</div>
<script>