diff --git a/csaxs_bec/bec_ipython_client/plugins/flomni/flomni_webpage_generator.py b/csaxs_bec/bec_ipython_client/plugins/flomni/flomni_webpage_generator.py index b30c51b..0df584b 100644 --- a/csaxs_bec/bec_ipython_client/plugins/flomni/flomni_webpage_generator.py +++ b/csaxs_bec/bec_ipython_client/plugins/flomni/flomni_webpage_generator.py @@ -164,8 +164,10 @@ def _derive_status( ) -> str: """ Returns one of: - scanning -- tomo heartbeat fresh (< _TOMO_HEARTBEAT_STALE_S) - running -- queue currently has an active scan + scanning -- tomo heartbeat fresh (< _TOMO_HEARTBEAT_STALE_S), OR + heartbeat recently seen AND queue still has active scan + (handles long individual projections > heartbeat timeout) + running -- queue has active scan but heartbeat has never been seen idle -- not scanning, last_active_time known unknown -- no activity ever seen since generator started @@ -177,6 +179,11 @@ def _derive_status( hb_age = _heartbeat_age_s(progress.get("heartbeat")) if hb_age < _TOMO_HEARTBEAT_STALE_S: return "scanning" + # Heartbeat stale but queue still active and heartbeat was seen recently + # enough (within 10× the stale window) — likely a long projection, not idle. + # Report as 'scanning' so audio system does not trigger a false alarm. + if queue_has_active_scan and hb_age < _TOMO_HEARTBEAT_STALE_S * 10: + return "scanning" if queue_has_active_scan: return "running" if last_active_time is not None or had_activity: @@ -625,10 +632,16 @@ class WebpageGeneratorBase: if not scan_id: return {} - # If scan unchanged and all output files exist, skip all work - thumb_key = f"{scan_id}_phase_thumb.jpg" - if (scan_id == self._last_ptycho_scan - and (self._output_dir / thumb_key).exists()): + # If scan unchanged, thumbs exist, and source hasn't changed, skip all work + thumb_key = f"{scan_id}_phase_thumb.jpg" + thumb_path = self._output_dir / thumb_key + src_phase = found.get("phase") + thumb_fresh = ( + thumb_path.exists() + and src_phase is not None + and thumb_path.stat().st_mtime >= src_phase.stat().st_mtime + ) + if (scan_id == self._last_ptycho_scan and thumb_fresh): # Rebuild result dict from existing files without doing I/O images = [] for role, _pat, primary in _ROLES: @@ -1211,6 +1224,22 @@ def _render_html(phone_numbers: list) -> str: }} .phone-num {{ font-size: 0.9rem; font-weight: 600; color: var(--text); }} + /* ── Drag-and-drop card ordering ── */ + #card-container {{ display: grid; gap: 1.25rem; }} + .draggable-card {{ position: relative; }} + .drag-handle {{ + position: absolute; top: 0.9rem; right: 0.9rem; + font-size: 1rem; color: var(--border); cursor: grab; + line-height: 1; letter-spacing: -2px; user-select: none; + transition: color 0.2s; + }} + .drag-handle:active {{ cursor: grabbing; }} + .draggable-card:hover .drag-handle {{ color: var(--text-dim); }} + .draggable-card.drag-over {{ + outline: 2px dashed var(--status-color); outline-offset: 3px; + }} + .draggable-card.dragging {{ opacity: 0.4; }} + /* ── Footer ── */ footer {{ font-family: var(--mono); font-size: 0.65rem; color: var(--text-dim); @@ -1295,37 +1324,17 @@ def _render_html(phone_numbers: list) -> str: - -
-
Reconstruction queue
-
-
Waiting-
-
Failed-
-
Queue name-
-
-
-
-
-
Ptychography reconstructions
-
-
No reconstruction found
-
-
+ +
- - - - -
+
+
⋮⋮
@@ -1351,14 +1360,44 @@ def _render_html(phone_numbers: list) -> str:
+ +
+
⋮⋮
+
Reconstruction queue
+
+
Waiting-
+
Failed-
+
Queue name-
+
+
+
+ +
+
⋮⋮
+
Ptychography reconstructions
+
+
No reconstruction found
+
+
+ + + + -
+
+
⋮⋮
Contacts
{phones_html}
+
+