Promote develop → main (2026-05-03 14:30 UTC) #17

Zusammengeführt
IntelSight_Admin hat 4 Commits von develop nach main 2026-05-03 16:30:36 +02:00 zusammengeführt
3 geänderte Dateien mit 97 neuen und 7 gelöschten Zeilen

Datei anzeigen

@@ -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 {

Datei anzeigen

@@ -354,9 +354,22 @@ const UI = {
const minBtn = document.getElementById('progress-popup-minimize'); const minBtn = document.getElementById('progress-popup-minimize');
if (minBtn) minBtn.style.display = state.isFirst ? 'none' : ''; 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'); 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 // Multi-pass info
const passEl = document.getElementById('progress-popup-pass'); const passEl = document.getElementById('progress-popup-pass');

Datei anzeigen

@@ -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;
@@ -101,6 +104,20 @@ const Pipeline = {
this._render(); this._render();
this._renderMini(); 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) { } catch (e) {
console.warn('Pipeline laden fehlgeschlagen:', e); console.warn('Pipeline laden fehlgeschlagen:', e);
this._renderEmpty('Pipeline-Daten konnten nicht geladen werden.'); this._renderEmpty('Pipeline-Daten konnten nicht geladen werden.');
@@ -166,14 +183,65 @@ 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();
this._renderMini();
},
/** 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();
this._renderMini();
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');