From 13986f05d75134a0eec5c4f5514490e051031ed6 Mon Sep 17 00:00:00 2001 From: Adriano Date: Fri, 20 Feb 2026 11:58:47 +0100 Subject: [PATCH] feat: add admin user management page with CRUD and i18n - New admin blueprint with CRUD proxy endpoints for users - Admin user management template with search, create, edit, toggle active - Navbar: add admin link for is_admin users (desktop + mobile) - Register admin blueprint in app factory - Add IT/EN translations for all admin UI strings Co-Authored-By: Claude Opus 4.6 --- client/app.py | 2 + client/blueprints/admin.py | 104 ++++ client/templates/admin/users.html | 535 ++++++++++++++++++ client/templates/components/navbar.html | 28 + .../translations/en/LC_MESSAGES/messages.po | 102 ++++ .../translations/it/LC_MESSAGES/messages.po | 102 ++++ 6 files changed, 873 insertions(+) create mode 100644 client/blueprints/admin.py create mode 100644 client/templates/admin/users.html diff --git a/client/app.py b/client/app.py index 3b79869..6226e1a 100644 --- a/client/app.py +++ b/client/app.py @@ -37,11 +37,13 @@ def create_app() -> Flask: from blueprints.measure import measure_bp from blueprints.maker import maker_bp from blueprints.statistics import statistics_bp + from blueprints.admin import admin_bp app.register_blueprint(auth_bp) app.register_blueprint(measure_bp, url_prefix="/measure") app.register_blueprint(maker_bp, url_prefix="/maker") app.register_blueprint(statistics_bp, url_prefix="/statistics") + app.register_blueprint(admin_bp, url_prefix="/admin") @app.route("/") def index(): diff --git a/client/blueprints/admin.py b/client/blueprints/admin.py new file mode 100644 index 0000000..c2abaec --- /dev/null +++ b/client/blueprints/admin.py @@ -0,0 +1,104 @@ +"""Admin blueprint - user management.""" +from flask import Blueprint, flash, jsonify, redirect, render_template, request, session, url_for +from flask_babel import gettext as _ + +from blueprints.auth import login_required +from services.api_client import api_client + +admin_bp = Blueprint("admin", __name__, url_prefix="/admin") + + +def admin_required(f): + """Decorator to require admin privileges.""" + from functools import wraps + @wraps(f) + def decorated(*args, **kwargs): + user = session.get("user", {}) + if not user.get("is_admin"): + flash(_("Accesso non autorizzato"), "error") + return redirect(url_for("measure.select_recipe")) + return f(*args, **kwargs) + return decorated + + +# ============================================================================ +# PAGINE (GET) +# ============================================================================ + +@admin_bp.route("/users") +@login_required +@admin_required +def user_list(): + """User management page.""" + resp = api_client.get("/api/users") + + if isinstance(resp, dict) and resp.get("error"): + flash(_("Errore nel caricamento degli utenti: %(error)s", error=resp.get("detail", "")), "error") + users = [] + elif isinstance(resp, list): + users = resp + else: + users = [] + + return render_template("admin/users.html", users=users) + + +# ============================================================================ +# API PROXY AJAX (JSON) +# ============================================================================ + +@admin_bp.route("/api/users", methods=["POST"]) +@login_required +@admin_required +def api_create_user(): + """Proxy: Create new user.""" + data = request.get_json(silent=True) or {} + resp = api_client.post("/api/users", data=data) + + if isinstance(resp, dict) and resp.get("error"): + return jsonify(resp), resp.get("status_code", 500) + + return jsonify(resp), 201 + + +@admin_bp.route("/api/users/", methods=["PUT"]) +@login_required +@admin_required +def api_update_user(user_id: int): + """Proxy: Update user.""" + data = request.get_json(silent=True) or {} + resp = api_client.put(f"/api/users/{user_id}", data=data) + + if isinstance(resp, dict) and resp.get("error"): + return jsonify(resp), resp.get("status_code", 500) + + return jsonify(resp), 200 + + +@admin_bp.route("/api/users//password", methods=["PUT"]) +@login_required +@admin_required +def api_change_password(user_id: int): + """Proxy: Change user password.""" + data = request.get_json(silent=True) or {} + resp = api_client.put(f"/api/users/{user_id}/password", data=data) + + if isinstance(resp, dict) and resp.get("error"): + return jsonify(resp), resp.get("status_code", 500) + + return jsonify(resp), 200 + + +@admin_bp.route("/api/users//toggle-active", methods=["POST"]) +@login_required +@admin_required +def api_toggle_active(user_id: int): + """Proxy: Toggle user active status.""" + data = request.get_json(silent=True) or {} + active = data.get("active", True) + resp = api_client.put(f"/api/users/{user_id}", data={"active": active}) + + if isinstance(resp, dict) and resp.get("error"): + return jsonify(resp), resp.get("status_code", 500) + + return jsonify(resp), 200 diff --git a/client/templates/admin/users.html b/client/templates/admin/users.html new file mode 100644 index 0000000..89e1cc2 --- /dev/null +++ b/client/templates/admin/users.html @@ -0,0 +1,535 @@ +{% extends "base.html" %} + +{% block title %}{{ _('Gestione Utenti') }} - TieMeasureFlow{% endblock %} + +{% block content %} + + +
+ + +
+

{{ _('Gestione Utenti') }}

+

{{ _('Crea, modifica e gestisci gli utenti del sistema') }}

+
+ + +
+ +
+ + + + +
+ + + +
+ + +
+
+ + + + + + + + + + + + + + +
{{ _('Username') }}{{ _('Ruoli') }}{{ _('Stato') }}{{ _('Azioni') }}
+
+ + + +
+ + +
+ {{ _('utenti') }} +
+ + +
+ +
+ + +
+ + +
+

+ +
+ + +
+ +
+ + + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ +
+ + + +
+
+ + +
+ +
+ +
+ + +
+ + +
+ + +
+ + +
+
+ + + +
+ + +
+ + +
+
+
+ + +
+
+
+

+

+ + ? +

+
+ + +
+
+
+ +
+{% endblock %} + +{% block extra_js %} + +{% endblock %} diff --git a/client/templates/components/navbar.html b/client/templates/components/navbar.html index d81aaa6..6ffacae 100644 --- a/client/templates/components/navbar.html +++ b/client/templates/components/navbar.html @@ -73,6 +73,22 @@ {% endif %} + {# Admin: Utenti #} + {% if current_user.get('is_admin') %} + + + + + + {{ _('Utenti') }} + + {% endif %} {% endif %} @@ -248,6 +264,18 @@ {% endif %} + {# Admin: Utenti #} + {% if current_user.get('is_admin') %} + + + + + {{ _('Utenti') }} + + {% endif %} diff --git a/client/translations/en/LC_MESSAGES/messages.po b/client/translations/en/LC_MESSAGES/messages.po index c71d8df..0512cd4 100644 --- a/client/translations/en/LC_MESSAGES/messages.po +++ b/client/translations/en/LC_MESSAGES/messages.po @@ -1605,6 +1605,108 @@ msgstr "Select a measurement point for the histogram" msgid "Errore nella generazione del report" msgstr "Error generating report" +# Admin - User Management +#: blueprints/admin.py:18 +msgid "Accesso non autorizzato" +msgstr "Unauthorized access" + +#: blueprints/admin.py:36 +#, python-format +msgid "Errore nel caricamento degli utenti: %(error)s" +msgstr "Error loading users: %(error)s" + +#: templates/admin/users.html:3 templates/admin/users.html:13 +msgid "Gestione Utenti" +msgstr "User Management" + +#: templates/admin/users.html:14 +msgid "Crea, modifica e gestisci gli utenti del sistema" +msgstr "Create, edit and manage system users" + +#: templates/admin/users.html:25 +msgid "Cerca utente..." +msgstr "Search user..." + +#: templates/admin/users.html:38 +msgid "Nuovo Utente" +msgstr "New User" + +#: templates/admin/users.html:50 +msgid "Email" +msgstr "Email" + +#: templates/admin/users.html:95 +msgid "Attivo" +msgstr "Active" + +#: templates/admin/users.html:95 templates/admin/users.html:323 +msgid "Disattivato" +msgstr "Deactivated" + +#: templates/admin/users.html:108 +msgid "Disattiva" +msgstr "Deactivate" + +#: templates/admin/users.html:108 templates/admin/users.html:323 +msgid "Riattiva" +msgstr "Reactivate" + +#: templates/admin/users.html:130 +msgid "Nessun utente trovato" +msgstr "No users found" + +#: templates/admin/users.html:137 +msgid "utenti" +msgstr "users" + +#: templates/admin/users.html:160 +msgid "Modifica Utente" +msgstr "Edit User" + +#: templates/admin/users.html:292 +msgid "Crea Utente" +msgstr "Create User" + +#: templates/admin/users.html:207 +msgid "lascia vuoto per non modificare" +msgstr "leave empty to keep unchanged" + +#: templates/admin/users.html:213 +msgid "Nuova password (opzionale)" +msgstr "New password (optional)" + +#: templates/admin/users.html:246 +msgid "Amministratore" +msgstr "Administrator" + +#: templates/admin/users.html:252 +msgid "Lingua" +msgstr "Language" + +#: templates/admin/users.html:263 +msgid "Tema" +msgstr "Theme" + +#: templates/admin/users.html:307 +msgid "Conferma Disattivazione" +msgstr "Confirm Deactivation" + +#: templates/admin/users.html:307 +msgid "Conferma Riattivazione" +msgstr "Confirm Reactivation" + +#: templates/admin/users.html:310 +msgid "Sei sicuro di voler disattivare l'utente" +msgstr "Are you sure you want to deactivate user" + +#: templates/admin/users.html:311 +msgid "Sei sicuro di voler riattivare l'utente" +msgstr "Are you sure you want to reactivate user" + +#: templates/admin/users.html:459 +msgid "Username, nome visualizzato e password sono obbligatori" +msgstr "Username, display name and password are required" + #~ msgid "Esci" #~ msgstr "Logout" diff --git a/client/translations/it/LC_MESSAGES/messages.po b/client/translations/it/LC_MESSAGES/messages.po index 8a7a212..62f9673 100644 --- a/client/translations/it/LC_MESSAGES/messages.po +++ b/client/translations/it/LC_MESSAGES/messages.po @@ -1635,6 +1635,108 @@ msgstr "Seleziona un punto di misura per l'istogramma" msgid "Errore nella generazione del report" msgstr "Errore nella generazione del report" +# Admin - User Management +#: blueprints/admin.py:18 +msgid "Accesso non autorizzato" +msgstr "Accesso non autorizzato" + +#: blueprints/admin.py:36 +#, python-format +msgid "Errore nel caricamento degli utenti: %(error)s" +msgstr "Errore nel caricamento degli utenti: %(error)s" + +#: templates/admin/users.html:3 templates/admin/users.html:13 +msgid "Gestione Utenti" +msgstr "Gestione Utenti" + +#: templates/admin/users.html:14 +msgid "Crea, modifica e gestisci gli utenti del sistema" +msgstr "Crea, modifica e gestisci gli utenti del sistema" + +#: templates/admin/users.html:25 +msgid "Cerca utente..." +msgstr "Cerca utente..." + +#: templates/admin/users.html:38 +msgid "Nuovo Utente" +msgstr "Nuovo Utente" + +#: templates/admin/users.html:50 +msgid "Email" +msgstr "Email" + +#: templates/admin/users.html:95 +msgid "Attivo" +msgstr "Attivo" + +#: templates/admin/users.html:95 templates/admin/users.html:323 +msgid "Disattivato" +msgstr "Disattivato" + +#: templates/admin/users.html:108 +msgid "Disattiva" +msgstr "Disattiva" + +#: templates/admin/users.html:108 templates/admin/users.html:323 +msgid "Riattiva" +msgstr "Riattiva" + +#: templates/admin/users.html:130 +msgid "Nessun utente trovato" +msgstr "Nessun utente trovato" + +#: templates/admin/users.html:137 +msgid "utenti" +msgstr "utenti" + +#: templates/admin/users.html:160 +msgid "Modifica Utente" +msgstr "Modifica Utente" + +#: templates/admin/users.html:292 +msgid "Crea Utente" +msgstr "Crea Utente" + +#: templates/admin/users.html:207 +msgid "lascia vuoto per non modificare" +msgstr "lascia vuoto per non modificare" + +#: templates/admin/users.html:213 +msgid "Nuova password (opzionale)" +msgstr "Nuova password (opzionale)" + +#: templates/admin/users.html:246 +msgid "Amministratore" +msgstr "Amministratore" + +#: templates/admin/users.html:252 +msgid "Lingua" +msgstr "Lingua" + +#: templates/admin/users.html:263 +msgid "Tema" +msgstr "Tema" + +#: templates/admin/users.html:307 +msgid "Conferma Disattivazione" +msgstr "Conferma Disattivazione" + +#: templates/admin/users.html:307 +msgid "Conferma Riattivazione" +msgstr "Conferma Riattivazione" + +#: templates/admin/users.html:310 +msgid "Sei sicuro di voler disattivare l'utente" +msgstr "Sei sicuro di voler disattivare l'utente" + +#: templates/admin/users.html:311 +msgid "Sei sicuro di voler riattivare l'utente" +msgstr "Sei sicuro di voler riattivare l'utente" + +#: templates/admin/users.html:459 +msgid "Username, nome visualizzato e password sono obbligatori" +msgstr "Username, nome visualizzato e password sono obbligatori" + #~ msgid "Esci" #~ msgstr "Esci"