diff --git a/app.py b/app.py index 57b3857..8b60298 100644 --- a/app.py +++ b/app.py @@ -11,6 +11,10 @@ Betrieb gemaess ISO 27001 / DSGVO / NIS 2 (Nutzer-Vorgabe, 2026-07-13): DSGVO-Angaben (Datenschutzbeauftragter, Aufbewahrungsfristen) an. Mandantenfaehig: mehrere Organisationen auf einer Instanz, Daten strikt per tenant_id getrennt (kein Datenzugriff ueber Mandantengrenzen). + - Automatische Aufbewahrungsfrist-Bereinigung (Hintergrund-Thread, siehe + db.run_retention_cleanup) -- loescht abgeschlossene Tickets/Audit-Log- + Eintraege, die aelter als die hinterlegte Frist sind (DSGVO Speicher- + begrenzung). AES bleibt ein buchbarer Service im Katalog, integriert per HTTP (kein Code- Import) -- siehe _aes_live_status(). @@ -19,11 +23,13 @@ ENV: DATABASE_URL postgresql://user:pass@host:5432/dbname ITSM_SECRET_KEY Flask-Session-Secret AES_DASHBOARD_URL Basis-URL des AES-Dashboards fuer die Service-Integration + RETENTION_INTERVAL_SECONDS Intervall fuer die Retention-Bereinigung (Default 24h) """ import os import html -import json +import time import secrets +import threading import datetime as _dt import urllib.request import urllib.error @@ -34,6 +40,7 @@ from werkzeug.security import generate_password_hash, check_password_hash import db AES_DASHBOARD_URL = os.getenv("AES_DASHBOARD_URL", "").rstrip("/") +RETENTION_INTERVAL_SECONDS = int(os.getenv("RETENTION_INTERVAL_SECONDS", str(24 * 60 * 60))) app = Flask(__name__) app.secret_key = os.getenv("ITSM_SECRET_KEY") or secrets.token_hex(32) @@ -303,7 +310,7 @@ def setup_new():
-
Diese Werte legen fest, wie lange Tickets bzw. Audit-Eintraege vorgehalten werden (DSGVO Speicherbegrenzung). Die automatische Loeschung nach Fristablauf ist als naechster Ausbauschritt vorgesehen -- aktuell wird nur die Frist hinterlegt.
+
Diese Werte legen fest, wie lange Tickets bzw. Audit-Eintraege vorgehalten werden (DSGVO Speicherbegrenzung). Ein Hintergrund-Job bereinigt automatisch alle 24 Stunden.
Bereits registriert? Anmelden
""" % (_CSS_TAG, err_html) @@ -654,6 +661,8 @@ def admin_route(): ) db.log_audit(tenant_id, session["user_id"], "tenant_settings_updated", "tenant", str(tenant_id), None, _client_ip()) notice = "

Gespeichert.

" + if request.args.get("cleanup"): + notice += "

Bereinigung durchgefuehrt -- Details im Audit-Log.

" t = db.get_tenant(tenant_id) body = """

Einstellungen

%s

%s @@ -673,13 +682,29 @@ def admin_route():
-""" % ( + +
+

Aufbewahrungsfrist-Bereinigung

+

Laeuft automatisch alle 24 Stunden im Hintergrund: geloeste/geschlossene Tickets und Audit-Log-Eintraege, die aelter als die oben hinterlegten Fristen sind, werden entfernt. Offene/laufende Tickets werden nie geloescht.

+
+ +
+
""" % ( html.escape(t["name"]), notice, t["sla_antwort_minuten"], t["sla_loesung_minuten"], html.escape(t["dsb_name"] or ""), html.escape(t["dsb_email"] or ""), t["retention_tickets_days"], t["retention_audit_days"]) return _shell("/admin", "Einstellungen", body) +@app.route("/admin/retention/run", methods=["POST"]) +def admin_retention_run(): + r = _require_admin() + if r: + return r + db.run_retention_cleanup() + return redirect(url_for("admin_route", cleanup="1")) + + @app.route("/admin/audit") def admin_audit_route(): r = _require_admin() @@ -746,5 +771,22 @@ def health_route(): db.init_db() +def _retention_loop(): + """Hintergrund-Thread: bereinigt abgelaufene Daten periodisch (siehe + db.run_retention_cleanup). Laeuft einmal kurz nach dem Start und danach im + festen Intervall. Der Postgres-Advisory-Lock in run_retention_cleanup() + schuetzt davor, dass mehrere Worker/Prozesse gleichzeitig loeschen.""" + time.sleep(30) + while True: + try: + db.run_retention_cleanup() + except Exception as e: + print("[retention] Fehler bei der Bereinigung:", e, flush=True) + time.sleep(RETENTION_INTERVAL_SECONDS) + + +threading.Thread(target=_retention_loop, daemon=True).start() + + if __name__ == "__main__": app.run(host="0.0.0.0", port=8090)