From 31fa17465a17bf19c1b5dec91b5a55d7c94897d1 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Sun, 3 May 2026 14:10:56 +0000 Subject: [PATCH 1/4] Pipeline-Icons: Snapshot/Restore bei Queue + Cancel Vorher: - Lage refreshen -> Lage geht in Queue, aber Pipeline-Icons bleiben gruen mit Haekchen vom letzten Refresh (suggeriert faelschlich "alles fertig") - Cancel/Error -> Pipeline bleibt im Mix-Zustand (teils active, teils pending) Nachher: - pipeline.beginQueue(id): macht Snapshot des aktuellen _stateByKey und setzt alle Steps auf pending. Ausgeloest aus app.js handleRefresh() und _restoreRefreshingState() (auch nach F5). - _onRefreshDoneSuccess: Snapshot verwerfen + API-Reload (wie bisher). - _onRefreshDoneCancel: Snapshot zurueckspielen -> vorheriger gruener Stand sichtbar. - _onRefreshDoneError: gleiches Verhalten wie Cancel. - bindToIncident: Snapshot mitloeschen (lagen-spezifisch). - Bei zweitem Refresh ohne Cancel dazwischen wird Snapshot bewusst ueberschrieben. --- src/static/js/app.js | 9 ++++++ src/static/js/pipeline.js | 62 +++++++++++++++++++++++++++++++++++---- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/src/static/js/app.js b/src/static/js/app.js index 1dd2e7b..0e65c4f 100644 --- a/src/static/js/app.js +++ b/src/static/js/app.js @@ -618,6 +618,10 @@ const App = { const inc = this.incidents.find(i => i.id === id); const isFirst = inc && !inc.has_summary; UI.showProgress('queued', { queue_position: idx + 1 }, id, isFirst); + // Pipeline-Reset auch nach F5: aktive Lage in Queue -> Icons grau + if (id === this.currentIncidentId && typeof Pipeline !== 'undefined' && Pipeline.beginQueue) { + Pipeline.beginQueue(id); + } }); } @@ -1926,6 +1930,11 @@ async handleRefresh() { this._updateRefreshButton(true); // showProgress called via handleStatusUpdate const result = await API.refreshIncident(this.currentIncidentId); + // Pipeline auf "pending" setzen, damit alte gruene Haekchen nicht + // faelschlich "schon fertig" suggerieren waehrend die Lage in der Queue steht + if (typeof Pipeline !== 'undefined' && Pipeline.beginQueue) { + Pipeline.beginQueue(this.currentIncidentId); + } if (result && result.status === 'skipped') { UI.showToast('Aktualisierung ist in der Warteschlange und wird ausgefuehrt, sobald die aktuelle Recherche abgeschlossen ist.', 'info'); } else { diff --git a/src/static/js/pipeline.js b/src/static/js/pipeline.js index a4b184b..d38fdd6 100644 --- a/src/static/js/pipeline.js +++ b/src/static/js/pipeline.js @@ -19,6 +19,7 @@ const Pipeline = { _incidentId: null, _definition: null, // PIPELINE_STEPS vom Backend _stateByKey: {}, // step_key -> {status, count_value, count_secondary, pass_number} + _snapshotState: null, // deep-copy von _stateByKey vor Refresh-Start (fuer Cancel-Restore) _isResearch: false, _passTotal: 1, _lastRefreshHeader: null, @@ -42,10 +43,11 @@ const Pipeline = { if (this._wsBound) return; if (typeof WS !== 'undefined' && WS.on) { WS.on('pipeline_step', (msg) => this._onWsStep(msg)); - // Bei Refresh-Complete den finalen Stand neu laden, damit Zahlen gefroren sichtbar bleiben - WS.on('refresh_complete', (msg) => this._onRefreshDone(msg)); - WS.on('refresh_cancelled', (msg) => this._onRefreshDone(msg)); - WS.on('refresh_error', (msg) => this._onRefreshDone(msg)); + // Erfolg: API-State neu laden (finaler Stand sichtbar) + WS.on('refresh_complete', (msg) => this._onRefreshDoneSuccess(msg)); + // Cancel/Error: vor-Refresh-Snapshot zurueckspielen, damit Pipeline nicht im Mix-Zustand stehen bleibt + WS.on('refresh_cancelled', (msg) => this._onRefreshDoneCancel(msg)); + WS.on('refresh_error', (msg) => this._onRefreshDoneError(msg)); this._wsBound = true; } // Hover-Tooltip-Element vorbereiten @@ -68,6 +70,7 @@ const Pipeline = { async bindToIncident(incidentId) { this._incidentId = incidentId; this._stateByKey = {}; + this._snapshotState = null; // Snapshot ist immer lagen-spezifisch this._isResearch = false; this._passTotal = 1; this._lastRefreshHeader = null; @@ -166,14 +169,63 @@ const Pipeline = { } }, - _onRefreshDone(msg) { + /** + * Wird vom Frontend gerufen, wenn ein Refresh angestossen wurde (queued). + * Macht einen Snapshot des aktuellen Pipeline-Stands (zur spaeteren Wiederherstellung + * bei Cancel/Error) und setzt dann alle Steps auf "pending" - damit der User sieht: + * "neuer Refresh laeuft an, alte gruene Haekchen sind nicht mehr aktuell". + */ + beginQueue(incidentId) { + if (this._incidentId !== incidentId) return; // andere Lage offen + if (!this._definition) return; // noch keine Pipeline-Definition geladen + // Aktuellen Stand sichern (deep-copy). Bei Mehrfach-Refresh ohne Cancel + // dazwischen wird der Snapshot bewusst ueberschrieben - er soll immer + // der "Stand kurz vor diesem Refresh" sein. + this._snapshotState = JSON.parse(JSON.stringify(this._stateByKey)); + // Alle Steps auf pending setzen + this._definition.forEach(s => { + if (this._stateByKey[s.key]) { + this._stateByKey[s.key].status = 'pending'; + } else { + this._stateByKey[s.key] = { status: 'pending', count_value: null, count_secondary: null, pass_number: 1 }; + } + }); + this._render(); + }, + + /** Restauriert den letzten Snapshot. Rueckgabe: true bei Erfolg, false wenn keiner da war. */ + _restoreSnapshot() { + if (!this._snapshotState) return false; + this._stateByKey = this._snapshotState; + this._snapshotState = null; + this._render(); + return true; + }, + + _onRefreshDoneSuccess(msg) { if (this._incidentId == null || (msg && msg.incident_id !== this._incidentId)) return; + this._snapshotState = null; // verworfen, neuer Stand wird vom API geladen // Daten frisch nachladen, damit Header (Dauer) und finale Zahlen passen setTimeout(() => { if (this._incidentId != null) this.bindToIncident(this._incidentId); }, 600); }, + _onRefreshDoneCancel(msg) { + if (this._incidentId == null || (msg && msg.incident_id !== this._incidentId)) return; + if (!this._restoreSnapshot()) { + // Kein Snapshot vorhanden (z.B. Page-Reload mitten im Refresh) -> wie bisher API-Reload + setTimeout(() => { + if (this._incidentId != null) this.bindToIncident(this._incidentId); + }, 600); + } + }, + + _onRefreshDoneError(msg) { + // Wie Cancel: vorheriger Stand zurueck (nicht im Mix-Zustand stehenbleiben) + this._onRefreshDoneCancel(msg); + }, + /** Vollbild-Pipeline (Tab "Analysepipeline") als 3x3-Snake rendern. */ _render() { const stage = document.getElementById('pipeline-stage'); From dc51ecafe8fc52c9123f1f5b24a450f58b52f172 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Sun, 3 May 2026 14:15:27 +0000 Subject: [PATCH 2/4] Pipeline-Snapshot: Mini-Pipeline auch zuruecksetzen beginQueue() und _restoreSnapshot() haben bisher nur _render() aufgerufen, aber NICHT _renderMini(). Daher blieben die kleinen Pipeline-Icons im "Aktualisierung laeuft"-Modal gruen, obwohl die Lage in Queue war. Fix: an beiden Stellen auch _renderMini() aufrufen. --- src/static/js/pipeline.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/static/js/pipeline.js b/src/static/js/pipeline.js index d38fdd6..f870d8e 100644 --- a/src/static/js/pipeline.js +++ b/src/static/js/pipeline.js @@ -191,6 +191,7 @@ const Pipeline = { } }); this._render(); + this._renderMini(); }, /** Restauriert den letzten Snapshot. Rueckgabe: true bei Erfolg, false wenn keiner da war. */ @@ -199,6 +200,7 @@ const Pipeline = { this._stateByKey = this._snapshotState; this._snapshotState = null; this._render(); + this._renderMini(); return true; }, From 77797f6027db0e06b7a489532da413f53c68ddf8 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Sun, 3 May 2026 14:18:17 +0000 Subject: [PATCH 3/4] Refresh-Modal: Titel je nach Status (queued/cancelling/laeuft) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bisher hing der Titel nur an state.isFirst -> stand auch "Aktualisierung laeuft" wenn die Lage tatsaechlich noch in der Queue wartete. Jetzt: - queued -> "In Warteschlange" (mit Position #N falls vorhanden) - cancelling -> "Wird abgebrochen…" - isFirst -> "Erste Recherche laeuft" - sonst -> "Aktualisierung laeuft" --- src/static/js/components.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/static/js/components.js b/src/static/js/components.js index 89762d0..b32dce0 100644 --- a/src/static/js/components.js +++ b/src/static/js/components.js @@ -354,9 +354,22 @@ const UI = { const minBtn = document.getElementById('progress-popup-minimize'); if (minBtn) minBtn.style.display = state.isFirst ? 'none' : ''; - // Title + // Title - haengt von Status ab (queued = wartet, cancelling = bricht ab, sonst laeuft) const titleEl = document.getElementById('progress-popup-title'); - if (titleEl) titleEl.textContent = state.isFirst ? 'Erste Recherche l\u00e4uft' : 'Aktualisierung l\u00e4uft'; + if (titleEl) { + let title; + if (status === 'queued') { + const pos = (state && state._queuePos) ? ' (#' + state._queuePos + ')' : ''; + title = 'In Warteschlange' + pos; + } else if (status === 'cancelling') { + title = 'Wird abgebrochen\u2026'; + } else if (state.isFirst) { + title = 'Erste Recherche l\u00e4uft'; + } else { + title = 'Aktualisierung l\u00e4uft'; + } + titleEl.textContent = title; + } // Multi-pass info const passEl = document.getElementById('progress-popup-pass'); From b1a0e97a34257f2aebd7e720fecb04dc0f5ee50d Mon Sep 17 00:00:00 2001 From: Claude Code Date: Sun, 3 May 2026 14:27:20 +0000 Subject: [PATCH 4/4] Pipeline: bei Lagen-Wechsel auf bereits-queued Lage automatisch beginQueue Wenn der User in der Sidebar auf eine Lage klickt, die schon in Queue wartet, ruft bindToIncident() die API auf und kriegt den letzten gespeicherten Pipeline-Stand (alles done = gruen). Das ist falsch fuer queued-Status. Fix: nach API-Load pruefen, ob die Lage in App._refreshingIncidents ist UND in UI._progressState mit step=queued -> beginQueue() selbst ausloesen. Damit zeigt die Pipeline grau, sobald man auf die queued-Lage wechselt. --- src/static/js/pipeline.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/static/js/pipeline.js b/src/static/js/pipeline.js index f870d8e..03979d5 100644 --- a/src/static/js/pipeline.js +++ b/src/static/js/pipeline.js @@ -104,6 +104,20 @@ const Pipeline = { this._render(); this._renderMini(); + + // Edge-Case: Lage ist gerade in Queue (z.B. via Lagen-Wechsel beim + // Klick in der Sidebar). API liefert den LETZTEN gespeicherten Stand + // (alles done = gruen), aber tatsaechlich wartet ein neuer Refresh. + // -> beginQueue() selbst ausloesen, damit Icons grau zeigen. + try { + if (typeof App !== 'undefined' && App._refreshingIncidents + && App._refreshingIncidents.has(incidentId) + && typeof UI !== 'undefined' && UI._progressState + && UI._progressState[incidentId] + && UI._progressState[incidentId].step === 'queued') { + this.beginQueue(incidentId); + } + } catch (e) { /* tolerant */ } } catch (e) { console.warn('Pipeline laden fehlgeschlagen:', e); this._renderEmpty('Pipeline-Daten konnten nicht geladen werden.');