Fehler beseitigt, Zeitpläne und Status, Export Funktion
einzelne Sortierregeln
This commit is contained in:
parent
e2fe187c5d
commit
adbb1935ec
6 changed files with 206 additions and 56 deletions
7
.claude/settings.json
Executable file
7
.claude/settings.json
Executable file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(mysql:*)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Docker - Image/V 2.2/V 2.2.tar
Executable file
BIN
Docker - Image/V 2.2/V 2.2.tar
Executable file
Binary file not shown.
BIN
Docker - Image/V 2.3/V 2.3.tar
Executable file
BIN
Docker - Image/V 2.3/V 2.3.tar
Executable file
Binary file not shown.
|
|
@ -1307,6 +1307,28 @@ def export_regeln(db: Session = Depends(get_db)):
|
||||||
return {"regeln": export_data, "anzahl": len(export_data)}
|
return {"regeln": export_data, "anzahl": len(export_data)}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/regeln/{id}/export")
|
||||||
|
def export_einzelne_regel(id: int, db: Session = Depends(get_db)):
|
||||||
|
"""Exportiert eine einzelne Regel als JSON"""
|
||||||
|
regel = db.query(SortierRegel).filter(SortierRegel.id == id).first()
|
||||||
|
if not regel:
|
||||||
|
raise HTTPException(status_code=404, detail="Regel nicht gefunden")
|
||||||
|
|
||||||
|
export_data = {
|
||||||
|
"name": regel.name,
|
||||||
|
"prioritaet": regel.prioritaet,
|
||||||
|
"muster": regel.muster,
|
||||||
|
"extraktion": regel.extraktion,
|
||||||
|
"schema": regel.schema,
|
||||||
|
"unterordner": regel.unterordner,
|
||||||
|
"ziel_ordner": regel.ziel_ordner,
|
||||||
|
"nur_umbenennen": regel.nur_umbenennen,
|
||||||
|
"ist_fallback": regel.ist_fallback,
|
||||||
|
"aktiv": regel.aktiv
|
||||||
|
}
|
||||||
|
return {"regeln": [export_data], "anzahl": 1, "regel_name": regel.name}
|
||||||
|
|
||||||
|
|
||||||
class RegelnImportRequest(BaseModel):
|
class RegelnImportRequest(BaseModel):
|
||||||
regeln: List[dict]
|
regeln: List[dict]
|
||||||
modus: str = "hinzufuegen" # "hinzufuegen", "ersetzen", "aktualisieren"
|
modus: str = "hinzufuegen" # "hinzufuegen", "ersetzen", "aktualisieren"
|
||||||
|
|
@ -2578,10 +2600,11 @@ def regel_vorschau(id: int, data: ExtraktionTestRequest, db: Session = Depends(g
|
||||||
|
|
||||||
class ZeitplanCreate(BaseModel):
|
class ZeitplanCreate(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
typ: str # "mail_abruf", "grobsortierung", "sortierregeln"
|
typ: str # "mail_abruf", "grobsortierung", "sortierregeln", "db_backup"
|
||||||
postfach_id: Optional[int] = None
|
postfach_id: Optional[int] = None
|
||||||
quell_ordner_id: Optional[int] = None
|
quell_ordner_id: Optional[int] = None
|
||||||
regel_id: Optional[int] = None # Für typ="sortierregeln"
|
regel_id: Optional[int] = None # Für typ="sortierregeln"
|
||||||
|
datenbank_id: Optional[int] = None # Für typ="db_backup"
|
||||||
intervall: str # "stündlich", "täglich", "wöchentlich", "monatlich"
|
intervall: str # "stündlich", "täglich", "wöchentlich", "monatlich"
|
||||||
stunde: int = 6
|
stunde: int = 6
|
||||||
minute: int = 0
|
minute: int = 0
|
||||||
|
|
@ -2597,6 +2620,7 @@ class ZeitplanResponse(BaseModel):
|
||||||
postfach_id: Optional[int]
|
postfach_id: Optional[int]
|
||||||
quell_ordner_id: Optional[int]
|
quell_ordner_id: Optional[int]
|
||||||
regel_id: Optional[int]
|
regel_id: Optional[int]
|
||||||
|
datenbank_id: Optional[int]
|
||||||
intervall: str
|
intervall: str
|
||||||
stunde: int
|
stunde: int
|
||||||
minute: int
|
minute: int
|
||||||
|
|
@ -2639,8 +2663,17 @@ def erstelle_zeitplan(data: ZeitplanCreate, db: Session = Depends(get_db)):
|
||||||
from ..services.scheduler_service import sync_zeitplaene, trigger_zeitplan_manuell
|
from ..services.scheduler_service import sync_zeitplaene, trigger_zeitplan_manuell
|
||||||
sync_zeitplaene()
|
sync_zeitplaene()
|
||||||
|
|
||||||
# Zeitplan direkt beim Erstellen einmal ausführen
|
# Zeitplan direkt beim Erstellen einmal ausführen (in separatem Thread)
|
||||||
trigger_zeitplan_manuell(zeitplan.id)
|
import threading
|
||||||
|
def run_zeitplan():
|
||||||
|
try:
|
||||||
|
trigger_zeitplan_manuell(zeitplan.id)
|
||||||
|
except Exception as e:
|
||||||
|
import logging
|
||||||
|
logging.getLogger(__name__).warning(f"Fehler bei sofortiger Ausführung: {e}")
|
||||||
|
|
||||||
|
thread = threading.Thread(target=run_zeitplan, daemon=True)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
return zeitplan
|
return zeitplan
|
||||||
|
|
||||||
|
|
@ -2662,8 +2695,17 @@ def aktualisiere_zeitplan(id: int, data: ZeitplanCreate, db: Session = Depends(g
|
||||||
from ..services.scheduler_service import sync_zeitplaene, trigger_zeitplan_manuell
|
from ..services.scheduler_service import sync_zeitplaene, trigger_zeitplan_manuell
|
||||||
sync_zeitplaene()
|
sync_zeitplaene()
|
||||||
|
|
||||||
# Zeitplan direkt beim Speichern einmal ausführen
|
# Zeitplan direkt beim Speichern einmal ausführen (in separatem Thread)
|
||||||
trigger_zeitplan_manuell(zeitplan.id)
|
import threading
|
||||||
|
def run_zeitplan():
|
||||||
|
try:
|
||||||
|
trigger_zeitplan_manuell(zeitplan.id)
|
||||||
|
except Exception as e:
|
||||||
|
import logging
|
||||||
|
logging.getLogger(__name__).warning(f"Fehler bei sofortiger Ausführung: {e}")
|
||||||
|
|
||||||
|
thread = threading.Thread(target=run_zeitplan, daemon=True)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
return zeitplan
|
return zeitplan
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -465,14 +465,19 @@ def execute_mail_abruf(db, zeitplan: Zeitplan) -> Dict:
|
||||||
))
|
))
|
||||||
|
|
||||||
# Postfach-Status aktualisieren
|
# Postfach-Status aktualisieren
|
||||||
postfach.letzter_abruf = datetime.utcnow()
|
try:
|
||||||
postfach.letzte_anzahl = len(ergebnisse)
|
postfach.letzter_abruf = datetime.utcnow()
|
||||||
db.commit()
|
postfach.letzte_anzahl = len(ergebnisse)
|
||||||
|
db.commit()
|
||||||
|
except Exception as commit_error:
|
||||||
|
logger.warning(f"Postfach-Status Update fehlgeschlagen: {commit_error}")
|
||||||
|
db.rollback()
|
||||||
|
|
||||||
gesamt_dateien += len(ergebnisse)
|
gesamt_dateien += len(ergebnisse)
|
||||||
fetcher.disconnect()
|
fetcher.disconnect()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
db.rollback()
|
||||||
fehler.append(f"{postfach.name}: {str(e)[:100]}")
|
fehler.append(f"{postfach.name}: {str(e)[:100]}")
|
||||||
|
|
||||||
if fehler:
|
if fehler:
|
||||||
|
|
|
||||||
|
|
@ -1166,6 +1166,7 @@ function renderRegeln(regeln) {
|
||||||
<button class="btn btn-sm" onclick="bearbeiteRegel(${r.id})" title="Bearbeiten">✎</button>
|
<button class="btn btn-sm" onclick="bearbeiteRegel(${r.id})" title="Bearbeiten">✎</button>
|
||||||
<button class="btn btn-sm" onclick="regelAktivieren(${r.id})" title="${r.aktiv ? 'Deaktivieren' : 'Aktivieren'}">${r.aktiv ? '⏸' : '▶'}</button>
|
<button class="btn btn-sm" onclick="regelAktivieren(${r.id})" title="${r.aktiv ? 'Deaktivieren' : 'Aktivieren'}">${r.aktiv ? '⏸' : '▶'}</button>
|
||||||
<button class="btn btn-sm" onclick="kopiereRegel(${r.id})" title="Regel kopieren">📋</button>
|
<button class="btn btn-sm" onclick="kopiereRegel(${r.id})" title="Regel kopieren">📋</button>
|
||||||
|
<button class="btn btn-sm" onclick="exportiereEinzelneRegel(${r.id})" title="Regel exportieren">📤</button>
|
||||||
<button class="btn btn-sm btn-danger" onclick="regelLoeschen(${r.id})" title="Löschen">×</button>
|
<button class="btn btn-sm btn-danger" onclick="regelLoeschen(${r.id})" title="Löschen">×</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1185,6 +1186,30 @@ async function kopiereRegel(id) {
|
||||||
|
|
||||||
// ============ Regeln Import/Export ============
|
// ============ Regeln Import/Export ============
|
||||||
|
|
||||||
|
async function exportiereEinzelneRegel(id) {
|
||||||
|
try {
|
||||||
|
const result = await api(`/regeln/${id}/export`);
|
||||||
|
const json = JSON.stringify(result.regeln, null, 2);
|
||||||
|
|
||||||
|
// Download als Datei
|
||||||
|
const blob = new Blob([json], { type: 'application/json' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
// Dateiname mit Regelname
|
||||||
|
const fileName = `regel_${result.regel_name.replace(/[^a-zA-Z0-9äöüÄÖÜß_-]/g, '_')}.json`;
|
||||||
|
a.download = fileName;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
|
||||||
|
showAlert(`Regel "${result.regel_name}" exportiert`, 'success');
|
||||||
|
} catch (error) {
|
||||||
|
showAlert('Fehler beim Export: ' + error.message, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function exportiereRegeln() {
|
async function exportiereRegeln() {
|
||||||
try {
|
try {
|
||||||
const result = await api('/regeln/export');
|
const result = await api('/regeln/export');
|
||||||
|
|
@ -2500,15 +2525,16 @@ function renderZeitplaene(zeitplaene) {
|
||||||
const naechste = zp.naechste_ausfuehrung ? formatDatum(zp.naechste_ausfuehrung) : '-';
|
const naechste = zp.naechste_ausfuehrung ? formatDatum(zp.naechste_ausfuehrung) : '-';
|
||||||
const letzte = zp.letzte_ausfuehrung ? formatDatum(zp.letzte_ausfuehrung) : 'Noch nie';
|
const letzte = zp.letzte_ausfuehrung ? formatDatum(zp.letzte_ausfuehrung) : 'Noch nie';
|
||||||
|
|
||||||
|
const statusText = zp.letzter_status === 'erfolg' ? 'Erfolg' : (zp.letzter_status === 'fehler' ? 'Fehler' : zp.letzter_status);
|
||||||
return `
|
return `
|
||||||
<div class="config-item" style="${aktivClass}">
|
<div class="config-item" style="${aktivClass}">
|
||||||
<div class="config-item-info">
|
<div class="config-item-info">
|
||||||
<h4>${typIcon} ${escapeHtml(zp.name)}
|
<h4>${typIcon} ${escapeHtml(zp.name)}
|
||||||
<span class="badge ${zp.aktiv ? 'badge-success' : 'badge-danger'}">${zp.aktiv ? 'Aktiv' : 'Inaktiv'}</span>
|
<span class="badge ${zp.aktiv ? 'badge-success' : 'badge-danger'}">${zp.aktiv ? 'Aktiv' : 'Inaktiv'}</span>
|
||||||
<span class="badge badge-info">${zp.intervall}</span>
|
<span class="badge badge-info">${formatIntervall(zp.intervall)}</span>
|
||||||
</h4>
|
</h4>
|
||||||
<small>Nächste: ${naechste} | Letzte: ${letzte}</small>
|
<small>Nächste: ${naechste} | Letzte: ${letzte}</small>
|
||||||
${zp.letzter_status ? `<small style="display:block;" class="${statusClass}">Status: ${zp.letzter_status}${zp.letzte_meldung ? ' - ' + escapeHtml(zp.letzte_meldung.substring(0, 80)) : ''}</small>` : ''}
|
${zp.letzter_status ? `<small style="display:block;" class="${statusClass}">Status: ${statusText}${zp.letzte_meldung ? ' - ' + escapeHtml(zp.letzte_meldung.substring(0, 80)) : ''}</small>` : ''}
|
||||||
</div>
|
</div>
|
||||||
<div class="config-item-actions">
|
<div class="config-item-actions">
|
||||||
<button class="btn btn-sm btn-success" onclick="zeitplanAusfuehren(${zp.id})" title="Jetzt ausführen">▶</button>
|
<button class="btn btn-sm btn-success" onclick="zeitplanAusfuehren(${zp.id})" title="Jetzt ausführen">▶</button>
|
||||||
|
|
@ -2523,50 +2549,92 @@ function renderZeitplaene(zeitplaene) {
|
||||||
async function ladeStatus() {
|
async function ladeStatus() {
|
||||||
try {
|
try {
|
||||||
const result = await api('/status/uebersicht');
|
const result = await api('/status/uebersicht');
|
||||||
renderStatusUebersicht(result);
|
renderStatusFuerTabs(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Fehler:', error);
|
console.error('Fehler beim Laden des Status:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderStatusUebersicht(status) {
|
function renderStatusFuerTabs(status) {
|
||||||
const container = document.getElementById('status-uebersicht');
|
// Mailabruf Status
|
||||||
|
const mailabrufContainer = document.getElementById('status-mailabruf');
|
||||||
let html = '<div class="status-grid">';
|
if (mailabrufContainer) {
|
||||||
|
let html = '';
|
||||||
// Postfächer
|
if (status.postfaecher && status.postfaecher.length > 0) {
|
||||||
html += '<div class="status-section"><h4>📧 Postfächer</h4>';
|
for (const p of status.postfaecher) {
|
||||||
if (status.postfaecher && status.postfaecher.length > 0) {
|
const aktiv = p.aktiv ? '🟢' : '⚪';
|
||||||
for (const p of status.postfaecher) {
|
const letzte = p.letzter_abruf ? formatDatum(p.letzter_abruf) : 'Nie';
|
||||||
const aktiv = p.aktiv ? '🟢' : '⚪';
|
html += `<div class="status-item">${aktiv} ${escapeHtml(p.name)}: ${letzte} (${p.letzte_anzahl || 0} Dateien)</div>`;
|
||||||
const letzte = p.letzter_abruf ? formatDatum(p.letzter_abruf) : 'Nie';
|
}
|
||||||
html += `<div class="status-item">${aktiv} ${escapeHtml(p.name)}: ${letzte} (${p.letzte_anzahl || 0} Dateien)</div>`;
|
} else {
|
||||||
|
html = '<p class="empty-state">Keine Postfächer konfiguriert</p>';
|
||||||
}
|
}
|
||||||
} else {
|
mailabrufContainer.innerHTML = html;
|
||||||
html += '<div class="status-item">Keine Postfächer</div>';
|
|
||||||
}
|
}
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
// Grobsortierung
|
// Grobsortierung Status
|
||||||
html += '<div class="status-section"><h4>📁 Grobsortierung</h4>';
|
const grobContainer = document.getElementById('status-grobsortierung');
|
||||||
if (status.quell_ordner && status.quell_ordner.length > 0) {
|
if (grobContainer) {
|
||||||
for (const o of status.quell_ordner) {
|
let html = '';
|
||||||
const aktiv = o.aktiv ? '🟢' : '⚪';
|
if (status.quell_ordner && status.quell_ordner.length > 0) {
|
||||||
html += `<div class="status-item">${aktiv} ${escapeHtml(o.name)}</div>`;
|
for (const o of status.quell_ordner) {
|
||||||
|
const aktiv = o.aktiv ? '🟢' : '⚪';
|
||||||
|
html += `<div class="status-item">${aktiv} ${escapeHtml(o.name)}: ${escapeHtml(o.pfad)}</div>`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
html = '<p class="empty-state">Keine Quellordner konfiguriert</p>';
|
||||||
}
|
}
|
||||||
} else {
|
grobContainer.innerHTML = html;
|
||||||
html += '<div class="status-item">Keine Ordner</div>';
|
|
||||||
}
|
}
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
// Scheduler
|
// Feinsortierung Status
|
||||||
html += '<div class="status-section"><h4>⏰ Scheduler</h4>';
|
const feinContainer = document.getElementById('status-feinsortierung');
|
||||||
const schedulerStatus = status.scheduler?.scheduler_laeuft ? '🟢 Läuft' : '🔴 Gestoppt';
|
if (feinContainer) {
|
||||||
html += `<div class="status-item">${schedulerStatus}</div>`;
|
let html = '';
|
||||||
html += '</div>';
|
const schedulerStatus = status.scheduler?.scheduler_laeuft ? '🟢 Scheduler läuft' : '🔴 Scheduler gestoppt';
|
||||||
|
const regelnZeitplaene = (status.scheduler?.zeitplaene || []).filter(z => z.typ === 'sortierregeln');
|
||||||
|
html += `<div class="status-item">${schedulerStatus}</div>`;
|
||||||
|
if (regelnZeitplaene.length > 0) {
|
||||||
|
for (const z of regelnZeitplaene) {
|
||||||
|
const statusIcon = z.letzter_status === 'erfolg' ? '✓' : (z.letzter_status === 'fehler' ? '✗' : '○');
|
||||||
|
html += `<div class="status-item">${statusIcon} ${escapeHtml(z.name)}: ${z.letzte_meldung || 'Noch nicht ausgeführt'}</div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
feinContainer.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
html += '</div>';
|
// DB Backup Status
|
||||||
container.innerHTML = html;
|
const dbContainer = document.getElementById('status-dbbackup');
|
||||||
|
if (dbContainer) {
|
||||||
|
let html = '';
|
||||||
|
const schedulerStatus = status.scheduler?.scheduler_laeuft ? '🟢 Scheduler läuft' : '🔴 Scheduler gestoppt';
|
||||||
|
const dbZeitplaene = (status.scheduler?.zeitplaene || []).filter(z => z.typ === 'db_backup');
|
||||||
|
html += `<div class="status-item">${schedulerStatus}</div>`;
|
||||||
|
if (dbZeitplaene.length > 0) {
|
||||||
|
for (const z of dbZeitplaene) {
|
||||||
|
const statusIcon = z.letzter_status === 'erfolg' ? '✓' : (z.letzter_status === 'fehler' ? '✗' : '○');
|
||||||
|
const letzteAusfuehrung = z.letzte_ausfuehrung ? formatDatum(z.letzte_ausfuehrung) : 'Noch nie';
|
||||||
|
html += `<div class="status-item">${statusIcon} ${escapeHtml(z.name)}</div>`;
|
||||||
|
html += `<div class="status-item" style="margin-left: 20px; font-size: 0.9em;">Letztes Backup: ${letzteAusfuehrung}</div>`;
|
||||||
|
if (z.letzte_meldung) {
|
||||||
|
html += `<div class="status-item" style="margin-left: 20px; font-size: 0.9em;">${escapeHtml(z.letzte_meldung)}</div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
html += '<div class="status-item">Keine DB-Backup Zeitpläne konfiguriert</div>';
|
||||||
|
}
|
||||||
|
dbContainer.innerHTML = html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatIntervall(intervall) {
|
||||||
|
const mapping = {
|
||||||
|
'stündlich': 'Stündlich',
|
||||||
|
'täglich': 'Täglich',
|
||||||
|
'wöchentlich': 'Wöchentlich',
|
||||||
|
'monatlich': 'Monatlich'
|
||||||
|
};
|
||||||
|
return mapping[intervall] || intervall;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDatum(isoString) {
|
function formatDatum(isoString) {
|
||||||
|
|
@ -2599,6 +2667,7 @@ async function zeigeZeitplanModal(zeitplan = null) {
|
||||||
document.getElementById('zp-postfach').value = zeitplan?.postfach_id || '';
|
document.getElementById('zp-postfach').value = zeitplan?.postfach_id || '';
|
||||||
document.getElementById('zp-ordner').value = zeitplan?.quell_ordner_id || '';
|
document.getElementById('zp-ordner').value = zeitplan?.quell_ordner_id || '';
|
||||||
document.getElementById('zp-regel').value = zeitplan?.regel_id || '';
|
document.getElementById('zp-regel').value = zeitplan?.regel_id || '';
|
||||||
|
document.getElementById('zp-db').value = zeitplan?.datenbank_id || '';
|
||||||
|
|
||||||
zeitplanTypChanged();
|
zeitplanTypChanged();
|
||||||
zeitplanIntervallChanged();
|
zeitplanIntervallChanged();
|
||||||
|
|
@ -2673,6 +2742,7 @@ async function speichereZeitplan() {
|
||||||
const postfachId = document.getElementById('zp-postfach').value;
|
const postfachId = document.getElementById('zp-postfach').value;
|
||||||
const ordnerId = document.getElementById('zp-ordner').value;
|
const ordnerId = document.getElementById('zp-ordner').value;
|
||||||
const regelId = document.getElementById('zp-regel').value;
|
const regelId = document.getElementById('zp-regel').value;
|
||||||
|
const dbId = document.getElementById('zp-db').value;
|
||||||
|
|
||||||
if (data.typ === 'mail_abruf' && postfachId) {
|
if (data.typ === 'mail_abruf' && postfachId) {
|
||||||
data.postfach_id = parseInt(postfachId);
|
data.postfach_id = parseInt(postfachId);
|
||||||
|
|
@ -2683,6 +2753,9 @@ async function speichereZeitplan() {
|
||||||
if (data.typ === 'sortierregeln' && regelId) {
|
if (data.typ === 'sortierregeln' && regelId) {
|
||||||
data.regel_id = parseInt(regelId);
|
data.regel_id = parseInt(regelId);
|
||||||
}
|
}
|
||||||
|
if (data.typ === 'db_backup' && dbId) {
|
||||||
|
data.datenbank_id = parseInt(dbId);
|
||||||
|
}
|
||||||
|
|
||||||
if (!data.name) {
|
if (!data.name) {
|
||||||
showAlert('Bitte einen Namen eingeben', 'warning');
|
showAlert('Bitte einen Namen eingeben', 'warning');
|
||||||
|
|
@ -2696,17 +2769,34 @@ async function speichereZeitplan() {
|
||||||
await api('/zeitplaene', { method: 'POST', body: JSON.stringify(data) });
|
await api('/zeitplaene', { method: 'POST', body: JSON.stringify(data) });
|
||||||
}
|
}
|
||||||
schliesseModal('zeitplan-modal');
|
schliesseModal('zeitplan-modal');
|
||||||
ladeZeitplaene();
|
aktualisiereZeitplanListen();
|
||||||
ladeStatus();
|
ladeStatus();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showAlert(error.message, 'error');
|
showAlert(error.message, 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function aktualisiereZeitplanListen() {
|
||||||
|
ladeZeitplaene();
|
||||||
|
const aktiverTab = localStorage.getItem('aktiver-tab');
|
||||||
|
if (aktiverTab) {
|
||||||
|
const tabMapping = {
|
||||||
|
'mailabruf': ['mail_abruf', 'zeitplaene-mailabruf'],
|
||||||
|
'grobsortierung': ['grobsortierung', 'zeitplaene-grobsortierung'],
|
||||||
|
'feinsortierung': ['sortierregeln', 'zeitplaene-feinsortierung'],
|
||||||
|
'dbbackup': ['db_backup', 'zeitplaene-dbbackup']
|
||||||
|
};
|
||||||
|
const mapping = tabMapping[aktiverTab];
|
||||||
|
if (mapping) {
|
||||||
|
ladeZeitplaeneNachTyp(mapping[0], mapping[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function zeitplanAktivieren(id) {
|
async function zeitplanAktivieren(id) {
|
||||||
try {
|
try {
|
||||||
await api(`/zeitplaene/${id}/aktivieren`, { method: 'POST' });
|
await api(`/zeitplaene/${id}/aktivieren`, { method: 'POST' });
|
||||||
ladeZeitplaene();
|
aktualisiereZeitplanListen();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showAlert(error.message, 'error');
|
showAlert(error.message, 'error');
|
||||||
}
|
}
|
||||||
|
|
@ -2717,7 +2807,7 @@ async function zeitplanAusfuehren(id) {
|
||||||
zeigeLoading('Führe Zeitplan aus...');
|
zeigeLoading('Führe Zeitplan aus...');
|
||||||
const result = await api(`/zeitplaene/${id}/ausfuehren`, { method: 'POST' });
|
const result = await api(`/zeitplaene/${id}/ausfuehren`, { method: 'POST' });
|
||||||
showAlert(result.meldung || 'Ausgeführt', 'success', 'Zeitplan ausgeführt');
|
showAlert(result.meldung || 'Ausgeführt', 'success', 'Zeitplan ausgeführt');
|
||||||
ladeZeitplaene();
|
aktualisiereZeitplanListen();
|
||||||
ladeStatus();
|
ladeStatus();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showAlert(error.message, 'error');
|
showAlert(error.message, 'error');
|
||||||
|
|
@ -2739,7 +2829,7 @@ async function zeitplanLoeschen(id) {
|
||||||
if (!await showConfirm('Zeitplan wirklich löschen?')) return;
|
if (!await showConfirm('Zeitplan wirklich löschen?')) return;
|
||||||
try {
|
try {
|
||||||
await api(`/zeitplaene/${id}`, { method: 'DELETE' });
|
await api(`/zeitplaene/${id}`, { method: 'DELETE' });
|
||||||
ladeZeitplaene();
|
aktualisiereZeitplanListen();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showAlert(error.message, 'error');
|
showAlert(error.message, 'error');
|
||||||
}
|
}
|
||||||
|
|
@ -2835,8 +2925,8 @@ function debugLog(message, type = 'info') {
|
||||||
|
|
||||||
async function ladeZeitplaeneNachTyp(typ, containerId) {
|
async function ladeZeitplaeneNachTyp(typ, containerId) {
|
||||||
try {
|
try {
|
||||||
const zeitplaene = await api('/zeitplaene');
|
const result = await api('/zeitplaene');
|
||||||
const gefiltert = zeitplaene.filter(z => z.typ === typ);
|
const gefiltert = (result.zeitplaene || []).filter(z => z.typ === typ);
|
||||||
const container = document.getElementById(containerId);
|
const container = document.getElementById(containerId);
|
||||||
|
|
||||||
if (gefiltert.length === 0) {
|
if (gefiltert.length === 0) {
|
||||||
|
|
@ -2844,19 +2934,25 @@ async function ladeZeitplaeneNachTyp(typ, containerId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
container.innerHTML = gefiltert.map(z => `
|
container.innerHTML = gefiltert.map(z => {
|
||||||
<div class="config-item">
|
const letzteAusfuehrung = z.letzte_ausfuehrung ? formatDatum(z.letzte_ausfuehrung) : 'Noch nie';
|
||||||
|
const naechsteAusfuehrung = z.naechste_ausfuehrung ? formatDatum(z.naechste_ausfuehrung) : '-';
|
||||||
|
return `
|
||||||
|
<div class="config-item" style="${z.aktiv ? '' : 'opacity: 0.5;'}">
|
||||||
<div class="config-item-info">
|
<div class="config-item-info">
|
||||||
<h4>${escapeHtml(z.name)} ${z.aktiv ? '✓' : ''}</h4>
|
<h4>${escapeHtml(z.name)} ${z.aktiv ? '✓' : ''}</h4>
|
||||||
<small>${z.intervall} ${z.stunde != null ? `um ${z.stunde}:${String(z.minute || 0).padStart(2, '0')}` : ''}</small>
|
<small>${formatIntervall(z.intervall)} ${z.stunde != null ? `um ${z.stunde}:${String(z.minute || 0).padStart(2, '0')} Uhr` : ''}</small>
|
||||||
|
<small style="display:block;">Nächste: ${naechsteAusfuehrung} | Letzte: ${letzteAusfuehrung}</small>
|
||||||
|
${z.letzter_status ? `<small style="display:block;" class="${z.letzter_status === 'erfolg' ? 'success' : 'error'}">Status: ${z.letzter_status === 'erfolg' ? 'Erfolg' : 'Fehler'}${z.letzte_meldung ? ' - ' + escapeHtml(z.letzte_meldung.substring(0, 60)) : ''}</small>` : ''}
|
||||||
</div>
|
</div>
|
||||||
<div class="config-item-actions">
|
<div class="config-item-actions">
|
||||||
<button class="btn btn-sm" onclick="zeitplanAusfuehren(${z.id})" title="Jetzt ausführen">▶</button>
|
<button class="btn btn-sm btn-success" onclick="zeitplanAusfuehren(${z.id})" title="Jetzt ausführen">▶</button>
|
||||||
<button class="btn btn-sm" onclick="zeitplanAktivieren(${z.id})">${z.aktiv ? '⏸' : '▶'}</button>
|
<button class="btn btn-sm" onclick="zeitplanBearbeiten(${z.id})" title="Bearbeiten">✎</button>
|
||||||
<button class="btn btn-sm btn-danger" onclick="zeitplanLoeschen(${z.id})">🗑</button>
|
<button class="btn btn-sm" onclick="zeitplanAktivieren(${z.id})" title="${z.aktiv ? 'Deaktivieren' : 'Aktivieren'}">${z.aktiv ? '⏸' : '▶'}</button>
|
||||||
|
<button class="btn btn-sm btn-danger" onclick="zeitplanLoeschen(${z.id})" title="Löschen">🗑</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`}).join('');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Fehler beim Laden der Zeitpläne:', error);
|
console.error('Fehler beim Laden der Zeitpläne:', error);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue