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.
Dieser Commit ist enthalten in:
Claude Code
2026-05-03 14:10:56 +00:00
Ursprung 2a654cc882
Commit 31fa17465a
2 geänderte Dateien mit 66 neuen und 5 gelöschten Zeilen

Datei anzeigen

@@ -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');