/**
* 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 = '/mnt';
function oeffneBrowser(inputId) {
browserTargetInput = inputId;
const currentValue = document.getElementById(inputId).value;
browserCurrentPath = currentValue || '/mnt';
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;
// Berechtigungen des aktuellen Ordners anzeigen
const permStr = getPermissionString(data.readable, data.writable);
document.getElementById('browser-current-path').innerHTML = `${escapeHtml(data.current)} ${permStr} `;
let html = '';
// Parent directory
if (data.parent) {
html += `
đ ..
`;
}
// Directories mit Berechtigungsanzeige
for (const entry of data.entries) {
const entryPermStr = getPermissionString(entry.readable, entry.writable);
const permClass = !entry.readable ? 'perm-no-read' : (!entry.writable ? 'perm-no-write' : 'perm-ok');
html += `
đ ${escapeHtml(entry.name)} ${entryPermStr}
`;
}
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 getPermissionString(readable, writable) {
if (readable && writable) return 'â RW';
if (readable && !writable) return 'â R';
if (!readable) return 'â ---';
return '?';
}
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', () => {
ladeTheme(); // Theme zuerst laden
ladePostfaecher();
ladeOrdner();
ladeRegeln();
ladeTypRegeln(); // Typ-Regeln fĂŒr Schnell-Regeln Dropdown laden
ladeOcrBackupEinstellungen(); // OCR-Backup Einstellungen laden
});
// ============ Theme Management ============
function ladeTheme() {
const gespeichertesTheme = localStorage.getItem('theme') || 'auto';
const select = document.getElementById('theme-select');
if (select) {
select.value = gespeichertesTheme;
}
wendeThemeAn(gespeichertesTheme);
}
function wechsleTheme(theme) {
localStorage.setItem('theme', theme);
wendeThemeAn(theme);
}
function wendeThemeAn(theme) {
const html = document.documentElement;
if (theme === 'auto') {
// System-PrÀferenz nutzen
html.removeAttribute('data-theme');
} else if (theme === 'dark') {
// Original Dark Theme (kein data-theme = default CSS)
html.removeAttribute('data-theme');
// Aber System-PrĂ€ferenz ĂŒberschreiben durch explizites Setzen
html.setAttribute('data-theme', 'dark');
} else {
// Breeze Themes
html.setAttribute('data-theme', theme);
}
}
// Original Dark Theme explizit definieren
// (wird verwendet wenn "dark" gewÀhlt ist, auch bei Light System-PrÀferenz)
// ============ BEREICH 1: Mail-Abruf ============
async function ladePostfaecher() {
try {
const postfaecher = await api('/postfaecher');
renderPostfaecher(postfaecher);
} catch (error) {
console.error('Fehler:', error);
}
}
let bearbeitetesPostfachId = null;
// Aktive Abrufe tracken
let aktiveAbrufe = {};
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 => {
const istAktiv = aktiveAbrufe[p.id];
return `
${escapeHtml(p.name)} ${istAktiv ? 'LĂ€uft... ' : ''}
${escapeHtml(p.email)} â ${escapeHtml(p.ziel_ordner)}
${istAktiv
? `Stoppen `
: `Abrufen `
}
Bearbeiten
Testen
Ă
`}).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';
// Datum formatieren fĂŒr date input (YYYY-MM-DD)
if (postfach?.ab_datum) {
const d = new Date(postfach.ab_datum);
document.getElementById('pf-ab-datum').value = d.toISOString().split('T')[0];
} else {
document.getElementById('pf-ab-datum').value = '';
}
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;
}
// Datum konvertieren
const abDatumValue = document.getElementById('pf-ab-datum').value;
let abDatum = null;
if (abDatumValue) {
abDatum = new Date(abDatumValue).toISOString();
}
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',
ab_datum: abDatum,
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...
';
// Als aktiv markieren
aktiveAbrufe[id] = true;
ladePostfaecher();
// EventSource fĂŒr Server-Sent Events
const eventSource = new EventSource(`/api/postfaecher/${id}/abrufen/stream`);
aktiveAbrufe[id] = eventSource; // EventSource speichern fĂŒr Stopp
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 'abgebrochen':
logContainer.innerHTML += `
â ${escapeHtml(data.nachricht)}
`;
break;
case 'fertig':
const msg = data.abgebrochen
? `â Abgebrochen: ${data.anzahl} Dateien gespeichert`
: `â Fertig: ${data.anzahl} Dateien gespeichert`;
logContainer.innerHTML += `
${msg}
`;
eventSource.close();
delete aktiveAbrufe[id];
ladePostfaecher();
break;
}
};
eventSource.onerror = (error) => {
logContainer.innerHTML += `
â Verbindung unterbrochen
`;
eventSource.close();
delete aktiveAbrufe[id];
ladePostfaecher();
};
}
async function postfachAbrufStoppen(id) {
try {
const result = await api(`/postfaecher/${id}/abrufen/stoppen`, { method: 'POST' });
if (result.erfolg) {
const logContainer = document.getElementById('abruf-log');
logContainer.innerHTML += `
â Stopp angefordert...
`;
}
} catch (error) {
alert('Fehler: ' + error.message);
}
}
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(', ')}
Scannen
Ă
`).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;
// VerfĂŒgbare Typ-Regeln (vom Server geladen)
let verfuegbareTypRegeln = [];
async function ladeTypRegeln() {
try {
verfuegbareTypRegeln = await api('/typ-regeln');
} catch (error) {
console.error('Fehler beim Laden der Typ-Regeln:', error);
}
}
async function ladeRegeln() {
try {
const regeln = await api('/regeln');
renderRegeln(regeln);
} catch (error) {
console.error('Fehler:', error);
}
}
function renderRegeln(regeln) {
const schnellContainer = document.getElementById('schnell-regeln-liste');
const feinContainer = document.getElementById('regeln-liste');
// Regeln aufteilen: Typ-basierte (Schnell) vs. Inhalt-basierte (Fein)
const schnellRegeln = [];
const feinRegeln = [];
for (const r of (regeln || [])) {
const muster = r.muster || {};
// Schnell-Regel wenn sie Typ-basierte Muster hat (ohne Keywords/Text-Match)
const istTypRegel = (
('ist_zugferd' in muster || 'ist_signiert' in muster ||
'ist_bild' in muster || 'ist_pdf' in muster ||
'hat_text' in muster || 'dateityp_ist' in muster) &&
!muster.keywords && !muster.text_match && !muster.text_match_any && !muster.text_regex
);
if (istTypRegel) {
schnellRegeln.push(r);
} else {
feinRegeln.push(r);
}
}
// Schnell-Regeln rendern (sortiert nach PrioritÀt)
schnellRegeln.sort((a, b) => a.prioritaet - b.prioritaet);
if (schnellRegeln.length === 0) {
schnellContainer.innerHTML = 'Keine Schnell-Regeln definiert
';
} else {
schnellContainer.innerHTML = schnellRegeln.map((r, index) => {
const typBadge = getTypRegelBadge(r.muster);
const istFallback = r.prioritaet >= 900;
const fallbackClass = istFallback ? 'fallback-regel' : '';
return `
${escapeHtml(r.name)} Prio ${r.prioritaet} ${typBadge}
â ${escapeHtml(r.unterordner || 'Zielordner')}
âČ
âŒ
Ă
`}).join('');
}
// Fein-Regeln rendern (sortiert nach PrioritÀt)
feinRegeln.sort((a, b) => a.prioritaet - b.prioritaet);
if (feinRegeln.length === 0) {
feinContainer.innerHTML = 'Keine Fein-Regeln definiert
';
} else {
feinContainer.innerHTML = feinRegeln.map((r, index) => `
${escapeHtml(r.name)} Prio ${r.prioritaet}
${escapeHtml(r.schema)}
âČ
âŒ
Bearbeiten
Ă
`).join('');
}
}
function getTypRegelBadge(muster) {
const badges = [];
if (muster.ist_zugferd) badges.push('ZUGFeRD ');
if (muster.ist_signiert) badges.push('Signiert ');
if (muster.ist_bild) badges.push('Bild ');
if (muster.ist_pdf) badges.push('PDF ');
if (muster.hat_text === false) badges.push('Ohne Text ');
return badges.join(' ');
}
// ============ Schnell-Regeln UI ============
function zeigeSchnellRegelModal() {
// Dropdown befĂŒllen
const select = document.getElementById('schnell-regel-typ');
select.innerHTML = '-- Bitte wÀhlen -- ';
for (const typ of verfuegbareTypRegeln) {
select.innerHTML += `${escapeHtml(typ.name)} `;
}
// Details verstecken
document.getElementById('schnell-regel-details').classList.add('hidden');
document.getElementById('schnell-regel-speichern-btn').disabled = true;
document.getElementById('schnell-regel-modal').classList.remove('hidden');
}
function schnellRegelTypGeaendert() {
const typId = document.getElementById('schnell-regel-typ').value;
const detailsDiv = document.getElementById('schnell-regel-details');
const speichernBtn = document.getElementById('schnell-regel-speichern-btn');
if (!typId) {
detailsDiv.classList.add('hidden');
speichernBtn.disabled = true;
return;
}
const typ = verfuegbareTypRegeln.find(t => t.id === typId);
if (!typ) return;
// Details anzeigen
document.getElementById('schnell-regel-name').textContent = typ.name;
document.getElementById('schnell-regel-beschreibung').textContent = typ.beschreibung;
document.getElementById('schnell-regel-muster').textContent = JSON.stringify(typ.muster);
document.getElementById('schnell-regel-unterordner').value = typ.unterordner || '';
document.getElementById('schnell-regel-prioritaet').value = typ.prioritaet || 10;
detailsDiv.classList.remove('hidden');
speichernBtn.disabled = false;
}
async function speichereSchnellRegel() {
const typId = document.getElementById('schnell-regel-typ').value;
const unterordner = document.getElementById('schnell-regel-unterordner').value.trim();
const prioritaet = parseInt(document.getElementById('schnell-regel-prioritaet').value) || 10;
if (!typId) {
alert('Bitte Regel-Typ auswÀhlen');
return;
}
try {
zeigeLoading('Erstelle Schnell-Regel...');
await api('/regeln/typ', {
method: 'POST',
body: JSON.stringify({
typ_id: typId,
unterordner: unterordner || null,
prioritaet: prioritaet
})
});
schliesseModal('schnell-regel-modal');
ladeRegeln();
} catch (error) {
alert('Fehler: ' + error.message);
} finally {
versteckeLoading();
}
}
// ============ PrioritÀts-Verwaltung ============
async function regelPrioritaetAendern(regelId, delta) {
try {
// Aktuelle Regeln holen um PrioritÀt zu finden
const regeln = await api('/regeln');
const regel = regeln.find(r => r.id === regelId);
if (!regel) return;
const neuePrio = Math.max(1, regel.prioritaet + delta);
await api(`/regeln/${regelId}/prioritaet`, {
method: 'PUT',
body: JSON.stringify({ prioritaet: neuePrio })
});
ladeRegeln();
} catch (error) {
alert('Fehler: ' + error.message);
}
}
// ============ Datei-Browser fĂŒr Regel-Unterordner ============
let regelBrowserQuellOrdner = null;
async function oeffneBrowserFuerRegel() {
// Wenn Quell-Ordner konfiguriert sind, deren Ziel-Ordner als Basis nehmen
try {
const ordner = await api('/ordner');
if (ordner.length > 0) {
// Ersten konfigurierten Ziel-Ordner als Startpunkt
browserCurrentPath = ordner[0].ziel_ordner || '/mnt';
regelBrowserQuellOrdner = ordner[0];
} else {
browserCurrentPath = '/mnt';
}
browserTargetInput = 'regel-unterordner';
ladeBrowserInhalt(browserCurrentPath);
document.getElementById('browser-modal').classList.remove('hidden');
} catch (error) {
browserCurrentPath = '/mnt';
browserTargetInput = 'regel-unterordner';
ladeBrowserInhalt(browserCurrentPath);
document.getElementById('browser-modal').classList.remove('hidden');
}
}
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 ============
let sortierungAktiv = false;
let sortierungEventSource = null;
// ============ OCR-Backup Einstellungen ============
function toggleOcrBackup() {
const checkbox = document.getElementById('ocr-backup-aktiv');
const ordnerGruppe = document.getElementById('ocr-backup-ordner-gruppe');
if (checkbox.checked) {
ordnerGruppe.classList.remove('hidden');
} else {
ordnerGruppe.classList.add('hidden');
}
// Einstellung im localStorage speichern
localStorage.setItem('ocr-backup-aktiv', checkbox.checked);
localStorage.setItem('ocr-backup-ordner', document.getElementById('ocr-backup-ordner').value);
}
function ladeOcrBackupEinstellungen() {
const aktiv = localStorage.getItem('ocr-backup-aktiv') === 'true';
const ordner = localStorage.getItem('ocr-backup-ordner') || '';
const checkbox = document.getElementById('ocr-backup-aktiv');
const input = document.getElementById('ocr-backup-ordner');
const gruppe = document.getElementById('ocr-backup-ordner-gruppe');
if (checkbox) {
checkbox.checked = aktiv;
if (aktiv) {
gruppe.classList.remove('hidden');
}
}
if (input) {
input.value = ordner;
// Event-Listener fĂŒr Ănderungen
input.addEventListener('change', () => {
localStorage.setItem('ocr-backup-ordner', input.value);
});
}
}
function oeffneBrowserFuerOcrBackup() {
browserCurrentPath = '/mnt';
browserTargetInput = 'ocr-backup-ordner';
ladeBrowserInhalt(browserCurrentPath);
document.getElementById('browser-modal').classList.remove('hidden');
}
async function sortierungStarten(testmodus = false) {
const logContainer = document.getElementById('sortierung-log');
const modeText = testmodus ? 'Starte Testlauf (keine Ănderungen)...' : 'Starte Sortierung...';
logContainer.innerHTML = `${modeText}
`;
// Button aktualisieren
sortierungAktiv = true;
aktualisiereSortierungsButtons();
// URL mit optionalem Testmodus und Backup-Ordner bauen
let url = '/api/sortierung/stream?';
const params = [];
if (testmodus) params.push('testmodus=true');
// OCR-Backup-Ordner hinzufĂŒgen wenn aktiviert
const backupAktiv = document.getElementById('ocr-backup-aktiv')?.checked;
const backupOrdner = document.getElementById('ocr-backup-ordner')?.value?.trim();
if (backupAktiv && backupOrdner) {
params.push(`ocr_backup_ordner=${encodeURIComponent(backupOrdner)}`);
}
url += params.join('&');
sortierungEventSource = new EventSource(url);
let stats = { gesamt: 0, sortiert: 0, zugferd: 0, fehler: 0 };
sortierungEventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
const testBadge = data.testmodus ? 'TEST ' : '';
switch (data.type) {
case 'start':
const modeInfo = data.testmodus
? 'đ TESTMODUS - Dateien werden nur analysiert, nicht verschoben!'
: 'Sortierung gestartet';
logContainer.innerHTML = `
${modeInfo}: ${data.ordner} Ordner, ${data.regeln} Regeln
`;
break;
case 'ordner':
logContainer.innerHTML += `
đ ${escapeHtml(data.name)}
${escapeHtml(data.pfad)}
`;
break;
case 'dateien_gefunden':
logContainer.innerHTML += `
${data.anzahl} Dateien gefunden
`;
break;
case 'datei':
stats.gesamt++;
let icon, statusClass, statusText;
if (data.status === 'sortiert') {
stats.sortiert++;
icon = data.testmodus ? 'â' : 'â';
statusClass = data.testmodus ? 'info' : 'success';
statusText = data.testmodus ? 'wĂŒrde sortiert' : 'sortiert';
} else if (data.status === 'zugferd') {
stats.zugferd++;
icon = 'đ§Ÿ';
statusClass = 'info';
statusText = data.testmodus ? 'ZUGFeRD erkannt' : 'ZUGFeRD';
} else if (data.status === 'keine_regel') {
stats.fehler++;
icon = '?';
statusClass = '';
statusText = 'keine Regel';
} else {
stats.fehler++;
icon = 'â';
statusClass = 'error';
statusText = 'Fehler';
}
// Zielordner im Testmodus anzeigen
const zielInfo = data.testmodus && data.ziel_ordner
? `â ${escapeHtml(data.ziel_ordner)} `
: '';
logContainer.innerHTML += `
${testBadge}${icon} ${escapeHtml(data.neuer_name || data.original)}
${data.regel ? `Regel: ${escapeHtml(data.regel)} ` : ''}
${data.fehler ? `${escapeHtml(data.fehler)} ` : ''}
${zielInfo}
`;
logContainer.scrollTop = logContainer.scrollHeight;
break;
case 'warnung':
logContainer.innerHTML += `
â ${escapeHtml(data.nachricht)}
`;
break;
case 'abgebrochen':
logContainer.innerHTML += `
â Sortierung abgebrochen
`;
break;
case 'fertig':
const fertigText = data.testmodus
? `đ Testlauf fertig: ${data.sortiert} wĂŒrden sortiert, ${data.zugferd} ZUGFeRD, ${data.fehler} ohne Regel`
: `â Fertig: ${data.sortiert} sortiert, ${data.zugferd} ZUGFeRD, ${data.fehler} ohne Regel`;
const fertigHint = data.testmodus
? 'Keine Dateien wurden verschoben. Starte echte Sortierung wenn zufrieden.
'
: '';
logContainer.innerHTML += `
${fertigText}
${fertigHint}`;
sortierungEventSource.close();
sortierungAktiv = false;
sortierungEventSource = null;
aktualisiereSortierungsButtons();
break;
}
};
sortierungEventSource.onerror = (error) => {
logContainer.innerHTML += `
â Verbindung unterbrochen
`;
sortierungEventSource.close();
sortierungAktiv = false;
sortierungEventSource = null;
aktualisiereSortierungsButtons();
};
}
async function sortierungStoppen() {
try {
const result = await api('/sortierung/stoppen', { method: 'POST' });
if (result.erfolg) {
const logContainer = document.getElementById('sortierung-log');
logContainer.innerHTML += `
â Stopp angefordert...
`;
}
} catch (error) {
alert('Fehler: ' + error.message);
}
}
function aktualisiereSortierungsButtons() {
const startBtn = document.getElementById('sortierung-start-btn');
const testBtn = document.getElementById('sortierung-test-btn');
const stoppBtn = document.getElementById('sortierung-stopp-btn');
if (startBtn && stoppBtn) {
if (sortierungAktiv) {
startBtn.classList.add('hidden');
if (testBtn) testBtn.classList.add('hidden');
stoppBtn.classList.remove('hidden');
} else {
startBtn.classList.remove('hidden');
if (testBtn) testBtn.classList.remove('hidden');
stoppBtn.classList.add('hidden');
}
}
}
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'));
}
});
// ============ Datenbank-Management ============
async function dbZuruecksetzen() {
if (!confirm('Datenbank wirklich zurĂŒcksetzen?\n\nDies löscht alle EintrĂ€ge ĂŒber verarbeitete Mails und Dateien.\nPostfĂ€cher, Ordner und Regeln bleiben erhalten.\n\nBeim nĂ€chsten Abruf werden alle Mails erneut verarbeitet!')) {
return;
}
try {
zeigeLoading('Setze Datenbank zurĂŒck...');
const result = await api('/db/reset', { method: 'POST' });
if (result.erfolg) {
alert(`Datenbank zurĂŒckgesetzt!\n\nGelöscht:\n- ${result.geloescht.mails} verarbeitete Mails\n- ${result.geloescht.dateien} verarbeitete Dateien`);
} else {
alert('Fehler: ' + result.nachricht);
}
} catch (error) {
alert('Fehler: ' + error.message);
} finally {
versteckeLoading();
}
}
async function zeigeStatistik() {
document.getElementById('statistik-modal').classList.remove('hidden');
document.getElementById('statistik-inhalt').innerHTML = 'Wird geladen...';
try {
const stats = await api('/db/statistik');
document.getElementById('statistik-inhalt').innerHTML = `
PostfÀcher
${stats.postfaecher}
Quell-Ordner
${stats.quell_ordner}
Sortier-Regeln
${stats.regeln}
Verarbeitete Mails
${stats.verarbeitete_mails}
Verarbeitete Dateien
${stats.verarbeitete_dateien}
`;
} catch (error) {
document.getElementById('statistik-inhalt').innerHTML = `Fehler: ${error.message}
`;
}
}
// ============ Regel-Hilfe ============
function zeigeRegelHilfe() {
document.getElementById('hilfe-modal').classList.remove('hidden');
document.getElementById('hilfe-text').value = '';
document.getElementById('hilfe-ergebnis').classList.add('hidden');
}
// Speichert das letzte Analyse-Ergebnis
let letzteAnalyse = null;
// Standard-Regex-Muster fĂŒr hĂ€ufige Felder
const STANDARD_REGEX = {
datum: [
{ label: 'DD.MM.YYYY', regex: '(\\d{2}\\.\\d{2}\\.\\d{4})' },
{ label: 'Rechnungsdatum: DD.MM.YYYY', regex: 'Rechnungsdatum[:\\s]*(\\d{2}\\.\\d{2}\\.\\d{4})' },
{ label: 'Datum: DD.MM.YYYY', regex: 'Datum[:\\s]*(\\d{2}\\.\\d{2}\\.\\d{4})' },
{ label: 'YYYY-MM-DD', regex: '(\\d{4}-\\d{2}-\\d{2})' }
],
betrag: [
{ label: 'Gesamtbetrag: X,XX', regex: 'Gesamtbetrag[:\\s]*([\\d.,]+)' },
{ label: 'Summe: X,XX', regex: 'Summe[:\\s]*([\\d.,]+)' },
{ label: 'Rechnungsbetrag: X,XX', regex: 'Rechnungsbetrag[:\\s]*([\\d.,]+)' },
{ label: 'Total: X,XX', regex: 'Total[:\\s]*([\\d.,]+)' },
{ label: 'Brutto: X,XX', regex: 'Brutto[:\\s]*([\\d.,]+)' },
{ label: 'Netto: X,XX', regex: 'Netto[:\\s]*([\\d.,]+)' },
{ label: 'EUR X,XX (letzter)', regex: 'EUR\\s*([\\d.,]+)(?!.*EUR\\s*[\\d.,]+)' }
],
nummer: [
{ label: 'Rechnungsnummer: XXX', regex: 'Rechnungsnummer[:\\s]*(\\S+)' },
{ label: 'Rechnung Nr. XXX', regex: 'Rechnung\\s*(?:Nr\\.?|Nummer)?[:\\s]*(\\S+)' },
{ label: 'Belegnummer: XXX', regex: 'Belegnummer[:\\s]*(\\S+)' },
{ label: 'Bestellnummer: XXX', regex: 'Bestellnummer[:\\s]*(\\S+)' },
{ label: 'Dokumentnummer: XXX', regex: 'Dokumentnummer[:\\s]*(\\S+)' }
]
};
async function analysiereText() {
const text = document.getElementById('hilfe-text').value.trim();
if (!text) {
alert('Bitte Text eingeben oder Datei hochladen');
return;
}
try {
zeigeLoading('Analysiere Text...');
const result = await api('/extraktion/test', {
method: 'POST',
body: JSON.stringify({ text: text })
});
letzteAnalyse = result;
const container = document.getElementById('hilfe-analyse');
container.innerHTML = `
Automatisch extrahierte Felder:
${JSON.stringify(result.extrahiert, null, 2)}
Falls Werte falsch sind, passe unten die Regex-Muster an!
`;
// Felder vorausfĂŒllen wenn gefunden
document.getElementById('hilfe-firma').value = result.extrahiert.firma || '';
// Standard-Regex vorausfĂŒllen basierend auf erkannten Werten
if (result.extrahiert.datum) {
// PrĂŒfen welches Muster gepasst haben könnte
document.getElementById('hilfe-datum-regex').value = STANDARD_REGEX.datum[0].regex;
} else {
document.getElementById('hilfe-datum-regex').value = STANDARD_REGEX.datum[0].regex;
}
if (result.extrahiert.betrag) {
document.getElementById('hilfe-betrag-regex').value = STANDARD_REGEX.betrag[0].regex;
} else {
document.getElementById('hilfe-betrag-regex').value = STANDARD_REGEX.betrag[0].regex;
}
if (result.extrahiert.nummer) {
document.getElementById('hilfe-nummer-regex').value = STANDARD_REGEX.nummer[0].regex;
} else {
document.getElementById('hilfe-nummer-regex').value = STANDARD_REGEX.nummer[0].regex;
}
// Keywords aus ersten erkennbaren Wörtern
const words = text.split(/\s+/).filter(w => w.length > 4 && /^[a-zA-ZĂ€Ă¶ĂŒĂĂĂĂ]+$/.test(w));
document.getElementById('hilfe-keywords').value = words.slice(0, 3).join(', ').toLowerCase();
document.getElementById('hilfe-ergebnis').classList.remove('hidden');
document.getElementById('hilfe-regel-vorschau').classList.add('hidden');
} catch (error) {
alert('Fehler: ' + error.message);
} finally {
versteckeLoading();
}
}
async function testeMitRegex() {
const text = document.getElementById('hilfe-text').value.trim();
if (!text) {
alert('Bitte erst Text eingeben');
return;
}
const firma = document.getElementById('hilfe-firma').value.trim();
const datumRegex = document.getElementById('hilfe-datum-regex').value.trim();
const betragRegex = document.getElementById('hilfe-betrag-regex').value.trim();
const nummerRegex = document.getElementById('hilfe-nummer-regex').value.trim();
try {
zeigeLoading('Teste Regex...');
// Custom Regex an Backend senden
const result = await api('/extraktion/test-custom', {
method: 'POST',
body: JSON.stringify({
text: text,
firma: firma,
datum_regex: datumRegex,
betrag_regex: betragRegex,
nummer_regex: nummerRegex
})
});
letzteAnalyse = result;
const container = document.getElementById('hilfe-analyse');
container.innerHTML = `
Extrahierte Felder (mit deinen Regex):
${JSON.stringify(result.extrahiert, null, 2)}
${result.fehler ? `Fehler: ${escapeHtml(result.fehler)}
` : ''}
`;
} catch (error) {
alert('Fehler: ' + error.message);
} finally {
versteckeLoading();
}
}
function erstelleRegelAusHilfe() {
const firma = document.getElementById('hilfe-firma').value.trim();
const datumRegex = document.getElementById('hilfe-datum-regex').value.trim();
const betragRegex = document.getElementById('hilfe-betrag-regex').value.trim();
const nummerRegex = document.getElementById('hilfe-nummer-regex').value.trim();
const keywords = document.getElementById('hilfe-keywords').value.trim();
if (!keywords) {
alert('Bitte mindestens Keywords fĂŒr die Erkennung eingeben');
return;
}
// Regel-Objekt bauen
const regel = {
name: firma ? `${firma} Rechnung` : 'Neue Regel',
prioritaet: 50,
muster: {
text_match: keywords.split(',').map(k => k.trim().toLowerCase()).filter(k => k)
},
extraktion: {},
schema: '{datum} - Rechnung - {firma} - {nummer} - {betrag} EUR.pdf',
unterordner: firma ? firma.toLowerCase().replace(/[^a-z0-9]/g, '_') : null
};
// Extraktion hinzufĂŒgen
if (firma) {
regel.extraktion.firma = { wert: firma };
}
if (datumRegex) {
regel.extraktion.datum = { regex: datumRegex };
}
if (betragRegex) {
regel.extraktion.betrag = { regex: betragRegex, typ: 'betrag' };
}
if (nummerRegex) {
regel.extraktion.nummer = { regex: nummerRegex };
}
// Vorschau anzeigen
const jsonStr = JSON.stringify(regel, null, 2);
document.getElementById('hilfe-regel-json').textContent = jsonStr;
document.getElementById('hilfe-regel-vorschau').classList.remove('hidden');
// In Regel-Modal ĂŒbernehmen Button
if (confirm('Regel ĂŒbernehmen und im Editor öffnen?')) {
// Modal schlieĂen
schliesseModal('hilfe-modal');
// Regel-Modal öffnen und befĂŒllen
editierteRegelId = null;
document.getElementById('regel-modal-title').textContent = 'Regel hinzufĂŒgen';
document.getElementById('regel-name').value = regel.name;
document.getElementById('regel-prioritaet').value = regel.prioritaet;
document.getElementById('regel-muster').value = JSON.stringify(regel.muster, null, 2);
document.getElementById('regel-extraktion').value = JSON.stringify(regel.extraktion, null, 2);
document.getElementById('regel-schema').value = regel.schema;
document.getElementById('regel-unterordner').value = regel.unterordner || '';
document.getElementById('regel-test-text').value = document.getElementById('hilfe-text').value;
document.getElementById('regel-test-ergebnis').classList.add('hidden');
document.getElementById('regel-modal').classList.remove('hidden');
}
}
async function ladeHilfeDatei(input) {
const file = input.files[0];
if (!file) return;
if (file.type === 'text/plain') {
// TXT Datei direkt lesen
const reader = new FileReader();
reader.onload = (e) => {
document.getElementById('hilfe-text').value = e.target.result;
};
reader.readAsText(file);
} else if (file.type === 'application/pdf') {
// PDF serverseitig verarbeiten
try {
zeigeLoading('Extrahiere Text aus PDF...');
const formData = new FormData();
formData.append('file', file);
const response = await fetch('/api/extraktion/upload-pdf', {
method: 'POST',
body: formData
});
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new Error(error.detail || 'PDF-Verarbeitung fehlgeschlagen');
}
const result = await response.json();
document.getElementById('hilfe-text').value = result.text || '';
if (result.ocr_durchgefuehrt) {
alert('Text wurde per OCR extrahiert (Bild-PDF erkannt)');
}
// Automatisch analysieren nach Upload
versteckeLoading();
await analysiereText();
return; // Loading wird in analysiereText() gehandelt
} catch (error) {
alert('Fehler beim PDF-Upload: ' + error.message);
} finally {
versteckeLoading();
}
}
// Input zurĂŒcksetzen fĂŒr erneuten Upload
input.value = '';
}
// Regex-Preset aus Dropdown ĂŒbernehmen
function setzeRegexPreset(typ) {
const select = document.getElementById(`hilfe-${typ}-preset`);
const input = document.getElementById(`hilfe-${typ}-regex`);
if (select.value) {
input.value = select.value;
}
select.value = ''; // Reset dropdown
}
// ============ Datei-Browser fĂŒr PDFs aus Quell-Ordnern ============
let pdfBrowserOrdner = [];
async function zeigePdfBrowser() {
document.getElementById('pdf-browser-modal').classList.remove('hidden');
document.getElementById('pdf-browser-liste').innerHTML = 'Lade Ordner...';
try {
// Quell-Ordner laden
const ordner = await api('/ordner');
pdfBrowserOrdner = ordner;
if (ordner.length === 0) {
document.getElementById('pdf-browser-liste').innerHTML =
'Keine Quell-Ordner konfiguriert
';
return;
}
// Ordner-Auswahl anzeigen
let html = '';
html += 'Ordner auswÀhlen: ';
html += '';
html += '-- Ordner wÀhlen -- ';
for (const o of ordner) {
html += `${escapeHtml(o.name)} (${escapeHtml(o.pfad)}) `;
}
html += '
';
html += '
';
document.getElementById('pdf-browser-liste').innerHTML = html;
} catch (error) {
document.getElementById('pdf-browser-liste').innerHTML =
`Fehler: ${escapeHtml(error.message)}
`;
}
}
async function ladePdfDateien() {
const ordnerId = document.getElementById('pdf-browser-ordner').value;
const container = document.getElementById('pdf-dateien-liste');
if (!ordnerId) {
container.innerHTML = '';
return;
}
container.innerHTML = 'Scanne Ordner...';
try {
const result = await api(`/ordner/${ordnerId}/scannen`);
if (!result.dateien || result.dateien.length === 0) {
container.innerHTML = 'Keine Dateien im Ordner
';
return;
}
// Ordner-Daten fĂŒr Pfad-Konstruktion
const ordner = pdfBrowserOrdner.find(o => o.id == ordnerId);
const basePfad = ordner?.pfad || '';
let html = `${result.anzahl} Dateien gefunden:
`;
for (const datei of result.dateien) {
const fullPath = basePfad + (basePfad.endsWith('/') ? '' : '/') + datei;
html += `
đ ${escapeHtml(datei)}
`;
}
html += ' ';
container.innerHTML = html;
} catch (error) {
container.innerHTML = `Fehler: ${escapeHtml(error.message)}
`;
}
}
async function waehleServerPdf(pfad) {
try {
zeigeLoading('Extrahiere Text aus PDF...');
const response = await api('/extraktion/from-file', {
method: 'POST',
body: JSON.stringify({ pfad: pfad })
});
document.getElementById('hilfe-text').value = response.text || '';
schliesseModal('pdf-browser-modal');
if (response.ocr_durchgefuehrt) {
alert('Text wurde per OCR extrahiert (Bild-PDF erkannt)');
}
// Automatisch analysieren nach Auswahl
versteckeLoading();
await analysiereText();
return;
} catch (error) {
alert('Fehler: ' + error.message);
} finally {
versteckeLoading();
}
}