feat: hook post-job cosma-nav + style dashboard + docker-compose update
This commit is contained in:
@@ -174,6 +174,8 @@ code { background: rgba(255,255,255,0.05); padding: 0 0.25rem; border-radius: 3p
|
|||||||
.docs-link:hover { background: #2d2f31; }
|
.docs-link:hover { background: #2d2f31; }
|
||||||
.btn-glb, .btn-ply-dl { display: inline-block; text-decoration: none; padding: 3px 10px; border: 1px solid #8bc34a; border-radius: 3px; color: #8bc34a; font-size: 0.72rem; background: transparent; cursor: pointer; font-family: inherit; }
|
.btn-glb, .btn-ply-dl { display: inline-block; text-decoration: none; padding: 3px 10px; border: 1px solid #8bc34a; border-radius: 3px; color: #8bc34a; font-size: 0.72rem; background: transparent; cursor: pointer; font-family: inherit; }
|
||||||
.btn-glb:hover, .btn-ply-dl:hover { background: #8bc34a; color: #000; }
|
.btn-glb:hover, .btn-ply-dl:hover { background: #8bc34a; color: #000; }
|
||||||
|
.btn-qc { display: inline-block; text-decoration: none; padding: 3px 10px; border: 1px solid #29b6f6; border-radius: 3px; color: #29b6f6; font-size: 0.72rem; background: transparent; cursor: pointer; font-family: inherit; }
|
||||||
|
.btn-qc:hover { background: #29b6f6; color: #000; }
|
||||||
|
|
||||||
/* Section évolutions */
|
/* Section évolutions */
|
||||||
#evolutions { margin-top: 2rem; padding-top: 1rem; border-top: 1px solid var(--border, #333); }
|
#evolutions { margin-top: 2rem; padding-top: 1rem; border-top: 1px solid var(--border, #333); }
|
||||||
@@ -192,3 +194,7 @@ code { background: rgba(255,255,255,0.05); padding: 0 0.25rem; border-radius: 3p
|
|||||||
.pipeline-box ol { margin: 0; padding-left: 1.4rem; }
|
.pipeline-box ol { margin: 0; padding-left: 1.4rem; }
|
||||||
.pipeline-box li { padding: 0.18rem 0; font-size: 0.78rem; color: var(--muted, #888); }
|
.pipeline-box li { padding: 0.18rem 0; font-size: 0.78rem; color: var(--muted, #888); }
|
||||||
.pipeline-box code { font-size: 0.73rem; background: rgba(255,255,255,0.07); padding: 1px 5px; border-radius: 3px; color: #cef; }
|
.pipeline-box code { font-size: 0.73rem; background: rgba(255,255,255,0.07); padding: 1px 5px; border-radius: 3px; color: #cef; }
|
||||||
|
|
||||||
|
.viewer-btn { background: #1a3a2a; color: #4ade80; border: 1px solid #4ade80; border-radius: 3px; padding: 2px 8px; cursor: pointer; font-size: 0.8rem; }
|
||||||
|
.viewer-btn:hover { background: #4ade80; color: #0a1a10; }
|
||||||
|
.viewer-btn:disabled { opacity: 0.5; cursor: wait; }
|
||||||
|
|||||||
@@ -75,16 +75,26 @@ document.addEventListener('click', async (e) => {
|
|||||||
const btn = e.target.closest('.viewer-btn');
|
const btn = e.target.closest('.viewer-btn');
|
||||||
if (!btn) return;
|
if (!btn) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const url = btn.dataset.viewUrl;
|
const liveUrl = btn.dataset.liveUrl;
|
||||||
|
const viewUrl = btn.dataset.viewUrl;
|
||||||
btn.textContent = '…';
|
btn.textContent = '…';
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
|
let url = null;
|
||||||
|
if (liveUrl) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(url, { method: 'POST' });
|
const res = await fetch(liveUrl, { method: 'POST' });
|
||||||
const data = await res.json();
|
if (res.ok) { const d = await res.json(); url = d.url; }
|
||||||
if (res.ok && data.url) window.open(data.url, '_blank');
|
} catch {}
|
||||||
else alert(data.detail || 'Erreur lancement viewer');
|
}
|
||||||
|
if (!url && viewUrl) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(viewUrl, { method: 'POST' });
|
||||||
|
if (res.ok) { const d = await res.json(); url = d.url; }
|
||||||
|
else { const d = await res.json(); alert(d.detail || 'Erreur lancement viewer'); }
|
||||||
} catch (err) { alert('Erreur réseau: ' + err); }
|
} catch (err) { alert('Erreur réseau: ' + err); }
|
||||||
btn.textContent = 'viser';
|
}
|
||||||
|
if (url) window.open(url, '_blank');
|
||||||
|
btn.textContent = 'viser ↗';
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "3849:8000"
|
- "3849:8000"
|
||||||
volumes:
|
volumes:
|
||||||
- /home/floppyrj45/cosma-qc-data:/var/lib/cosma-qc
|
- /home/cosma/cosma-qc-data:/var/lib/cosma-qc
|
||||||
- /home/floppyrj45/.ssh:/ssh-in:ro
|
- /home/cosma/.ssh:/ssh-in:ro
|
||||||
environment:
|
environment:
|
||||||
COSMA_QC_WORKERS: |
|
COSMA_QC_WORKERS: |
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -806,6 +806,43 @@ def run_one_stitch(stitch: sqlite3.Row):
|
|||||||
finished_at=_now_iso())
|
finished_at=_now_iso())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ML_STACK_HOST = "192.168.0.84"
|
||||||
|
ML_STACK_ALIAS = "ml-stack"
|
||||||
|
_PRE_DECIMATE = "/root/cosma-nav/scripts/pre_decimate.py"
|
||||||
|
_ARCHIVE_SH = "/root/cosma-nav/scripts/archive_job.sh"
|
||||||
|
|
||||||
|
|
||||||
|
def _post_job_qc_sync(job_id: int, worker: dict, frames_dir: str):
|
||||||
|
"""Fire-and-forget: decimate PLY + archive to NAS after a successful job.
|
||||||
|
Only runs when the worker is ml-stack (.84) where the scripts live.
|
||||||
|
"""
|
||||||
|
if worker["host"] != ML_STACK_HOST:
|
||||||
|
print(f" post_job #{job_id}: worker={worker['host']} != ml-stack, skip QC sync", flush=True)
|
||||||
|
return
|
||||||
|
alias = ML_STACK_ALIAS
|
||||||
|
parent = str(Path(frames_dir).parent)
|
||||||
|
pre_cmd = (
|
||||||
|
f"python3 {_PRE_DECIMATE} {job_id} "
|
||||||
|
f"--frames-dir {shlex.quote(parent)} "
|
||||||
|
f"> /tmp/pre_decimate_{job_id}.log 2>&1"
|
||||||
|
)
|
||||||
|
rc_pre, _, _ = ssh(alias, pre_cmd, timeout=600)
|
||||||
|
if rc_pre == 0:
|
||||||
|
print(f" post_job #{job_id}: pre_decimate OK", flush=True)
|
||||||
|
else:
|
||||||
|
tail = ssh(alias, f"tail -5 /tmp/pre_decimate_{job_id}.log")[1]
|
||||||
|
print(f" post_job #{job_id}: pre_decimate FAIL: {tail[:300]}", flush=True)
|
||||||
|
|
||||||
|
arc_cmd = f"bash {_ARCHIVE_SH} {job_id} > /tmp/archive_{job_id}.log 2>&1"
|
||||||
|
rc_arc, _, _ = ssh(alias, arc_cmd, timeout=600)
|
||||||
|
if rc_arc == 0:
|
||||||
|
print(f" post_job #{job_id}: archive OK", flush=True)
|
||||||
|
else:
|
||||||
|
tail = ssh(alias, f"tail -5 /tmp/archive_{job_id}.log")[1]
|
||||||
|
print(f" post_job #{job_id}: archive FAIL: {tail[:300]}", flush=True)
|
||||||
|
|
||||||
|
|
||||||
def run_one(job: sqlite3.Row) -> bool:
|
def run_one(job: sqlite3.Row) -> bool:
|
||||||
"""Returns True if a worker was picked and work started."""
|
"""Returns True if a worker was picked and work started."""
|
||||||
job_id = job["id"]
|
job_id = job["id"]
|
||||||
@@ -825,6 +862,7 @@ def run_one(job: sqlite3.Row) -> bool:
|
|||||||
set_status(job_id, status="done", viser_url=viser_url, ply_path=ply_path,
|
set_status(job_id, status="done", viser_url=viser_url, ply_path=ply_path,
|
||||||
progress=100, log_tail=log, finished_at=_now_iso())
|
progress=100, log_tail=log, finished_at=_now_iso())
|
||||||
_maybe_create_per_auv_stitch(job_id)
|
_maybe_create_per_auv_stitch(job_id)
|
||||||
|
threading.Thread(target=_post_job_qc_sync, args=(job_id, worker, frames_dir), daemon=True).start()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# do_extract raises "skipped_short" after flagging status='skipped' — don't override.
|
# do_extract raises "skipped_short" after flagging status='skipped' — don't override.
|
||||||
if "skipped_short" not in str(e):
|
if "skipped_short" not in str(e):
|
||||||
|
|||||||
Reference in New Issue
Block a user