profil hübscher

This commit is contained in:
Bkolb 2026-04-10 16:11:44 +02:00
parent 4956fdf6ef
commit 226d3bf69c
4 changed files with 174 additions and 37 deletions

View File

@ -490,10 +490,9 @@ def profil():
return render_template(
"profil.html",
page_title="Profil",
active_page="profil",
profile=profile,
gruppen=gruppen,
groups=gruppen,
mandant_level_label=format_level(profile["mandant_level"]),
**get_current_user()
)
@ -1930,10 +1929,12 @@ def useradmin_user_upload():
if not uploaded_file or uploaded_file.filename == "":
form_error = "Bitte eine CSV-Datei auswählen."
else:
file_bytes = uploaded_file.read()
try:
content = uploaded_file.read().decode("utf-8-sig")
except Exception:
form_error = "Die Datei konnte nicht gelesen werden. Bitte UTF-8 CSV verwenden."
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=";")
@ -1942,13 +1943,22 @@ def useradmin_user_upload():
if not rows:
form_error = "Die Datei ist leer."
# Nur echte Datenzeilen zählen (keine leeren)
data_rows = [
row for row in rows
if row and any(str(col).strip() for col in row)
]
if len(data_rows) > 50:
form_error = f"Maximal 50 Benutzer erlaubt. Datei enthält {len(data_rows)} Zeilen."
parsed_users = []
if not form_error:
email_pattern = r"^[^@\s]+@[^@\s]+\.[^@\s]+$"
seen_emails_in_file = set()
for line_no, row in enumerate(rows, start=1):
for line_no, row in enumerate(data_rows, start=1):
if not row or all(not str(col).strip() for col in row):
continue
@ -2060,3 +2070,60 @@ def useradmin_user_delete(user_id):
conn.close()
return redirect(url_for("useradmin_mandant"))
@app.route("/pwdchange", methods=["GET", "POST"])
@login_required
def pwdchange():
user_id = session.get("user_id")
form_error = None
success_message = None
if request.method == "POST":
old_password = request.form.get("old_password", "")
new_password = request.form.get("new_password", "")
new_password2 = request.form.get("new_password2", "")
if not old_password or not new_password or not new_password2:
form_error = "Alle Felder sind erforderlich."
elif new_password != new_password2:
form_error = "Neue Passwörter stimmen nicht überein."
elif len(new_password) < 8:
form_error = "Das Passwort muss mindestens 8 Zeichen lang sein."
else:
conn = get_connection()
cur = conn.cursor()
cur.execute("""
SELECT password_hash
FROM app_user
WHERE id = %s
""", (user_id,))
row = cur.fetchone()
if not row:
form_error = "User nicht gefunden."
elif not check_password_hash(row[0], old_password):
form_error = "Altes Passwort ist falsch."
else:
new_hash = generate_password_hash(new_password)
cur.execute("""
UPDATE app_user
SET password_hash = %s
WHERE id = %s
""", (new_hash, user_id))
conn.commit()
success_message = "Passwort erfolgreich geändert."
cur.close()
conn.close()
return render_template(
"pwdchange.html",
page_title="Passwort ändern",
form_error=form_error,
success_message=success_message,
**get_current_user()
)

View File

@ -2,35 +2,47 @@
{% block content %}
<div class="page-header">
<h1>Profil</h1>
</div>
<table class="admin-table">
<tr><th>ID</th><td>{{ profile.id }}</td></tr>
<tr><th>Name</th><td>{{ profile.name }}</td></tr>
<tr><th>E-Mail</th><td>{{ profile.email }}</td></tr>
<tr><th>Mandant</th><td>{{ profile.mandant_name }} ({{ profile.mandant_kuerzel }})</td></tr>
<tr><th>Mandant E-Mail</th><td>{{ profile.mandant_email or '-' }}</td></tr>
<tr><th>Mandant Level</th><td>{{ profile.mandant_level_label }}</td></tr>
<section class="admin-section">
<div class="admin-panel profile-panel">
<tr>
<th>Gruppen im Mandanten</th>
<td>
{% if gruppen %}
<div class="group-badges">
{% for gruppe in gruppen %}
<span class="group-badge">{{ gruppe }}</span>
<div class="profile-grid">
<div class="profile-label">ID</div>
<div class="profile-value">{{ profile.id }}</div>
<div class="profile-label">Name</div>
<div class="profile-value">{{ profile.name }}</div>
<div class="profile-label">E-Mail</div>
<div class="profile-value">{{ profile.email }}</div>
<div class="profile-label">Mandant</div>
<div class="profile-value">{{ profile.mandant_name }}</div>
<div class="profile-label">Mandant E-Mail</div>
<div class="profile-value">{{ profile.mandant_email }}</div>
<div class="profile-label">Mandant Level</div>
<div class="profile-value">{{ mandant_level_label }}</div>
<div class="profile-label">Gruppen</div>
<div class="profile-value">
{% for g in groups %}
<span class="badge">{{ g }}</span>
{% endfor %}
</div>
{% else %}
-
{% endif %}
</td>
</tr>
</table>
</div>
<div class="admin-actions">
<div class="profile-actions">
<a href="/pwdchange" class="btn-primary">Passwort ändern</a>
</div>
</div>
</section>
{% endblock %}

View File

@ -1215,3 +1215,58 @@ button {
margin: 6px 0 0;
color: #526172;
}
/* ===============================
Profil Layout
=============================== */
.profile-grid {
display: grid;
grid-template-columns: 220px 1fr;
row-gap: 14px;
column-gap: 20px;
align-items: center;
margin-top: 20px;
}
/* Labels linksbündig */
.profile-label {
text-align: left;
font-weight: 700;
color: #1e3a5f;
}
/* Werte */
.profile-value {
color: #1f2937;
}
/* Gruppen-Badges */
.badge {
display: inline-block;
padding: 6px 14px;
margin-right: 8px;
border-radius: 999px;
background: #e7edf5;
color: #1e3a5f;
font-weight: 600;
font-size: 14px;
}
@media (max-width: 700px) {
.profile-grid {
grid-template-columns: 1fr;
}
.profile-label {
margin-top: 10px;
}
}
.profile-label {
color: #64748b;
}
.profile-value {
font-weight: 500;
}

View File

@ -0,0 +1,3 @@
Max Mustermann;mm@kolb.cc;1;Geheimnis
Susi Wunderschön;sw@kolb.cc;1;Geheimnis
Petra Sauberfrau;ps@kolb.cc;1;Geheimnis
1 Max Mustermann mm@kolb.cc 1 Geheimnis
2 Susi Wunderschön sw@kolb.cc 1 Geheimnis
3 Petra Sauberfrau ps@kolb.cc 1 Geheimnis