diff --git a/app/flask-postgres/app/app.py b/app/flask-postgres/app/app.py index e872ae3..b4a6cd4 100644 --- a/app/flask-postgres/app/app.py +++ b/app/flask-postgres/app/app.py @@ -346,7 +346,7 @@ def login(): cur = conn.cursor() cur.execute(""" - SELECT id, email, name, mandant_id, password_hash, status + SELECT id, email, name, mandant_id, password_hash, status, level FROM app_user WHERE lower(email) = %s """, (email,)) @@ -355,7 +355,7 @@ def login(): if not row: error_message = "Benutzer nicht gefunden." else: - user_id, user_email, user_name, mandant_id, password_hash, status = row + user_id, user_email, user_name, mandant_id, password_hash, status, user_level = row if status == 0: error_message = "Benutzer ist noch nicht aktiviert." @@ -370,6 +370,7 @@ def login(): session["user_email"] = user_email session["user_name"] = user_name session["mandant_id"] = mandant_id + session["user_level"] = user_level cur.execute(""" UPDATE app_user @@ -461,6 +462,7 @@ def profil(): u.email, u.name, u.mandant_id, + u.level AS user_level, m.name AS mandant_name, m.kontakt_email AS mandant_email, m.level AS mandant_level @@ -500,6 +502,7 @@ def profil(): groups=groups, certificates=certificates, mandant_level_label=format_level(profile["mandant_level"]), + user_level_label=format_level(profile["user_level"]), **get_current_user() ) @@ -715,7 +718,8 @@ def useradmin_mandant(): u.id, u.email, u.name, - u.status + u.status, + u.level FROM app_user u WHERE u.mandant_id = %s ORDER BY u.name, u.email @@ -725,6 +729,8 @@ def useradmin_mandant(): for user in users: user["status_label"] = format_user_status(user["status"]) user["can_delete"] = user["id"] != current_user_id + user["level_label"] = format_level(user["level"]) + cur.close() conn.close() @@ -743,11 +749,23 @@ def useradmin_mandant(): @app.route("/useradmin/mandant/new", methods=["GET", "POST"]) @user_admin_required def useradmin_user_new(): + import re + current_mandant_id = session.get("mandant_id") conn = get_connection() cur = conn.cursor() + # Mandanten-Level laden + cur.execute(""" + SELECT level + FROM mandant + WHERE id = %s + """, (current_mandant_id,)) + mandant_row = cur.fetchone() + mandant_level = mandant_row[0] if mandant_row else 3 + + # Gruppen des Mandanten laden cur.execute(""" SELECT id, group_name FROM app_group @@ -757,26 +775,31 @@ def useradmin_user_new(): gruppen = fetchall_dict(cur) form_error = None + success_message = None + form_values = { "email": "", "name": "", "status": "1", - "selected_groups": [] + "level": str(mandant_level), + "selected_groups": [], } if request.method == "POST": email = request.form.get("email", "").strip().lower() name = request.form.get("name", "").strip() + status = request.form.get("status", "1").strip() + level = request.form.get("level", str(mandant_level)).strip() password = request.form.get("password", "") password2 = request.form.get("password2", "") - status = request.form.get("status", "1").strip() selected_groups = request.form.getlist("group_ids") form_values = { "email": email, "name": name, "status": status, - "selected_groups": selected_groups + "level": level, + "selected_groups": selected_groups, } email_pattern = r"^[^@\s]+@[^@\s]+\.[^@\s]+$" @@ -787,6 +810,12 @@ def useradmin_user_new(): form_error = "Bitte eine gültige E-Mail-Adresse eingeben." elif not name: form_error = "Name ist ein Pflichtfeld." + elif status not in ("0", "1"): + form_error = "Status muss 0 oder 1 sein." + elif level not in ("1", "2", "3"): + form_error = "Bitte ein gültiges User-Level wählen." + elif int(level) < int(mandant_level): + form_error = "Der User-Level darf nicht höher als der Mandanten-Level sein." elif not password: form_error = "Passwort ist ein Pflichtfeld." elif not password2: @@ -805,51 +834,58 @@ def useradmin_user_new(): if existing_user: form_error = "Ein Benutzer mit dieser E-Mail existiert bereits." - else: - password_hash = generate_password_hash(password) + if not form_error: + password_hash = generate_password_hash(password) + + cur.execute(""" + INSERT INTO app_user (email, name, mandant_id, password_hash, status, level) + VALUES (%s, %s, %s, %s, %s, %s) + RETURNING id + """, ( + email, + name, + current_mandant_id, + password_hash, + int(status), + int(level) + )) + new_user_id = cur.fetchone()[0] + + selected_group_ids = [] + for gid in selected_groups: + try: + selected_group_ids.append(int(gid)) + except ValueError: + pass + + if selected_group_ids: cur.execute(""" - INSERT INTO app_user ( - email, - name, - mandant_id, - password_hash, - status - ) - VALUES (%s, %s, %s, %s, %s) - RETURNING id - """, ( - email, - name, - current_mandant_id, - password_hash, - int(status or 1) - )) - new_user_id = cur.fetchone()[0] - - if selected_groups: - selected_group_ids = [int(gid) for gid in selected_groups] + SELECT id + FROM app_group + WHERE mandant_id = %s + AND id = ANY(%s) + """, (current_mandant_id, selected_group_ids)) + valid_groups = cur.fetchall() + for row in valid_groups: + group_id = row[0] cur.execute(""" - SELECT id - FROM app_group - WHERE mandant_id = %s - AND id = ANY(%s) - """, (current_mandant_id, selected_group_ids)) - valid_groups = cur.fetchall() + INSERT INTO user_group (user_id, group_id, mandant_id) + VALUES (%s, %s, %s) + """, (new_user_id, group_id, current_mandant_id)) - for row in valid_groups: - group_id = row[0] - cur.execute(""" - INSERT INTO user_group (user_id, group_id, mandant_id) - VALUES (%s, %s, %s) - """, (new_user_id, group_id, current_mandant_id)) + conn.commit() + success_message = "Benutzer wurde erfolgreich angelegt." - conn.commit() - cur.close() - conn.close() - - return redirect(url_for("useradmin_mandant")) + # Formular leeren / auf Defaults zurücksetzen + form_values = { + "email": "", + "name": "", + "status": "1", + "level": str(mandant_level), + "selected_groups": [], + } cur.close() conn.close() @@ -859,8 +895,11 @@ def useradmin_user_new(): page_title="Neuer User", active_page="useradmin", gruppen=gruppen, - form_error=form_error, form_values=form_values, + form_error=form_error, + success_message=success_message, + mandant_level=mandant_level, + mandant_level_label=format_level(mandant_level), **get_current_user() ) @@ -885,8 +924,18 @@ def useradmin_user_edit(user_id): conn = get_connection() cur = conn.cursor() + # Mandanten-Level laden cur.execute(""" - SELECT id, email, name, mandant_id, status + SELECT level + FROM mandant + WHERE id = %s + """, (current_mandant_id,)) + mandant_row = cur.fetchone() + mandant_level = mandant_row[0] if mandant_row else None + + # User laden + cur.execute(""" + SELECT id, email, name, mandant_id, status, level FROM app_user WHERE id = %s AND mandant_id = %s @@ -898,6 +947,7 @@ def useradmin_user_edit(user_id): conn.close() abort(404) + # Gruppen des Mandanten cur.execute(""" SELECT id, group_name FROM app_group @@ -906,6 +956,7 @@ def useradmin_user_edit(user_id): """, (current_mandant_id,)) gruppen = fetchall_dict(cur) + # Zugeordnete Gruppen des Users cur.execute(""" SELECT group_id FROM user_group @@ -922,6 +973,7 @@ def useradmin_user_edit(user_id): "email": user["email"], "name": user["name"], "status": str(user["status"]), + "level": str(user["level"] if user.get("level") is not None else 1), "selected_groups": assigned_group_ids, } @@ -929,6 +981,7 @@ def useradmin_user_edit(user_id): email = request.form.get("email", "").strip().lower() name = request.form.get("name", "").strip() status = request.form.get("status", "1").strip() + level = request.form.get("level", "1").strip() password = request.form.get("password", "") password2 = request.form.get("password2", "") selected_groups = request.form.getlist("group_ids") @@ -937,6 +990,7 @@ def useradmin_user_edit(user_id): "email": email, "name": name, "status": status, + "level": level, "selected_groups": selected_groups, } @@ -948,6 +1002,12 @@ def useradmin_user_edit(user_id): form_error = "Bitte eine gültige E-Mail-Adresse eingeben." elif not name: form_error = "Name ist ein Pflichtfeld." + elif status not in ("0", "1"): + form_error = "Status muss 0 oder 1 sein." + elif level not in ("1", "2", "3"): + form_error = "Bitte ein gültiges User-Level wählen." + elif mandant_level is not None and int(level) < int(mandant_level): + form_error = "Der User-Level darf nicht höher als der Mandanten-Level sein." else: cur.execute(""" SELECT id @@ -975,10 +1035,18 @@ def useradmin_user_edit(user_id): UPDATE app_user SET email = %s, name = %s, - status = %s + status = %s, + level = %s WHERE id = %s AND mandant_id = %s - """, (email, name, int(status or 1), user_id, current_mandant_id)) + """, ( + email, + name, + int(status), + int(level), + user_id, + current_mandant_id + )) if password: password_hash = generate_password_hash(password) @@ -1021,6 +1089,7 @@ def useradmin_user_edit(user_id): conn.commit() success_message = "Benutzer wurde erfolgreich aktualisiert." + # Formularwerte nach dem Speichern neu laden cur.execute(""" SELECT group_id FROM user_group @@ -1030,6 +1099,16 @@ def useradmin_user_edit(user_id): assigned_rows = cur.fetchall() form_values["selected_groups"] = [str(row[0]) for row in assigned_rows] + # edit_user für Anzeige aktualisieren + user["email"] = email + user["name"] = name + user["status"] = int(status) + user["level"] = int(level) + + # Falls der aktuell eingeloggte User sich selbst geändert hat: Session-Level aktualisieren + if session.get("user_id") == user_id: + session["user_level"] = int(level) + cur.close() conn.close() @@ -1042,6 +1121,8 @@ def useradmin_user_edit(user_id): form_values=form_values, form_error=form_error, success_message=success_message, + mandant_level=mandant_level, + mandant_level_label=format_level(mandant_level) if mandant_level is not None else "-", **get_current_user() ) @@ -1050,6 +1131,7 @@ def useradmin_user_edit(user_id): def course_list(): user_id = session.get("user_id") mandant_level = session.get("mandant_level", 0) + user_level = session.get("user_level", 3) conn = get_connection() cur = conn.cursor() @@ -1066,7 +1148,7 @@ def course_list(): # Filter nach Level available_courses = [ c for c in all_courses - if is_course_allowed_for_level(c["code"], mandant_level) + if is_course_allowed_for_level(c["code"], user_level) ] # Bestandene Assessments laden @@ -1870,6 +1952,9 @@ def admin_questions_course(course_id): @login_required def course_assessment(course_id): mandant_level = session.get("mandant_level", 0) + user_level = session.get("user_level", 3) + if not is_course_allowed_for_level(course["code"], user_level): + abort(403) conn = get_connection() cur = conn.cursor() @@ -1996,6 +2081,10 @@ def course_assessment(course_id): @app.route("/useradmin/mandant/upload", methods=["GET", "POST"]) @user_admin_required def useradmin_user_upload(): + import csv + import io + import re + current_mandant_id = session.get("mandant_id") form_error = None success_message = None @@ -2004,13 +2093,15 @@ def useradmin_user_upload(): conn = get_connection() cur = conn.cursor() + # Mandant + Mandanten-Level laden cur.execute(""" - SELECT name + SELECT name, level FROM mandant WHERE id = %s """, (current_mandant_id,)) mandant_row = cur.fetchone() mandant_name = mandant_row[0] if mandant_row else f"Mandant {current_mandant_id}" + mandant_level = mandant_row[1] if mandant_row else 3 if request.method == "POST": uploaded_file = request.files.get("csv_file") @@ -2020,10 +2111,14 @@ def useradmin_user_upload(): else: file_bytes = uploaded_file.read() - try: - content = file_bytes.decode("utf-8-sig") - except UnicodeDecodeError: - form_error = "Die Datei ist nicht im UTF-8 Format. Bitte als UTF-8 CSV speichern." + if not file_bytes: + form_error = "Die Datei ist leer." + + if not form_error: + try: + content = file_bytes.decode("utf-8-sig") + except UnicodeDecodeError: + form_error = "Die Datei ist nicht im UTF-8 Format. Bitte als UTF-8 CSV speichern." if not form_error: reader = csv.reader(io.StringIO(content), delimiter=";") @@ -2032,7 +2127,8 @@ def useradmin_user_upload(): if not rows: form_error = "Die Datei ist leer." - # Nur echte Datenzeilen zählen (keine leeren) + if not form_error: + # Nur echte Datenzeilen zählen data_rows = [ row for row in rows if row and any(str(col).strip() for col in row) @@ -2048,9 +2144,6 @@ def useradmin_user_upload(): seen_emails_in_file = set() for line_no, row in enumerate(data_rows, start=1): - if not row or all(not str(col).strip() for col in row): - continue - if len(row) != 4: row_errors.append(f"Zeile {line_no}: Es müssen genau 4 Felder vorhanden sein.") continue @@ -2064,14 +2157,17 @@ def useradmin_user_upload(): if not name: line_problems.append("Name fehlt") + if not email: line_problems.append("E-Mail fehlt") elif not re.match(email_pattern, email): line_problems.append("E-Mail ungültig") + if not status: line_problems.append("Status fehlt") elif status not in ("0", "1"): line_problems.append("Status muss 0 oder 1 sein") + if not password: line_problems.append("Passwort fehlt") elif ";" in password: @@ -2102,6 +2198,7 @@ def useradmin_user_upload(): "email": email, "status": int(status), "password_hash": generate_password_hash(password), + "level": mandant_level, # 👈 neu }) if row_errors: @@ -2109,14 +2206,15 @@ def useradmin_user_upload(): else: for u in parsed_users: cur.execute(""" - INSERT INTO app_user (email, name, mandant_id, password_hash, status) - VALUES (%s, %s, %s, %s, %s) + INSERT INTO app_user (email, name, mandant_id, password_hash, status, level) + VALUES (%s, %s, %s, %s, %s, %s) """, ( u["email"], u["name"], current_mandant_id, u["password_hash"], - u["status"] + u["status"], + u["level"] )) conn.commit() @@ -2133,6 +2231,7 @@ def useradmin_user_upload(): success_message=success_message, row_errors=row_errors, mandant_name=mandant_name, + mandant_level_label=format_level(mandant_level), **get_current_user() ) @@ -2221,59 +2320,93 @@ def pwdchange(): @user_admin_required def reporting_assessments(): current_mandant_id = session.get("mandant_id") - mandant_level = session.get("mandant_level") conn = get_connection() cur = conn.cursor() - # Mandant - cur.execute("SELECT name, level FROM mandant WHERE id = %s", (current_mandant_id,)) - mandant_row = cur.fetchone() - mandant_name = mandant_row[0] - mandant_level_value = mandant_row[1] - - # User + # Mandant laden cur.execute(""" - SELECT id, name, email, status + SELECT name, level + FROM mandant + WHERE id = %s + """, (current_mandant_id,)) + mandant_row = cur.fetchone() + + if not mandant_row: + cur.close() + conn.close() + abort(404) + + mandant_name = mandant_row[0] + mandant_level = mandant_row[1] + + # User des Mandanten + cur.execute(""" + SELECT id, name, email, status, level FROM app_user WHERE mandant_id = %s + ORDER BY name, email """, (current_mandant_id,)) users = fetchall_dict(cur) - # Kurse + # Alle aktiven Kurse cur.execute(""" - SELECT id, code, title, min_level + SELECT id, code, title, min_level, sort_order FROM course WHERE is_active = TRUE + ORDER BY code, sort_order, id """) all_courses = fetchall_dict(cur) + # Reporting basiert auf Mandanten-Level, NICHT auf User-Level available_courses = [ c for c in all_courses if is_course_allowed_for_level(c["code"], mandant_level) ] - # Assessments (nur bestanden) + # Erfolgreiche Assessments cur.execute(""" - SELECT user_id, course_id, score, created_at + SELECT user_id, course_id, score, passed, created_at FROM user_assessment WHERE passed = TRUE """) - assessments = fetchall_dict(cur) + assessment_rows = fetchall_dict(cur) cur.close() conn.close() - passed_map = {(a["user_id"], a["course_id"]): a for a in assessments} + # Letzten erfolgreichen Abschluss je User/Kurs merken + passed_map = {} + for row in assessment_rows: + key = (row["user_id"], row["course_id"]) + if key not in passed_map or row["created_at"] > passed_map[key]["created_at"]: + passed_map[key] = row + + # Filterlisten + available_courses = sorted(available_courses, key=lambda c: c["code"] or "") + user_list = sorted( + [{"id": u["id"], "name": u["name"]} for u in users], + key=lambda x: (x["name"] or "").lower() + ) + + # Detaildaten + detail_rows = [] + completed_count = 0 + open_count = 0 detail_rows = [] completed_count = 0 open_count = 0 - for u in users: - for c in available_courses: - a = passed_map.get((u["id"], c["id"])) - done = a is not None + for user in users: + user_allowed_courses = [ + c for c in all_courses + if is_course_allowed_for_level(c["code"], user["level"]) + ] + + for course in user_allowed_courses: + passed_row = passed_map.get((user["id"], course["id"])) + done = passed_row is not None if done: completed_count += 1 @@ -2281,37 +2414,39 @@ def reporting_assessments(): open_count += 1 detail_rows.append({ - "user_name": u["name"], - "user_email": u["email"], - "course_code": c["code"], - "course_title": c["title"], + "user_id": user["id"], + "user_name": user["name"], + "user_email": user["email"], + "user_status": user["status"], + "user_level": user["level"], + "user_level_label": format_level(user["level"]), + "course_id": course["id"], + "course_code": course["code"], + "course_title": course["title"], "done": done, "done_label": "erledigt" if done else "offen", - "completed_at": a["created_at"] if done else None, - "score": a["score"] if done else None, + "completed_at": passed_row["created_at"] if done else None, + "score": passed_row["score"] if done else None, }) total_expected = len(users) * len(available_courses) - progress_percent = int((completed_count / total_expected) * 100) if total_expected else 0 - - users = sorted(users, key=lambda u: (u["name"] or "", u["email"] or "")) - available_courses = sorted(available_courses, key=lambda c: c["code"]) - - user_list = sorted( - [{"id": u["id"], "name": u["name"]} for u in users], - key=lambda x: x["name"] or "" - ) + progress_percent = int((completed_count / total_expected) * 100) if total_expected > 0 else 0 return render_template( "reporting_assessments.html", + page_title="Assessment Reporting", + active_page="reporting_assessments", mandant_name=mandant_name, - mandant_level_label=format_level(mandant_level_value), + mandant_level=mandant_level, + mandant_level_label=format_level(mandant_level), available_courses=available_courses, user_list=user_list, detail_rows=detail_rows, + dashboard_total_users=len(users), + dashboard_total_courses=len(available_courses), + dashboard_total_expected=total_expected, dashboard_completed=completed_count, dashboard_open=open_count, - dashboard_total_expected=total_expected, dashboard_progress_percent=progress_percent, **get_current_user() ) diff --git a/app/flask-postgres/app/certificates.py b/app/flask-postgres/app/certificates.py index f9ee5bd..6e4bb92 100644 --- a/app/flask-postgres/app/certificates.py +++ b/app/flask-postgres/app/certificates.py @@ -146,8 +146,10 @@ def generate_certificate_pdf_for_user(user_id, module_code): reference_url = f"https://cert.compliance-verification.info/verification/{guid_value}" pdf_path = os.path.join(CERT_OUTPUT_DIR, f"{guid_value}.pdf") + template_name = get_certificate_template_for_module(data["module_code"]) + html = render_template( - "certificate_template.html", + template_name, user_name=data["user_name"], mandant_name=data["mandant_name"], module_code=data["module_code"], @@ -281,4 +283,15 @@ def ensure_certificate_for_user_module(user_id, module_code): return guid_value def is_module_completed_for_user(user_id, module_code): - return get_user_module_completion(user_id, module_code) is not None \ No newline at end of file + return get_user_module_completion(user_id, module_code) is not None + +def get_certificate_template_for_module(module_code): + module_code = (module_code or "").upper() + + mapping = { + "A": "certificates/certificate_A.html", + "B": "certificates/certificate_B.html", + "C": "certificates/certificate_C.html", + } + + return mapping.get(module_code, "certificates/certificate_A.html") \ No newline at end of file diff --git a/app/flask-postgres/app/permissions.py b/app/flask-postgres/app/permissions.py index 9de5c3e..c520845 100644 --- a/app/flask-postgres/app/permissions.py +++ b/app/flask-postgres/app/permissions.py @@ -28,27 +28,20 @@ def is_video_allowed_for_level(filename: str, mandant_level: int | None) -> bool -def is_course_allowed_for_level(code: str, mandant_level: int | None) -> bool: - if mandant_level is None: +def is_course_allowed_for_level(course_code, level): + if level is None: return False - prefix = (code or "")[:1].upper() + first_char = (course_code or "")[:1].upper() - # 0 = Admin = alles - if mandant_level == 0: + if level == 0: return True - - # 1 = Gold = A + B + C - if mandant_level == 1: - return prefix in ("A", "B", "C") - - # 2 = Silber = A + B - if mandant_level == 2: - return prefix in ("A", "B") - - # 3 = Bronze = A - if mandant_level == 3: - return prefix == "A" + if level == 1: + return first_char in ("A", "B", "C") + if level == 2: + return first_char in ("A", "B") + if level == 3: + return first_char == "A" return False diff --git a/app/flask-postgres/app/security.py b/app/flask-postgres/app/security.py index 3e09bcd..4486cc6 100644 --- a/app/flask-postgres/app/security.py +++ b/app/flask-postgres/app/security.py @@ -67,6 +67,7 @@ def get_current_user(): "user_name": session.get("user_name"), "user_email": session.get("user_email"), "mandant_id": session.get("mandant_id"), + "user_level": session.get("user_level"), "is_logged_in": bool(session.get("user_id")), "is_admin": user_is_admin() if session.get("user_id") else False, "is_user_admin": user_is_user_admin() if session.get("user_id") else False, diff --git a/app/flask-postgres/app/templates/base.html b/app/flask-postgres/app/templates/base.html index b853437..9ff368d 100644 --- a/app/flask-postgres/app/templates/base.html +++ b/app/flask-postgres/app/templates/base.html @@ -66,7 +66,7 @@ Userverwaltung Zertifikate - Reporting + Reporting {% endif %} diff --git a/app/flask-postgres/app/templates/certificates/certificate_A.html b/app/flask-postgres/app/templates/certificates/certificate_A.html new file mode 100644 index 0000000..6a5a5e9 --- /dev/null +++ b/app/flask-postgres/app/templates/certificates/certificate_A.html @@ -0,0 +1,299 @@ + + + + + Zertifikat Level A + + + +
+ +
+ + {% if logo_path %} + + {% endif %} + +
Nachweis über die erfolgreiche Teilnahme
+ +
Hiermit wird bestätigt, dass
+
{{ user_name }}
+
des Unternehmens {{ mandant_name }}
+ +
das Schulungsprogramm
+
„Compliance Verification – Level A“
+ +

+ erfolgreich absolviert und alle vorgesehenen Prüfungen bestanden hat. +

+ +

+ Im Rahmen der Schulung wurden strukturierte Kenntnisse im Bereich des Einsatzes + von Künstlicher Intelligenz in Organisationen vermittelt. +

+ +
+
Level A – Vermittelt wurden grundlegende Kenntnisse zu:
+
    +
  • Funktionsweise und Einsatz von KI-Systemen
  • +
  • Risiken, Verantwortung und menschlicher Kontrolle
  • +
  • Transparenz- und Kennzeichnungspflichten
  • +
  • grundlegender Dokumentation und internen Prozessen
  • +
+
+ +

+ Dieser Nachweis dokumentiert ausschließlich die erfolgreiche Teilnahme an der Schulung + sowie das Bestehen der innerhalb der Plattform vorgesehenen Prüfungen. +

+ +

+ Er stellt keine Prüfung, Bewertung oder Bestätigung der tatsächlichen Umsetzung + im Unternehmen dar. +

+ +

+ Ebenso handelt es sich nicht um eine Bestätigung rechtlicher Konformität, + keine behördliche Anerkennung und keinen Nachweis im Sinne gesetzlicher + oder normativer Anforderungen. +

+ +
+
Datum: {{ valid_from }}
+
Gültig bis: {{ valid_until }}
+
+ +
+
+
+ Nachweis-ID: + {{ reference_id }} +
+
+ +
+
Ausgestellt durch:
+
ABC UG (haftungsbeschränkt)
+ + {% if signature_path %} +
+ Unterschrift +
+ {% endif %} +
+
+ +
+ + + +
+ + \ No newline at end of file diff --git a/app/flask-postgres/app/templates/certificates/certificate_B.html b/app/flask-postgres/app/templates/certificates/certificate_B.html new file mode 100644 index 0000000..9c168af --- /dev/null +++ b/app/flask-postgres/app/templates/certificates/certificate_B.html @@ -0,0 +1,301 @@ + + + + + Zertifikat Level A + + + +
+ +
+ + {% if logo_path %} + + {% endif %} + +
Nachweis über die erfolgreiche Teilnahme
+ +
Hiermit wird bestätigt, dass
+
{{ user_name }}
+
des Unternehmens {{ mandant_name }}
+ +
das Schulungsprogramm
+
„Compliance Verification – Level B“
+ +

+ erfolgreich absolviert und alle vorgesehenen Prüfungen bestanden hat. +

+ +

+ Im Rahmen der Schulung wurden strukturierte Kenntnisse im Bereich des Einsatzes + von Künstlicher Intelligenz in Organisationen vermittelt. +

+ +
+
Level B – Vermittelt wurden vertiefte Kenntnisse zu:
+
    +
  • datenschutzkonformer Nutzung von KI
  • +
  • unternehmensinternen Richtlinien und Prozessen
  • +
  • urheberrechtlichen Fragestellungen
  • +
  • Dokumentation und Vorbereitung auf Prüfungen
  • +
  • organisatorischer Verantwortung und Umsetzung
  • +
+
+ +

+ Dieser Nachweis dokumentiert ausschließlich die erfolgreiche Teilnahme an der Schulung + sowie das Bestehen der innerhalb der Plattform vorgesehenen Prüfungen. +

+ +

+ Er stellt keine Prüfung, Bewertung oder Bestätigung der tatsächlichen Umsetzung + im Unternehmen dar. +

+ +

+ Ebenso handelt es sich nicht um eine Bestätigung rechtlicher Konformität, + keine behördliche Anerkennung und keinen Nachweis im Sinne gesetzlicher + oder normativer Anforderungen. +

+ +
+
Datum: {{ valid_from }}
+
Gültig bis: {{ valid_until }}
+
+ +
+
+
+ Nachweis-ID: + {{ reference_id }} +
+
+ +
+
Ausgestellt durch:
+
ABC UG (haftungsbeschränkt)
+ + {% if signature_path %} +
+ Unterschrift +
+ {% endif %} +
+
+ +
+ + + +
+ + + diff --git a/app/flask-postgres/app/templates/certificates/certificate_C.html b/app/flask-postgres/app/templates/certificates/certificate_C.html new file mode 100644 index 0000000..a237a43 --- /dev/null +++ b/app/flask-postgres/app/templates/certificates/certificate_C.html @@ -0,0 +1,301 @@ + + + + + Zertifikat Level A + + + +
+ +
+ + {% if logo_path %} + + {% endif %} + +
Nachweis über die erfolgreiche Teilnahme
+ +
Hiermit wird bestätigt, dass
+
{{ user_name }}
+
des Unternehmens {{ mandant_name }}
+ +
das Schulungsprogramm
+
„Compliance Verification – Level C“
+ +

+ erfolgreich absolviert und alle vorgesehenen Prüfungen bestanden hat. +

+ +

+ Im Rahmen der Schulung wurden strukturierte Kenntnisse im Bereich des Einsatzes + von Künstlicher Intelligenz in Organisationen vermittelt. +

+ +
+
Level C – Vermittelt wurden strategische Kenntnisse zu:
+
    +
  • Schutz und Nutzung geistigen Eigentums im KI-Kontext
  • +
  • Sicherheits- und Missbrauchsrisiken
  • +
  • Steuerung externer KI-Anbieter
  • +
  • unternehmensweiter Risikoanalyse und Governance
  • +
  • Umsetzung und fortlaufender Begleitung
  • +
+
+ +

+ Dieser Nachweis dokumentiert ausschließlich die erfolgreiche Teilnahme an der Schulung + sowie das Bestehen der innerhalb der Plattform vorgesehenen Prüfungen. +

+ +

+ Er stellt keine Prüfung, Bewertung oder Bestätigung der tatsächlichen Umsetzung + im Unternehmen dar. +

+ +

+ Ebenso handelt es sich nicht um eine Bestätigung rechtlicher Konformität, + keine behördliche Anerkennung und keinen Nachweis im Sinne gesetzlicher + oder normativer Anforderungen. +

+ +
+
Datum: {{ valid_from }}
+
Gültig bis: {{ valid_until }}
+
+ +
+
+
+ Nachweis-ID: + {{ reference_id }} +
+
+ +
+
Ausgestellt durch:
+
ABC UG (haftungsbeschränkt)
+ + {% if signature_path %} +
+ Unterschrift +
+ {% endif %} +
+
+ +
+ + + +
+ + + diff --git a/app/flask-postgres/app/templates/profil.html b/app/flask-postgres/app/templates/profil.html index d2cfc79..8767a69 100644 --- a/app/flask-postgres/app/templates/profil.html +++ b/app/flask-postgres/app/templates/profil.html @@ -29,6 +29,9 @@
Mandant Level
{{ mandant_level_label }}
+
User Level
+
{{ user_level_label }}
+
Gruppen
{% for g in groups %} diff --git a/app/flask-postgres/app/templates/reporting_assessments.html b/app/flask-postgres/app/templates/reporting_assessments.html index 822aa57..1e1c03c 100644 --- a/app/flask-postgres/app/templates/reporting_assessments.html +++ b/app/flask-postgres/app/templates/reporting_assessments.html @@ -68,7 +68,14 @@ data-status="{{ r.done_label }}" data-user="{{ r.user_name }}" > - {{ r.user_name }} + + + + + {{ r.user_name }} + + + {{ r.course_code }} - {{ r.course_title }} {{ r.done_label }} diff --git a/app/flask-postgres/app/templates/useradmin_mandant.html b/app/flask-postgres/app/templates/useradmin_mandant.html index b2847b9..88c1de6 100644 --- a/app/flask-postgres/app/templates/useradmin_mandant.html +++ b/app/flask-postgres/app/templates/useradmin_mandant.html @@ -4,75 +4,79 @@ -
-
-
-

Benutzerübersicht

-

- Level des Mandanten: {{ mandant_level_label }} -

-
- - - -
- Status: - 0 - deaktiviert - 1 - aktiv - 2 - gesperrt - 3 - disabled -
- -
- - - - - - - - - - - - {% for user in users %} - - - - - - - - {% endfor %} - -
IDNameE-MailStatusAktionen
{{ user.id }}{{ user.name }}{{ user.email }}{{ user.status_label }} -
- Bearbeiten - - {% if user.can_delete %} -
- -
- {% else %} - - {% endif %} -
-
-
+
+ + + + + + + + + + + + + + + {% for user in users %} + + + + + + + + + + + + + + {% endfor %} + + +
IDNameE-MailStatusLevel
{{ user.id }}{{ user.name }}{{ user.email }} + {% if user.status == 1 %} + Aktiv + {% else %} + Deaktiviert + {% endif %} + + + {{ user.level_label }} + + + + + ✏️ + + + {% if user.id != session.user_id %} +
+ +
+ {% endif %} + +
+ +
{% endblock %} \ No newline at end of file diff --git a/app/flask-postgres/app/templates/useradmin_user_edit.html b/app/flask-postgres/app/templates/useradmin_user_edit.html index 110923d..3bda1c8 100644 --- a/app/flask-postgres/app/templates/useradmin_user_edit.html +++ b/app/flask-postgres/app/templates/useradmin_user_edit.html @@ -4,7 +4,9 @@
@@ -18,7 +20,7 @@
{{ success_message }}
{% endif %} -
+
@@ -32,7 +34,8 @@ id="email" name="email" value="{{ form_values.email }}" - required> + required + >
@@ -42,26 +45,42 @@ id="name" name="name" value="{{ form_values.name }}" - required> + required + >
- + +
+
+ +
+ + +

+ Maximales Level dieses Mandanten: + {{ mandant_level_label }} +

+
+
+
+ placeholder="leer lassen = unverändert" + >
@@ -70,19 +89,22 @@ type="password" id="password2" name="password2" - placeholder="leer lassen = unverändert"> + placeholder="leer lassen = unverändert" + >
-
+ +
{% for gruppe in gruppen %} -
-
Zurück @@ -101,57 +122,4 @@
- - {% endblock %} \ No newline at end of file diff --git a/app/flask-postgres/app/templates/useradmin_user_new.html b/app/flask-postgres/app/templates/useradmin_user_new.html index 138dce5..b43fe22 100644 --- a/app/flask-postgres/app/templates/useradmin_user_new.html +++ b/app/flask-postgres/app/templates/useradmin_user_new.html @@ -3,8 +3,10 @@ {% block content %}
@@ -14,77 +16,118 @@
{{ form_error }}
{% endif %} - + {% if success_message %} +
{{ success_message }}
+ {% endif %} + + +
+ value="{{ form_values.email }}" + required + >
+
+ value="{{ form_values.name }}" + required + >
+ +
+ + +
+ + +
+ + +
+ + +

+ Maximales Level dieses Mandanten: + {{ mandant_level_label }} +

+
+
+ +
+ required + >
+
-
- -
- - + required + >
+
-
+ +
{% for gruppe in gruppen %} -
+
-
- - Zurück + + + + Zurück +
@@ -92,58 +135,4 @@
- - {% endblock %} \ No newline at end of file diff --git a/app/flask-postgres/app/templates/useradmin_user_upload.html b/app/flask-postgres/app/templates/useradmin_user_upload.html index 9104c05..b2faa22 100644 --- a/app/flask-postgres/app/templates/useradmin_user_upload.html +++ b/app/flask-postgres/app/templates/useradmin_user_upload.html @@ -54,6 +54,7 @@ Erika Musterfrau;erika@example.com;0;Passwort999
  • Passwort mindestens 8 Zeichen
  • E-Mail muss eindeutig und gültig sein
  • Trennzeichen ist Semikolon
  • +
  • Neue Benutzer erhalten automatisch das Level des aktuellen Mandanten: {{ mandant_level_label }}
  • diff --git a/app/flask-postgres/styles/site.css b/app/flask-postgres/styles/site.css index 6c4e72d..de768af 100644 --- a/app/flask-postgres/styles/site.css +++ b/app/flask-postgres/styles/site.css @@ -1650,4 +1650,179 @@ button { border-radius: 10px; text-decoration: none; font-weight: 600; +} + +.form-hint { + margin-top: 8px; + font-size: 14px; + color: #526172; +} + +.checkbox-grid { + display: flex; + flex-wrap: wrap; + gap: 14px; + margin-top: 8px; +} + +.checkbox-card { + display: inline-flex; + align-items: center; + gap: 10px; + padding: 12px 16px; + border: 1px solid #dce3ea; + border-radius: 14px; + background: #f8fbff; + cursor: pointer; + font-weight: 600; + color: #1e3a5f; +} + +.checkbox-card input[type="checkbox"] { + width: 18px; + height: 18px; +} + +.admin-table { + width: 100%; + border-collapse: collapse; + margin-top: 20px; +} + +.admin-table th { + text-align: left; + padding: 10px; + background: #f1f5f9; +} + +.admin-table td { + padding: 10px; + border-bottom: 1px solid #e2e8f0; +} + +.status-active { + color: #15803d; + font-weight: 600; +} + +.status-inactive { + color: #b91c1c; + font-weight: 600; +} + +/* Level Badges */ +.level-badge { + padding: 6px 10px; + border-radius: 10px; + font-weight: 600; + font-size: 13px; +} + +.level-1 { + background: #dcfce7; + color: #166534; +} + +.level-2 { + background: #fef9c3; + color: #854d0e; +} + +.level-3 { + background: #e0f2fe; + color: #075985; +} + +.actions { + display: flex; + gap: 8px; + align-items: center; +} + +/* Basis Button */ +.btn-icon { + width: 34px; + height: 34px; + + display: inline-flex; + align-items: center; + justify-content: center; + + border-radius: 10px; + border: 1px solid transparent; + + font-size: 15px; + cursor: pointer; + + transition: all 0.2s ease; +} + +/* EDIT */ +.btn-edit { + background: #e0f2fe; + color: #075985; +} + +.btn-edit:hover { + background: #bae6fd; + transform: translateY(-1px); +} + +/* DELETE */ +.btn-delete { + background: #fee2e2; + color: #991b1b; + border: none; +} + +.btn-delete:hover { + background: #fecaca; + transform: translateY(-1px); +} + +.user-cell { + display: flex; + align-items: center; + gap: 10px; +} + +/* kleines Level-Icon */ +.user-level-icon { + width: 12px; + height: 12px; + border-radius: 50%; + display: inline-block; + box-shadow: 0 0 0 2px rgba(255,255,255,0.6); +} + +/* Farben passend zu deinen Badges */ + +/* 🟡 GOLD */ + +.level-1 { + + background: linear-gradient(145deg, #ffd700, #e6b800); + + border: 1px solid #c9a200; + +} + +/* ⚪ SILBER */ + +.level-2 { + + background: linear-gradient(145deg, #d1d5db, #9ca3af); + + border: 1px solid #6b7280; + +} + +/* 🟤 BRONZE */ + +.level-3 { + + background: linear-gradient(145deg, #cd7f32, #a65a1f); + + border: 1px solid #7c3e0a; + } \ No newline at end of file