diff --git a/Docker - Image/V 2.0/V 2.0.tar b/Docker - Image/V 2.0/V 2.0.tar
new file mode 100755
index 0000000..092072c
Binary files /dev/null and b/Docker - Image/V 2.0/V 2.0.tar differ
diff --git a/Docker - Image/V 2.1/V 2.1.tar b/Docker - Image/V 2.1/V 2.1.tar
new file mode 100755
index 0000000..e86b26a
Binary files /dev/null and b/Docker - Image/V 2.1/V 2.1.tar differ
diff --git a/Source/backend/app/routes/api.py b/Source/backend/app/routes/api.py
index 966fd37..abeccbb 100755
--- a/Source/backend/app/routes/api.py
+++ b/Source/backend/app/routes/api.py
@@ -142,6 +142,7 @@ class RegelResponse(BaseModel):
freie_ordner: Optional[List[str]] = []
ziel_ordner: Optional[str] = None
nur_umbenennen: bool = False
+ ordner_ids: List[int] = [] # IDs der zugeordneten Ordner
class Config:
from_attributes = True
@@ -959,9 +960,40 @@ def verarbeite_ordner(id: int, db: Session = Depends(get_db)):
# ============ Regeln ============
-@router.get("/regeln", response_model=List[RegelResponse])
+@router.get("/regeln")
def liste_regeln(db: Session = Depends(get_db)):
- return db.query(SortierRegel).order_by(SortierRegel.prioritaet).all()
+ """Gibt alle Regeln mit ihren zugeordneten Ordner-IDs zurück"""
+ regeln = db.query(SortierRegel).order_by(SortierRegel.prioritaet).all()
+
+ # Alle Ordner-Zuweisungen auf einmal laden
+ alle_zuweisungen = db.query(OrdnerRegel).all()
+ regel_ordner_map = {}
+ for z in alle_zuweisungen:
+ if z.regel_id not in regel_ordner_map:
+ regel_ordner_map[z.regel_id] = []
+ regel_ordner_map[z.regel_id].append(z.ordner_id)
+
+ # Regeln mit Ordner-IDs anreichern
+ result = []
+ for r in regeln:
+ regel_dict = {
+ "id": r.id,
+ "name": r.name,
+ "prioritaet": r.prioritaet,
+ "aktiv": r.aktiv,
+ "muster": r.muster or {},
+ "extraktion": r.extraktion or {},
+ "ist_fallback": r.ist_fallback,
+ "schema": r.schema,
+ "unterordner": r.unterordner,
+ "freie_ordner": r.freie_ordner or [],
+ "ziel_ordner": r.ziel_ordner,
+ "nur_umbenennen": r.nur_umbenennen,
+ "ordner_ids": regel_ordner_map.get(r.id, [])
+ }
+ result.append(regel_dict)
+
+ return result
@router.post("/regeln", response_model=RegelResponse)
diff --git a/Source/frontend/static/js/app.js b/Source/frontend/static/js/app.js
index fa90d64..b4221b9 100755
--- a/Source/frontend/static/js/app.js
+++ b/Source/frontend/static/js/app.js
@@ -1034,10 +1034,68 @@ function natuerlicheSortierung(a, b) {
return a.localeCompare(b, 'de', { numeric: true, sensitivity: 'base' });
}
+// Cache für Ordner (für Filter-Dropdown)
+let ordnerCache = [];
+
+async function ladeOrdnerFuerFilter() {
+ try {
+ const ordner = await api('/ordner');
+ ordnerCache = ordner;
+
+ const filterSelect = document.getElementById('regeln-ordner-filter');
+ if (!filterSelect) return;
+
+ // Gespeicherten Filter wiederherstellen
+ const gespeichert = localStorage.getItem('regeln-ordner-filter');
+
+ // Bestehende Optionen nach "Ohne Zuordnung" entfernen
+ while (filterSelect.options.length > 2) {
+ filterSelect.remove(2);
+ }
+
+ // Ordner hinzufügen
+ ordner.forEach(o => {
+ const option = document.createElement('option');
+ option.value = o.id;
+ option.textContent = o.name;
+ filterSelect.appendChild(option);
+ });
+
+ // Gespeicherten Wert setzen
+ if (gespeichert) {
+ filterSelect.value = gespeichert;
+ }
+ } catch (error) {
+ console.error('Fehler beim Laden der Ordner für Filter:', error);
+ }
+}
+
async function ladeRegeln() {
try {
const regeln = await api('/regeln');
+ // Ordner-Filter Dropdown beim ersten Mal befüllen
+ const filterSelect = document.getElementById('regeln-ordner-filter');
+ if (filterSelect && filterSelect.options.length <= 2) {
+ await ladeOrdnerFuerFilter();
+ }
+
+ // Filter anwenden
+ if (filterSelect) {
+ localStorage.setItem('regeln-ordner-filter', filterSelect.value);
+ }
+ const filter = filterSelect?.value || 'alle';
+
+ let gefilterteRegeln = regeln;
+ if (filter === 'ohne') {
+ // Nur Regeln ohne Ordner-Zuweisung
+ gefilterteRegeln = regeln.filter(r => !r.ordner_ids || r.ordner_ids.length === 0);
+ } else if (filter !== 'alle') {
+ // Nach spezifischem Ordner filtern
+ const ordnerId = parseInt(filter);
+ gefilterteRegeln = regeln.filter(r => r.ordner_ids && r.ordner_ids.includes(ordnerId));
+ }
+
// Sortierung anwenden (und in localStorage speichern)
const sortSelect = document.getElementById('regeln-sortierung');
if (sortSelect) {
@@ -1051,7 +1109,7 @@ async function ladeRegeln() {
}
const sortierung = sortSelect?.value || 'name_asc';
- regeln.sort((a, b) => {
+ gefilterteRegeln.sort((a, b) => {
switch (sortierung) {
case 'name_asc':
return natuerlicheSortierung(a.name || '', b.name || '');
@@ -1066,7 +1124,7 @@ async function ladeRegeln() {
}
});
- renderRegeln(regeln);
+ renderRegeln(gefilterteRegeln);
} catch (error) {
console.error('Fehler:', error);
}
@@ -1083,11 +1141,26 @@ function renderRegeln(regeln) {
container.innerHTML = regeln.map(r => {
const aktivClass = r.aktiv ? '' : 'opacity: 0.5;';
const aktivBadge = r.aktiv ? 'Aktiv' : 'Inaktiv';
+
+ // Warnung wenn keine Ordner zugeordnet
+ const ohneOrdner = !r.ordner_ids || r.ordner_ids.length === 0;
+ const ohneOrdnerBadge = ohneOrdner ? '⚠ Ohne Ordner' : '';
+ const ohneOrdnerStyle = ohneOrdner ? 'border-left: 3px solid var(--warning);' : '';
+
+ // Ordner-Namen anzeigen
+ let ordnerInfo = '';
+ if (r.ordner_ids && r.ordner_ids.length > 0 && ordnerCache.length > 0) {
+ const ordnerNamen = r.ordner_ids
+ .map(id => ordnerCache.find(o => o.id === id)?.name || `ID ${id}`)
+ .join(', ');
+ ordnerInfo = `
📁 ${escapeHtml(ordnerNamen)}`;
+ }
+
return `
-