feat: improve responsive admin experience
This commit is contained in:
+107
-9
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user