/** * Dateiverwaltung Frontend * Zwei getrennte Bereiche: Mail-Abruf und Datei-Sortierung */ // ============ API ============ async function api(endpoint, options = {}) { const response = await fetch(`/api${endpoint}`, { headers: { 'Content-Type': 'application/json', ...options.headers }, ...options }); if (!response.ok) { const error = await response.json().catch(() => ({})); throw new Error(error.detail || 'API Fehler'); } return response.json(); } // ============ Loading Overlay ============ function zeigeLoading(text = 'Wird geladen...') { document.getElementById('loading-text').textContent = text; document.getElementById('loading-overlay').classList.remove('hidden'); } function versteckeLoading() { document.getElementById('loading-overlay').classList.add('hidden'); } // ============ File Browser ============ let browserTargetInput = null; let browserCurrentPath = '/srv/http/dateiverwaltung/data'; function oeffneBrowser(inputId) { browserTargetInput = inputId; const currentValue = document.getElementById(inputId).value; browserCurrentPath = currentValue || '/srv/http/dateiverwaltung/data'; ladeBrowserInhalt(browserCurrentPath); document.getElementById('browser-modal').classList.remove('hidden'); } async function ladeBrowserInhalt(path) { try { const data = await api(`/browse?path=${encodeURIComponent(path)}`); if (data.error) { document.getElementById('browser-list').innerHTML = `
  • ${data.error}
  • `; return; } browserCurrentPath = data.current; document.getElementById('browser-current-path').textContent = data.current; let html = ''; // Parent directory if (data.parent) { html += `
  • 📁 ..
  • `; } // Directories for (const entry of data.entries) { html += `
  • 📁 ${entry.name}
  • `; } if (data.entries.length === 0 && !data.parent) { html = '
  • Keine Unterordner
  • '; } document.getElementById('browser-list').innerHTML = html; } catch (error) { document.getElementById('browser-list').innerHTML = `
  • Fehler: ${error.message}
  • `; } } function browserSelect(element, path) { document.querySelectorAll('.file-browser-item.selected').forEach(el => el.classList.remove('selected')); element.classList.add('selected'); browserCurrentPath = path; } function browserAuswahl() { if (browserTargetInput && browserCurrentPath) { document.getElementById(browserTargetInput).value = browserCurrentPath + '/'; } schliesseModal('browser-modal'); } // ============ Checkbox Helpers ============ function getCheckedTypes(groupId) { const checkboxes = document.querySelectorAll(`#${groupId} input[type="checkbox"]:checked`); return Array.from(checkboxes).map(cb => cb.value); } function setCheckedTypes(groupId, types) { const checkboxes = document.querySelectorAll(`#${groupId} input[type="checkbox"]`); checkboxes.forEach(cb => { cb.checked = types.includes(cb.value); }); } // ============ Init ============ document.addEventListener('DOMContentLoaded', () => { ladePostfaecher(); ladeOrdner(); ladeRegeln(); }); // ============ BEREICH 1: Mail-Abruf ============ async function ladePostfaecher() { try { const postfaecher = await api('/postfaecher'); renderPostfaecher(postfaecher); } catch (error) { console.error('Fehler:', error); } } let bearbeitetesPostfachId = null; function renderPostfaecher(postfaecher) { const container = document.getElementById('postfaecher-liste'); if (!postfaecher || postfaecher.length === 0) { container.innerHTML = '

    Keine PostfÀcher konfiguriert

    '; return; } container.innerHTML = postfaecher.map(p => `

    ${escapeHtml(p.name)}

    ${escapeHtml(p.email)} → ${escapeHtml(p.ziel_ordner)}
    `).join(''); } function zeigePostfachModal(postfach = null) { bearbeitetesPostfachId = postfach?.id || null; document.getElementById('pf-name').value = postfach?.name || ''; document.getElementById('pf-server').value = postfach?.imap_server || ''; document.getElementById('pf-port').value = postfach?.imap_port || '993'; document.getElementById('pf-email').value = postfach?.email || ''; document.getElementById('pf-passwort').value = ''; // Passwort nicht vorausfĂŒllen document.getElementById('pf-ordner').value = postfach?.ordner || 'INBOX'; document.getElementById('pf-alle-ordner').value = postfach?.alle_ordner ? 'true' : 'false'; document.getElementById('pf-ziel').value = postfach?.ziel_ordner || '/srv/http/dateiverwaltung/data/inbox/'; setCheckedTypes('pf-typen-gruppe', postfach?.erlaubte_typen || ['.pdf']); document.getElementById('pf-max-groesse').value = postfach?.max_groesse_mb || '25'; document.getElementById('postfach-modal').classList.remove('hidden'); } async function postfachBearbeiten(id) { try { const postfaecher = await api('/postfaecher'); const postfach = postfaecher.find(p => p.id === id); if (postfach) { zeigePostfachModal(postfach); } } catch (error) { alert('Fehler: ' + error.message); } } async function speicherePostfach() { const erlaubteTypen = getCheckedTypes('pf-typen-gruppe'); if (erlaubteTypen.length === 0) { alert('Bitte mindestens einen Dateityp auswĂ€hlen'); return; } const data = { name: document.getElementById('pf-name').value.trim(), imap_server: document.getElementById('pf-server').value.trim(), imap_port: parseInt(document.getElementById('pf-port').value), email: document.getElementById('pf-email').value.trim(), passwort: document.getElementById('pf-passwort').value, ordner: document.getElementById('pf-ordner').value.trim(), alle_ordner: document.getElementById('pf-alle-ordner').value === 'true', ziel_ordner: document.getElementById('pf-ziel').value.trim(), erlaubte_typen: erlaubteTypen, max_groesse_mb: parseInt(document.getElementById('pf-max-groesse').value) }; if (!data.name || !data.imap_server || !data.email || !data.ziel_ordner) { alert('Bitte alle Pflichtfelder ausfĂŒllen'); return; } // Bei Bearbeitung: Passwort nur senden wenn eingegeben if (bearbeitetesPostfachId && !data.passwort) { delete data.passwort; } else if (!data.passwort) { alert('Passwort ist erforderlich'); return; } try { if (bearbeitetesPostfachId) { await api(`/postfaecher/${bearbeitetesPostfachId}`, { method: 'PUT', body: JSON.stringify(data) }); } else { await api('/postfaecher', { method: 'POST', body: JSON.stringify(data) }); } schliesseModal('postfach-modal'); ladePostfaecher(); } catch (error) { alert('Fehler: ' + error.message); } } async function postfachTesten(id) { try { const result = await api(`/postfaecher/${id}/test`, { method: 'POST' }); alert(result.erfolg ? 'Verbindung erfolgreich!' : 'Fehler: ' + result.nachricht); } catch (error) { alert('Fehler: ' + error.message); } } async function postfachAbrufen(id) { const logContainer = document.getElementById('abruf-log'); logContainer.innerHTML = '
    Verbinde...
    '; // EventSource fĂŒr Server-Sent Events const eventSource = new EventSource(`/api/postfaecher/${id}/abrufen/stream`); let dateiCount = 0; let currentOrdner = ''; eventSource.onmessage = (event) => { const data = JSON.parse(event.data); switch (data.type) { case 'start': logContainer.innerHTML = `
    Starte Abruf: ${escapeHtml(data.postfach)} ${data.bereits_verarbeitet} bereits verarbeitet
    `; break; case 'info': logContainer.innerHTML += `
    ${escapeHtml(data.nachricht)}
    `; break; case 'ordner': currentOrdner = data.name; logContainer.innerHTML += `
    📁 ${escapeHtml(data.name)}
    `; break; case 'mails': const ordnerStatus = document.getElementById('ordner-status'); if (ordnerStatus) { ordnerStatus.innerHTML = `📁 ${escapeHtml(data.ordner)}: ${data.anzahl} Mails`; ordnerStatus.id = ''; // ID entfernen fĂŒr nĂ€chsten Ordner } break; case 'datei': dateiCount++; logContainer.innerHTML += `
    ✓ ${escapeHtml(data.original_name)} ${formatBytes(data.groesse)}
    `; // Scroll nach unten logContainer.scrollTop = logContainer.scrollHeight; break; case 'skip': logContainer.innerHTML += `
    ⊘ ${escapeHtml(data.datei)}: ${data.grund}
    `; break; case 'fehler': logContainer.innerHTML += `
    ✗ ${escapeHtml(data.nachricht)}
    `; break; case 'fertig': logContainer.innerHTML += `
    ✓ Fertig: ${data.anzahl} Dateien gespeichert
    `; eventSource.close(); ladePostfaecher(); break; } }; eventSource.onerror = (error) => { logContainer.innerHTML += `
    ✗ Verbindung unterbrochen
    `; eventSource.close(); }; } function formatBytes(bytes) { if (bytes < 1024) return bytes + ' B'; if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'; return (bytes / (1024 * 1024)).toFixed(1) + ' MB'; } async function allePostfaecherAbrufen() { try { zeigeLoading('Rufe alle PostfÀcher ab...'); const result = await api('/postfaecher/abrufen-alle', { method: 'POST' }); zeigeAbrufLog(result); ladePostfaecher(); } catch (error) { alert('Fehler: ' + error.message); } finally { versteckeLoading(); } } async function postfachLoeschen(id) { if (!confirm('Postfach wirklich löschen?')) return; try { await api(`/postfaecher/${id}`, { method: 'DELETE' }); ladePostfaecher(); } catch (error) { alert('Fehler: ' + error.message); } } function zeigeAbrufLog(result) { const container = document.getElementById('abruf-log'); if (!result.ergebnisse || result.ergebnisse.length === 0) { container.innerHTML = '

    Keine neuen Attachments gefunden

    '; return; } let html = ''; for (const r of result.ergebnisse) { const status = r.fehler ? 'error' : 'success'; const icon = r.fehler ? '✗' : '✓'; html += `
    ${icon} ${escapeHtml(r.postfach)}: ${r.anzahl || 0} Dateien ${r.fehler ? `${escapeHtml(r.fehler)}` : ''}
    `; if (r.dateien) { for (const d of r.dateien) { html += `
    → ${escapeHtml(d)}
    `; } } } container.innerHTML = html; } // ============ BEREICH 2: Datei-Sortierung ============ async function ladeOrdner() { try { const ordner = await api('/ordner'); renderOrdner(ordner); } catch (error) { console.error('Fehler:', error); } } function renderOrdner(ordner) { const container = document.getElementById('ordner-liste'); if (!ordner || ordner.length === 0) { container.innerHTML = '

    Keine Ordner konfiguriert

    '; return; } container.innerHTML = ordner.map(o => `

    ${escapeHtml(o.name)} ${o.rekursiv ? 'rekursiv' : ''}

    ${escapeHtml(o.pfad)} → ${escapeHtml(o.ziel_ordner)} ${(o.dateitypen || []).join(', ')}
    `).join(''); } function zeigeOrdnerModal() { document.getElementById('ord-name').value = ''; document.getElementById('ord-pfad').value = '/srv/http/dateiverwaltung/data/inbox/'; document.getElementById('ord-ziel').value = '/srv/http/dateiverwaltung/data/archiv/'; setCheckedTypes('ord-typen-gruppe', ['.pdf', '.jpg', '.jpeg', '.png', '.tiff']); document.getElementById('ord-rekursiv').value = 'true'; document.getElementById('ordner-modal').classList.remove('hidden'); } async function speichereOrdner() { const dateitypen = getCheckedTypes('ord-typen-gruppe'); if (dateitypen.length === 0) { alert('Bitte mindestens einen Dateityp auswĂ€hlen'); return; } const data = { name: document.getElementById('ord-name').value.trim(), pfad: document.getElementById('ord-pfad').value.trim(), ziel_ordner: document.getElementById('ord-ziel').value.trim(), rekursiv: document.getElementById('ord-rekursiv').value === 'true', dateitypen: dateitypen }; if (!data.name || !data.pfad || !data.ziel_ordner) { alert('Bitte alle Felder ausfĂŒllen'); return; } try { zeigeLoading('Speichere Ordner...'); await api('/ordner', { method: 'POST', body: JSON.stringify(data) }); schliesseModal('ordner-modal'); ladeOrdner(); } catch (error) { alert('Fehler: ' + error.message); } finally { versteckeLoading(); } } async function ordnerLoeschen(id) { if (!confirm('Ordner wirklich löschen?')) return; try { await api(`/ordner/${id}`, { method: 'DELETE' }); ladeOrdner(); } catch (error) { alert('Fehler: ' + error.message); } } async function ordnerScannen(id) { try { const result = await api(`/ordner/${id}/scannen`); alert(`${result.anzahl} Dateien im Ordner gefunden`); } catch (error) { alert('Fehler: ' + error.message); } } // ============ Regeln ============ let editierteRegelId = null; async function ladeRegeln() { try { const regeln = await api('/regeln'); renderRegeln(regeln); } catch (error) { console.error('Fehler:', error); } } function renderRegeln(regeln) { const container = document.getElementById('regeln-liste'); if (!regeln || regeln.length === 0) { container.innerHTML = '

    Keine Regeln definiert

    '; return; } container.innerHTML = regeln.map(r => `

    ${escapeHtml(r.name)} Prio ${r.prioritaet}

    ${escapeHtml(r.schema)}
    `).join(''); } function zeigeRegelModal(regel = null) { editierteRegelId = regel?.id || null; document.getElementById('regel-modal-title').textContent = regel ? 'Regel bearbeiten' : 'Regel hinzufĂŒgen'; document.getElementById('regel-name').value = regel?.name || ''; document.getElementById('regel-prioritaet').value = regel?.prioritaet || 100; document.getElementById('regel-muster').value = JSON.stringify(regel?.muster || {"text_match_any": [], "text_match": []}, null, 2); document.getElementById('regel-extraktion').value = JSON.stringify(regel?.extraktion || {}, null, 2); document.getElementById('regel-schema').value = regel?.schema || '{datum} - Dokument.pdf'; document.getElementById('regel-unterordner').value = regel?.unterordner || ''; document.getElementById('regel-test-text').value = ''; document.getElementById('regel-test-ergebnis').classList.add('hidden'); document.getElementById('regel-modal').classList.remove('hidden'); } async function bearbeiteRegel(id) { try { const regeln = await api('/regeln'); const regel = regeln.find(r => r.id === id); if (regel) zeigeRegelModal(regel); } catch (error) { alert('Fehler: ' + error.message); } } async function speichereRegel() { let muster, extraktion; try { muster = JSON.parse(document.getElementById('regel-muster').value); } catch (e) { alert('UngĂŒltiges JSON im Muster-Feld'); return; } try { extraktion = JSON.parse(document.getElementById('regel-extraktion').value); } catch (e) { alert('UngĂŒltiges JSON im Extraktion-Feld'); return; } const data = { name: document.getElementById('regel-name').value.trim(), prioritaet: parseInt(document.getElementById('regel-prioritaet').value), muster, extraktion, schema: document.getElementById('regel-schema').value.trim(), unterordner: document.getElementById('regel-unterordner').value.trim() || null }; if (!data.name) { alert('Bitte einen Namen eingeben'); return; } try { if (editierteRegelId) { await api(`/regeln/${editierteRegelId}`, { method: 'PUT', body: JSON.stringify(data) }); } else { await api('/regeln', { method: 'POST', body: JSON.stringify(data) }); } schliesseModal('regel-modal'); ladeRegeln(); } catch (error) { alert('Fehler: ' + error.message); } } async function regelLoeschen(id) { if (!confirm('Regel wirklich löschen?')) return; try { await api(`/regeln/${id}`, { method: 'DELETE' }); ladeRegeln(); } catch (error) { alert('Fehler: ' + error.message); } } async function testeRegel() { const text = document.getElementById('regel-test-text').value; if (!text) { alert('Bitte Testtext eingeben'); return; } let muster, extraktion; try { muster = JSON.parse(document.getElementById('regel-muster').value); extraktion = JSON.parse(document.getElementById('regel-extraktion').value); } catch (e) { alert('UngĂŒltiges JSON'); return; } const regel = { name: 'Test', muster, extraktion, schema: document.getElementById('regel-schema').value.trim() }; try { const result = await api('/regeln/test', { method: 'POST', body: JSON.stringify({ regel, text }) }); const container = document.getElementById('regel-test-ergebnis'); container.classList.remove('hidden', 'success', 'error'); if (result.passt) { container.classList.add('success'); container.textContent = `✓ Regel passt!\n\nExtrahiert:\n${JSON.stringify(result.extrahiert, null, 2)}\n\nDateiname:\n${result.dateiname}`; } else { container.classList.add('error'); container.textContent = '✗ Regel passt nicht'; } } catch (error) { alert('Fehler: ' + error.message); } } // ============ Sortierung starten ============ async function sortierungStarten() { try { zeigeLoading('Sortiere Dateien...'); const result = await api('/sortierung/starten', { method: 'POST' }); zeigeSortierungLog(result); } catch (error) { alert('Fehler: ' + error.message); } finally { versteckeLoading(); } } function zeigeSortierungLog(result) { const container = document.getElementById('sortierung-log'); if (!result.verarbeitet || result.verarbeitet.length === 0) { container.innerHTML = '

    Keine Dateien verarbeitet

    '; return; } let html = `
    Gesamt: ${result.gesamt} | Sortiert: ${result.sortiert} | ZUGFeRD: ${result.zugferd} | Fehler: ${result.fehler}
    `; for (const d of result.verarbeitet) { const status = d.fehler ? 'error' : (d.zugferd ? 'info' : 'success'); const icon = d.fehler ? '✗' : (d.zugferd ? 'đŸ§Ÿ' : '✓'); html += `
    ${icon} ${escapeHtml(d.neuer_name || d.original)} ${d.fehler ? `${escapeHtml(d.fehler)}` : ''}
    `; } container.innerHTML = html; } // ============ Utilities ============ function schliesseModal(id) { document.getElementById(id).classList.add('hidden'); } function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } document.addEventListener('click', (e) => { if (e.target.classList.contains('modal')) { e.target.classList.add('hidden'); } }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { document.querySelectorAll('.modal:not(.hidden)').forEach(m => m.classList.add('hidden')); } });