init
This commit is contained in:
768
routes/admin.py
Normal file
768
routes/admin.py
Normal file
@@ -0,0 +1,768 @@
|
||||
from flask import Blueprint, request, jsonify, session
|
||||
from models import Admin, User, UserGroup, ParserAPI, SiteConfig, SMTPConfig, ParseLog, DailyParseStat, HealthCheckConfig
|
||||
from models import db
|
||||
from utils.security import hash_password, verify_password, get_client_ip
|
||||
from utils.admin_auth import admin_required, verify_2fa, generate_2fa_secret, get_2fa_qrcode_url
|
||||
from utils.email import EmailService
|
||||
from datetime import datetime, timedelta, date
|
||||
from sqlalchemy import func
|
||||
import qrcode
|
||||
import io
|
||||
import base64
|
||||
|
||||
admin_bp = Blueprint('admin', __name__)
|
||||
|
||||
@admin_bp.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
"""管理员登录"""
|
||||
if request.method == 'GET':
|
||||
from flask import render_template
|
||||
return render_template('admin_login.html')
|
||||
|
||||
data = request.get_json()
|
||||
username = data.get('username')
|
||||
password = data.get('password')
|
||||
code_2fa = data.get('code_2fa')
|
||||
|
||||
if not all([username, password]):
|
||||
return jsonify({'success': False, 'message': '请填写完整信息'}), 400
|
||||
|
||||
admin = Admin.query.filter_by(username=username).first()
|
||||
|
||||
if not admin or not verify_password(password, admin.password):
|
||||
return jsonify({'success': False, 'message': '用户名或密码错误'}), 401
|
||||
|
||||
# 检查2FA
|
||||
if admin.is_2fa_enabled:
|
||||
if not code_2fa:
|
||||
return jsonify({'success': False, 'message': '请输入2FA验证码', 'require_2fa': True}), 400
|
||||
|
||||
if not verify_2fa(admin, code_2fa):
|
||||
return jsonify({'success': False, 'message': '2FA验证码错误'}), 401
|
||||
|
||||
# 更新登录信息
|
||||
admin.last_login_ip = get_client_ip(request)
|
||||
admin.last_login_at = datetime.utcnow()
|
||||
db.session.commit()
|
||||
|
||||
# 设置会话
|
||||
session['admin_id'] = admin.id
|
||||
session['admin_username'] = admin.username
|
||||
|
||||
return jsonify({'success': True, 'message': '登录成功'})
|
||||
|
||||
@admin_bp.route('/logout', methods=['POST'])
|
||||
@admin_required
|
||||
def logout():
|
||||
"""管理员登出"""
|
||||
session.pop('admin_id', None)
|
||||
session.pop('admin_username', None)
|
||||
return jsonify({'success': True, 'message': '已退出登录'})
|
||||
|
||||
@admin_bp.route('/dashboard', methods=['GET'])
|
||||
@admin_required
|
||||
def dashboard():
|
||||
"""仪表板页面"""
|
||||
from flask import render_template
|
||||
return render_template('admin_dashboard.html')
|
||||
|
||||
@admin_bp.route('/api/dashboard', methods=['GET'])
|
||||
@admin_required
|
||||
def dashboard_api():
|
||||
"""仪表板统计API"""
|
||||
today = date.today()
|
||||
|
||||
# 今日统计
|
||||
today_stats = db.session.query(
|
||||
func.sum(DailyParseStat.parse_count).label('total'),
|
||||
func.sum(DailyParseStat.success_count).label('success'),
|
||||
func.sum(DailyParseStat.fail_count).label('fail')
|
||||
).filter(DailyParseStat.date == today).first()
|
||||
|
||||
# 总用户数
|
||||
total_users = User.query.count()
|
||||
|
||||
# 总解析次数
|
||||
total_parses = ParseLog.query.count()
|
||||
|
||||
# 活跃API数
|
||||
active_apis = ParserAPI.query.filter_by(is_enabled=True, health_status=True).count()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': {
|
||||
'today': {
|
||||
'total': today_stats.total or 0,
|
||||
'success': today_stats.success or 0,
|
||||
'fail': today_stats.fail or 0
|
||||
},
|
||||
'total_users': total_users,
|
||||
'total_parses': total_parses,
|
||||
'active_apis': active_apis
|
||||
}
|
||||
})
|
||||
|
||||
@admin_bp.route('/users', methods=['GET'])
|
||||
@admin_required
|
||||
def users_page():
|
||||
"""用户管理页面"""
|
||||
from flask import render_template
|
||||
return render_template('admin_users.html')
|
||||
|
||||
@admin_bp.route('/api/users', methods=['GET'])
|
||||
@admin_required
|
||||
def get_users():
|
||||
"""获取用户列表API"""
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = request.args.get('per_page', 20, type=int)
|
||||
group_id = request.args.get('group_id', type=int)
|
||||
|
||||
# 构建查询
|
||||
query = User.query
|
||||
if group_id:
|
||||
query = query.filter_by(group_id=group_id)
|
||||
|
||||
pagination = query.order_by(User.created_at.desc()).paginate(
|
||||
page=page, per_page=per_page, error_out=False
|
||||
)
|
||||
|
||||
users = [{
|
||||
'id': u.id,
|
||||
'username': u.username,
|
||||
'email': u.email,
|
||||
'group_id': u.group_id,
|
||||
'group_name': u.group.name if u.group else '',
|
||||
'total_parse_count': u.total_parse_count,
|
||||
'is_active': u.is_active,
|
||||
'created_at': u.created_at.isoformat()
|
||||
} for u in pagination.items]
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': users,
|
||||
'pagination': {
|
||||
'page': page,
|
||||
'per_page': per_page,
|
||||
'total': pagination.total,
|
||||
'pages': pagination.pages
|
||||
}
|
||||
})
|
||||
|
||||
@admin_bp.route('/api/users/<int:user_id>', methods=['PUT'])
|
||||
@admin_required
|
||||
def update_user(user_id):
|
||||
"""更新用户信息"""
|
||||
user = User.query.get_or_404(user_id)
|
||||
data = request.get_json()
|
||||
|
||||
if 'group_id' in data:
|
||||
user.group_id = data['group_id']
|
||||
if 'is_active' in data:
|
||||
user.is_active = data['is_active']
|
||||
|
||||
db.session.commit()
|
||||
return jsonify({'success': True, 'message': '更新成功'})
|
||||
|
||||
@admin_bp.route('/api/groups', methods=['GET'])
|
||||
@admin_required
|
||||
def get_groups():
|
||||
"""获取用户分组列表"""
|
||||
groups = UserGroup.query.all()
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': [{
|
||||
'id': g.id,
|
||||
'name': g.name,
|
||||
'daily_limit': g.daily_limit,
|
||||
'description': g.description
|
||||
} for g in groups]
|
||||
})
|
||||
|
||||
@admin_bp.route('/api/groups/<int:group_id>', methods=['PUT'])
|
||||
@admin_required
|
||||
def update_group(group_id):
|
||||
"""更新用户分组"""
|
||||
group = UserGroup.query.get_or_404(group_id)
|
||||
data = request.get_json()
|
||||
|
||||
if 'daily_limit' in data:
|
||||
group.daily_limit = data['daily_limit']
|
||||
if 'description' in data:
|
||||
group.description = data['description']
|
||||
|
||||
db.session.commit()
|
||||
return jsonify({'success': True, 'message': '更新成功'})
|
||||
|
||||
@admin_bp.route('/apis', methods=['GET'])
|
||||
@admin_required
|
||||
def apis_page():
|
||||
"""接口管理页面"""
|
||||
from flask import render_template
|
||||
return render_template('admin_apis.html')
|
||||
|
||||
@admin_bp.route('/api/apis', methods=['GET'])
|
||||
@admin_required
|
||||
def get_apis():
|
||||
"""获取解析接口列表API"""
|
||||
apis = ParserAPI.query.all()
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': [{
|
||||
'id': a.id,
|
||||
'name': a.name,
|
||||
'platform': a.platform,
|
||||
'api_url': a.api_url,
|
||||
'weight': a.weight,
|
||||
'is_enabled': a.is_enabled,
|
||||
'health_status': a.health_status,
|
||||
'total_calls': a.total_calls,
|
||||
'success_calls': a.success_calls,
|
||||
'avg_response_time': a.avg_response_time,
|
||||
'last_check_at': a.last_check_at.isoformat() if a.last_check_at else None
|
||||
} for a in apis]
|
||||
})
|
||||
|
||||
@admin_bp.route('/api/apis', methods=['POST'])
|
||||
@admin_required
|
||||
def create_api():
|
||||
"""创建解析接口"""
|
||||
data = request.get_json()
|
||||
|
||||
api = ParserAPI(
|
||||
name=data['name'],
|
||||
platform=data['platform'],
|
||||
api_url=data['api_url'],
|
||||
api_key=data.get('api_key'),
|
||||
weight=data.get('weight', 1),
|
||||
is_enabled=data.get('is_enabled', True)
|
||||
)
|
||||
db.session.add(api)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'success': True, 'message': '创建成功', 'id': api.id})
|
||||
|
||||
@admin_bp.route('/api/apis/<int:api_id>', methods=['PUT'])
|
||||
@admin_required
|
||||
def update_api(api_id):
|
||||
"""更新解析接口"""
|
||||
api = ParserAPI.query.get_or_404(api_id)
|
||||
data = request.get_json()
|
||||
|
||||
for key in ['name', 'api_url', 'api_key', 'weight', 'is_enabled']:
|
||||
if key in data:
|
||||
setattr(api, key, data[key])
|
||||
|
||||
db.session.commit()
|
||||
return jsonify({'success': True, 'message': '更新成功'})
|
||||
|
||||
@admin_bp.route('/api/apis/<int:api_id>', methods=['DELETE'])
|
||||
@admin_required
|
||||
def delete_api(api_id):
|
||||
"""删除解析接口"""
|
||||
api = ParserAPI.query.get_or_404(api_id)
|
||||
db.session.delete(api)
|
||||
db.session.commit()
|
||||
return jsonify({'success': True, 'message': '删除成功'})
|
||||
|
||||
@admin_bp.route('/api/apis/<int:api_id>/test', methods=['POST'])
|
||||
@admin_required
|
||||
def test_api(api_id):
|
||||
"""测试解析接口"""
|
||||
from parsers.factory import ParserFactory
|
||||
import time
|
||||
|
||||
api = ParserAPI.query.get_or_404(api_id)
|
||||
data = request.get_json()
|
||||
test_url = data.get('test_url')
|
||||
|
||||
if not test_url:
|
||||
return jsonify({'success': False, 'message': '请提供测试链接'}), 400
|
||||
|
||||
try:
|
||||
parser = ParserFactory.create_parser(api)
|
||||
start_time = time.time()
|
||||
result = parser.parse(test_url)
|
||||
response_time = int((time.time() - start_time) * 1000)
|
||||
|
||||
# 更新健康状态和统计
|
||||
api.health_status = True
|
||||
api.fail_count = 0
|
||||
api.total_calls += 1
|
||||
api.success_calls += 1
|
||||
api.avg_response_time = int((api.avg_response_time * (api.total_calls - 1) + response_time) / api.total_calls)
|
||||
api.last_check_at = datetime.utcnow()
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '测试成功',
|
||||
'data': result,
|
||||
'response_time': response_time
|
||||
})
|
||||
except Exception as e:
|
||||
# 更新失败状态
|
||||
api.fail_count += 1
|
||||
api.total_calls += 1
|
||||
if api.fail_count >= 3:
|
||||
api.health_status = False
|
||||
api.last_check_at = datetime.utcnow()
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'success': False, 'message': f'测试失败: {str(e)}'}), 500
|
||||
|
||||
@admin_bp.route('/config', methods=['GET'])
|
||||
@admin_required
|
||||
def config_page():
|
||||
"""站点配置页面"""
|
||||
from flask import render_template
|
||||
return render_template('admin_config.html')
|
||||
|
||||
@admin_bp.route('/api/config', methods=['GET'])
|
||||
@admin_required
|
||||
def get_config():
|
||||
"""获取站点配置API"""
|
||||
configs = SiteConfig.query.all()
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': {c.config_key: c.config_value for c in configs}
|
||||
})
|
||||
|
||||
@admin_bp.route('/api/config', methods=['PUT'])
|
||||
@admin_required
|
||||
def update_config():
|
||||
"""更新站点配置"""
|
||||
data = request.get_json()
|
||||
|
||||
for key, value in data.items():
|
||||
config = SiteConfig.query.filter_by(config_key=key).first()
|
||||
if config:
|
||||
config.config_value = str(value)
|
||||
else:
|
||||
config = SiteConfig(config_key=key, config_value=str(value))
|
||||
db.session.add(config)
|
||||
|
||||
db.session.commit()
|
||||
return jsonify({'success': True, 'message': '更新成功'})
|
||||
|
||||
|
||||
@admin_bp.route('/smtp', methods=['GET'])
|
||||
@admin_required
|
||||
def smtp_page():
|
||||
"""SMTP配置页面"""
|
||||
from flask import render_template
|
||||
return render_template('admin_smtp.html')
|
||||
@admin_bp.route('/api/smtp', methods=['GET'])
|
||||
@admin_required
|
||||
def get_smtp():
|
||||
"""获取SMTP配置列表"""
|
||||
smtps = SMTPConfig.query.all()
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': [{
|
||||
'id': s.id,
|
||||
'name': s.name,
|
||||
'host': s.host,
|
||||
'port': s.port,
|
||||
'username': s.username,
|
||||
'from_email': s.from_email,
|
||||
'from_name': s.from_name,
|
||||
'use_tls': s.use_tls,
|
||||
'is_enabled': s.is_enabled,
|
||||
'is_default': s.is_default,
|
||||
'weight': s.weight,
|
||||
'send_count': s.send_count,
|
||||
'fail_count': s.fail_count
|
||||
} for s in smtps]
|
||||
})
|
||||
@admin_bp.route('/api/smtp', methods=['POST'])
|
||||
@admin_required
|
||||
def create_smtp():
|
||||
"""创建SMTP配置"""
|
||||
data = request.get_json()
|
||||
|
||||
smtp = SMTPConfig(
|
||||
name=data['name'],
|
||||
host=data['host'],
|
||||
port=data['port'],
|
||||
username=data['username'],
|
||||
password=data['password'],
|
||||
from_email=data['from_email'],
|
||||
from_name=data.get('from_name', ''),
|
||||
use_tls=data.get('use_tls', True),
|
||||
is_enabled=data.get('is_enabled', True),
|
||||
weight=data.get('weight', 1)
|
||||
)
|
||||
db.session.add(smtp)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'success': True, 'message': '创建成功', 'id': smtp.id})
|
||||
|
||||
@admin_bp.route('/api/smtp/<int:smtp_id>', methods=['PUT'])
|
||||
@admin_required
|
||||
def update_smtp(smtp_id):
|
||||
"""更新SMTP配置"""
|
||||
smtp = SMTPConfig.query.get_or_404(smtp_id)
|
||||
data = request.get_json()
|
||||
|
||||
for key in ['name', 'host', 'port', 'username', 'from_email', 'from_name', 'use_tls', 'is_enabled', 'weight']:
|
||||
if key in data:
|
||||
setattr(smtp, key, data[key])
|
||||
|
||||
# Only update password when user explicitly provides one, so empty edits won't wipe valid credentials.
|
||||
if 'password' in data and data.get('password'):
|
||||
smtp.password = data['password']
|
||||
|
||||
db.session.commit()
|
||||
return jsonify({'success': True, 'message': '更新成功'})
|
||||
|
||||
@admin_bp.route('/api/smtp/test', methods=['POST'])
|
||||
@admin_required
|
||||
def test_smtp():
|
||||
"""测试SMTP配置"""
|
||||
data = request.get_json()
|
||||
email = data.get('email')
|
||||
|
||||
if not email:
|
||||
return jsonify({'success': False, 'message': '请提供测试邮箱'}), 400
|
||||
|
||||
try:
|
||||
EmailService.send_email(
|
||||
email,
|
||||
'【短视频解析平台】SMTP测试邮件',
|
||||
'<h2>测试成功</h2><p>如果您收到此邮件,说明SMTP配置正常。</p>',
|
||||
html=True
|
||||
)
|
||||
return jsonify({'success': True, 'message': '测试邮件已发送'})
|
||||
except Exception as e:
|
||||
import traceback
|
||||
error_detail = traceback.format_exc()
|
||||
print(f"SMTP测试失败详细信息:\n{error_detail}")
|
||||
|
||||
# 提供更友好的错误提示
|
||||
error_msg = str(e)
|
||||
if 'Connection unexpectedly closed' in error_msg:
|
||||
error_msg = '连接被服务器关闭,请检查:1) 端口和加密方式是否匹配 2) QQ邮箱需使用授权码而非密码 3) 用户名是否正确'
|
||||
elif 'Authentication failed' in error_msg or '535' in error_msg:
|
||||
error_msg = '认证失败,请检查用户名和密码(QQ邮箱需使用授权码)'
|
||||
elif 'timed out' in error_msg:
|
||||
error_msg = '连接超时,请检查网络和服务器地址'
|
||||
|
||||
return jsonify({'success': False, 'message': f'邮件发送失败: {error_msg}'}), 500
|
||||
|
||||
@admin_bp.route('/api/stats/parse', methods=['GET'])
|
||||
@admin_required
|
||||
def get_parse_stats():
|
||||
"""获取解析统计"""
|
||||
days = request.args.get('days', 7, type=int)
|
||||
start_date = date.today() - timedelta(days=days-1)
|
||||
|
||||
stats = db.session.query(
|
||||
DailyParseStat.date,
|
||||
func.sum(DailyParseStat.parse_count).label('total'),
|
||||
func.sum(DailyParseStat.success_count).label('success'),
|
||||
func.sum(DailyParseStat.fail_count).label('fail')
|
||||
).filter(DailyParseStat.date >= start_date).group_by(DailyParseStat.date).all()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': [{
|
||||
'date': s.date.isoformat(),
|
||||
'total': s.total or 0,
|
||||
'success': s.success or 0,
|
||||
'fail': s.fail or 0
|
||||
} for s in stats]
|
||||
})
|
||||
|
||||
@admin_bp.route('/api/stats/platform', methods=['GET'])
|
||||
@admin_required
|
||||
def get_platform_stats():
|
||||
"""获取平台统计"""
|
||||
stats = db.session.query(
|
||||
ParseLog.platform,
|
||||
func.count(ParseLog.id).label('count')
|
||||
).group_by(ParseLog.platform).all()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': [{
|
||||
'platform': s.platform,
|
||||
'count': s.count
|
||||
} for s in stats]
|
||||
})
|
||||
|
||||
@admin_bp.route('/api/2fa/enable', methods=['POST'])
|
||||
@admin_required
|
||||
def enable_2fa():
|
||||
"""启用2FA"""
|
||||
admin_id = session.get('admin_id')
|
||||
admin = Admin.query.get(admin_id)
|
||||
|
||||
if admin.is_2fa_enabled:
|
||||
return jsonify({'success': False, 'message': '2FA已启用'}), 400
|
||||
|
||||
# 生成密钥
|
||||
secret = generate_2fa_secret()
|
||||
qr_url = get_2fa_qrcode_url(admin, secret)
|
||||
|
||||
# 生成二维码
|
||||
qr = qrcode.QRCode(version=1, box_size=10, border=5)
|
||||
qr.add_data(qr_url)
|
||||
qr.make(fit=True)
|
||||
img = qr.make_image(fill_color="black", back_color="white")
|
||||
|
||||
# 转换为base64
|
||||
buffer = io.BytesIO()
|
||||
img.save(buffer, format='PNG')
|
||||
img_str = base64.b64encode(buffer.getvalue()).decode()
|
||||
|
||||
# 临时保存密钥(需要验证后才正式启用)
|
||||
session['temp_2fa_secret'] = secret
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'secret': secret,
|
||||
'qr_code': f'data:image/png;base64,{img_str}'
|
||||
})
|
||||
|
||||
@admin_bp.route('/api/2fa/verify', methods=['POST'])
|
||||
@admin_required
|
||||
def verify_2fa_setup():
|
||||
"""验证并启用2FA"""
|
||||
admin_id = session.get('admin_id')
|
||||
admin = Admin.query.get(admin_id)
|
||||
data = request.get_json()
|
||||
code = data.get('code')
|
||||
|
||||
secret = session.get('temp_2fa_secret')
|
||||
if not secret:
|
||||
return jsonify({'success': False, 'message': '请先生成2FA密钥'}), 400
|
||||
|
||||
# 验证代码
|
||||
import pyotp
|
||||
totp = pyotp.TOTP(secret)
|
||||
if not totp.verify(code, valid_window=1):
|
||||
return jsonify({'success': False, 'message': '验证码错误'}), 400
|
||||
|
||||
# 启用2FA
|
||||
admin.totp_secret = secret
|
||||
admin.is_2fa_enabled = True
|
||||
db.session.commit()
|
||||
|
||||
session.pop('temp_2fa_secret', None)
|
||||
|
||||
return jsonify({'success': True, 'message': '2FA已启用'})
|
||||
|
||||
@admin_bp.route('/api/2fa/disable', methods=['POST'])
|
||||
@admin_required
|
||||
def disable_2fa():
|
||||
"""禁用2FA"""
|
||||
admin_id = session.get('admin_id')
|
||||
admin = Admin.query.get(admin_id)
|
||||
data = request.get_json()
|
||||
code = data.get('code')
|
||||
|
||||
if not admin.is_2fa_enabled:
|
||||
return jsonify({'success': False, 'message': '2FA未启用'}), 400
|
||||
|
||||
# 验证代码
|
||||
if not verify_2fa(admin, code):
|
||||
return jsonify({'success': False, 'message': '验证码错误'}), 401
|
||||
|
||||
# 禁用2FA
|
||||
admin.is_2fa_enabled = False
|
||||
admin.totp_secret = None
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'success': True, 'message': '2FA已禁用'})
|
||||
|
||||
@admin_bp.route('/api/smtp/<int:smtp_id>', methods=['DELETE'])
|
||||
@admin_required
|
||||
def delete_smtp(smtp_id):
|
||||
"""删除SMTP配置"""
|
||||
smtp = SMTPConfig.query.get_or_404(smtp_id)
|
||||
db.session.delete(smtp)
|
||||
db.session.commit()
|
||||
return jsonify({'success': True, 'message': '删除成功'})
|
||||
|
||||
@admin_bp.route('/api/smtp/<int:smtp_id>/set-default', methods=['POST'])
|
||||
@admin_required
|
||||
def set_default_smtp(smtp_id):
|
||||
"""设置默认SMTP"""
|
||||
SMTPConfig.query.update({'is_default': False})
|
||||
smtp = SMTPConfig.query.get_or_404(smtp_id)
|
||||
smtp.is_default = True
|
||||
db.session.commit()
|
||||
return jsonify({'success': True, 'message': '已设置为默认SMTP'})
|
||||
|
||||
|
||||
@admin_bp.route('/logs', methods=['GET'])
|
||||
@admin_required
|
||||
def logs_page():
|
||||
"""解析日志页面"""
|
||||
from flask import render_template
|
||||
return render_template('admin_logs.html')
|
||||
@admin_bp.route('/api/logs', methods=['GET'])
|
||||
@admin_required
|
||||
def get_logs():
|
||||
"""获取解析日志"""
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = request.args.get('per_page', 50, type=int)
|
||||
platform = request.args.get('platform')
|
||||
status = request.args.get('status')
|
||||
|
||||
query = ParseLog.query
|
||||
if platform:
|
||||
query = query.filter_by(platform=platform)
|
||||
if status:
|
||||
query = query.filter_by(status=status)
|
||||
|
||||
pagination = query.order_by(ParseLog.created_at.desc()).paginate(
|
||||
page=page, per_page=per_page, error_out=False
|
||||
)
|
||||
|
||||
logs = [{
|
||||
'id': log.id,
|
||||
'user_id': log.user_id,
|
||||
'ip_address': log.ip_address,
|
||||
'platform': log.platform,
|
||||
'video_url': log.video_url,
|
||||
'status': log.status,
|
||||
'error_message': log.error_message,
|
||||
'response_time': log.response_time,
|
||||
'created_at': log.created_at.isoformat()
|
||||
} for log in pagination.items]
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': logs,
|
||||
'pagination': {
|
||||
'page': page,
|
||||
'per_page': per_page,
|
||||
'total': pagination.total,
|
||||
'pages': pagination.pages
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@admin_bp.route('/health-checks', methods=['GET'])
|
||||
@admin_required
|
||||
def health_checks_page():
|
||||
"""健康检查配置页面"""
|
||||
from flask import render_template
|
||||
return render_template('admin_health_checks.html')
|
||||
@admin_bp.route('/api/health-checks', methods=['GET'])
|
||||
@admin_required
|
||||
def get_health_checks():
|
||||
"""获取健康检查配置"""
|
||||
configs = HealthCheckConfig.query.all()
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': [{
|
||||
'id': c.id,
|
||||
'platform': c.platform,
|
||||
'test_url': c.test_url,
|
||||
'check_interval': c.check_interval,
|
||||
'is_enabled': c.is_enabled,
|
||||
'alert_email': c.alert_email
|
||||
} for c in configs]
|
||||
})
|
||||
@admin_bp.route('/api/health-checks', methods=['POST'])
|
||||
@admin_required
|
||||
def create_health_check():
|
||||
"""创建健康检查配置"""
|
||||
data = request.get_json()
|
||||
config = HealthCheckConfig(
|
||||
platform=data['platform'],
|
||||
test_url=data['test_url'],
|
||||
check_interval=data.get('check_interval', 300),
|
||||
is_enabled=data.get('is_enabled', True),
|
||||
alert_email=data.get('alert_email')
|
||||
)
|
||||
db.session.add(config)
|
||||
db.session.commit()
|
||||
return jsonify({'success': True, 'message': '创建成功', 'id': config.id})
|
||||
|
||||
@admin_bp.route('/api/health-checks/<int:config_id>', methods=['PUT'])
|
||||
@admin_required
|
||||
def update_health_check(config_id):
|
||||
"""更新健康检查配置"""
|
||||
config = HealthCheckConfig.query.get_or_404(config_id)
|
||||
data = request.get_json()
|
||||
|
||||
for key in ['test_url', 'check_interval', 'is_enabled', 'alert_email']:
|
||||
if key in data:
|
||||
setattr(config, key, data[key])
|
||||
|
||||
db.session.commit()
|
||||
return jsonify({'success': True, 'message': '更新成功'})
|
||||
|
||||
@admin_bp.route('/api/health-checks/<int:config_id>', methods=['DELETE'])
|
||||
@admin_required
|
||||
def delete_health_check(config_id):
|
||||
"""删除健康检查配置"""
|
||||
config = HealthCheckConfig.query.get_or_404(config_id)
|
||||
db.session.delete(config)
|
||||
db.session.commit()
|
||||
return jsonify({'success': True, 'message': '删除成功'})
|
||||
|
||||
|
||||
@admin_bp.route('/profile', methods=['GET'])
|
||||
@admin_required
|
||||
def profile_page():
|
||||
"""账号管理页面"""
|
||||
from flask import render_template
|
||||
return render_template('admin_profile.html')
|
||||
@admin_bp.route('/api/profile', methods=['GET'])
|
||||
@admin_required
|
||||
def get_profile():
|
||||
"""获取管理员信息"""
|
||||
admin_id = session.get('admin_id')
|
||||
admin = Admin.query.get(admin_id)
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': {
|
||||
'id': admin.id,
|
||||
'username': admin.username,
|
||||
'email': admin.email,
|
||||
'is_2fa_enabled': admin.is_2fa_enabled,
|
||||
'last_login_ip': admin.last_login_ip,
|
||||
'last_login_at': admin.last_login_at.isoformat() if admin.last_login_at else None
|
||||
}
|
||||
})
|
||||
|
||||
@admin_bp.route('/api/profile/password', methods=['PUT'])
|
||||
@admin_required
|
||||
def change_password():
|
||||
"""修改管理员密码"""
|
||||
admin_id = session.get('admin_id')
|
||||
admin = Admin.query.get(admin_id)
|
||||
data = request.get_json()
|
||||
|
||||
old_password = data.get('old_password')
|
||||
new_password = data.get('new_password')
|
||||
|
||||
if not all([old_password, new_password]):
|
||||
return jsonify({'success': False, 'message': '请填写完整信息'}), 400
|
||||
|
||||
if not verify_password(old_password, admin.password):
|
||||
return jsonify({'success': False, 'message': '原密码错误'}), 401
|
||||
|
||||
admin.password = hash_password(new_password)
|
||||
db.session.commit()
|
||||
return jsonify({'success': True, 'message': '密码修改成功'})
|
||||
|
||||
@admin_bp.route('/api/profile/email', methods=['PUT'])
|
||||
@admin_required
|
||||
def change_email():
|
||||
"""修改管理员邮箱"""
|
||||
admin_id = session.get('admin_id')
|
||||
admin = Admin.query.get(admin_id)
|
||||
data = request.get_json()
|
||||
|
||||
email = data.get('email')
|
||||
if not email:
|
||||
return jsonify({'success': False, 'message': '请提供邮箱地址'}), 400
|
||||
|
||||
admin.email = email
|
||||
db.session.commit()
|
||||
return jsonify({'success': True, 'message': '邮箱修改成功'})
|
||||
Reference in New Issue
Block a user