Großes Cleanup: Bugs fixen, Features fertigstellen, toten Code entfernen

Bugs behoben:
- handleEdit() async keyword hinzugefügt (E-Mail-Checkboxen funktionieren jetzt)
- parseUTC() Funktion definiert (Fortschritts-Timer nutzt Server-Startzeit)
- Status cancelling wird im Frontend korrekt angezeigt

Features fertiggestellt:
- Sidebar: Lagen nach Typ getrennt (adhoc/research) mit Zählern
- Quellen-Bearbeiten: Edit-Button pro Quelle, Formular vorausfüllen
- Lizenz-Info: Org-Name und Lizenzstatus im Header angezeigt

Toter Code entfernt:
- 5 verwaiste Dateien gelöscht (alte rss_parser, style.css, components.js, layout.js, setup_users)
- 6 ungenutzte Pydantic Models entfernt
- Ungenutzte Funktionen/Imports in auth.py, routers, agents, config
- Tote API-Methoden, Legacy-UI-Methoden, verwaiste WS-Handler
- Abgeschlossene DB-Migrationen aufgeräumt

Sonstiges:
- requirements.txt: passlib[bcrypt] durch bcrypt ersetzt
- Umlaute korrigiert (index.html)
- CSS: incident-type-label → incident-type-badge, .login-success hinzugefügt
- Schließen statt Schliessen im Feedback-Modal
Dieser Commit ist enthalten in:
claude-dev
2026-03-04 18:45:38 +01:00
Ursprung 2a155c084d
Commit 71296edb97
23 geänderte Dateien mit 269 neuen und 4768 gelöschten Zeilen

Datei anzeigen

@@ -370,7 +370,6 @@ const App = {
currentIncidentId: null,
incidents: [],
_originalTitle: document.title,
_notificationCount: 0,
_refreshingIncidents: new Set(),
_editingIncidentId: null,
_currentArticles: [],
@@ -403,6 +402,42 @@ const App = {
const user = await API.getMe();
this._currentUsername = user.username;
document.getElementById('header-user').textContent = user.username;
// Org-Name anzeigen
const orgNameEl = document.getElementById('header-org-name');
if (orgNameEl && user.org_name) {
orgNameEl.textContent = user.org_name;
orgNameEl.title = user.org_name;
}
// Lizenz-Badge anzeigen
const badgeEl = document.getElementById('header-license-badge');
if (badgeEl) {
const licenseLabels = {
trial: 'Trial',
annual: 'Annual',
permanent: 'Permanent',
expired: 'Abgelaufen',
unknown: 'Unbekannt'
};
const status = user.read_only ? 'expired' : (user.license_status || 'unknown');
const cssClass = user.read_only ? 'license-expired'
: user.license_type === 'trial' ? 'license-trial'
: user.license_type === 'annual' ? 'license-annual'
: user.license_type === 'permanent' ? 'license-permanent'
: 'license-unknown';
const label = user.read_only ? 'Abgelaufen'
: licenseLabels[user.license_type] || licenseLabels[user.license_status] || 'Unbekannt';
badgeEl.textContent = label;
badgeEl.className = 'header-license-badge ' + cssClass;
}
// Warnung bei abgelaufener Lizenz
const warningEl = document.getElementById('header-license-warning');
if (warningEl && user.read_only) {
warningEl.textContent = 'Lizenz abgelaufen – nur Lesezugriff';
warningEl.classList.add('visible');
}
} catch {
window.location.href = '/';
return;
@@ -432,7 +467,6 @@ const App = {
WS.connect();
WS.on('status_update', (msg) => this.handleStatusUpdate(msg));
WS.on('refresh_complete', (msg) => this.handleRefreshComplete(msg));
WS.on('notification', (msg) => this.handleNotification(msg));
WS.on('refresh_summary', (msg) => this.handleRefreshSummary(msg));
WS.on('refresh_error', (msg) => this.handleRefreshError(msg));
WS.on('refresh_cancelled', (msg) => this.handleRefreshCancelled(msg));
@@ -476,6 +510,7 @@ const App = {
renderSidebar() {
const activeContainer = document.getElementById('active-incidents');
const researchContainer = document.getElementById('active-research');
const archivedContainer = document.getElementById('archived-incidents');
// Filter-Buttons aktualisieren
@@ -491,19 +526,34 @@ const App = {
filtered = filtered.filter(i => i.created_by_username === this._currentUsername);
}
const active = filtered.filter(i => i.status === 'active');
// Aktive Lagen nach Typ aufteilen
const activeAdhoc = filtered.filter(i => i.status === 'active' && (!i.type || i.type === 'adhoc'));
const activeResearch = filtered.filter(i => i.status === 'active' && i.type === 'research');
const archived = filtered.filter(i => i.status === 'archived');
const emptyLabel = this._sidebarFilter === 'mine' ? 'Keine eigenen Lagen' : 'Keine aktiven Lagen';
const emptyLabelAdhoc = this._sidebarFilter === 'mine' ? 'Keine eigenen Ad-hoc-Lagen' : 'Keine Ad-hoc-Lagen';
const emptyLabelResearch = this._sidebarFilter === 'mine' ? 'Keine eigenen Recherchen' : 'Keine Recherchen';
activeContainer.innerHTML = active.length
? active.map(i => UI.renderIncidentItem(i, i.id === this.currentIncidentId)).join('')
: `<div style="padding:8px 12px;font-size:12px;color:var(--text-tertiary);">${emptyLabel}</div>`;
activeContainer.innerHTML = activeAdhoc.length
? activeAdhoc.map(i => UI.renderIncidentItem(i, i.id === this.currentIncidentId)).join('')
: `<div style="padding:8px 12px;font-size:12px;color:var(--text-tertiary);">${emptyLabelAdhoc}</div>`;
researchContainer.innerHTML = activeResearch.length
? activeResearch.map(i => UI.renderIncidentItem(i, i.id === this.currentIncidentId)).join('')
: `<div style="padding:8px 12px;font-size:12px;color:var(--text-tertiary);">${emptyLabelResearch}</div>`;
archivedContainer.innerHTML = archived.length
? archived.map(i => UI.renderIncidentItem(i, i.id === this.currentIncidentId)).join('')
: '<div style="padding:8px 12px;font-size:12px;color:var(--text-tertiary);">Kein Archiv</div>';
// Zähler aktualisieren
const countAdhoc = document.getElementById('count-active-incidents');
const countResearch = document.getElementById('count-active-research');
const countArchived = document.getElementById('count-archived-incidents');
if (countAdhoc) countAdhoc.textContent = `(${activeAdhoc.length})`;
if (countResearch) countResearch.textContent = `(${activeResearch.length})`;
if (countArchived) countArchived.textContent = `(${archived.length})`;
// Sidebar-Stats aktualisieren
this.updateSidebarStats();
},
@@ -1547,7 +1597,7 @@ const App = {
}
},
handleEdit() {
async handleEdit() {
if (!this.currentIncidentId) return;
const incident = this.incidents.find(i => i.id === this.currentIncidentId);
if (!incident) return;
@@ -1677,16 +1727,7 @@ const App = {
await this.loadIncidents();
},
handleNotification(msg) {
// Legacy-Fallback: Einzelne Notifications ans NotificationCenter weiterleiten
const incident = this.incidents.find(i => i.id === msg.incident_id);
NotificationCenter.add({
incident_id: msg.incident_id,
title: incident ? incident.title : 'Lage #' + msg.incident_id,
text: msg.data.message || 'Neue Entwicklung',
icon: 'warning',
});
},
handleRefreshSummary(msg) {
const d = msg.data;
@@ -2322,9 +2363,17 @@ const App = {
document.getElementById('src-discovery-result').style.display = 'none';
document.getElementById('src-discover-btn').disabled = false;
document.getElementById('src-discover-btn').textContent = 'Erkennen';
// Save-Button Text zurücksetzen
const saveBtn = document.querySelector('#src-discovery-result .sources-discovery-actions .btn-primary');
if (saveBtn) saveBtn.textContent = 'Speichern';
// Block-Form ausblenden
const blockForm = document.getElementById('sources-block-form');
if (blockForm) blockForm.style.display = 'none';
} else {
// Beim Schließen: Bearbeitungsmodus zurücksetzen
this._editingSourceId = null;
const saveBtn = document.querySelector('#src-discovery-result .sources-discovery-actions .btn-primary');
if (saveBtn) saveBtn.textContent = 'Speichern';
}
},
@@ -2417,6 +2466,66 @@ const App = {
}
},
editSource(id) {
const source = this._sourcesOnly.find(s => s.id === id);
if (!source) {
UI.showToast('Quelle nicht gefunden.', 'error');
return;
}
this._editingSourceId = id;
// Formular öffnen falls geschlossen (direkt, ohne toggleSourceForm das _editingSourceId zurücksetzt)
const form = document.getElementById('sources-add-form');
if (form) {
form.style.display = 'block';
const blockForm = document.getElementById('sources-block-form');
if (blockForm) blockForm.style.display = 'none';
}
// Discovery-URL mit vorhandener URL/Domain befüllen
const discoverUrlInput = document.getElementById('src-discover-url');
if (discoverUrlInput) {
discoverUrlInput.value = source.url || source.domain || '';
}
// Discovery-Ergebnis anzeigen und Felder befüllen
document.getElementById('src-discovery-result').style.display = 'block';
document.getElementById('src-name').value = source.name || '';
document.getElementById('src-category').value = source.category || 'sonstige';
document.getElementById('src-notes').value = source.notes || '';
document.getElementById('src-domain').value = source.domain || '';
const typeLabel = source.source_type === 'rss_feed' ? 'RSS-Feed' : 'Web-Quelle';
document.getElementById('src-type-display').value = typeLabel;
const rssGroup = document.getElementById('src-rss-url-group');
const rssInput = document.getElementById('src-rss-url');
if (source.url) {
rssInput.value = source.url;
rssGroup.style.display = 'block';
} else {
rssInput.value = '';
rssGroup.style.display = 'none';
}
// _discoveredData setzen damit saveSource() die richtigen Werte nutzt
this._discoveredData = {
name: source.name,
domain: source.domain,
category: source.category,
source_type: source.source_type,
rss_url: source.url,
};
// Submit-Button-Text ändern
const saveBtn = document.querySelector('#src-discovery-result .sources-discovery-actions .btn-primary');
if (saveBtn) saveBtn.textContent = 'Quelle speichern';
// Zum Formular scrollen
if (form) form.scrollIntoView({ behavior: 'smooth', block: 'start' });
},
async saveSource() {
const name = document.getElementById('src-name').value.trim();
if (!name) {