dashboard — vrai tableau + probe viser_url alive + CSS propre

1. _jobs_table.html: remplace la liste <li>flex par un vrai <table> avec
   colonnes explicites: status · preview · #id · AUV+GP · label · duree
   video · frames · hors-eau · progression · temps · actions. Stitches
   restent en <ul> compacte.

2. main.py _build_acquisitions: probe TCP le viser_url avec cache 8s
   avant de le passer au template. Si port mort -> d[viser_url]=None ->
   pas de bouton affiche. Fini les liens qui mennent a rien.

3. style.css: purge des regles flex conflictuelles, rewrite propre pour
   table.jobs-table, badges, prog-bar, btn-viser direct link.
This commit is contained in:
Flag
2026-04-22 22:43:14 +00:00
parent 960ebc0393
commit f13d27b997
4 changed files with 224 additions and 74 deletions

View File

@@ -1,89 +1,110 @@
{% if not acquisitions %}
<p class="muted">Aucune acquisition. Ingeste un dossier via <code>scripts/ingest.py</code>.</p>
{% else %}
<div class="acq-grid">
<div class="acq-list">
{% for acq in acquisitions %}
<div class="acq-col">
<section class="acq">
<h3 class="acq-title">
{{ acq.name }} <span class="total">{{ acq.total_duration }}</span>
</h3>
<ul class="job-list">
{% for j in acq.jobs %}
<li class="job-item {{ j.status }}">
<span class="icon">
{% if j.status == 'done' %}<span class="check"></span>
{% elif j.status in ('running','extracting') %}<span class="spin"></span>
{% elif j.status == 'error' %}<span class="err"></span>
{% else %}<span class="sq"></span>{% endif %}
</span>
<span class="label">
{% if j.has_thumbnail %}<img class="thumb" src="jobs/{{ j.id }}/thumbnail" alt="" loading="lazy">{% endif %}
<span class="job-id">#{{ j.id }}</span>
<span class="auv-gp" title="{{ j.gopro_serial }}">{{ j.auv }} {{ j.gp_label }}</span>
<span class="seg">{{ j.segment_label }}</span>
{% if j.video_duration_fmt != '—' %}<span class="meta">{{ j.video_duration_fmt }}{% if j.frame_count %} · {{ j.frame_count }} f{% if j.trimmed_total %} · {{ j.trimmed_total }} hors-eau{% endif %}{% endif %}</span>{% endif %}
{% if j.status in ('extracting','running') %}
<span class="prog-wrap"><span class="prog-fill" style="width:{{ j.progress }}%"></span><span class="prog-text">{{ j.progress }}%</span></span>
{% endif %}
{% if j.status == 'done' and j.viser_url %}
<a class="ext viser-link" href="{{ j.viser_url }}" target="_blank" rel="noopener" title="viser natif ({{ j.viser_url }})">viser</a>
{% endif %}
</span>
<span class="dur">{{ j._duration }}</span>
{% if j.status in ('queued','extracting','running') %}
<button class="mini" hx-post="jobs/{{ j.id }}/cancel" hx-target="#jobs-table">×</button>
{% elif j.status == 'error' %}
<button class="mini" hx-post="jobs/{{ j.id }}/retry" hx-target="#jobs-table"></button>
{% else %}
<span></span>
{% endif %}
</li>
{% if j.error %}<li class="err-line">{{ j.error }}</li>{% endif %}
{% endfor %}
</ul>
<table class="jobs-table">
<thead>
<tr>
<th class="col-status"></th>
<th class="col-thumb">preview</th>
<th class="col-id">#</th>
<th class="col-auv">AUV · GP</th>
<th class="col-seg">label</th>
<th class="col-dur">durée</th>
<th class="col-frames">frames</th>
<th class="col-trim">hors-eau</th>
<th class="col-progress">progression</th>
<th class="col-elapsed">temps</th>
<th class="col-actions"></th>
</tr>
</thead>
<tbody>
{% for j in acq.jobs %}
<tr class="job-row {{ j.status }}">
<td class="col-status" title="{{ j.status }}">
{% if j.status == 'done' %}<span class="badge ok"></span>
{% elif j.status in ('running','extracting') %}<span class="badge busy"></span>
{% elif j.status == 'error' %}<span class="badge err"></span>
{% elif j.status == 'skipped' %}<span class="badge muted"></span>
{% else %}<span class="badge muted"></span>{% endif %}
</td>
<td class="col-thumb">
{% if j.has_thumbnail %}<img src="jobs/{{ j.id }}/thumbnail" alt="" loading="lazy">{% else %}<span class="thumb-placeholder"></span>{% endif %}
</td>
<td class="col-id">#{{ j.id }}</td>
<td class="col-auv"><strong>{{ j.auv }}</strong> · <span title="{{ j.gopro_serial }}">{{ j.gp_label }}</span></td>
<td class="col-seg">{{ j.segment_label }}</td>
<td class="col-dur">{{ j.video_duration_fmt }}</td>
<td class="col-frames">{% if j.frame_count %}{{ j.frame_count }}{% else %}—{% endif %}</td>
<td class="col-trim">{% if j.trimmed_total %}{{ j.trimmed_total }}{% else %}—{% endif %}</td>
<td class="col-progress">
{% if j.status in ('extracting','running') %}
<span class="prog-bar"><span class="prog-fill" style="width:{{ j.progress }}%"></span></span>
<span class="prog-text">{{ j.progress }}%</span>
{% elif j.status == 'done' and j.viser_url %}
<a class="btn-viser" href="{{ j.viser_url }}" target="_blank" rel="noopener">viser ↗</a>
{% elif j.status == 'skipped' %}
<span class="muted">skipped</span>
{% elif j.status == 'error' %}
<span class="muted" title="{{ j.error }}">failed</span>
{% else %}
<span class="muted"></span>
{% endif %}
</td>
<td class="col-elapsed">{{ j._duration }}</td>
<td class="col-actions">
{% if j.status in ('queued','extracting','running') %}
<button class="mini" hx-post="jobs/{{ j.id }}/cancel" hx-target="#jobs-table" title="annuler">×</button>
{% elif j.status == 'error' %}
<button class="mini" hx-post="jobs/{{ j.id }}/retry" hx-target="#jobs-table" title="retry"></button>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="stitch-section">
<div class="stitch-title">
<span class="icon"><span class="sq"></span></span>
<span>stitch</span>
</div>
<div class="stitch-title">stitch</div>
{% if acq.stitches %}
<ul class="stitch-children">
<ul class="stitch-list">
{% for s in acq.stitches %}
<li class="sub {{ s.status }}">
<span class="icon stitch-icon">
{% if s.status == 'done' %}<span class="check ok"></span>
{% elif s.status == 'running' %}<span class="spin"></span>
{% elif s.status == 'error' %}<span class="err"></span>
{% else %}<span class="sq"></span>{% endif %}
<li class="stitch-item {{ s.status }}">
<span class="badge {% if s.status == 'done' %}ok{% elif s.status == 'running' %}busy{% elif s.status == 'error' %}err{% else %}muted{% endif %}">
{% if s.status == 'done' %}✓{% elif s.status == 'running' %}↻{% elif s.status == 'error' %}✕{% else %}■{% endif %}
</span>
<span>
<span class="stitch-label">
{% if s.level == 'per_auv' %}pair GP1↔GP2 {{ s.auv }}
{% else %}merge final{% endif %}
{% if s._duration %}<span class="dur muted"> — {{ s._duration }}</span>{% endif %}
{% if s.status == 'done' and s.output_ply %}
<button class="ext viewer-btn" data-view-url="stitches/{{ s.id }}/view" title="{{ s.output_ply }}">viser</button>
{% endif %}
</span>
{% if s._duration %}<span class="muted"> · {{ s._duration }}</span>{% endif %}
{% if s.status == 'done' and s.output_ply %}
<button class="btn-viser" data-view-url="stitches/{{ s.id }}/view">viser ↗</button>
{% endif %}
{% if s.status in ('queued','running') %}
<button class="mini" hx-post="stitches/{{ s.id }}/cancel" hx-target="#jobs-table">×</button>
{% elif s.status == 'error' %}
<button class="mini" hx-post="stitches/{{ s.id }}/retry" hx-target="#jobs-table"></button>
{% endif %}
{% if s.error %}<div class="err-line">{{ s.error[:140] }}</div>{% endif %}
</li>
{% if s.error %}<li class="err-line" style="padding-left:42px">{{ s.error[:120] }}</li>{% endif %}
{% endfor %}
</ul>
{% else %}
<ul class="stitch-children">
<li class="sub pending"><span class="sq"></span> pair GP1↔GP2 per AUV</li>
<li class="sub pending"><span class="sq"></span> cross-AUV merge</li>
<li class="sub pending"><span class="sq"></span> final PLY</li>
<ul class="stitch-list">
<li class="stitch-item pending"><span class="badge muted"></span> <span class="muted">pair GP1↔GP2 per AUV</span></li>
<li class="stitch-item pending"><span class="badge muted"></span> <span class="muted">cross-AUV merge</span></li>
<li class="stitch-item pending"><span class="badge muted"></span> <span class="muted">final PLY</span></li>
</ul>
{% endif %}
</div>
</div>
</section>
{% endfor %}
</div>
{% endif %}