scaffold — FastAPI + SQLite + HTMX dashboard, ingest + dispatcher
- app/main.py : dashboard /, partials /partials/{jobs,monitor} (htmx polling)
- app/templates/ : index, jobs table, monitor card par worker
- app/static/style.css : thème sombre cohérent
- scripts/ingest.py : scan SSD d'acquisition, EXIF CreateDate → segments
continus par (AUV, GoPro serial) avec seuil configurable
- scripts/dispatcher.py : polling queue, pick worker selon VRAM free,
extraction ffmpeg + lingbot-map windowed --offload_to_cpu, progression DB
- DB : SQLite (acquisitions + jobs), lifecycle queued→extracting→running→done
- Workers par défaut : .87 (3060 12GB) + .84 (3090 24GB)
Contexte : QC terrain le jour-même (avant photogrammétrie à 30 jours),
plusieurs heures × 2 GoPros × 2-3 AUVs d'enregistrement à traiter en parallèle.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
44
app/templates/_jobs_table.html
Normal file
44
app/templates/_jobs_table.html
Normal file
@@ -0,0 +1,44 @@
|
||||
{% if not jobs %}
|
||||
<p class="muted">Aucun job. Ingeste un dossier d'acquisition via <code>scripts/ingest.py</code>.</p>
|
||||
{% else %}
|
||||
<table class="jobs">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th><th>Acquisition</th><th>AUV</th><th>GoPro</th><th>Segment</th>
|
||||
<th>Frames</th><th>Status</th><th>Worker</th><th>Progress</th><th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for j in jobs %}
|
||||
<tr class="status-{{ j.status }}">
|
||||
<td>{{ j.id }}</td>
|
||||
<td>{{ j.acquisition_name }}</td>
|
||||
<td>{{ j.auv }}</td>
|
||||
<td><code>{{ j.gopro_serial }}</code></td>
|
||||
<td>{{ j.segment_label }}</td>
|
||||
<td>{{ j.frame_count or "—" }}</td>
|
||||
<td><span class="pill {{ j.status }}">{{ j.status }}</span></td>
|
||||
<td>{{ j.worker_host or "—" }}</td>
|
||||
<td>
|
||||
{% if j.status == 'running' or j.status == 'extracting' %}
|
||||
<progress value="{{ j.progress }}" max="100"></progress> {{ j.progress }}%
|
||||
{% elif j.status == 'done' and j.viser_url %}
|
||||
<a href="{{ j.viser_url }}" target="_blank">viser</a>
|
||||
{% if j.ply_path %} · <a href="/api/jobs/{{ j.id }}/ply">PLY</a>{% endif %}
|
||||
{% else %}—{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if j.status in ['queued','extracting','running'] %}
|
||||
<button hx-post="/jobs/{{ j.id }}/cancel" hx-target="#jobs-table" hx-swap="outerHTML">Stop</button>
|
||||
{% elif j.status == 'error' %}
|
||||
<button hx-post="/jobs/{{ j.id }}/retry" hx-target="#jobs-table" hx-swap="outerHTML">Retry</button>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% if j.error %}
|
||||
<tr class="err-row"><td colspan="10"><small>{{ j.error }}</small></td></tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
26
app/templates/_monitor.html
Normal file
26
app/templates/_monitor.html
Normal file
@@ -0,0 +1,26 @@
|
||||
<div class="worker-grid">
|
||||
{% for w in workers %}
|
||||
<div class="worker {% if not w.online %}offline{% endif %}">
|
||||
<div class="hdr">
|
||||
<b>{{ w.host }}</b>
|
||||
<span class="gpu">{{ w.gpu }}</span>
|
||||
<span class="state">{% if w.online %}online{% else %}offline{% endif %}</span>
|
||||
</div>
|
||||
{% if w.online %}
|
||||
<div class="bar">
|
||||
<span>VRAM</span>
|
||||
<progress value="{{ w.vram_used_mib or 0 }}" max="{{ w.vram_total_mib or 1 }}"></progress>
|
||||
<small>{{ w.vram_used_mib }} / {{ w.vram_total_mib }} MiB</small>
|
||||
</div>
|
||||
<div class="bar">
|
||||
<span>GPU</span>
|
||||
<progress value="{{ w.gpu_util_pct or 0 }}" max="100"></progress>
|
||||
<small>{{ w.gpu_util_pct }}%</small>
|
||||
</div>
|
||||
<div class="bar"><span>Disk /</span><small>{{ w.disk_used_pct }}</small></div>
|
||||
{% else %}
|
||||
<div class="err">{{ w.error or "unreachable" }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
27
app/templates/index.html
Normal file
27
app/templates/index.html
Normal file
@@ -0,0 +1,27 @@
|
||||
<!doctype html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>cosma-qc — dashboard</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>cosma-qc</h1>
|
||||
<span class="sub">post-acquisition QC · lingbot-map pipeline</span>
|
||||
</header>
|
||||
|
||||
<section id="monitor" hx-get="/partials/monitor" hx-trigger="load, every 5s" hx-swap="innerHTML">
|
||||
<p class="muted">Chargement des workers…</p>
|
||||
</section>
|
||||
|
||||
<section id="jobs">
|
||||
<h2>Jobs</h2>
|
||||
<div id="jobs-table" hx-get="/partials/jobs" hx-trigger="load, every 3s" hx-swap="innerHTML">
|
||||
<p class="muted">Chargement…</p>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user