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:
@@ -618,6 +618,10 @@ const App = {
|
|||||||
const inc = this.incidents.find(i => i.id === id);
|
const inc = this.incidents.find(i => i.id === id);
|
||||||
const isFirst = inc && !inc.has_summary;
|
const isFirst = inc && !inc.has_summary;
|
||||||
UI.showProgress('queued', { queue_position: idx + 1 }, id, isFirst);
|
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);
|
this._updateRefreshButton(true);
|
||||||
// showProgress called via handleStatusUpdate
|
// showProgress called via handleStatusUpdate
|
||||||
const result = await API.refreshIncident(this.currentIncidentId);
|
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') {
|
if (result && result.status === 'skipped') {
|
||||||
UI.showToast('Aktualisierung ist in der Warteschlange und wird ausgefuehrt, sobald die aktuelle Recherche abgeschlossen ist.', 'info');
|
UI.showToast('Aktualisierung ist in der Warteschlange und wird ausgefuehrt, sobald die aktuelle Recherche abgeschlossen ist.', 'info');
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const Pipeline = {
|
|||||||
_incidentId: null,
|
_incidentId: null,
|
||||||
_definition: null, // PIPELINE_STEPS vom Backend
|
_definition: null, // PIPELINE_STEPS vom Backend
|
||||||
_stateByKey: {}, // step_key -> {status, count_value, count_secondary, pass_number}
|
_stateByKey: {}, // step_key -> {status, count_value, count_secondary, pass_number}
|
||||||
|
_snapshotState: null, // deep-copy von _stateByKey vor Refresh-Start (fuer Cancel-Restore)
|
||||||
_isResearch: false,
|
_isResearch: false,
|
||||||
_passTotal: 1,
|
_passTotal: 1,
|
||||||
_lastRefreshHeader: null,
|
_lastRefreshHeader: null,
|
||||||
@@ -42,10 +43,11 @@ const Pipeline = {
|
|||||||
if (this._wsBound) return;
|
if (this._wsBound) return;
|
||||||
if (typeof WS !== 'undefined' && WS.on) {
|
if (typeof WS !== 'undefined' && WS.on) {
|
||||||
WS.on('pipeline_step', (msg) => this._onWsStep(msg));
|
WS.on('pipeline_step', (msg) => this._onWsStep(msg));
|
||||||
// Bei Refresh-Complete den finalen Stand neu laden, damit Zahlen gefroren sichtbar bleiben
|
// Erfolg: API-State neu laden (finaler Stand sichtbar)
|
||||||
WS.on('refresh_complete', (msg) => this._onRefreshDone(msg));
|
WS.on('refresh_complete', (msg) => this._onRefreshDoneSuccess(msg));
|
||||||
WS.on('refresh_cancelled', (msg) => this._onRefreshDone(msg));
|
// Cancel/Error: vor-Refresh-Snapshot zurueckspielen, damit Pipeline nicht im Mix-Zustand stehen bleibt
|
||||||
WS.on('refresh_error', (msg) => this._onRefreshDone(msg));
|
WS.on('refresh_cancelled', (msg) => this._onRefreshDoneCancel(msg));
|
||||||
|
WS.on('refresh_error', (msg) => this._onRefreshDoneError(msg));
|
||||||
this._wsBound = true;
|
this._wsBound = true;
|
||||||
}
|
}
|
||||||
// Hover-Tooltip-Element vorbereiten
|
// Hover-Tooltip-Element vorbereiten
|
||||||
@@ -68,6 +70,7 @@ const Pipeline = {
|
|||||||
async bindToIncident(incidentId) {
|
async bindToIncident(incidentId) {
|
||||||
this._incidentId = incidentId;
|
this._incidentId = incidentId;
|
||||||
this._stateByKey = {};
|
this._stateByKey = {};
|
||||||
|
this._snapshotState = null; // Snapshot ist immer lagen-spezifisch
|
||||||
this._isResearch = false;
|
this._isResearch = false;
|
||||||
this._passTotal = 1;
|
this._passTotal = 1;
|
||||||
this._lastRefreshHeader = null;
|
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;
|
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
|
// Daten frisch nachladen, damit Header (Dauer) und finale Zahlen passen
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this._incidentId != null) this.bindToIncident(this._incidentId);
|
if (this._incidentId != null) this.bindToIncident(this._incidentId);
|
||||||
}, 600);
|
}, 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. */
|
/** Vollbild-Pipeline (Tab "Analysepipeline") als 3x3-Snake rendern. */
|
||||||
_render() {
|
_render() {
|
||||||
const stage = document.getElementById('pipeline-stage');
|
const stage = document.getElementById('pipeline-stage');
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren