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:
@@ -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) {
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren