fix: Lagen im Netzwerk-Modal sortiert — Live alphabetisch, dann Analyse alphabetisch
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -1,447 +1,454 @@
|
|||||||
/**
|
/**
|
||||||
* Netzwerkanalyse-Erweiterungen für App-Objekt.
|
* Netzwerkanalyse-Erweiterungen für App-Objekt.
|
||||||
* Wird nach app.js geladen und erweitert App um Netzwerk-Funktionalität.
|
* Wird nach app.js geladen und erweitert App um Netzwerk-Funktionalität.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// State-Erweiterung
|
// State-Erweiterung
|
||||||
App.networkAnalyses = [];
|
App.networkAnalyses = [];
|
||||||
App.currentNetworkId = null;
|
App.currentNetworkId = null;
|
||||||
App._networkGenerating = new Set();
|
App._networkGenerating = new Set();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Netzwerkanalysen laden und Sidebar rendern.
|
* Netzwerkanalysen laden und Sidebar rendern.
|
||||||
*/
|
*/
|
||||||
App.loadNetworkAnalyses = async function() {
|
App.loadNetworkAnalyses = async function() {
|
||||||
try {
|
try {
|
||||||
this.networkAnalyses = await API.listNetworkAnalyses();
|
this.networkAnalyses = await API.listNetworkAnalyses();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('Netzwerkanalysen laden fehlgeschlagen:', e);
|
console.warn('Netzwerkanalysen laden fehlgeschlagen:', e);
|
||||||
this.networkAnalyses = [];
|
this.networkAnalyses = [];
|
||||||
}
|
}
|
||||||
this.renderNetworkSidebar();
|
this.renderNetworkSidebar();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Netzwerkanalysen-Sektion in der Sidebar rendern.
|
* Netzwerkanalysen-Sektion in der Sidebar rendern.
|
||||||
*/
|
*/
|
||||||
App.renderNetworkSidebar = function() {
|
App.renderNetworkSidebar = function() {
|
||||||
var container = document.getElementById('network-analyses-list');
|
var container = document.getElementById('network-analyses-list');
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
var countEl = document.getElementById('count-network-analyses');
|
var countEl = document.getElementById('count-network-analyses');
|
||||||
if (countEl) countEl.textContent = '(' + this.networkAnalyses.length + ')';
|
if (countEl) countEl.textContent = '(' + this.networkAnalyses.length + ')';
|
||||||
|
|
||||||
if (this.networkAnalyses.length === 0) {
|
if (this.networkAnalyses.length === 0) {
|
||||||
container.innerHTML = '<div style="padding:8px 12px;font-size:12px;color:var(--text-tertiary);">Keine Netzwerkanalysen</div>';
|
container.innerHTML = '<div style="padding:8px 12px;font-size:12px;color:var(--text-tertiary);">Keine Netzwerkanalysen</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
container.innerHTML = this.networkAnalyses.map(function(na) {
|
container.innerHTML = this.networkAnalyses.map(function(na) {
|
||||||
var isActive = na.id === self.currentNetworkId;
|
var isActive = na.id === self.currentNetworkId;
|
||||||
var statusClass = na.status === 'generating' ? 'generating' : (na.status === 'error' ? 'error' : 'ready');
|
var statusClass = na.status === 'generating' ? 'generating' : (na.status === 'error' ? 'error' : 'ready');
|
||||||
var countText = na.status === 'ready' ? (na.entity_count + ' / ' + na.relation_count) : na.status === 'generating' ? '...' : '';
|
var countText = na.status === 'ready' ? (na.entity_count + ' / ' + na.relation_count) : na.status === 'generating' ? '...' : '';
|
||||||
return '<div class="sidebar-network-item' + (isActive ? ' active' : '') + '" onclick="App.selectNetworkAnalysis(' + na.id + ')">' +
|
return '<div class="sidebar-network-item' + (isActive ? ' active' : '') + '" onclick="App.selectNetworkAnalysis(' + na.id + ')">' +
|
||||||
'<svg class="network-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="5" r="3"/><circle cx="5" cy="19" r="3"/><circle cx="19" cy="19" r="3"/><line x1="12" y1="8" x2="5" y2="16"/><line x1="12" y1="8" x2="19" y2="16"/></svg>' +
|
'<svg class="network-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="5" r="3"/><circle cx="5" cy="19" r="3"/><circle cx="19" cy="19" r="3"/><line x1="12" y1="8" x2="5" y2="16"/><line x1="12" y1="8" x2="19" y2="16"/></svg>' +
|
||||||
'<span class="network-item-name" title="' + _escHtml(na.name) + '">' + _escHtml(na.name) + '</span>' +
|
'<span class="network-item-name" title="' + _escHtml(na.name) + '">' + _escHtml(na.name) + '</span>' +
|
||||||
'<span class="network-item-count">' + countText + '</span>' +
|
'<span class="network-item-count">' + countText + '</span>' +
|
||||||
'<span class="network-status-dot ' + statusClass + '"></span>' +
|
'<span class="network-status-dot ' + statusClass + '"></span>' +
|
||||||
'</div>';
|
'</div>';
|
||||||
}).join('');
|
}).join('');
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Netzwerkanalyse auswählen und anzeigen.
|
* Netzwerkanalyse auswählen und anzeigen.
|
||||||
*/
|
*/
|
||||||
App.selectNetworkAnalysis = async function(id) {
|
App.selectNetworkAnalysis = async function(id) {
|
||||||
this.currentNetworkId = id;
|
this.currentNetworkId = id;
|
||||||
this.currentIncidentId = null;
|
this.currentIncidentId = null;
|
||||||
localStorage.removeItem('selectedIncidentId');
|
localStorage.removeItem('selectedIncidentId');
|
||||||
localStorage.setItem('selectedNetworkId', id);
|
localStorage.setItem('selectedNetworkId', id);
|
||||||
|
|
||||||
// Views umschalten
|
// Views umschalten
|
||||||
document.getElementById('empty-state').style.display = 'none';
|
document.getElementById('empty-state').style.display = 'none';
|
||||||
document.getElementById('incident-view').style.display = 'none';
|
document.getElementById('incident-view').style.display = 'none';
|
||||||
document.getElementById('network-view').style.display = 'flex';
|
document.getElementById('network-view').style.display = 'flex';
|
||||||
|
|
||||||
// Sidebar aktualisieren
|
// Sidebar aktualisieren
|
||||||
this.renderSidebar();
|
this.renderSidebar();
|
||||||
this.renderNetworkSidebar();
|
this.renderNetworkSidebar();
|
||||||
|
|
||||||
// Analyse laden
|
// Analyse laden
|
||||||
try {
|
try {
|
||||||
var analysis = await API.getNetworkAnalysis(id);
|
var analysis = await API.getNetworkAnalysis(id);
|
||||||
this._renderNetworkHeader(analysis);
|
this._renderNetworkHeader(analysis);
|
||||||
|
|
||||||
if (analysis.status === 'ready') {
|
if (analysis.status === 'ready') {
|
||||||
this._hideNetworkProgress();
|
this._hideNetworkProgress();
|
||||||
var graphData = await API.getNetworkGraph(id);
|
var graphData = await API.getNetworkGraph(id);
|
||||||
NetworkGraph.init('network-graph-area', graphData);
|
NetworkGraph.init('network-graph-area', graphData);
|
||||||
this._setupNetworkFilters(graphData);
|
this._setupNetworkFilters(graphData);
|
||||||
|
|
||||||
// Update-Check
|
// Update-Check
|
||||||
try {
|
try {
|
||||||
var updateCheck = await API.checkNetworkUpdate(id);
|
var updateCheck = await API.checkNetworkUpdate(id);
|
||||||
var badge = document.getElementById('network-update-badge');
|
var badge = document.getElementById('network-update-badge');
|
||||||
if (badge) badge.style.display = updateCheck.has_update ? 'inline-flex' : 'none';
|
if (badge) badge.style.display = updateCheck.has_update ? 'inline-flex' : 'none';
|
||||||
} catch (e) { /* ignorieren */ }
|
} catch (e) { /* ignorieren */ }
|
||||||
} else if (analysis.status === 'generating') {
|
} else if (analysis.status === 'generating') {
|
||||||
this._showNetworkProgress('entity_extraction', 0);
|
this._showNetworkProgress('entity_extraction', 0);
|
||||||
} else if (analysis.status === 'error') {
|
} else if (analysis.status === 'error') {
|
||||||
this._hideNetworkProgress();
|
this._hideNetworkProgress();
|
||||||
var graphArea = document.getElementById('network-graph-area');
|
var graphArea = document.getElementById('network-graph-area');
|
||||||
if (graphArea) graphArea.innerHTML = '<div class="network-empty-state"><div class="network-empty-state-icon">⚠</div><div class="network-empty-state-text">Fehler bei der Generierung. Versuche es erneut.</div></div>';
|
if (graphArea) graphArea.innerHTML = '<div class="network-empty-state"><div class="network-empty-state-icon">⚠</div><div class="network-empty-state-text">Fehler bei der Generierung. Versuche es erneut.</div></div>';
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
UI.showToast('Fehler beim Laden der Netzwerkanalyse: ' + err.message, 'error');
|
UI.showToast('Fehler beim Laden der Netzwerkanalyse: ' + err.message, 'error');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Netzwerkanalyse-Header rendern.
|
* Netzwerkanalyse-Header rendern.
|
||||||
*/
|
*/
|
||||||
App._renderNetworkHeader = function(analysis) {
|
App._renderNetworkHeader = function(analysis) {
|
||||||
var el;
|
var el;
|
||||||
el = document.getElementById('network-title');
|
el = document.getElementById('network-title');
|
||||||
if (el) el.textContent = analysis.name;
|
if (el) el.textContent = analysis.name;
|
||||||
|
|
||||||
el = document.getElementById('network-entity-count');
|
el = document.getElementById('network-entity-count');
|
||||||
if (el) el.textContent = analysis.entity_count + ' Entitäten';
|
if (el) el.textContent = analysis.entity_count + ' Entitäten';
|
||||||
|
|
||||||
el = document.getElementById('network-relation-count');
|
el = document.getElementById('network-relation-count');
|
||||||
if (el) el.textContent = analysis.relation_count + ' Beziehungen';
|
if (el) el.textContent = analysis.relation_count + ' Beziehungen';
|
||||||
|
|
||||||
el = document.getElementById('network-incident-list-text');
|
el = document.getElementById('network-incident-list-text');
|
||||||
if (el) el.textContent = (analysis.incident_titles || []).join(', ') || '-';
|
if (el) el.textContent = (analysis.incident_titles || []).join(', ') || '-';
|
||||||
|
|
||||||
el = document.getElementById('network-last-generated');
|
el = document.getElementById('network-last-generated');
|
||||||
if (el) {
|
if (el) {
|
||||||
if (analysis.last_generated_at) {
|
if (analysis.last_generated_at) {
|
||||||
var d = parseUTC(analysis.last_generated_at) || new Date(analysis.last_generated_at);
|
var d = parseUTC(analysis.last_generated_at) || new Date(analysis.last_generated_at);
|
||||||
el.textContent = 'Generiert: ' + d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', timeZone: TIMEZONE }) + ' ' +
|
el.textContent = 'Generiert: ' + d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', timeZone: TIMEZONE }) + ' ' +
|
||||||
d.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', timeZone: TIMEZONE });
|
d.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', timeZone: TIMEZONE });
|
||||||
} else {
|
} else {
|
||||||
el.textContent = '';
|
el.textContent = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter-Controls in der Netzwerk-Sidebar aufsetzen.
|
* Filter-Controls in der Netzwerk-Sidebar aufsetzen.
|
||||||
*/
|
*/
|
||||||
App._setupNetworkFilters = function(graphData) {
|
App._setupNetworkFilters = function(graphData) {
|
||||||
// Typ-Filter-Buttons aktivieren
|
// Typ-Filter-Buttons aktivieren
|
||||||
var types = new Set();
|
var types = new Set();
|
||||||
(graphData.entities || []).forEach(function(e) { types.add(e.entity_type); });
|
(graphData.entities || []).forEach(function(e) { types.add(e.entity_type); });
|
||||||
var filterContainer = document.getElementById('network-type-filter-container');
|
var filterContainer = document.getElementById('network-type-filter-container');
|
||||||
if (filterContainer) {
|
if (filterContainer) {
|
||||||
var allTypes = ['person', 'organisation', 'location', 'event', 'military'];
|
var allTypes = ['person', 'organisation', 'location', 'event', 'military'];
|
||||||
var typeLabels = { person: 'Person', organisation: 'Organisation', location: 'Ort', event: 'Ereignis', military: 'Militär' };
|
var typeLabels = { person: 'Person', organisation: 'Organisation', location: 'Ort', event: 'Ereignis', military: 'Militär' };
|
||||||
filterContainer.innerHTML = allTypes.map(function(t) {
|
filterContainer.innerHTML = allTypes.map(function(t) {
|
||||||
var hasEntities = types.has(t);
|
var hasEntities = types.has(t);
|
||||||
return '<button class="network-type-filter active" data-type="' + t + '" onclick="App.toggleNetworkTypeFilter(this)" ' +
|
return '<button class="network-type-filter active" data-type="' + t + '" onclick="App.toggleNetworkTypeFilter(this)" ' +
|
||||||
(hasEntities ? '' : 'disabled style="opacity:0.3"') + '>' +
|
(hasEntities ? '' : 'disabled style="opacity:0.3"') + '>' +
|
||||||
'<span class="type-dot"></span><span>' + typeLabels[t] + '</span></button>';
|
'<span class="type-dot"></span><span>' + typeLabels[t] + '</span></button>';
|
||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gewicht-Slider
|
// Gewicht-Slider
|
||||||
var slider = document.getElementById('network-weight-slider');
|
var slider = document.getElementById('network-weight-slider');
|
||||||
if (slider) {
|
if (slider) {
|
||||||
slider.value = 1;
|
slider.value = 1;
|
||||||
slider.oninput = function() {
|
slider.oninput = function() {
|
||||||
var label = document.getElementById('network-weight-value');
|
var label = document.getElementById('network-weight-value');
|
||||||
if (label) label.textContent = this.value;
|
if (label) label.textContent = this.value;
|
||||||
NetworkGraph.filterByWeight(parseInt(this.value));
|
NetworkGraph.filterByWeight(parseInt(this.value));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Suche
|
// Suche
|
||||||
var searchInput = document.getElementById('network-search');
|
var searchInput = document.getElementById('network-search');
|
||||||
if (searchInput) {
|
if (searchInput) {
|
||||||
searchInput.value = '';
|
searchInput.value = '';
|
||||||
var timer = null;
|
var timer = null;
|
||||||
searchInput.oninput = function() {
|
searchInput.oninput = function() {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
var val = this.value;
|
var val = this.value;
|
||||||
timer = setTimeout(function() {
|
timer = setTimeout(function() {
|
||||||
NetworkGraph.search(val);
|
NetworkGraph.search(val);
|
||||||
}, 250);
|
}, 250);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Typ-Filter toggle.
|
* Typ-Filter toggle.
|
||||||
*/
|
*/
|
||||||
App.toggleNetworkTypeFilter = function(btn) {
|
App.toggleNetworkTypeFilter = function(btn) {
|
||||||
btn.classList.toggle('active');
|
btn.classList.toggle('active');
|
||||||
var activeTypes = [];
|
var activeTypes = [];
|
||||||
document.querySelectorAll('.network-type-filter.active').forEach(function(b) {
|
document.querySelectorAll('.network-type-filter.active').forEach(function(b) {
|
||||||
activeTypes.push(b.dataset.type);
|
activeTypes.push(b.dataset.type);
|
||||||
});
|
});
|
||||||
NetworkGraph.filterByType(new Set(activeTypes));
|
NetworkGraph.filterByType(new Set(activeTypes));
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Progress-Bar anzeigen.
|
* Progress-Bar anzeigen.
|
||||||
*/
|
*/
|
||||||
App._showNetworkProgress = function(phase, progress) {
|
App._showNetworkProgress = function(phase, progress) {
|
||||||
var bar = document.getElementById('network-progress-bar');
|
var bar = document.getElementById('network-progress-bar');
|
||||||
if (bar) bar.style.display = 'block';
|
if (bar) bar.style.display = 'block';
|
||||||
|
|
||||||
var steps = ['entity_extraction', 'relationship_extraction', 'correction'];
|
var steps = ['entity_extraction', 'relationship_extraction', 'correction'];
|
||||||
var stepEls = document.querySelectorAll('.network-progress-step');
|
var stepEls = document.querySelectorAll('.network-progress-step');
|
||||||
var connectorEls = document.querySelectorAll('.network-progress-connector');
|
var connectorEls = document.querySelectorAll('.network-progress-connector');
|
||||||
var phaseIndex = steps.indexOf(phase);
|
var phaseIndex = steps.indexOf(phase);
|
||||||
|
|
||||||
stepEls.forEach(function(el, i) {
|
stepEls.forEach(function(el, i) {
|
||||||
el.classList.remove('active', 'done');
|
el.classList.remove('active', 'done');
|
||||||
if (i < phaseIndex) el.classList.add('done');
|
if (i < phaseIndex) el.classList.add('done');
|
||||||
else if (i === phaseIndex) el.classList.add('active');
|
else if (i === phaseIndex) el.classList.add('active');
|
||||||
});
|
});
|
||||||
|
|
||||||
connectorEls.forEach(function(el, i) {
|
connectorEls.forEach(function(el, i) {
|
||||||
el.classList.remove('done');
|
el.classList.remove('done');
|
||||||
if (i < phaseIndex) el.classList.add('done');
|
if (i < phaseIndex) el.classList.add('done');
|
||||||
});
|
});
|
||||||
|
|
||||||
var fill = document.getElementById('network-progress-fill');
|
var fill = document.getElementById('network-progress-fill');
|
||||||
if (fill) {
|
if (fill) {
|
||||||
var pct = ((phaseIndex / steps.length) * 100) + (progress || 0) * (100 / steps.length) / 100;
|
var pct = ((phaseIndex / steps.length) * 100) + (progress || 0) * (100 / steps.length) / 100;
|
||||||
fill.style.width = Math.min(100, pct) + '%';
|
fill.style.width = Math.min(100, pct) + '%';
|
||||||
}
|
}
|
||||||
|
|
||||||
var label = document.getElementById('network-progress-label');
|
var label = document.getElementById('network-progress-label');
|
||||||
if (label) {
|
if (label) {
|
||||||
var labels = { entity_extraction: 'Entitäten werden extrahiert...', relationship_extraction: 'Beziehungen werden analysiert...', correction: 'Korrekturen werden angewendet...' };
|
var labels = { entity_extraction: 'Entitäten werden extrahiert...', relationship_extraction: 'Beziehungen werden analysiert...', correction: 'Korrekturen werden angewendet...' };
|
||||||
label.textContent = labels[phase] || 'Wird verarbeitet...';
|
label.textContent = labels[phase] || 'Wird verarbeitet...';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
App._hideNetworkProgress = function() {
|
App._hideNetworkProgress = function() {
|
||||||
var bar = document.getElementById('network-progress-bar');
|
var bar = document.getElementById('network-progress-bar');
|
||||||
if (bar) bar.style.display = 'none';
|
if (bar) bar.style.display = 'none';
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modal: Neue Netzwerkanalyse öffnen.
|
* Modal: Neue Netzwerkanalyse öffnen.
|
||||||
*/
|
*/
|
||||||
App.openNetworkModal = async function() {
|
App.openNetworkModal = async function() {
|
||||||
var list = document.getElementById('network-incident-options');
|
var list = document.getElementById('network-incident-options');
|
||||||
if (list) list.innerHTML = '<div style="padding:12px;color:var(--text-disabled);font-size:12px;">Lade Lagen...</div>';
|
if (list) list.innerHTML = '<div style="padding:12px;color:var(--text-disabled);font-size:12px;">Lade Lagen...</div>';
|
||||||
|
|
||||||
openModal('modal-network-new');
|
openModal('modal-network-new');
|
||||||
|
|
||||||
// Lagen laden
|
// Lagen laden
|
||||||
try {
|
try {
|
||||||
var incidents = await API.listIncidents();
|
var incidents = await API.listIncidents();
|
||||||
if (list) {
|
// Sortierung: zuerst Live (adhoc) alphabetisch, dann Analyse (research) alphabetisch
|
||||||
list.innerHTML = incidents.map(function(inc) {
|
incidents.sort(function(a, b) {
|
||||||
var typeLabel = inc.type === 'research' ? 'Analyse' : 'Live';
|
var typeA = (a.type === 'research') ? 1 : 0;
|
||||||
return '<label class="network-incident-option">' +
|
var typeB = (b.type === 'research') ? 1 : 0;
|
||||||
'<input type="checkbox" value="' + inc.id + '" class="network-incident-cb">' +
|
if (typeA !== typeB) return typeA - typeB;
|
||||||
'<span>' + _escHtml(inc.title) + '</span>' +
|
return (a.title || '').localeCompare(b.title || '', 'de');
|
||||||
'<span class="incident-option-type">' + typeLabel + '</span>' +
|
});
|
||||||
'</label>';
|
if (list) {
|
||||||
}).join('');
|
list.innerHTML = incidents.map(function(inc) {
|
||||||
}
|
var typeLabel = inc.type === 'research' ? 'Analyse' : 'Live';
|
||||||
} catch (e) {
|
return '<label class="network-incident-option">' +
|
||||||
if (list) list.innerHTML = '<div style="padding:12px;color:var(--error);font-size:12px;">Fehler beim Laden der Lagen</div>';
|
'<input type="checkbox" value="' + inc.id + '" class="network-incident-cb">' +
|
||||||
}
|
'<span>' + _escHtml(inc.title) + '</span>' +
|
||||||
|
'<span class="incident-option-type">' + typeLabel + '</span>' +
|
||||||
// Name-Feld leeren
|
'</label>';
|
||||||
var nameField = document.getElementById('network-name');
|
}).join('');
|
||||||
if (nameField) nameField.value = '';
|
}
|
||||||
|
} catch (e) {
|
||||||
// Suchfeld leeren
|
if (list) list.innerHTML = '<div style="padding:12px;color:var(--error);font-size:12px;">Fehler beim Laden der Lagen</div>';
|
||||||
var searchField = document.getElementById('network-incident-search');
|
}
|
||||||
if (searchField) {
|
|
||||||
searchField.value = '';
|
// Name-Feld leeren
|
||||||
searchField.oninput = function() {
|
var nameField = document.getElementById('network-name');
|
||||||
var term = this.value.toLowerCase();
|
if (nameField) nameField.value = '';
|
||||||
document.querySelectorAll('.network-incident-option').forEach(function(opt) {
|
|
||||||
var text = opt.textContent.toLowerCase();
|
// Suchfeld leeren
|
||||||
opt.style.display = text.includes(term) ? '' : 'none';
|
var searchField = document.getElementById('network-incident-search');
|
||||||
});
|
if (searchField) {
|
||||||
};
|
searchField.value = '';
|
||||||
}
|
searchField.oninput = function() {
|
||||||
};
|
var term = this.value.toLowerCase();
|
||||||
|
document.querySelectorAll('.network-incident-option').forEach(function(opt) {
|
||||||
/**
|
var text = opt.textContent.toLowerCase();
|
||||||
* Netzwerkanalyse erstellen.
|
opt.style.display = text.includes(term) ? '' : 'none';
|
||||||
*/
|
});
|
||||||
App.submitNetworkAnalysis = async function(e) {
|
};
|
||||||
if (e) e.preventDefault();
|
}
|
||||||
|
};
|
||||||
var name = (document.getElementById('network-name').value || '').trim();
|
|
||||||
if (!name) {
|
/**
|
||||||
UI.showToast('Bitte einen Namen eingeben.', 'warning');
|
* Netzwerkanalyse erstellen.
|
||||||
return;
|
*/
|
||||||
}
|
App.submitNetworkAnalysis = async function(e) {
|
||||||
|
if (e) e.preventDefault();
|
||||||
var incidentIds = [];
|
|
||||||
document.querySelectorAll('.network-incident-cb:checked').forEach(function(cb) {
|
var name = (document.getElementById('network-name').value || '').trim();
|
||||||
incidentIds.push(parseInt(cb.value));
|
if (!name) {
|
||||||
});
|
UI.showToast('Bitte einen Namen eingeben.', 'warning');
|
||||||
|
return;
|
||||||
if (incidentIds.length === 0) {
|
}
|
||||||
UI.showToast('Bitte mindestens eine Lage auswählen.', 'warning');
|
|
||||||
return;
|
var incidentIds = [];
|
||||||
}
|
document.querySelectorAll('.network-incident-cb:checked').forEach(function(cb) {
|
||||||
|
incidentIds.push(parseInt(cb.value));
|
||||||
var btn = document.getElementById('network-submit-btn');
|
});
|
||||||
if (btn) btn.disabled = true;
|
|
||||||
|
if (incidentIds.length === 0) {
|
||||||
try {
|
UI.showToast('Bitte mindestens eine Lage auswählen.', 'warning');
|
||||||
var result = await API.createNetworkAnalysis({ name: name, incident_ids: incidentIds });
|
return;
|
||||||
closeModal('modal-network-new');
|
}
|
||||||
await this.loadNetworkAnalyses();
|
|
||||||
await this.selectNetworkAnalysis(result.id);
|
var btn = document.getElementById('network-submit-btn');
|
||||||
UI.showToast('Netzwerkanalyse gestartet.', 'success');
|
if (btn) btn.disabled = true;
|
||||||
} catch (err) {
|
|
||||||
UI.showToast('Fehler: ' + err.message, 'error');
|
try {
|
||||||
} finally {
|
var result = await API.createNetworkAnalysis({ name: name, incident_ids: incidentIds });
|
||||||
if (btn) btn.disabled = false;
|
closeModal('modal-network-new');
|
||||||
}
|
await this.loadNetworkAnalyses();
|
||||||
};
|
await this.selectNetworkAnalysis(result.id);
|
||||||
|
UI.showToast('Netzwerkanalyse gestartet.', 'success');
|
||||||
/**
|
} catch (err) {
|
||||||
* Netzwerkanalyse neu generieren.
|
UI.showToast('Fehler: ' + err.message, 'error');
|
||||||
*/
|
} finally {
|
||||||
App.regenerateNetwork = async function() {
|
if (btn) btn.disabled = false;
|
||||||
if (!this.currentNetworkId) return;
|
}
|
||||||
if (!await confirmDialog('Netzwerkanalyse neu generieren? Bestehende Daten werden überschrieben.')) return;
|
};
|
||||||
|
|
||||||
try {
|
/**
|
||||||
await API.regenerateNetwork(this.currentNetworkId);
|
* Netzwerkanalyse neu generieren.
|
||||||
this._showNetworkProgress('entity_extraction', 0);
|
*/
|
||||||
await this.loadNetworkAnalyses();
|
App.regenerateNetwork = async function() {
|
||||||
UI.showToast('Neugenerierung gestartet.', 'success');
|
if (!this.currentNetworkId) return;
|
||||||
} catch (err) {
|
if (!await confirmDialog('Netzwerkanalyse neu generieren? Bestehende Daten werden überschrieben.')) return;
|
||||||
UI.showToast('Fehler: ' + err.message, 'error');
|
|
||||||
}
|
try {
|
||||||
};
|
await API.regenerateNetwork(this.currentNetworkId);
|
||||||
|
this._showNetworkProgress('entity_extraction', 0);
|
||||||
/**
|
await this.loadNetworkAnalyses();
|
||||||
* Netzwerkanalyse löschen.
|
UI.showToast('Neugenerierung gestartet.', 'success');
|
||||||
*/
|
} catch (err) {
|
||||||
App.deleteNetworkAnalysis = async function() {
|
UI.showToast('Fehler: ' + err.message, 'error');
|
||||||
if (!this.currentNetworkId) return;
|
}
|
||||||
if (!await confirmDialog('Netzwerkanalyse wirklich löschen? Alle Daten gehen verloren.')) return;
|
};
|
||||||
|
|
||||||
try {
|
/**
|
||||||
await API.deleteNetworkAnalysis(this.currentNetworkId);
|
* Netzwerkanalyse löschen.
|
||||||
this.currentNetworkId = null;
|
*/
|
||||||
localStorage.removeItem('selectedNetworkId');
|
App.deleteNetworkAnalysis = async function() {
|
||||||
NetworkGraph.destroy();
|
if (!this.currentNetworkId) return;
|
||||||
document.getElementById('network-view').style.display = 'none';
|
if (!await confirmDialog('Netzwerkanalyse wirklich löschen? Alle Daten gehen verloren.')) return;
|
||||||
document.getElementById('empty-state').style.display = 'flex';
|
|
||||||
await this.loadNetworkAnalyses();
|
try {
|
||||||
UI.showToast('Netzwerkanalyse gelöscht.', 'success');
|
await API.deleteNetworkAnalysis(this.currentNetworkId);
|
||||||
} catch (err) {
|
this.currentNetworkId = null;
|
||||||
UI.showToast('Fehler: ' + err.message, 'error');
|
localStorage.removeItem('selectedNetworkId');
|
||||||
}
|
NetworkGraph.destroy();
|
||||||
};
|
document.getElementById('network-view').style.display = 'none';
|
||||||
|
document.getElementById('empty-state').style.display = 'flex';
|
||||||
/**
|
await this.loadNetworkAnalyses();
|
||||||
* Netzwerkanalyse exportieren.
|
UI.showToast('Netzwerkanalyse gelöscht.', 'success');
|
||||||
*/
|
} catch (err) {
|
||||||
App.exportNetwork = async function(format) {
|
UI.showToast('Fehler: ' + err.message, 'error');
|
||||||
if (!this.currentNetworkId) return;
|
}
|
||||||
|
};
|
||||||
if (format === 'png') {
|
|
||||||
NetworkGraph.exportPNG();
|
/**
|
||||||
return;
|
* Netzwerkanalyse exportieren.
|
||||||
}
|
*/
|
||||||
|
App.exportNetwork = async function(format) {
|
||||||
try {
|
if (!this.currentNetworkId) return;
|
||||||
var resp = await API.exportNetworkAnalysis(this.currentNetworkId, format);
|
|
||||||
if (!resp.ok) throw new Error('Export fehlgeschlagen');
|
if (format === 'png') {
|
||||||
var blob = await resp.blob();
|
NetworkGraph.exportPNG();
|
||||||
var url = URL.createObjectURL(blob);
|
return;
|
||||||
var a = document.createElement('a');
|
}
|
||||||
a.href = url;
|
|
||||||
a.download = 'netzwerk-' + this.currentNetworkId + '.' + format;
|
try {
|
||||||
a.click();
|
var resp = await API.exportNetworkAnalysis(this.currentNetworkId, format);
|
||||||
URL.revokeObjectURL(url);
|
if (!resp.ok) throw new Error('Export fehlgeschlagen');
|
||||||
} catch (err) {
|
var blob = await resp.blob();
|
||||||
UI.showToast('Export fehlgeschlagen: ' + err.message, 'error');
|
var url = URL.createObjectURL(blob);
|
||||||
}
|
var a = document.createElement('a');
|
||||||
};
|
a.href = url;
|
||||||
|
a.download = 'netzwerk-' + this.currentNetworkId + '.' + format;
|
||||||
/**
|
a.click();
|
||||||
* WebSocket-Handler für Netzwerk-Events.
|
URL.revokeObjectURL(url);
|
||||||
*/
|
} catch (err) {
|
||||||
App._handleNetworkStatus = function(msg) {
|
UI.showToast('Export fehlgeschlagen: ' + err.message, 'error');
|
||||||
if (msg.analysis_id === this.currentNetworkId) {
|
}
|
||||||
this._showNetworkProgress(msg.phase, msg.progress || 0);
|
};
|
||||||
}
|
|
||||||
};
|
/**
|
||||||
|
* WebSocket-Handler für Netzwerk-Events.
|
||||||
App._handleNetworkComplete = async function(msg) {
|
*/
|
||||||
this._networkGenerating.delete(msg.analysis_id);
|
App._handleNetworkStatus = function(msg) {
|
||||||
|
if (msg.analysis_id === this.currentNetworkId) {
|
||||||
if (msg.analysis_id === this.currentNetworkId) {
|
this._showNetworkProgress(msg.phase, msg.progress || 0);
|
||||||
this._hideNetworkProgress();
|
}
|
||||||
// Graph neu laden
|
};
|
||||||
try {
|
|
||||||
var graphData = await API.getNetworkGraph(msg.analysis_id);
|
App._handleNetworkComplete = async function(msg) {
|
||||||
NetworkGraph.init('network-graph-area', graphData);
|
this._networkGenerating.delete(msg.analysis_id);
|
||||||
this._setupNetworkFilters(graphData);
|
|
||||||
|
if (msg.analysis_id === this.currentNetworkId) {
|
||||||
var analysis = await API.getNetworkAnalysis(msg.analysis_id);
|
this._hideNetworkProgress();
|
||||||
this._renderNetworkHeader(analysis);
|
// Graph neu laden
|
||||||
} catch (e) {
|
try {
|
||||||
console.error('Graph nach Generierung laden fehlgeschlagen:', e);
|
var graphData = await API.getNetworkGraph(msg.analysis_id);
|
||||||
}
|
NetworkGraph.init('network-graph-area', graphData);
|
||||||
UI.showToast('Netzwerkanalyse fertig: ' + (msg.entity_count || 0) + ' Entitäten, ' + (msg.relation_count || 0) + ' Beziehungen', 'success');
|
this._setupNetworkFilters(graphData);
|
||||||
}
|
|
||||||
|
var analysis = await API.getNetworkAnalysis(msg.analysis_id);
|
||||||
await this.loadNetworkAnalyses();
|
this._renderNetworkHeader(analysis);
|
||||||
};
|
} catch (e) {
|
||||||
|
console.error('Graph nach Generierung laden fehlgeschlagen:', e);
|
||||||
App._handleNetworkError = function(msg) {
|
}
|
||||||
this._networkGenerating.delete(msg.analysis_id);
|
UI.showToast('Netzwerkanalyse fertig: ' + (msg.entity_count || 0) + ' Entitäten, ' + (msg.relation_count || 0) + ' Beziehungen', 'success');
|
||||||
|
}
|
||||||
if (msg.analysis_id === this.currentNetworkId) {
|
|
||||||
this._hideNetworkProgress();
|
await this.loadNetworkAnalyses();
|
||||||
var graphArea = document.getElementById('network-graph-area');
|
};
|
||||||
if (graphArea) graphArea.innerHTML = '<div class="network-empty-state"><div class="network-empty-state-icon">⚠</div><div class="network-empty-state-text">Fehler: ' + _escHtml(msg.error || 'Unbekannter Fehler') + '</div></div>';
|
|
||||||
}
|
App._handleNetworkError = function(msg) {
|
||||||
|
this._networkGenerating.delete(msg.analysis_id);
|
||||||
UI.showToast('Netzwerkanalyse fehlgeschlagen: ' + (msg.error || 'Unbekannter Fehler'), 'error');
|
|
||||||
this.loadNetworkAnalyses();
|
if (msg.analysis_id === this.currentNetworkId) {
|
||||||
};
|
this._hideNetworkProgress();
|
||||||
|
var graphArea = document.getElementById('network-graph-area');
|
||||||
/**
|
if (graphArea) graphArea.innerHTML = '<div class="network-empty-state"><div class="network-empty-state-icon">⚠</div><div class="network-empty-state-text">Fehler: ' + _escHtml(msg.error || 'Unbekannter Fehler') + '</div></div>';
|
||||||
* Cluster isolieren (nur verbundene Knoten zeigen).
|
}
|
||||||
*/
|
|
||||||
App.isolateNetworkCluster = function() {
|
UI.showToast('Netzwerkanalyse fehlgeschlagen: ' + (msg.error || 'Unbekannter Fehler'), 'error');
|
||||||
if (NetworkGraph._selectedNode) {
|
this.loadNetworkAnalyses();
|
||||||
NetworkGraph.isolateCluster(NetworkGraph._selectedNode.id);
|
};
|
||||||
}
|
|
||||||
};
|
/**
|
||||||
|
* Cluster isolieren (nur verbundene Knoten zeigen).
|
||||||
/**
|
*/
|
||||||
* Graph-Ansicht zurücksetzen.
|
App.isolateNetworkCluster = function() {
|
||||||
*/
|
if (NetworkGraph._selectedNode) {
|
||||||
App.resetNetworkView = function() {
|
NetworkGraph.isolateCluster(NetworkGraph._selectedNode.id);
|
||||||
NetworkGraph.resetView();
|
}
|
||||||
// Typ-Filter zurücksetzen
|
};
|
||||||
document.querySelectorAll('.network-type-filter').forEach(function(btn) {
|
|
||||||
if (!btn.disabled) btn.classList.add('active');
|
/**
|
||||||
});
|
* Graph-Ansicht zurücksetzen.
|
||||||
var slider = document.getElementById('network-weight-slider');
|
*/
|
||||||
if (slider) { slider.value = 1; var lbl = document.getElementById('network-weight-value'); if (lbl) lbl.textContent = '1'; }
|
App.resetNetworkView = function() {
|
||||||
var search = document.getElementById('network-search');
|
NetworkGraph.resetView();
|
||||||
if (search) search.value = '';
|
// Typ-Filter zurücksetzen
|
||||||
};
|
document.querySelectorAll('.network-type-filter').forEach(function(btn) {
|
||||||
|
if (!btn.disabled) btn.classList.add('active');
|
||||||
// HTML-Escape Hilfsfunktion (falls nicht global verfügbar)
|
});
|
||||||
function _escHtml(text) {
|
var slider = document.getElementById('network-weight-slider');
|
||||||
if (typeof UI !== 'undefined' && UI.escape) return UI.escape(text);
|
if (slider) { slider.value = 1; var lbl = document.getElementById('network-weight-value'); if (lbl) lbl.textContent = '1'; }
|
||||||
var d = document.createElement('div');
|
var search = document.getElementById('network-search');
|
||||||
d.textContent = text || '';
|
if (search) search.value = '';
|
||||||
return d.innerHTML;
|
};
|
||||||
}
|
|
||||||
|
// HTML-Escape Hilfsfunktion (falls nicht global verfügbar)
|
||||||
|
function _escHtml(text) {
|
||||||
|
if (typeof UI !== 'undefined' && UI.escape) return UI.escape(text);
|
||||||
|
var d = document.createElement('div');
|
||||||
|
d.textContent = text || '';
|
||||||
|
return d.innerHTML;
|
||||||
|
}
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren