Retention-Cleanup-Job (automatische Aufbewahrungsfrist-Bereinigung, Advisory-Lock)
This commit is contained in:
parent
7c24951db7
commit
86d2cf4c4a
48
app.py
48
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():
|
|||
<div class="formrow"><label>Aufbewahrung Tickets (Tage)</label><input name="retention_tickets_days" type="number" value="1095"></div>
|
||||
<div class="formrow"><label>Aufbewahrung Audit-Log (Tage)</label><input name="retention_audit_days" type="number" value="1825"></div>
|
||||
</div>
|
||||
<div class="hint">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.</div>
|
||||
<div class="hint">Diese Werte legen fest, wie lange Tickets bzw. Audit-Eintraege vorgehalten werden (DSGVO Speicherbegrenzung). Ein Hintergrund-Job bereinigt automatisch alle 24 Stunden.</div>
|
||||
<button class="btn" type="submit" style="margin-top:8px">Organisation anlegen</button>
|
||||
<div class="sz" style="margin-top:12px">Bereits registriert? <a href="/login">Anmelden</a></div>
|
||||
</form></div></body></html>""" % (_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 = "<p class='sz' style='color:var(--ok)'>Gespeichert.</p>"
|
||||
if request.args.get("cleanup"):
|
||||
notice += "<p class='sz' style='color:var(--ok)'>Bereinigung durchgefuehrt -- Details im Audit-Log.</p>"
|
||||
t = db.get_tenant(tenant_id)
|
||||
body = """<h1 class="page-title">Einstellungen</h1>
|
||||
<div class="panel"><h3 style="margin-top:0">%s</h3>%s
|
||||
|
|
@ -673,13 +682,29 @@ def admin_route():
|
|||
<div class="formrow"><label>Aufbewahrung Audit-Log (Tage)</label><input type="number" name="retention_audit_days" value="%s"></div>
|
||||
</div>
|
||||
<button class="btn" type="submit">Speichern</button>
|
||||
</form></div>""" % (
|
||||
</form></div>
|
||||
<div class="panel">
|
||||
<h3 style="margin-top:0">Aufbewahrungsfrist-Bereinigung</h3>
|
||||
<p class="sz">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.</p>
|
||||
<form method="post" action="/admin/retention/run">
|
||||
<button class="btn ghost" type="submit">Jetzt manuell bereinigen</button>
|
||||
</form>
|
||||
</div>""" % (
|
||||
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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue