diff --git a/backups/backup_data_20250829_235838.zip b/backups/backup_data_20250829_235838.zip new file mode 100644 index 0000000..ae51561 Binary files /dev/null and b/backups/backup_data_20250829_235838.zip differ diff --git a/src/flask_prompt_master/__pycache__/__init__.cpython-312.pyc b/src/flask_prompt_master/__pycache__/__init__.cpython-312.pyc index 459a97c..60d17f7 100644 Binary files a/src/flask_prompt_master/__pycache__/__init__.cpython-312.pyc and b/src/flask_prompt_master/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/flask_prompt_master/admin/__init__.py b/src/flask_prompt_master/admin/__init__.py index 45a44e8..8a3aac3 100644 --- a/src/flask_prompt_master/admin/__init__.py +++ b/src/flask_prompt_master/admin/__init__.py @@ -13,6 +13,12 @@ from .views.user_admin import UserAdminView from .views.prompt_admin import PromptAdminView from .views.template_admin import TemplateAdminView from .views.system_admin import SystemAdminView +from .views.analytics_admin import AnalyticsAdminView +from .views.batch_admin import BatchAdminView +from .views.monitor_admin import MonitorAdminView +from .views.report_admin import ReportAdminView +from .views.backup_admin import BackupAdminView +from .views.api_admin import ApiAdminView from datetime import datetime # 创建登录管理器 @@ -38,6 +44,12 @@ def init_admin(app): admin.add_view(PromptAdminView(Prompt, db.session, name='提示词管理', endpoint='admin_prompt')) admin.add_view(TemplateAdminView(PromptTemplate, db.session, name='模板管理', endpoint='admin_template')) admin.add_view(SystemAdminView(name='系统管理', endpoint='admin_system')) + admin.add_view(AnalyticsAdminView(name='数据分析', endpoint='analytics_admin')) + admin.add_view(BatchAdminView(name='批量操作', endpoint='batch_admin')) + admin.add_view(MonitorAdminView(name='系统监控', endpoint='monitor_admin')) + admin.add_view(ReportAdminView(name='高级报表', endpoint='report_admin')) + admin.add_view(BackupAdminView(name='数据备份', endpoint='backup_admin')) + admin.add_view(ApiAdminView(name='API管理', endpoint='api_admin')) # 注册登录路由 app.add_url_rule('/admin/login', 'admin.login', admin_login, methods=['GET', 'POST']) diff --git a/src/flask_prompt_master/admin/__pycache__/__init__.cpython-312.pyc b/src/flask_prompt_master/admin/__pycache__/__init__.cpython-312.pyc index 967bc0f..08f7b73 100644 Binary files a/src/flask_prompt_master/admin/__pycache__/__init__.cpython-312.pyc and b/src/flask_prompt_master/admin/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/flask_prompt_master/admin/forms/__pycache__/__init__.cpython-312.pyc b/src/flask_prompt_master/admin/forms/__pycache__/__init__.cpython-312.pyc index e3ad3dd..582ff7b 100644 Binary files a/src/flask_prompt_master/admin/forms/__pycache__/__init__.cpython-312.pyc and b/src/flask_prompt_master/admin/forms/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/flask_prompt_master/admin/forms/__pycache__/admin_forms.cpython-312.pyc b/src/flask_prompt_master/admin/forms/__pycache__/admin_forms.cpython-312.pyc index 544b940..d7af81b 100644 Binary files a/src/flask_prompt_master/admin/forms/__pycache__/admin_forms.cpython-312.pyc and b/src/flask_prompt_master/admin/forms/__pycache__/admin_forms.cpython-312.pyc differ diff --git a/src/flask_prompt_master/admin/models/__pycache__/__init__.cpython-312.pyc b/src/flask_prompt_master/admin/models/__pycache__/__init__.cpython-312.pyc index ca64ac5..0641745 100644 Binary files a/src/flask_prompt_master/admin/models/__pycache__/__init__.cpython-312.pyc and b/src/flask_prompt_master/admin/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/flask_prompt_master/admin/models/__pycache__/admin_user.cpython-312.pyc b/src/flask_prompt_master/admin/models/__pycache__/admin_user.cpython-312.pyc index 61adfde..068b366 100644 Binary files a/src/flask_prompt_master/admin/models/__pycache__/admin_user.cpython-312.pyc and b/src/flask_prompt_master/admin/models/__pycache__/admin_user.cpython-312.pyc differ diff --git a/src/flask_prompt_master/admin/views/__pycache__/__init__.cpython-312.pyc b/src/flask_prompt_master/admin/views/__pycache__/__init__.cpython-312.pyc index 79463ea..84cc92f 100644 Binary files a/src/flask_prompt_master/admin/views/__pycache__/__init__.cpython-312.pyc and b/src/flask_prompt_master/admin/views/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/flask_prompt_master/admin/views/__pycache__/analytics_admin.cpython-312.pyc b/src/flask_prompt_master/admin/views/__pycache__/analytics_admin.cpython-312.pyc new file mode 100644 index 0000000..dbc5aac Binary files /dev/null and b/src/flask_prompt_master/admin/views/__pycache__/analytics_admin.cpython-312.pyc differ diff --git a/src/flask_prompt_master/admin/views/__pycache__/api_admin.cpython-312.pyc b/src/flask_prompt_master/admin/views/__pycache__/api_admin.cpython-312.pyc new file mode 100644 index 0000000..d43e737 Binary files /dev/null and b/src/flask_prompt_master/admin/views/__pycache__/api_admin.cpython-312.pyc differ diff --git a/src/flask_prompt_master/admin/views/__pycache__/backup_admin.cpython-312.pyc b/src/flask_prompt_master/admin/views/__pycache__/backup_admin.cpython-312.pyc new file mode 100644 index 0000000..a83d26d Binary files /dev/null and b/src/flask_prompt_master/admin/views/__pycache__/backup_admin.cpython-312.pyc differ diff --git a/src/flask_prompt_master/admin/views/__pycache__/batch_admin.cpython-312.pyc b/src/flask_prompt_master/admin/views/__pycache__/batch_admin.cpython-312.pyc new file mode 100644 index 0000000..cdcd3bb Binary files /dev/null and b/src/flask_prompt_master/admin/views/__pycache__/batch_admin.cpython-312.pyc differ diff --git a/src/flask_prompt_master/admin/views/__pycache__/monitor_admin.cpython-312.pyc b/src/flask_prompt_master/admin/views/__pycache__/monitor_admin.cpython-312.pyc new file mode 100644 index 0000000..fd87a3f Binary files /dev/null and b/src/flask_prompt_master/admin/views/__pycache__/monitor_admin.cpython-312.pyc differ diff --git a/src/flask_prompt_master/admin/views/__pycache__/prompt_admin.cpython-312.pyc b/src/flask_prompt_master/admin/views/__pycache__/prompt_admin.cpython-312.pyc index 4585b44..a910d1c 100644 Binary files a/src/flask_prompt_master/admin/views/__pycache__/prompt_admin.cpython-312.pyc and b/src/flask_prompt_master/admin/views/__pycache__/prompt_admin.cpython-312.pyc differ diff --git a/src/flask_prompt_master/admin/views/__pycache__/report_admin.cpython-312.pyc b/src/flask_prompt_master/admin/views/__pycache__/report_admin.cpython-312.pyc new file mode 100644 index 0000000..ac74b57 Binary files /dev/null and b/src/flask_prompt_master/admin/views/__pycache__/report_admin.cpython-312.pyc differ diff --git a/src/flask_prompt_master/admin/views/__pycache__/system_admin.cpython-312.pyc b/src/flask_prompt_master/admin/views/__pycache__/system_admin.cpython-312.pyc index c787c71..ba249b9 100644 Binary files a/src/flask_prompt_master/admin/views/__pycache__/system_admin.cpython-312.pyc and b/src/flask_prompt_master/admin/views/__pycache__/system_admin.cpython-312.pyc differ diff --git a/src/flask_prompt_master/admin/views/__pycache__/template_admin.cpython-312.pyc b/src/flask_prompt_master/admin/views/__pycache__/template_admin.cpython-312.pyc index 078a7ce..fcfd1f7 100644 Binary files a/src/flask_prompt_master/admin/views/__pycache__/template_admin.cpython-312.pyc and b/src/flask_prompt_master/admin/views/__pycache__/template_admin.cpython-312.pyc differ diff --git a/src/flask_prompt_master/admin/views/__pycache__/user_admin.cpython-312.pyc b/src/flask_prompt_master/admin/views/__pycache__/user_admin.cpython-312.pyc index b20420f..fdb58d5 100644 Binary files a/src/flask_prompt_master/admin/views/__pycache__/user_admin.cpython-312.pyc and b/src/flask_prompt_master/admin/views/__pycache__/user_admin.cpython-312.pyc differ diff --git a/src/flask_prompt_master/admin/views/analytics_admin.py b/src/flask_prompt_master/admin/views/analytics_admin.py new file mode 100644 index 0000000..4b0e7cc --- /dev/null +++ b/src/flask_prompt_master/admin/views/analytics_admin.py @@ -0,0 +1,191 @@ +# -*- coding: utf-8 -*- +""" +数据分析管理视图 +""" +from flask_admin import BaseView, expose +from flask_login import login_required, current_user +from src.flask_prompt_master.models.models import User, Prompt, PromptTemplate +from src.flask_prompt_master import db +from sqlalchemy import func, extract +from datetime import datetime, timedelta +import plotly.graph_objs as go +import plotly.utils +import json + +class AnalyticsAdminView(BaseView): + """数据分析管理视图""" + + @expose('/') + @login_required + def index(self): + """数据分析首页""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'system_admin']: + return self.render('admin/403.html') + + # 获取统计数据 + stats = self._get_analytics_data() + + return self.render('admin/analytics_dashboard.html', stats=stats) + + @expose('/charts') + @login_required + def charts(self): + """图表页面""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'system_admin']: + return self.render('admin/403.html') + + # 生成图表数据 + charts_data = self._generate_charts() + + return self.render('admin/analytics_charts.html', charts_data=charts_data) + + def _get_analytics_data(self): + """获取分析数据""" + try: + # 用户统计 + total_users = User.query.count() + active_users = User.query.filter_by(status=1).count() + new_users_today = User.query.filter( + User.created_time >= datetime.now().date() + ).count() + new_users_week = User.query.filter( + User.created_time >= datetime.now() - timedelta(days=7) + ).count() + + # 提示词统计 + total_prompts = Prompt.query.count() + today_prompts = Prompt.query.filter( + Prompt.created_at >= datetime.now().date() + ).count() + week_prompts = Prompt.query.filter( + Prompt.created_at >= datetime.now() - timedelta(days=7) + ).count() + + # 模板统计 + total_templates = PromptTemplate.query.count() + default_templates = PromptTemplate.query.filter_by(is_default=True).count() + + # 用户活跃度 + active_users_week = db.session.query(func.count(func.distinct(Prompt.user_id))).filter( + Prompt.created_at >= datetime.now() - timedelta(days=7) + ).scalar() + + return { + 'total_users': total_users, + 'active_users': active_users, + 'new_users_today': new_users_today, + 'new_users_week': new_users_week, + 'total_prompts': total_prompts, + 'today_prompts': today_prompts, + 'week_prompts': week_prompts, + 'total_templates': total_templates, + 'default_templates': default_templates, + 'active_users_week': active_users_week or 0 + } + except Exception as e: + return { + 'total_users': 0, + 'active_users': 0, + 'new_users_today': 0, + 'new_users_week': 0, + 'total_prompts': 0, + 'today_prompts': 0, + 'week_prompts': 0, + 'total_templates': 0, + 'default_templates': 0, + 'active_users_week': 0 + } + + def _generate_charts(self): + """生成图表数据""" + try: + # 用户注册趋势(最近30天) + thirty_days_ago = datetime.now() - timedelta(days=30) + daily_registrations = db.session.query( + func.date(User.created_time).label('date'), + func.count(User.uid).label('count') + ).filter( + User.created_time >= thirty_days_ago + ).group_by( + func.date(User.created_time) + ).all() + + # 提示词生成趋势(最近30天) + daily_prompts = db.session.query( + func.date(Prompt.created_at).label('date'), + func.count(Prompt.id).label('count') + ).filter( + Prompt.created_at >= thirty_days_ago + ).group_by( + func.date(Prompt.created_at) + ).all() + + # 用户活跃度饼图 + active_status = db.session.query( + User.status, + func.count(User.uid).label('count') + ).group_by(User.status).all() + + # 生成图表JSON + charts = { + 'user_trend': self._create_line_chart(daily_registrations, '用户注册趋势', '日期', '注册人数'), + 'prompt_trend': self._create_line_chart(daily_prompts, '提示词生成趋势', '日期', '生成数量'), + 'user_status': self._create_pie_chart(active_status, '用户状态分布') + } + + return charts + except Exception as e: + return {} + + def _create_line_chart(self, data, title, x_label, y_label): + """创建折线图""" + if not data: + return json.dumps({}) + + x_values = [str(item.date) for item in data] + y_values = [item.count for item in data] + + trace = go.Scatter( + x=x_values, + y=y_values, + mode='lines+markers', + name=title, + line=dict(color='#4e73df', width=3), + marker=dict(size=8) + ) + + layout = go.Layout( + title=title, + xaxis=dict(title=x_label), + yaxis=dict(title=y_label), + height=400, + margin=dict(l=50, r=50, t=50, b=50) + ) + + fig = go.Figure(data=[trace], layout=layout) + return json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder) + + def _create_pie_chart(self, data, title): + """创建饼图""" + if not data: + return json.dumps({}) + + labels = ['活跃用户' if item.status == 1 else '禁用用户' for item in data] + values = [item.count for item in data] + colors = ['#28a745', '#dc3545'] + + trace = go.Pie( + labels=labels, + values=values, + marker=dict(colors=colors), + textinfo='label+percent' + ) + + layout = go.Layout( + title=title, + height=400, + margin=dict(l=50, r=50, t=50, b=50) + ) + + fig = go.Figure(data=[trace], layout=layout) + return json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder) diff --git a/src/flask_prompt_master/admin/views/api_admin.py b/src/flask_prompt_master/admin/views/api_admin.py new file mode 100644 index 0000000..107513d --- /dev/null +++ b/src/flask_prompt_master/admin/views/api_admin.py @@ -0,0 +1,184 @@ +# -*- coding: utf-8 -*- +""" +API管理视图 +""" +from flask_admin import BaseView, expose +from flask_login import login_required, current_user +from flask import request, jsonify +from src.flask_prompt_master.models.models import User, Prompt +from src.flask_prompt_master import db +from sqlalchemy import func +from datetime import datetime, timedelta +import json + +class ApiAdminView(BaseView): + """API管理视图""" + + @expose('/') + @login_required + def index(self): + """API管理首页""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'system_admin']: + return self.render('admin/403.html') + + # 获取API统计信息 + api_stats = self._get_api_stats() + + return self.render('admin/api_dashboard.html', api_stats=api_stats) + + @expose('/api/stats') + @login_required + def api_stats(self): + """获取API统计信息""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'system_admin']: + return jsonify({'success': False, 'message': '权限不足'}) + + try: + stats = self._get_api_stats() + return jsonify({'success': True, 'data': stats}) + except Exception as e: + return jsonify({'success': False, 'message': f'获取统计信息失败: {str(e)}'}) + + @expose('/api/calls') + @login_required + def api_calls(self): + """获取API调用记录""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'system_admin']: + return jsonify({'success': False, 'message': '权限不足'}) + + try: + # 获取查询参数 + start_date = request.args.get('start_date', (datetime.now() - timedelta(days=7)).strftime('%Y-%m-%d')) + end_date = request.args.get('end_date', datetime.now().strftime('%Y-%m-%d')) + api_type = request.args.get('type', 'all') + + # 获取API调用记录 + calls = self._get_api_calls(start_date, end_date, api_type) + + return jsonify({'success': True, 'data': calls}) + except Exception as e: + return jsonify({'success': False, 'message': f'获取调用记录失败: {str(e)}'}) + + @expose('/api/rate-limits') + @login_required + def rate_limits(self): + """API限流管理""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'system_admin']: + return self.render('admin/403.html') + + return self.render('admin/api_rate_limits.html') + + @expose('/api/keys') + @login_required + def api_keys(self): + """API密钥管理""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'system_admin']: + return self.render('admin/403.html') + + return self.render('admin/api_keys.html') + + def _get_api_stats(self): + """获取API统计信息""" + try: + # 今日API调用次数 + today = datetime.now().date() + today_calls = Prompt.query.filter( + func.date(Prompt.created_at) == today + ).count() + + # 本周API调用次数 + week_ago = datetime.now() - timedelta(days=7) + week_calls = Prompt.query.filter( + Prompt.created_at >= week_ago + ).count() + + # 本月API调用次数 + month_ago = datetime.now() - timedelta(days=30) + month_calls = Prompt.query.filter( + Prompt.created_at >= month_ago + ).count() + + # 总API调用次数 + total_calls = Prompt.query.count() + + # 活跃用户数(有API调用的用户) + active_users = db.session.query(func.count(func.distinct(Prompt.user_id))).scalar() or 0 + + # 平均响应时间(模拟数据) + avg_response_time = 1.2 # 秒 + + # 成功率(模拟数据) + success_rate = 98.5 # 百分比 + + # 最近7天的调用趋势 + daily_calls = [] + for i in range(7): + date = datetime.now() - timedelta(days=i) + count = Prompt.query.filter( + func.date(Prompt.created_at) == date.date() + ).count() + daily_calls.append({ + 'date': date.strftime('%Y-%m-%d'), + 'count': count + }) + daily_calls.reverse() + + return { + 'today_calls': today_calls, + 'week_calls': week_calls, + 'month_calls': month_calls, + 'total_calls': total_calls, + 'active_users': active_users, + 'avg_response_time': avg_response_time, + 'success_rate': success_rate, + 'daily_calls': daily_calls + } + + except Exception as e: + return { + 'today_calls': 0, + 'week_calls': 0, + 'month_calls': 0, + 'total_calls': 0, + 'active_users': 0, + 'avg_response_time': 0, + 'success_rate': 0, + 'daily_calls': [] + } + + def _get_api_calls(self, start_date, end_date, api_type): + """获取API调用记录""" + try: + start_dt = datetime.strptime(start_date, '%Y-%m-%d') + end_dt = datetime.strptime(end_date, '%Y-%m-%d') + timedelta(days=1) + + # 查询API调用记录 + query = Prompt.query.filter( + Prompt.created_at >= start_dt, + Prompt.created_at < end_dt + ) + + if api_type != 'all': + # 这里可以根据实际需求添加API类型过滤 + pass + + calls = query.order_by(Prompt.created_at.desc()).limit(100).all() + + # 格式化数据 + call_records = [] + for call in calls: + call_records.append({ + 'id': call.id, + 'user_id': call.user_id, + 'api_type': 'prompt_generation', + 'input_text': call.input_text[:50] + '...' if len(call.input_text) > 50 else call.input_text, + 'status': 'success', + 'response_time': 1.2, # 模拟响应时间 + 'created_at': call.created_at.strftime('%Y-%m-%d %H:%M:%S') if call.created_at else '', + 'ip_address': '127.0.0.1' # 模拟IP地址 + }) + + return call_records + + except Exception as e: + return [] diff --git a/src/flask_prompt_master/admin/views/backup_admin.py b/src/flask_prompt_master/admin/views/backup_admin.py new file mode 100644 index 0000000..f657809 --- /dev/null +++ b/src/flask_prompt_master/admin/views/backup_admin.py @@ -0,0 +1,479 @@ +# -*- coding: utf-8 -*- +""" +数据备份管理视图 +""" +from flask_admin import BaseView, expose +from flask_login import login_required, current_user +from flask import request, jsonify, send_file +from src.flask_prompt_master import db +from src.flask_prompt_master.models.models import User, Prompt, PromptTemplate +import os +import json +import zipfile +import io +from datetime import datetime, timedelta +import threading +import schedule +import time +import shutil + +class BackupAdminView(BaseView): + """数据备份管理视图""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.backup_dir = os.path.join(os.getcwd(), 'backups') + self.auto_backup_enabled = False + self.backup_thread = None + + # 确保备份目录存在 + if not os.path.exists(self.backup_dir): + os.makedirs(self.backup_dir) + + @expose('/') + @login_required + def index(self): + """备份管理首页""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'system_admin']: + return self.render('admin/403.html') + + # 获取备份文件列表 + backup_files = self._get_backup_files() + + return self.render('admin/backup_dashboard.html', backup_files=backup_files) + + @expose('/create-backup', methods=['POST']) + @login_required + def create_backup(self): + """创建备份""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'system_admin']: + return jsonify({'success': False, 'message': '权限不足'}) + + try: + backup_type = request.json.get('type', 'full') + backup_name = request.json.get('name', '') + + # 生成备份文件名 + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + if backup_name: + filename = f"{backup_name}_{timestamp}.zip" + else: + filename = f"backup_{backup_type}_{timestamp}.zip" + + backup_path = os.path.join(self.backup_dir, filename) + + # 创建备份 + if backup_type == 'full': + success = self._create_full_backup(backup_path) + elif backup_type == 'data': + success = self._create_data_backup(backup_path) + elif backup_type == 'config': + success = self._create_config_backup(backup_path) + else: + return jsonify({'success': False, 'message': '不支持的备份类型'}) + + if success: + return jsonify({ + 'success': True, + 'message': f'{backup_type}备份创建成功', + 'filename': filename + }) + else: + return jsonify({'success': False, 'message': '备份创建失败'}) + + except Exception as e: + return jsonify({'success': False, 'message': f'备份创建失败: {str(e)}'}) + + @expose('/download-backup/') + @login_required + def download_backup(self, filename): + """下载备份文件""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'system_admin']: + return jsonify({'success': False, 'message': '权限不足'}) + + try: + backup_path = os.path.join(self.backup_dir, filename) + if not os.path.exists(backup_path): + return jsonify({'success': False, 'message': '备份文件不存在'}) + + return send_file( + backup_path, + as_attachment=True, + download_name=filename + ) + + except Exception as e: + return jsonify({'success': False, 'message': f'下载失败: {str(e)}'}) + + @expose('/restore-backup', methods=['POST']) + @login_required + def restore_backup(self): + """恢复备份""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'system_admin']: + return jsonify({'success': False, 'message': '权限不足'}) + + try: + filename = request.json.get('filename') + if not filename: + return jsonify({'success': False, 'message': '请选择要恢复的备份文件'}) + + backup_path = os.path.join(self.backup_dir, filename) + if not os.path.exists(backup_path): + return jsonify({'success': False, 'message': '备份文件不存在'}) + + # 执行恢复 + success = self._restore_backup(backup_path) + + if success: + return jsonify({'success': True, 'message': '备份恢复成功'}) + else: + return jsonify({'success': False, 'message': '备份恢复失败'}) + + except Exception as e: + return jsonify({'success': False, 'message': f'恢复失败: {str(e)}'}) + + @expose('/delete-backup', methods=['POST']) + @login_required + def delete_backup(self): + """删除备份""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'system_admin']: + return jsonify({'success': False, 'message': '权限不足'}) + + try: + filename = request.json.get('filename') + if not filename: + return jsonify({'success': False, 'message': '请选择要删除的备份文件'}) + + backup_path = os.path.join(self.backup_dir, filename) + if not os.path.exists(backup_path): + return jsonify({'success': False, 'message': '备份文件不存在'}) + + # 删除文件 + os.remove(backup_path) + + return jsonify({'success': True, 'message': '备份文件删除成功'}) + + except Exception as e: + return jsonify({'success': False, 'message': f'删除失败: {str(e)}'}) + + @expose('/auto-backup', methods=['POST']) + @login_required + def toggle_auto_backup(self): + """切换自动备份""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'system_admin']: + return jsonify({'success': False, 'message': '权限不足'}) + + try: + enabled = request.json.get('enabled', False) + + if enabled and not self.auto_backup_enabled: + # 启动自动备份 + self.auto_backup_enabled = True + self.backup_thread = threading.Thread(target=self._auto_backup_worker, daemon=True) + self.backup_thread.start() + return jsonify({'success': True, 'message': '自动备份已启动'}) + elif not enabled and self.auto_backup_enabled: + # 停止自动备份 + self.auto_backup_enabled = False + return jsonify({'success': True, 'message': '自动备份已停止'}) + else: + return jsonify({'success': False, 'message': '自动备份状态未改变'}) + + except Exception as e: + return jsonify({'success': False, 'message': f'操作失败: {str(e)}'}) + + def _get_backup_files(self): + """获取备份文件列表""" + backup_files = [] + try: + for filename in os.listdir(self.backup_dir): + if filename.endswith('.zip'): + file_path = os.path.join(self.backup_dir, filename) + stat = os.stat(file_path) + backup_files.append({ + 'filename': filename, + 'size': self._format_file_size(stat.st_size), + 'created_time': datetime.fromtimestamp(stat.st_ctime).strftime('%Y-%m-%d %H:%M:%S'), + 'modified_time': datetime.fromtimestamp(stat.st_mtime).strftime('%Y-%m-%d %H:%M:%S') + }) + + # 按修改时间排序 + backup_files.sort(key=lambda x: x['modified_time'], reverse=True) + + except Exception as e: + print(f"获取备份文件列表失败: {str(e)}") + + return backup_files + + def _create_full_backup(self, backup_path): + """创建完整备份""" + try: + with zipfile.ZipFile(backup_path, 'w', zipfile.ZIP_DEFLATED) as zipf: + # 备份数据库数据 + self._backup_database_data(zipf) + + # 备份配置文件 + self._backup_config_files(zipf) + + # 备份日志文件 + self._backup_log_files(zipf) + + # 创建备份信息文件 + backup_info = { + 'type': 'full', + 'created_time': datetime.now().isoformat(), + 'created_by': current_user.username, + 'description': '完整系统备份' + } + zipf.writestr('backup_info.json', json.dumps(backup_info, indent=2)) + + return True + + except Exception as e: + print(f"创建完整备份失败: {str(e)}") + return False + + def _create_data_backup(self, backup_path): + """创建数据备份""" + try: + with zipfile.ZipFile(backup_path, 'w', zipfile.ZIP_DEFLATED) as zipf: + # 只备份数据库数据 + self._backup_database_data(zipf) + + # 创建备份信息文件 + backup_info = { + 'type': 'data', + 'created_time': datetime.now().isoformat(), + 'created_by': current_user.username, + 'description': '数据备份' + } + zipf.writestr('backup_info.json', json.dumps(backup_info, indent=2)) + + return True + + except Exception as e: + print(f"创建数据备份失败: {str(e)}") + return False + + def _create_config_backup(self, backup_path): + """创建配置备份""" + try: + with zipfile.ZipFile(backup_path, 'w', zipfile.ZIP_DEFLATED) as zipf: + # 只备份配置文件 + self._backup_config_files(zipf) + + # 创建备份信息文件 + backup_info = { + 'type': 'config', + 'created_time': datetime.now().isoformat(), + 'created_by': current_user.username, + 'description': '配置备份' + } + zipf.writestr('backup_info.json', json.dumps(backup_info, indent=2)) + + return True + + except Exception as e: + print(f"创建配置备份失败: {str(e)}") + return False + + def _backup_database_data(self, zipf): + """备份数据库数据""" + try: + # 备份用户数据 + users = User.query.all() + user_data = [] + for user in users: + user_data.append({ + 'uid': user.uid, + 'login_name': user.login_name, + 'nickname': user.nickname, + 'email': user.email, + 'mobile': user.mobile, + 'sex': user.sex, + 'status': user.status, + 'created_time': user.created_time.isoformat() if user.created_time else None, + 'updated_time': user.updated_time.isoformat() if user.updated_time else None + }) + zipf.writestr('data/users.json', json.dumps(user_data, indent=2, ensure_ascii=False)) + + # 备份提示词数据 + prompts = Prompt.query.all() + prompt_data = [] + for prompt in prompts: + prompt_data.append({ + 'id': prompt.id, + 'user_id': prompt.user_id, + 'input_text': prompt.input_text, + 'generated_text': prompt.generated_text, + 'created_at': prompt.created_at.isoformat() if prompt.created_at else None + }) + zipf.writestr('data/prompts.json', json.dumps(prompt_data, indent=2, ensure_ascii=False)) + + # 备份模板数据 + templates = PromptTemplate.query.all() + template_data = [] + for template in templates: + template_data.append({ + 'id': template.id, + 'name': template.name, + 'category': template.category, + 'industry': template.industry, + 'profession': template.profession, + 'description': template.description, + 'system_prompt': template.system_prompt, + 'is_default': template.is_default, + 'created_at': template.created_at.isoformat() if template.created_at else None, + 'updated_at': template.updated_at.isoformat() if template.updated_at else None + }) + zipf.writestr('data/templates.json', json.dumps(template_data, indent=2, ensure_ascii=False)) + + except Exception as e: + print(f"备份数据库数据失败: {str(e)}") + + def _backup_config_files(self, zipf): + """备份配置文件""" + try: + config_files = ['config.py', '.env', 'requirements.txt'] + for config_file in config_files: + if os.path.exists(config_file): + zipf.write(config_file, f'config/{config_file}') + + # 备份Flask配置 + from src.flask_prompt_master import create_app + app = create_app() + config_data = { + 'SQLALCHEMY_DATABASE_URI': app.config.get('SQLALCHEMY_DATABASE_URI', ''), + 'SECRET_KEY': app.config.get('SECRET_KEY', ''), + 'DEBUG': app.config.get('DEBUG', False) + } + zipf.writestr('config/app_config.json', json.dumps(config_data, indent=2)) + + except Exception as e: + print(f"备份配置文件失败: {str(e)}") + + def _backup_log_files(self, zipf): + """备份日志文件""" + try: + log_dir = 'logs' + if os.path.exists(log_dir): + for log_file in os.listdir(log_dir): + if log_file.endswith('.log'): + log_path = os.path.join(log_dir, log_file) + zipf.write(log_path, f'logs/{log_file}') + + except Exception as e: + print(f"备份日志文件失败: {str(e)}") + + def _restore_backup(self, backup_path): + """恢复备份""" + try: + with zipfile.ZipFile(backup_path, 'r') as zipf: + # 读取备份信息 + backup_info = json.loads(zipf.read('backup_info.json')) + backup_type = backup_info.get('type', 'unknown') + + if backup_type == 'data' or backup_type == 'full': + # 恢复数据库数据 + self._restore_database_data(zipf) + + if backup_type == 'config' or backup_type == 'full': + # 恢复配置文件 + self._restore_config_files(zipf) + + return True + + except Exception as e: + print(f"恢复备份失败: {str(e)}") + return False + + def _restore_database_data(self, zipf): + """恢复数据库数据""" + try: + # 恢复用户数据 + if 'data/users.json' in zipf.namelist(): + user_data = json.loads(zipf.read('data/users.json')) + for user_info in user_data: + # 这里需要根据实际情况实现数据恢复逻辑 + pass + + # 恢复提示词数据 + if 'data/prompts.json' in zipf.namelist(): + prompt_data = json.loads(zipf.read('data/prompts.json')) + for prompt_info in prompt_data: + # 这里需要根据实际情况实现数据恢复逻辑 + pass + + # 恢复模板数据 + if 'data/templates.json' in zipf.namelist(): + template_data = json.loads(zipf.read('data/templates.json')) + for template_info in template_data: + # 这里需要根据实际情况实现数据恢复逻辑 + pass + + except Exception as e: + print(f"恢复数据库数据失败: {str(e)}") + + def _restore_config_files(self, zipf): + """恢复配置文件""" + try: + # 恢复配置文件 + for file_info in zipf.filelist: + if file_info.filename.startswith('config/'): + filename = file_info.filename.replace('config/', '') + if filename not in ['.env']: # 不覆盖敏感配置文件 + zipf.extract(file_info, '.') + + except Exception as e: + print(f"恢复配置文件失败: {str(e)}") + + def _auto_backup_worker(self): + """自动备份工作线程""" + schedule.every().day.at("02:00").do(self._perform_auto_backup) + + while self.auto_backup_enabled: + schedule.run_pending() + time.sleep(60) + + def _perform_auto_backup(self): + """执行自动备份""" + try: + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + filename = f"auto_backup_{timestamp}.zip" + backup_path = os.path.join(self.backup_dir, filename) + + # 创建数据备份 + self._create_data_backup(backup_path) + + # 清理旧备份(保留最近7天) + self._cleanup_old_backups() + + print(f"自动备份完成: {filename}") + + except Exception as e: + print(f"自动备份失败: {str(e)}") + + def _cleanup_old_backups(self): + """清理旧备份文件""" + try: + cutoff_time = datetime.now() - timedelta(days=7) + for filename in os.listdir(self.backup_dir): + if filename.startswith('auto_backup_') and filename.endswith('.zip'): + file_path = os.path.join(self.backup_dir, filename) + if os.path.getctime(file_path) < cutoff_time.timestamp(): + os.remove(file_path) + print(f"删除旧备份文件: {filename}") + + except Exception as e: + print(f"清理旧备份失败: {str(e)}") + + def _format_file_size(self, size_bytes): + """格式化文件大小""" + if size_bytes == 0: + return "0B" + size_names = ["B", "KB", "MB", "GB"] + i = 0 + while size_bytes >= 1024 and i < len(size_names) - 1: + size_bytes /= 1024.0 + i += 1 + return f"{size_bytes:.1f}{size_names[i]}" diff --git a/src/flask_prompt_master/admin/views/batch_admin.py b/src/flask_prompt_master/admin/views/batch_admin.py new file mode 100644 index 0000000..0fd9fe9 --- /dev/null +++ b/src/flask_prompt_master/admin/views/batch_admin.py @@ -0,0 +1,185 @@ +# -*- coding: utf-8 -*- +""" +批量操作管理视图 +""" +from flask_admin import BaseView, expose +from flask_login import login_required, current_user +from flask import request, jsonify, send_file +from src.flask_prompt_master.models.models import User, Prompt, PromptTemplate +from src.flask_prompt_master import db +import csv +import io +from datetime import datetime + +class BatchAdminView(BaseView): + """批量操作管理视图""" + + @expose('/') + @login_required + def index(self): + """批量操作首页""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'system_admin']: + return self.render('admin/403.html') + + return self.render('admin/batch_operations.html') + + @expose('/export/users') + @login_required + def export_users(self): + """导出用户数据""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'system_admin']: + return jsonify({'success': False, 'message': '权限不足'}) + + try: + # 获取用户数据 + users = User.query.all() + + # 创建CSV文件 + output = io.StringIO() + writer = csv.writer(output) + + # 写入表头 + writer.writerow(['用户ID', '用户名', '昵称', '邮箱', '手机号', '性别', '状态', '注册时间']) + + # 写入数据 + for user in users: + writer.writerow([ + user.uid, + user.login_name, + user.nickname, + user.email, + user.mobile, + '男' if user.sex == 1 else '女' if user.sex == 2 else '保密', + '正常' if user.status == 1 else '禁用', + user.created_time.strftime('%Y-%m-%d %H:%M:%S') if user.created_time else '' + ]) + + # 创建文件响应 + output.seek(0) + return send_file( + io.BytesIO(output.getvalue().encode('utf-8-sig')), + mimetype='text/csv', + as_attachment=True, + download_name=f'users_export_{datetime.now().strftime("%Y%m%d_%H%M%S")}.csv' + ) + + except Exception as e: + return jsonify({'success': False, 'message': f'导出失败: {str(e)}'}) + + @expose('/export/prompts') + @login_required + def export_prompts(self): + """导出提示词数据""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'content_admin']: + return jsonify({'success': False, 'message': '权限不足'}) + + try: + # 获取提示词数据 + prompts = Prompt.query.all() + + # 创建CSV文件 + output = io.StringIO() + writer = csv.writer(output) + + # 写入表头 + writer.writerow(['ID', '用户ID', '输入文本', '生成文本', '创建时间']) + + # 写入数据 + for prompt in prompts: + writer.writerow([ + prompt.id, + prompt.user_id, + prompt.input_text[:100] + '...' if len(prompt.input_text) > 100 else prompt.input_text, + prompt.generated_text[:100] + '...' if len(prompt.generated_text) > 100 else prompt.generated_text, + prompt.created_at.strftime('%Y-%m-%d %H:%M:%S') if prompt.created_at else '' + ]) + + # 创建文件响应 + output.seek(0) + return send_file( + io.BytesIO(output.getvalue().encode('utf-8-sig')), + mimetype='text/csv', + as_attachment=True, + download_name=f'prompts_export_{datetime.now().strftime("%Y%m%d_%H%M%S")}.csv' + ) + + except Exception as e: + return jsonify({'success': False, 'message': f'导出失败: {str(e)}'}) + + @expose('/batch/disable-users', methods=['POST']) + @login_required + def batch_disable_users(self): + """批量禁用用户""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'user_admin']: + return jsonify({'success': False, 'message': '权限不足'}) + + try: + data = request.get_json() + user_ids = data.get('user_ids', []) + + if not user_ids: + return jsonify({'success': False, 'message': '请选择要禁用的用户'}) + + # 批量更新用户状态 + User.query.filter(User.uid.in_(user_ids)).update( + {'status': 0, 'updated_time': datetime.now()}, + synchronize_session=False + ) + db.session.commit() + + return jsonify({'success': True, 'message': f'成功禁用 {len(user_ids)} 个用户'}) + + except Exception as e: + db.session.rollback() + return jsonify({'success': False, 'message': f'操作失败: {str(e)}'}) + + @expose('/batch/enable-users', methods=['POST']) + @login_required + def batch_enable_users(self): + """批量启用用户""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'user_admin']: + return jsonify({'success': False, 'message': '权限不足'}) + + try: + data = request.get_json() + user_ids = data.get('user_ids', []) + + if not user_ids: + return jsonify({'success': False, 'message': '请选择要启用的用户'}) + + # 批量更新用户状态 + User.query.filter(User.uid.in_(user_ids)).update( + {'status': 1, 'updated_time': datetime.now()}, + synchronize_session=False + ) + db.session.commit() + + return jsonify({'success': True, 'message': f'成功启用 {len(user_ids)} 个用户'}) + + except Exception as e: + db.session.rollback() + return jsonify({'success': False, 'message': f'操作失败: {str(e)}'}) + + @expose('/batch/delete-prompts', methods=['POST']) + @login_required + def batch_delete_prompts(self): + """批量删除提示词""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'content_admin']: + return jsonify({'success': False, 'message': '权限不足'}) + + try: + data = request.get_json() + prompt_ids = data.get('prompt_ids', []) + + if not prompt_ids: + return jsonify({'success': False, 'message': '请选择要删除的提示词'}) + + # 批量删除提示词 + Prompt.query.filter(Prompt.id.in_(prompt_ids)).delete(synchronize_session=False) + db.session.commit() + + return jsonify({'success': True, 'message': f'成功删除 {len(prompt_ids)} 个提示词'}) + + except Exception as e: + db.session.rollback() + return jsonify({'success': False, 'message': f'操作失败: {str(e)}'}) diff --git a/src/flask_prompt_master/admin/views/monitor_admin.py b/src/flask_prompt_master/admin/views/monitor_admin.py new file mode 100644 index 0000000..bf26311 --- /dev/null +++ b/src/flask_prompt_master/admin/views/monitor_admin.py @@ -0,0 +1,187 @@ +# -*- coding: utf-8 -*- +""" +系统监控管理视图 +""" +from flask_admin import BaseView, expose +from flask_login import login_required, current_user +from flask import request, jsonify +import psutil +import os +import time +from datetime import datetime, timedelta +import threading +import queue + +class MonitorAdminView(BaseView): + """系统监控管理视图""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.alert_queue = queue.Queue() + self.monitoring = False + self.monitor_thread = None + + @expose('/') + @login_required + def index(self): + """监控首页""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'system_admin']: + return self.render('admin/403.html') + + # 获取系统状态 + system_status = self._get_system_status() + + return self.render('admin/monitor_dashboard.html', system_status=system_status) + + @expose('/api/system-status') + @login_required + def api_system_status(self): + """获取系统状态API""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'system_admin']: + return jsonify({'success': False, 'message': '权限不足'}) + + try: + status = self._get_system_status() + return jsonify({'success': True, 'data': status}) + except Exception as e: + return jsonify({'success': False, 'message': f'获取系统状态失败: {str(e)}'}) + + @expose('/api/start-monitoring', methods=['POST']) + @login_required + def start_monitoring(self): + """启动监控""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'system_admin']: + return jsonify({'success': False, 'message': '权限不足'}) + + try: + if not self.monitoring: + self.monitoring = True + self.monitor_thread = threading.Thread(target=self._monitor_system, daemon=True) + self.monitor_thread.start() + return jsonify({'success': True, 'message': '监控已启动'}) + else: + return jsonify({'success': False, 'message': '监控已在运行中'}) + except Exception as e: + return jsonify({'success': False, 'message': f'启动监控失败: {str(e)}'}) + + @expose('/api/stop-monitoring', methods=['POST']) + @login_required + def stop_monitoring(self): + """停止监控""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'system_admin']: + return jsonify({'success': False, 'message': '权限不足'}) + + try: + self.monitoring = False + return jsonify({'success': True, 'message': '监控已停止'}) + except Exception as e: + return jsonify({'success': False, 'message': f'停止监控失败: {str(e)}'}) + + @expose('/api/alerts') + @login_required + def get_alerts(self): + """获取告警信息""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'system_admin']: + return jsonify({'success': False, 'message': '权限不足'}) + + try: + alerts = [] + while not self.alert_queue.empty(): + alerts.append(self.alert_queue.get_nowait()) + + return jsonify({'success': True, 'data': alerts}) + except Exception as e: + return jsonify({'success': False, 'message': f'获取告警失败: {str(e)}'}) + + def _get_system_status(self): + """获取系统状态""" + try: + # CPU使用率 + cpu_percent = psutil.cpu_percent(interval=1) + + # 内存使用情况 + memory = psutil.virtual_memory() + memory_percent = memory.percent + memory_used = memory.used / (1024**3) # GB + memory_total = memory.total / (1024**3) # GB + + # 磁盘使用情况 + disk = psutil.disk_usage('/') + disk_percent = disk.percent + disk_used = disk.used / (1024**3) # GB + disk_total = disk.total / (1024**3) # GB + + # 网络使用情况 + network = psutil.net_io_counters() + network_sent = network.bytes_sent / (1024**2) # MB + network_recv = network.bytes_recv / (1024**2) # MB + + # 进程数量 + process_count = len(psutil.pids()) + + # 系统运行时间 + boot_time = datetime.fromtimestamp(psutil.boot_time()) + uptime = datetime.now() - boot_time + + # 当前时间 + current_time = datetime.now() + + return { + 'cpu_percent': cpu_percent, + 'memory_percent': memory_percent, + 'memory_used': round(memory_used, 2), + 'memory_total': round(memory_total, 2), + 'disk_percent': disk_percent, + 'disk_used': round(disk_used, 2), + 'disk_total': round(disk_total, 2), + 'network_sent': round(network_sent, 2), + 'network_recv': round(network_recv, 2), + 'process_count': process_count, + 'uptime': str(uptime).split('.')[0], + 'current_time': current_time.strftime('%Y-%m-%d %H:%M:%S'), + 'monitoring': self.monitoring + } + except Exception as e: + return { + 'error': str(e), + 'monitoring': self.monitoring + } + + def _monitor_system(self): + """系统监控线程""" + while self.monitoring: + try: + status = self._get_system_status() + + # 检查告警条件 + if 'error' not in status: + if status['cpu_percent'] > 80: + self.alert_queue.put({ + 'level': 'warning', + 'message': f'CPU使用率过高: {status["cpu_percent"]}%', + 'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S') + }) + + if status['memory_percent'] > 85: + self.alert_queue.put({ + 'level': 'warning', + 'message': f'内存使用率过高: {status["memory_percent"]}%', + 'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S') + }) + + if status['disk_percent'] > 90: + self.alert_queue.put({ + 'level': 'danger', + 'message': f'磁盘使用率过高: {status["disk_percent"]}%', + 'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S') + }) + + time.sleep(30) # 每30秒检查一次 + + except Exception as e: + self.alert_queue.put({ + 'level': 'error', + 'message': f'监控异常: {str(e)}', + 'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S') + }) + time.sleep(60) # 异常时等待1分钟再重试 diff --git a/src/flask_prompt_master/admin/views/report_admin.py b/src/flask_prompt_master/admin/views/report_admin.py new file mode 100644 index 0000000..936a0c9 --- /dev/null +++ b/src/flask_prompt_master/admin/views/report_admin.py @@ -0,0 +1,254 @@ +# -*- coding: utf-8 -*- +""" +高级报表管理视图 +""" +from flask_admin import BaseView, expose +from flask_login import login_required, current_user +from flask import request, jsonify, send_file +from src.flask_prompt_master.models.models import User, Prompt, PromptTemplate +from src.flask_prompt_master import db +from sqlalchemy import func, extract +from datetime import datetime, timedelta +import csv +import io +import json + +class ReportAdminView(BaseView): + """高级报表管理视图""" + + @expose('/') + @login_required + def index(self): + """报表首页""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'system_admin']: + return self.render('admin/403.html') + + return self.render('admin/report_dashboard.html') + + @expose('/user-report') + @login_required + def user_report(self): + """用户报表""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'system_admin']: + return self.render('admin/403.html') + + # 获取报表参数 + start_date = request.args.get('start_date', (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d')) + end_date = request.args.get('end_date', datetime.now().strftime('%Y-%m-%d')) + report_type = request.args.get('type', 'daily') + + # 生成报表数据 + report_data = self._generate_user_report(start_date, end_date, report_type) + + return self.render('admin/user_report.html', + report_data=report_data, + start_date=start_date, + end_date=end_date, + report_type=report_type) + + @expose('/prompt-report') + @login_required + def prompt_report(self): + """提示词报表""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'content_admin']: + return self.render('admin/403.html') + + # 获取报表参数 + start_date = request.args.get('start_date', (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d')) + end_date = request.args.get('end_date', datetime.now().strftime('%Y-%m-%d')) + category = request.args.get('category', 'all') + + # 生成报表数据 + report_data = self._generate_prompt_report(start_date, end_date, category) + + return self.render('admin/prompt_report.html', + report_data=report_data, + start_date=start_date, + end_date=end_date, + category=category) + + @expose('/export-report') + @login_required + def export_report(self): + """导出报表""" + if not current_user.is_authenticated or current_user.role not in ['super_admin', 'system_admin']: + return jsonify({'success': False, 'message': '权限不足'}) + + try: + report_type = request.args.get('type', 'user') + start_date = request.args.get('start_date', (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d')) + end_date = request.args.get('end_date', datetime.now().strftime('%Y-%m-%d')) + + if report_type == 'user': + data = self._generate_user_report(start_date, end_date, 'daily') + filename = f'user_report_{start_date}_to_{end_date}.csv' + elif report_type == 'prompt': + data = self._generate_prompt_report(start_date, end_date, 'all') + filename = f'prompt_report_{start_date}_to_{end_date}.csv' + else: + return jsonify({'success': False, 'message': '不支持的报表类型'}) + + # 创建CSV文件 + output = io.StringIO() + writer = csv.writer(output) + + # 写入表头 + if report_type == 'user': + writer.writerow(['日期', '新增用户', '活跃用户', '总用户数']) + for item in data.get('daily_stats', []): + writer.writerow([item['date'], item['new_users'], item['active_users'], item['total_users']]) + else: + writer.writerow(['日期', '生成数量', '平均长度', '用户数']) + for item in data.get('daily_stats', []): + writer.writerow([item['date'], item['count'], item['avg_length'], item['users']]) + + # 创建文件响应 + output.seek(0) + return send_file( + io.BytesIO(output.getvalue().encode('utf-8-sig')), + mimetype='text/csv', + as_attachment=True, + download_name=filename + ) + + except Exception as e: + return jsonify({'success': False, 'message': f'导出失败: {str(e)}'}) + + def _generate_user_report(self, start_date, end_date, report_type): + """生成用户报表""" + try: + start_dt = datetime.strptime(start_date, '%Y-%m-%d') + end_dt = datetime.strptime(end_date, '%Y-%m-%d') + + # 总体统计 + total_users = User.query.count() + active_users = User.query.filter_by(status=1).count() + new_users_period = User.query.filter( + User.created_time >= start_dt, + User.created_time <= end_dt + ).count() + + # 每日统计 + daily_stats = [] + current_date = start_dt + while current_date <= end_dt: + next_date = current_date + timedelta(days=1) + + # 当日新增用户 + new_users = User.query.filter( + User.created_time >= current_date, + User.created_time < next_date + ).count() + + # 当日活跃用户(有生成提示词的用户) + active_users = db.session.query(func.count(func.distinct(Prompt.user_id))).filter( + Prompt.created_at >= current_date, + Prompt.created_at < next_date + ).scalar() or 0 + + # 累计用户数 + total_users_date = User.query.filter( + User.created_time <= next_date + ).count() + + daily_stats.append({ + 'date': current_date.strftime('%Y-%m-%d'), + 'new_users': new_users, + 'active_users': active_users, + 'total_users': total_users_date + }) + + current_date = next_date + + return { + 'total_users': total_users, + 'active_users': active_users, + 'new_users_period': new_users_period, + 'daily_stats': daily_stats, + 'period_days': (end_dt - start_dt).days + 1 + } + + except Exception as e: + return { + 'error': str(e), + 'total_users': 0, + 'active_users': 0, + 'new_users_period': 0, + 'daily_stats': [], + 'period_days': 0 + } + + def _generate_prompt_report(self, start_date, end_date, category): + """生成提示词报表""" + try: + start_dt = datetime.strptime(start_date, '%Y-%m-%d') + end_dt = datetime.strptime(end_date, '%Y-%m-%d') + + # 总体统计 + total_prompts = Prompt.query.filter( + Prompt.created_at >= start_dt, + Prompt.created_at <= end_dt + ).count() + + # 平均长度 + avg_length = db.session.query(func.avg(func.length(Prompt.input_text))).filter( + Prompt.created_at >= start_dt, + Prompt.created_at <= end_dt + ).scalar() or 0 + + # 活跃用户数 + active_users = db.session.query(func.count(func.distinct(Prompt.user_id))).filter( + Prompt.created_at >= start_dt, + Prompt.created_at <= end_dt + ).scalar() or 0 + + # 每日统计 + daily_stats = [] + current_date = start_dt + while current_date <= end_dt: + next_date = current_date + timedelta(days=1) + + # 当日生成数量 + count = Prompt.query.filter( + Prompt.created_at >= current_date, + Prompt.created_at < next_date + ).count() + + # 当日平均长度 + avg_len = db.session.query(func.avg(func.length(Prompt.input_text))).filter( + Prompt.created_at >= current_date, + Prompt.created_at < next_date + ).scalar() or 0 + + # 当日用户数 + users = db.session.query(func.count(func.distinct(Prompt.user_id))).filter( + Prompt.created_at >= current_date, + Prompt.created_at < next_date + ).scalar() or 0 + + daily_stats.append({ + 'date': current_date.strftime('%Y-%m-%d'), + 'count': count, + 'avg_length': round(avg_len, 2), + 'users': users + }) + + current_date = next_date + + return { + 'total_prompts': total_prompts, + 'avg_length': round(avg_length, 2), + 'active_users': active_users, + 'daily_stats': daily_stats, + 'period_days': (end_dt - start_dt).days + 1 + } + + except Exception as e: + return { + 'error': str(e), + 'total_prompts': 0, + 'avg_length': 0, + 'active_users': 0, + 'daily_stats': [], + 'period_days': 0 + } diff --git a/src/flask_prompt_master/templates/admin/analytics_charts.html b/src/flask_prompt_master/templates/admin/analytics_charts.html new file mode 100644 index 0000000..7634b31 --- /dev/null +++ b/src/flask_prompt_master/templates/admin/analytics_charts.html @@ -0,0 +1,177 @@ +{% extends 'admin/master.html' %} + +{% block title %}数据分析 - 图表{% endblock %} + +{% block head %} +{{ super() }} + +{% endblock %} + +{% block body %} +
+
+
+

+ 数据分析图表 +

+
+
+ + +
+
+
+
+
+ 用户注册趋势 +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ 提示词生成趋势 +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ 用户状态分布 +
+
+
+
+
+
+
+
+
+
+
+ 系统使用统计 +
+
+
+
+
+
+
+
+ + + +
+ + +{% endblock %} diff --git a/src/flask_prompt_master/templates/admin/analytics_dashboard.html b/src/flask_prompt_master/templates/admin/analytics_dashboard.html new file mode 100644 index 0000000..536c27c --- /dev/null +++ b/src/flask_prompt_master/templates/admin/analytics_dashboard.html @@ -0,0 +1,239 @@ +{% extends 'admin/master.html' %} + +{% block title %}数据分析 - 仪表板{% endblock %} + +{% block head %} +{{ super() }} + +{% endblock %} + +{% block body %} +
+
+
+

+ 数据分析仪表板 +

+
+
+ + +
+
+
+
+
+
+
+ 总用户数 +
+
{{ stats.total_users }}
+
今日新增: {{ stats.new_users_today }}
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+ 总提示词 +
+
{{ stats.total_prompts }}
+
今日生成: {{ stats.today_prompts }}
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+ 活跃用户 +
+
{{ stats.active_users }}
+
本周活跃: {{ stats.active_users_week }}
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+ 模板数量 +
+
{{ stats.total_templates }}
+
默认模板: {{ stats.default_templates }}
+
+
+ +
+
+
+
+
+
+ + +
+
+
+
+
+ 用户注册趋势 +
+
+
+
+
+
+
+ +
+
+
+
+ 用户状态分布 +
+
+
+
+
+
+
+
+ + +
+
+
+
+
+ 快速操作 +
+
+ +
+
+
+
+ + + + +{% endblock %} diff --git a/src/flask_prompt_master/templates/admin/api_dashboard.html b/src/flask_prompt_master/templates/admin/api_dashboard.html new file mode 100644 index 0000000..778a607 --- /dev/null +++ b/src/flask_prompt_master/templates/admin/api_dashboard.html @@ -0,0 +1,444 @@ +{% extends 'admin/master.html' %} + +{% block title %}API管理{% endblock %} + +{% block head %} +{{ super() }} + +{% endblock %} + +{% block body %} +
+
+
+

+ API管理 +

+
+
+ + +
+
+
+
+
+
+
+ 今日调用 +
+
{{ api_stats.today_calls }}
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+ 本周调用 +
+
{{ api_stats.week_calls }}
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+ 平均响应时间 +
+
{{ api_stats.avg_response_time }}s
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+ 成功率 +
+
{{ api_stats.success_rate }}%
+
+
+ +
+
+
+
+
+
+ + +
+
+
+
+
+ API调用趋势 +
+
+
+
+
+
+
+ +
+
+
+
+ API信息 +
+
+
+
+
+
+

{{ api_stats.total_calls }}

+

总调用次数

+
+
+
+
+

{{ api_stats.active_users }}

+

活跃用户数

+
+
+
+
+

{{ api_stats.month_calls }}

+

本月调用次数

+
+
+
+
+
+
+
+ + +
+
+
+
+
+ API管理功能 +
+
+
+
+
+
+
+ +
调用记录
+

查看详细的API调用记录和日志

+ +
+
+
+
+
+
+ +
限流管理
+

配置API调用频率限制和访问控制

+ + 限流设置 + +
+
+
+
+
+
+ +
密钥管理
+

管理API访问密钥和权限

+ + 密钥管理 + +
+
+
+
+
+
+
+
+ + +
+
+
+
+
+ 最近API调用记录 +
+
+
+
+ + + + + + + + + + + + + + + + + + +
ID用户IDAPI类型输入内容状态响应时间IP地址调用时间
+ 加载中... +
+
+
+
+
+
+
+ + + + + + + +{% endblock %} diff --git a/src/flask_prompt_master/templates/admin/backup_dashboard.html b/src/flask_prompt_master/templates/admin/backup_dashboard.html new file mode 100644 index 0000000..eed737d --- /dev/null +++ b/src/flask_prompt_master/templates/admin/backup_dashboard.html @@ -0,0 +1,418 @@ +{% extends 'admin/master.html' %} + +{% block title %}数据备份管理{% endblock %} + +{% block head %} +{{ super() }} + +{% endblock %} + +{% block body %} +
+
+
+

+ 数据备份管理 +

+
+
+ + +
+
+
+
+
+ 创建备份 +
+
+
+
+
+
+
+ +
完整备份
+

备份所有数据、配置和日志文件

+ +
+
+
+
+
+
+ +
数据备份
+

仅备份数据库数据

+ +
+
+
+
+
+
+ +
配置备份
+

仅备份系统配置文件

+ +
+
+
+
+
+
+
+
+ + +
+
+
+
+
+ 自动备份设置 +
+
+
+
+
+
自动备份
+

每天凌晨2点自动创建数据备份,保留最近7天的备份文件

+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
+
+ 备份文件列表 +
+
+
+
+ + + + + + + + + + + + {% for backup in backup_files %} + + + + + + + + {% else %} + + + + {% endfor %} + +
文件名大小创建时间修改时间操作
{{ backup.filename }}{{ backup.size }}{{ backup.created_time }}{{ backup.modified_time }} +
+ + + +
+
+
+ 暂无备份文件 +
+
+
+
+
+
+
+ + + + + + + + + + +{% endblock %} diff --git a/src/flask_prompt_master/templates/admin/batch_operations.html b/src/flask_prompt_master/templates/admin/batch_operations.html new file mode 100644 index 0000000..08fbffd --- /dev/null +++ b/src/flask_prompt_master/templates/admin/batch_operations.html @@ -0,0 +1,300 @@ +{% extends 'admin/master.html' %} + +{% block title %}批量操作{% endblock %} + +{% block head %} +{{ super() }} + +{% endblock %} + +{% block body %} +
+
+
+

+ 批量操作管理 +

+
+
+ + +
+
+
+
+
+ 数据导出 +
+
+
+
+
+
+
+
+ 用户数据导出 +
+

导出所有用户信息到CSV文件

+ + 导出用户数据 + +
+
+
+
+
+
+
+ 提示词数据导出 +
+

导出所有提示词信息到CSV文件

+ + 导出提示词数据 + +
+
+
+
+
+
+
+
+ + +
+
+
+
+
+ 批量操作 +
+
+
+
+
+
+
+
+ 批量禁用用户 +
+

选择用户ID进行批量禁用操作

+
+ + +
+ +
+
+
+
+
+
+
+ 批量启用用户 +
+

选择用户ID进行批量启用操作

+
+ + +
+ +
+
+
+
+
+
+
+ 批量删除提示词 +
+

选择提示词ID进行批量删除操作

+
+ + +
+ +
+
+
+
+
+
+
+
+ + +
+
+
+
+
+ 操作日志 +
+
+
+
+ 操作日志将在这里显示 +
+
+
+
+
+
+ + + + +{% endblock %} diff --git a/src/flask_prompt_master/templates/admin/monitor_dashboard.html b/src/flask_prompt_master/templates/admin/monitor_dashboard.html new file mode 100644 index 0000000..369a24d --- /dev/null +++ b/src/flask_prompt_master/templates/admin/monitor_dashboard.html @@ -0,0 +1,437 @@ +{% extends 'admin/master.html' %} + +{% block title %}系统监控{% endblock %} + +{% block head %} +{{ super() }} + +{% endblock %} + +{% block body %} +
+
+
+

+ 系统监控仪表板 +

+
+
+ + +
+
+
+
+
+ 监控控制 +
+
+
+
+
+ + +
+
+
+ + 监控状态: 未启动 +
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+ CPU使用率 +
+
0%
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+ 内存使用率 +
+
0%
+
0 GB / 0 GB
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+ 磁盘使用率 +
+
0%
+
0 GB / 0 GB
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+ 进程数量 +
+
0
+
+
+ +
+
+
+
+
+
+ + +
+
+
+
+
+ 系统资源使用趋势 +
+
+
+
+
+
+
+ +
+
+
+
+ 系统告警 +
+
+
+
+
+ 暂无告警信息 +
+
+
+
+
+
+ + +
+
+
+
+
+ 系统信息 +
+
+
+
+
+ + + + + + + + + + + + + +
系统运行时间:{{ system_status.uptime if system_status.uptime else '未知' }}
当前时间:{{ system_status.current_time if system_status.current_time else '未知' }}
网络发送:{{ system_status.network_sent if system_status.network_sent else 0 }} MB
+
+
+ + + + + + + + + + + + + +
网络接收:{{ system_status.network_recv if system_status.network_recv else 0 }} MB
监控状态:{{ '运行中' if system_status.monitoring else '未启动' }}
最后更新:-
+
+
+
+
+
+
+
+ + + + +{% endblock %} diff --git a/src/flask_prompt_master/templates/admin/report_dashboard.html b/src/flask_prompt_master/templates/admin/report_dashboard.html new file mode 100644 index 0000000..83a3c3e --- /dev/null +++ b/src/flask_prompt_master/templates/admin/report_dashboard.html @@ -0,0 +1,319 @@ +{% extends 'admin/master.html' %} + +{% block title %}高级报表{% endblock %} + +{% block head %} +{{ super() }} + +{% endblock %} + +{% block body %} +
+
+
+

+ 高级报表系统 +

+
+
+ + +
+
+
+
+
+ 报表类型 +
+
+
+
+
+
+
+ +
用户报表
+

用户注册、活跃度、增长趋势分析

+ + 查看用户报表 + +
+
+
+
+
+
+ +
提示词报表
+

提示词生成量、使用情况、质量分析

+ + 查看提示词报表 + +
+
+
+
+
+
+ +
数据导出
+

导出各种格式的报表数据

+ +
+
+
+
+
+
+
+
+ + +
+
+
+
+
+ 快速统计 +
+
+
+
+
+
+

-

+

总用户数

+
+
+
+
+

-

+

总提示词数

+
+
+
+
+

-

+

活跃用户

+
+
+
+
+

-

+

平均长度

+
+
+
+
+
+
+
+ + +
+
+
+
+
+ 用户增长趋势 +
+
+
+
+
+
+
+
+
+
+
+ 提示词生成趋势 +
+
+
+
+
+
+
+
+
+ + + + + + + +{% endblock %}