feat: improve responsive admin experience

This commit is contained in:
2026-08-04 16:47:14 +08:00
parent c71b1351d6
commit 631a362501
20 changed files with 522 additions and 801 deletions
+107 -9
View File
@@ -8,7 +8,8 @@ from datetime import datetime, timedelta
from flask import Blueprint, abort, current_app, flash, jsonify, redirect, render_template, request, send_file, session, url_for
from flask_login import current_user, login_required, login_user, logout_user
from sqlalchemy.orm import joinedload
from sqlalchemy import or_
from sqlalchemy.orm import joinedload, selectinload
from .config import BASE_DIR
from .email import (
@@ -29,6 +30,7 @@ REFERENCE_PDF_NAME = "20260630标准文本——供水管道健康状态与剩
TEMPLATE_EXCEL_NAME = "管道预测数据模板.xlsx"
REGISTRATION_SETTING_KEY = "allow_registration"
RECORDS_PER_PAGE = 10
USERS_PER_PAGE = 20
EMAIL_RE = re.compile(r"^[^\s@]+@[^\s@]+\.[^\s@]+$")
EMAIL_CODE_PURPOSES = {
"register",
@@ -129,9 +131,9 @@ def registration_allowed() -> bool:
return AppSetting.get_bool(REGISTRATION_SETTING_KEY, current_app.config["ALLOW_REGISTRATION"])
def requested_page() -> int:
def requested_page(parameter: str = "page") -> int:
try:
return max(int(request.args.get("page", 1)), 1)
return max(int(request.args.get(parameter, 1)), 1)
except (TypeError, ValueError):
return 1
@@ -148,6 +150,44 @@ def paginated_uploads(query, endpoint: str):
return pagination, None
def paginated_users():
search = request.args.get("q", "").strip()
page = requested_page("user_page")
query = User.query.filter(User.is_admin.is_(False)).options(
selectinload(User.trusted_devices)
)
if search:
pattern = f"%{search}%"
query = query.filter(
or_(User.username.ilike(pattern), User.email.ilike(pattern))
)
pagination = query.order_by(User.created_at.desc(), User.id.desc()).paginate(
page=page,
per_page=USERS_PER_PAGE,
error_out=False,
)
if pagination.pages and page > pagination.pages:
return pagination, search, redirect(
url_for(
"main.admin_dashboard",
page=requested_page(),
user_page=pagination.pages,
q=search,
)
)
return pagination, search, None
def admin_dashboard_redirect():
return redirect(
url_for(
"main.admin_dashboard",
user_page=request.form.get("user_page", 1),
q=request.form.get("q", ""),
)
)
def code_hash(email: str, purpose: str, code: str) -> str:
return hashlib.sha256(f"{email}:{purpose}:{code}".encode()).hexdigest()
@@ -432,8 +472,32 @@ def history_page():
@bp.route("/admin")
@login_required
def admin_dashboard():
require_admin(); pagination, page_redirect = paginated_uploads(UploadRecord.query.options(joinedload(UploadRecord.user)), "main.admin_dashboard")
return page_redirect or render_template("admin.html", pagination=pagination, records=pagination.items, password_reset_users=User.query.filter(User.is_admin.is_(False)).order_by(User.username).all(), registration_allowed=registration_allowed())
require_admin()
pagination, page_redirect = paginated_uploads(
UploadRecord.query.options(joinedload(UploadRecord.user)),
"main.admin_dashboard",
)
user_pagination, user_search, user_page_redirect = paginated_users()
active_device_counts = {
user.id: sum(
device.expires_at > utc_now()
and device.auth_version == user.auth_version
for device in user.trusted_devices
)
for user in user_pagination.items
}
if page_redirect or user_page_redirect:
return page_redirect or user_page_redirect
return render_template(
"admin.html",
pagination=pagination,
records=pagination.items,
user_pagination=user_pagination,
user_search=user_search,
active_device_counts=active_device_counts,
registration_allowed=registration_allowed(),
)
@bp.route("/admin/registration", methods=["POST"])
@login_required
@@ -443,8 +507,10 @@ def update_registration_setting():
@bp.route("/admin/users/<int:user_id>/password-reset", methods=["POST"])
@login_required
def admin_password_reset(user_id: int):
require_admin(); user = db.session.get(User, user_id)
if not user or user.is_admin: return jsonify({"error": "用户不存在或不支持此操作"}), 404
require_admin()
user = db.session.get(User, user_id)
if not user or user.is_admin:
abort(404)
try:
send_transactional_email(
to=user.email,
@@ -455,8 +521,40 @@ def admin_password_reset(user_id: int):
),
)
except (EmailConfigurationError, EmailDeliveryError):
return jsonify({"error": "邮件发送失败"}), 503
return jsonify({"message": "密码重置通知已发送至用户邮箱"})
flash("邮件发送失败,请稍后重试。", "error")
else:
flash("密码重置通知已发送至用户邮箱。", "info")
return admin_dashboard_redirect()
@bp.route("/admin/users/<int:user_id>/trusted-devices/revoke", methods=["POST"])
@login_required
def admin_revoke_trusted_devices(user_id: int):
require_admin()
user = db.session.get(User, user_id)
if user is None or user.is_admin:
abort(404)
user.revoke_authentication()
TrustedDevice.query.filter_by(user_id=user.id).delete()
db.session.commit()
flash(f"已撤销 {user.username} 的所有受信设备。", "info")
return admin_dashboard_redirect()
@bp.route("/admin/users/<int:user_id>/account-status", methods=["POST"])
@login_required
def admin_update_account_status(user_id: int):
require_admin()
user = db.session.get(User, user_id)
if user is None or user.is_admin:
abort(404)
user.is_active_account = request.form.get("is_active") == "true"
user.revoke_authentication()
TrustedDevice.query.filter_by(user_id=user.id).delete()
db.session.commit()
message = "已启用账号" if user.is_active_account else "已停用账号并撤销所有会话"
flash(f"{user.username}{message}", "info")
return admin_dashboard_redirect()
@bp.route("/download/<int:record_id>/<file_type>")
@login_required