feat: 用户登录/管理 + 操作日志模块
- tb_user 用户表、tb_log 日志表 - Flask-Login 认证(login/logout/权限装饰器) - 用户管理页(admin 专有):增删改查、改密、角色设置 - 操作日志页:分页查询、按用户/类型筛选 - 测试操作区指令自动记录日志 - 所有页面加 @login_required 保护 - 默认管理员 admin/admin123(首次启动自动创建)
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,12 +1,14 @@
|
||||
"""设备页面 API"""
|
||||
|
||||
from flask import Blueprint, jsonify, render_template, request
|
||||
from flask_login import login_required
|
||||
from app.models import get_all_devices, update_device_name
|
||||
|
||||
bp = Blueprint("devices", __name__)
|
||||
|
||||
|
||||
@bp.route("/")
|
||||
@login_required
|
||||
def index():
|
||||
"""设备列表页(默认首页)"""
|
||||
return render_template("devices.html")
|
||||
|
||||
32
edc-web/app/routes/logs.py
Normal file
32
edc-web/app/routes/logs.py
Normal file
@@ -0,0 +1,32 @@
|
||||
"""日志查询 API"""
|
||||
|
||||
from flask import Blueprint, jsonify, render_template, request
|
||||
from flask_login import login_required
|
||||
from app.auth import admin_required
|
||||
from app.models import get_logs
|
||||
|
||||
bp = Blueprint("logs", __name__, url_prefix="/logs")
|
||||
|
||||
|
||||
@bp.route("/")
|
||||
@admin_required
|
||||
def logs_page():
|
||||
return render_template("logs.html")
|
||||
|
||||
|
||||
@bp.route("/api/logs")
|
||||
@admin_required
|
||||
def api_logs():
|
||||
page = request.args.get("page", 1, type=int)
|
||||
per_page = request.args.get("per_page", 30, type=int)
|
||||
username = request.args.get("username", "", type=str)
|
||||
action_type = request.args.get("action_type", "", type=str)
|
||||
|
||||
records, total = get_logs(page, per_page, username, action_type)
|
||||
return jsonify({
|
||||
"records": records,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"per_page": per_page,
|
||||
"pages": (total + per_page - 1) // per_page if total > 0 else 1,
|
||||
})
|
||||
@@ -3,6 +3,7 @@
|
||||
import csv
|
||||
import io
|
||||
from flask import Blueprint, jsonify, render_template, request, Response
|
||||
from flask_login import login_required
|
||||
from app.models import get_test_data, get_all_test_data_for_export
|
||||
|
||||
bp = Blueprint("test_data", __name__)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""测试操作 API"""
|
||||
|
||||
import time
|
||||
from flask import Blueprint, jsonify, render_template, request
|
||||
from flask_login import login_required, current_user
|
||||
from app.models import (
|
||||
get_device_by_id,
|
||||
insert_serialnet,
|
||||
@@ -9,12 +9,12 @@ from app.models import (
|
||||
get_latest_test_state,
|
||||
get_automation_averages,
|
||||
clear_serialnet_records,
|
||||
insert_log,
|
||||
)
|
||||
|
||||
bp = Blueprint("test_op", __name__)
|
||||
|
||||
# DG430 指令 (addr=0x01, ADDR=0x81)
|
||||
# XOR/SUM 预计算值
|
||||
COMMANDS = {
|
||||
"B0": "7F8101B03032", # 开始测试
|
||||
"B1": "7F8101B13133", # 测试复原
|
||||
@@ -23,8 +23,17 @@ COMMANDS = {
|
||||
"BC": "7F8101BC3C3E", # 电机停止
|
||||
}
|
||||
|
||||
CMD_NAMES = {
|
||||
"B0": "开始测试",
|
||||
"B1": "测试复原",
|
||||
"BA": "电机前进",
|
||||
"BB": "电机后退",
|
||||
"BC": "电机停止",
|
||||
}
|
||||
|
||||
|
||||
@bp.route("/test/<int:dnt_id>")
|
||||
@login_required
|
||||
def test_page(dnt_id):
|
||||
"""测试操作页面"""
|
||||
device = get_device_by_id(dnt_id)
|
||||
@@ -34,6 +43,7 @@ def test_page(dnt_id):
|
||||
|
||||
|
||||
@bp.route("/api/command", methods=["POST"])
|
||||
@login_required
|
||||
def api_command():
|
||||
"""发送单次指令"""
|
||||
data = request.get_json()
|
||||
@@ -43,21 +53,54 @@ def api_command():
|
||||
if cmd not in COMMANDS:
|
||||
return jsonify({"ok": False, "error": f"未知指令: {cmd}"}), 400
|
||||
|
||||
device = get_device_by_id(dnt_id)
|
||||
target = f"{device['serial']}" if device else f"dnt_id={dnt_id}"
|
||||
|
||||
send_pkg = COMMANDS[cmd]
|
||||
record_id = insert_serialnet(dnt_id, send_pkg)
|
||||
return jsonify({"ok": True, "record_id": record_id, "send_pkg": send_pkg})
|
||||
cmd_name = CMD_NAMES.get(cmd, cmd)
|
||||
try:
|
||||
record_id = insert_serialnet(dnt_id, send_pkg)
|
||||
insert_log(
|
||||
current_user.id, current_user.username, "command",
|
||||
target=target,
|
||||
detail=f"{cmd_name}({cmd}) → {send_pkg}",
|
||||
result="ok",
|
||||
ip=request.remote_addr or "",
|
||||
)
|
||||
return jsonify({"ok": True, "record_id": record_id, "send_pkg": send_pkg})
|
||||
except Exception as e:
|
||||
insert_log(
|
||||
current_user.id, current_user.username, "command",
|
||||
target=target,
|
||||
detail=f"{cmd_name}({cmd}) 失败: {e}",
|
||||
result="error",
|
||||
ip=request.remote_addr or "",
|
||||
)
|
||||
return jsonify({"ok": False, "error": str(e)}), 500
|
||||
|
||||
|
||||
@bp.route("/api/automation/start", methods=["POST"])
|
||||
@login_required
|
||||
def api_automation_start():
|
||||
"""开始自动化测试"""
|
||||
data = request.get_json()
|
||||
dnt_id = data.get("dnt_id")
|
||||
count = int(data.get("count", 1))
|
||||
|
||||
device = get_device_by_id(dnt_id)
|
||||
target = f"{device['serial']}" if device else f"dnt_id={dnt_id}"
|
||||
|
||||
# 清除旧记录,然后插入第一条 0xB0
|
||||
clear_serialnet_records(dnt_id)
|
||||
record_id = insert_serialnet(dnt_id, COMMANDS["B0"])
|
||||
|
||||
insert_log(
|
||||
current_user.id, current_user.username, "command",
|
||||
target=target,
|
||||
detail=f"自动化测试开始 ×{count} 次",
|
||||
result="ok",
|
||||
ip=request.remote_addr or "",
|
||||
)
|
||||
return jsonify({
|
||||
"ok": True,
|
||||
"total": count,
|
||||
@@ -66,6 +109,7 @@ def api_automation_start():
|
||||
|
||||
|
||||
@bp.route("/api/automation/<int:dnt_id>/progress")
|
||||
@login_required
|
||||
def api_automation_progress(dnt_id):
|
||||
"""获取自动化进度"""
|
||||
stats = get_serialnet_stats(dnt_id)
|
||||
|
||||
54
edc-web/app/routes/users.py
Normal file
54
edc-web/app/routes/users.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""用户管理 API"""
|
||||
|
||||
from flask import Blueprint, jsonify, render_template, request
|
||||
from flask_login import login_required, current_user
|
||||
from werkzeug.security import generate_password_hash
|
||||
from app.auth import admin_required
|
||||
from app.models import get_all_users, create_user, update_user, get_user_by_username
|
||||
|
||||
bp = Blueprint("users", __name__, url_prefix="/users")
|
||||
|
||||
|
||||
@bp.route("/")
|
||||
@admin_required
|
||||
def users_page():
|
||||
return render_template("users.html")
|
||||
|
||||
|
||||
@bp.route("/api/users")
|
||||
@admin_required
|
||||
def api_users():
|
||||
return jsonify(get_all_users())
|
||||
|
||||
|
||||
@bp.route("/api/users", methods=["POST"])
|
||||
@admin_required
|
||||
def api_create_user():
|
||||
data = request.get_json()
|
||||
username = data.get("username", "").strip()
|
||||
password = data.get("password", "").strip()
|
||||
role = data.get("role", "operator")
|
||||
|
||||
if not username or not password:
|
||||
return jsonify({"ok": False, "error": "用户名和密码不能为空"}), 400
|
||||
if get_user_by_username(username):
|
||||
return jsonify({"ok": False, "error": "用户名已存在"}), 400
|
||||
|
||||
create_user(username, generate_password_hash(password), role)
|
||||
return jsonify({"ok": True})
|
||||
|
||||
|
||||
@bp.route("/api/users/<int:user_id>", methods=["PUT"])
|
||||
@admin_required
|
||||
def api_update_user(user_id):
|
||||
data = request.get_json()
|
||||
kwargs = {}
|
||||
if "password" in data and data["password"]:
|
||||
kwargs["password_hash"] = generate_password_hash(data["password"])
|
||||
if "role" in data:
|
||||
kwargs["role"] = data["role"]
|
||||
if "is_active" in data:
|
||||
kwargs["is_active"] = data["is_active"]
|
||||
if kwargs:
|
||||
update_user(user_id, **kwargs)
|
||||
return jsonify({"ok": True})
|
||||
Reference in New Issue
Block a user