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

@@ -134,6 +134,34 @@ templates = Jinja2Templates(directory=Path(__file__).parent / "templates")
app.mount("/static", StaticFiles(directory=Path(__file__).parent / "static"), name="static")
_viser_probe_cache: dict[str, tuple[float, bool]] = {}
_VISER_PROBE_TTL = 8.0 # seconds
def _viser_alive(url: str) -> bool:
"""Fast TCP check with short cache so we never surface a dead link in the dashboard."""
import time as _t
import socket
now = _t.time()
cached = _viser_probe_cache.get(url)
if cached and now - cached[0] < _VISER_PROBE_TTL:
return cached[1]
try:
from urllib.parse import urlparse
p = urlparse(url)
host, port = p.hostname, p.port
if not host or not port:
return False
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(0.4)
s.connect((host, port))
alive = True
except OSError:
alive = False
_viser_probe_cache[url] = (now, alive)
return alive
def _build_acquisitions():
with closing(db()) as conn:
acqs = conn.execute(
@@ -168,8 +196,9 @@ def _build_acquisitions():
d["video_duration_fmt"] = _fmt_dur(int(j["video_duration_s"] or 0)) if (j["video_duration_s"] or 0) > 0 else ""
d["trimmed_total"] = (j["trimmed_head"] or 0) + (j["trimmed_tail"] or 0)
d["has_thumbnail"] = (DB_PATH.parent / "thumbnails" / f"job_{j['id']}.jpg").exists()
# Only expose a native viser link when port is listening. Probed on render via TCP check.
d["native_viser_url"] = None # filled below
# Mask the viser link when the demo.py that was serving it has since died.
if j["status"] == "done" and j["viser_url"] and not _viser_alive(j["viser_url"]):
d["viser_url"] = None
by_acq.setdefault(j["acquisition_id"], []).append(d)
by_acq_total[j["acquisition_id"]] = by_acq_total.get(j["acquisition_id"], 0) + dur_s