Milestone ultima alpha
This commit is contained in:
143
login_window.py
143
login_window.py
@@ -7,7 +7,10 @@ from tkinter import messagebox, ttk
|
||||
from typing import Any
|
||||
|
||||
from audit_log import log_session_event
|
||||
from busy_overlay import InlineBusyOverlay
|
||||
from gestione_aree import AsyncRunner
|
||||
from locale_text import load_locale_catalog, text as loc_text
|
||||
from ui_theme import theme_section, theme_value
|
||||
from user_session import UserSession, create_user_session
|
||||
|
||||
|
||||
@@ -48,17 +51,21 @@ def _rows_to_dicts(res: Any) -> list[dict[str, Any]]:
|
||||
class LoginWindow(tk.Toplevel):
|
||||
"""Small modal dialog used to authenticate one warehouse operator."""
|
||||
|
||||
def __init__(self, parent: tk.Misc, db_client):
|
||||
def __init__(self, parent: tk.Misc, db_client, *, compact: bool = False):
|
||||
super().__init__(parent)
|
||||
self.db_client = db_client
|
||||
self.compact = bool(compact)
|
||||
self.result_session: UserSession | None = None
|
||||
self._async = AsyncRunner(self)
|
||||
self._theme = theme_section("login_window", {})
|
||||
self._locale_catalog = load_locale_catalog()
|
||||
self._login_button: ttk.Button | None = None
|
||||
self._cancel_button: ttk.Button | None = None
|
||||
self._status_var = tk.StringVar(value="")
|
||||
self._busy = InlineBusyOverlay(self, self._theme)
|
||||
|
||||
self.title("Login Warehouse")
|
||||
self.geometry("420x250")
|
||||
self.title(loc_text("login.msg.title", catalog=self._locale_catalog, default="Login"))
|
||||
self.geometry("170x145+0+0" if self.compact else str(theme_value(self._theme, "window_geometry", "420x250")))
|
||||
self.resizable(False, False)
|
||||
try:
|
||||
if parent is not None and parent.winfo_viewable():
|
||||
@@ -81,45 +88,85 @@ class LoginWindow(tk.Toplevel):
|
||||
def _build_ui(self) -> None:
|
||||
"""Build the compact operator login form."""
|
||||
|
||||
body = ttk.Frame(self, padding=12)
|
||||
body = ttk.Frame(self, padding=8 if self.compact else 12)
|
||||
body.pack(fill="both", expand=True)
|
||||
body.columnconfigure(1, weight=1)
|
||||
|
||||
ttk.Label(
|
||||
body,
|
||||
text="Autenticazione operatore",
|
||||
font=("Segoe UI", 11, "bold"),
|
||||
).grid(row=0, column=0, columnspan=2, sticky="w", pady=(4, 14))
|
||||
row_offset = 0
|
||||
if not self.compact:
|
||||
ttk.Label(
|
||||
body,
|
||||
text=loc_text("login.heading", catalog=self._locale_catalog, default="Autenticazione operatore"),
|
||||
font=("Segoe UI", 11, "bold"),
|
||||
).grid(row=0, column=0, columnspan=2, sticky="w", pady=(4, 14))
|
||||
row_offset = 1
|
||||
|
||||
ttk.Label(body, text="Login").grid(row=1, column=0, sticky="w", padx=(0, 10), pady=6)
|
||||
self.login_entry = ttk.Entry(body, textvariable=self.login_var, width=28)
|
||||
self.login_entry.grid(row=1, column=1, sticky="ew", pady=6)
|
||||
|
||||
ttk.Label(body, text="Password").grid(row=2, column=0, sticky="w", padx=(0, 10), pady=6)
|
||||
self.password_entry = ttk.Entry(body, textvariable=self.password_var, width=28, show="*")
|
||||
self.password_entry.grid(row=2, column=1, sticky="ew", pady=6)
|
||||
|
||||
self.info_label = ttk.Label(
|
||||
body,
|
||||
text="Per ora tutti gli operatori autenticati possono usare tutte le funzioni.",
|
||||
justify="left",
|
||||
wraplength=320,
|
||||
ttk.Label(body, text="User:").grid(
|
||||
row=row_offset, column=0, sticky="w", padx=(0, 6), pady=4 if self.compact else 6
|
||||
)
|
||||
self.info_label.grid(row=3, column=0, columnspan=2, sticky="ew", pady=(10, 8))
|
||||
self.login_entry = ttk.Entry(body, textvariable=self.login_var, width=10, font=("Segoe UI", 10 if self.compact else 9))
|
||||
self.login_entry.grid(row=row_offset, column=1, sticky="ew", pady=4 if self.compact else 6)
|
||||
|
||||
self.status_label = ttk.Label(body, textvariable=self._status_var, foreground="#555555")
|
||||
self.status_label.grid(row=4, column=0, columnspan=2, sticky="w", pady=(2, 2))
|
||||
ttk.Label(body, text="Pwd:").grid(row=row_offset + 1, column=0, sticky="w", padx=(0, 6), pady=4 if self.compact else 6)
|
||||
self.password_entry = ttk.Entry(body, textvariable=self.password_var, width=10, show="*", font=("Segoe UI", 10 if self.compact else 9))
|
||||
self.password_entry.grid(row=row_offset + 1, column=1, sticky="ew", pady=4 if self.compact else 6)
|
||||
|
||||
actions = ttk.Frame(body)
|
||||
actions.grid(row=5, column=0, columnspan=2, sticky="ew", pady=(6, 0))
|
||||
actions.columnconfigure(0, weight=1)
|
||||
self._cancel_button = ttk.Button(actions, text="Annulla", command=self._on_cancel)
|
||||
self._cancel_button.grid(row=0, column=1, padx=(0, 8), pady=8)
|
||||
self._login_button = ttk.Button(actions, text="Accedi", command=self._on_login)
|
||||
self._login_button.grid(row=0, column=2, pady=8)
|
||||
if self.compact:
|
||||
actions = ttk.Frame(body)
|
||||
actions.grid(row=row_offset + 2, column=0, columnspan=2, sticky="ew", pady=(6, 0))
|
||||
self._cancel_button = ttk.Button(
|
||||
actions,
|
||||
text="Annulla",
|
||||
command=self._on_cancel,
|
||||
)
|
||||
self._cancel_button.grid(row=1, column=0, sticky="ew", pady=(4, 0))
|
||||
self._login_button = ttk.Button(
|
||||
actions,
|
||||
text="OK",
|
||||
command=self._on_login,
|
||||
)
|
||||
self._login_button.grid(row=0, column=0, sticky="ew")
|
||||
else:
|
||||
self.info_label = ttk.Label(
|
||||
body,
|
||||
text=loc_text(
|
||||
"login.info",
|
||||
catalog=self._locale_catalog,
|
||||
default="Per ora tutti gli operatori autenticati possono usare tutte le funzioni.",
|
||||
),
|
||||
justify="left",
|
||||
wraplength=320,
|
||||
)
|
||||
self.info_label.grid(row=3, column=0, columnspan=2, sticky="ew", pady=(10, 8))
|
||||
|
||||
self.status_label = ttk.Label(body, textvariable=self._status_var, foreground="#555555")
|
||||
self.status_label.grid(row=4, column=0, columnspan=2, sticky="w", pady=(2, 2))
|
||||
|
||||
actions = ttk.Frame(body)
|
||||
actions.grid(row=5, column=0, columnspan=2, sticky="ew", pady=(6, 0))
|
||||
actions.columnconfigure(0, weight=1)
|
||||
self._cancel_button = ttk.Button(
|
||||
actions,
|
||||
text=loc_text("login.button.cancel", catalog=self._locale_catalog, default="Annulla"),
|
||||
command=self._on_cancel,
|
||||
)
|
||||
self._cancel_button.grid(row=0, column=1, padx=(0, 8), pady=8)
|
||||
self._login_button = ttk.Button(
|
||||
actions,
|
||||
text=loc_text("login.button.submit", catalog=self._locale_catalog, default="Accedi"),
|
||||
command=self._on_login,
|
||||
)
|
||||
self._login_button.grid(row=0, column=2, pady=8)
|
||||
|
||||
self.bind("<Return>", lambda _e: self._on_login())
|
||||
self.bind("<Escape>", lambda _e: self._on_cancel())
|
||||
self.login_var.trace_add("write", lambda *_: self._limit_var(self.login_var, 10))
|
||||
self.password_var.trace_add("write", lambda *_: self._limit_var(self.password_var, 10))
|
||||
|
||||
def _limit_var(self, variable: tk.StringVar, max_len: int) -> None:
|
||||
value = str(variable.get() or "")
|
||||
if len(value) > max_len:
|
||||
variable.set(value[:max_len])
|
||||
|
||||
def _set_busy(self, busy: bool, message: str = "") -> None:
|
||||
"""Enable or disable user interaction during async authentication."""
|
||||
@@ -134,6 +181,10 @@ class LoginWindow(tk.Toplevel):
|
||||
self._cancel_button.configure(state=state)
|
||||
self.configure(cursor="watch" if busy else "")
|
||||
self._status_var.set(message)
|
||||
if busy:
|
||||
self._busy.show(message or loc_text("login.status.checking", catalog=self._locale_catalog, default="Verifico credenziali..."))
|
||||
else:
|
||||
self._busy.hide()
|
||||
self.update_idletasks()
|
||||
except Exception:
|
||||
pass
|
||||
@@ -167,11 +218,15 @@ class LoginWindow(tk.Toplevel):
|
||||
login = str(self.login_var.get() or "").strip()
|
||||
password = str(self.password_var.get() or "").strip()
|
||||
if not login or not password:
|
||||
messagebox.showwarning("Login", "Inserisci login e password.", parent=self)
|
||||
messagebox.showwarning(
|
||||
loc_text("login.msg.title", catalog=self._locale_catalog, default="Login"),
|
||||
loc_text("login.msg.missing", catalog=self._locale_catalog, default="Inserisci login e password."),
|
||||
parent=self,
|
||||
)
|
||||
return
|
||||
|
||||
params = {"login": login, "password": password}
|
||||
self._set_busy(True, "Verifico credenziali...")
|
||||
self._set_busy(True, loc_text("login.status.checking", catalog=self._locale_catalog, default="Verifico credenziali..."))
|
||||
|
||||
def _ok(res: Any) -> None:
|
||||
rows = _rows_to_dicts(res)
|
||||
@@ -183,7 +238,11 @@ class LoginWindow(tk.Toplevel):
|
||||
outcome="denied",
|
||||
details={"login": login},
|
||||
)
|
||||
messagebox.showerror("Login", "Credenziali non valide.", parent=self)
|
||||
messagebox.showerror(
|
||||
loc_text("login.msg.title", catalog=self._locale_catalog, default="Login"),
|
||||
loc_text("login.msg.invalid", catalog=self._locale_catalog, default="Credenziali non valide."),
|
||||
parent=self,
|
||||
)
|
||||
try:
|
||||
self.password_var.set("")
|
||||
self.password_entry.focus_force()
|
||||
@@ -215,7 +274,11 @@ class LoginWindow(tk.Toplevel):
|
||||
outcome="error",
|
||||
details={"login": login, "error": str(ex)},
|
||||
)
|
||||
messagebox.showerror("Login", f"Verifica credenziali fallita:\n{ex}", parent=self)
|
||||
messagebox.showerror(
|
||||
loc_text("login.msg.title", catalog=self._locale_catalog, default="Login"),
|
||||
loc_text("login.msg.error", catalog=self._locale_catalog, default="Verifica credenziali fallita:\n{error}").format(error=ex),
|
||||
parent=self,
|
||||
)
|
||||
|
||||
self._async.run(
|
||||
self.db_client.query_json(SQL_LOGIN, params),
|
||||
@@ -249,3 +312,11 @@ def prompt_login(parent: tk.Misc, db_client) -> UserSession | None:
|
||||
dialog = LoginWindow(parent, db_client)
|
||||
dialog.wait_window()
|
||||
return dialog.result_session
|
||||
|
||||
|
||||
def prompt_login_compact(parent: tk.Misc, db_client) -> UserSession | None:
|
||||
"""Open the barcode-oriented compact login modal."""
|
||||
|
||||
dialog = LoginWindow(parent, db_client, compact=True)
|
||||
dialog.wait_window()
|
||||
return dialog.result_session
|
||||
|
||||
Reference in New Issue
Block a user