From 299a32fa168829bed5a441c3c9008b50dabaa9b0 Mon Sep 17 00:00:00 2001 From: liuwei Date: Mon, 13 Apr 2026 17:09:21 +0800 Subject: [PATCH] feat: add real detail views for stats pages --- admin/dashboard/blueprints/stats.py | 31 ++++++++++++ admin/dashboard/templates/groups.html | 73 +++++++++++++++++++++++++-- admin/dashboard/templates/users.html | 32 +++++++++++- db/stats_db.py | 51 +++++++++++++++++++ 4 files changed, 182 insertions(+), 5 deletions(-) diff --git a/admin/dashboard/blueprints/stats.py b/admin/dashboard/blueprints/stats.py index 59cc1c7..3c0957a 100644 --- a/admin/dashboard/blueprints/stats.py +++ b/admin/dashboard/blueprints/stats.py @@ -76,6 +76,37 @@ def api_group_stats(): return jsonify({"success": False, "error": str(e)}), 500 +@stats_bp.route('/api/user_stats/') +@login_required +def api_user_stats_detail(user_id): + try: + server = current_app.dashboard_server + days = request.args.get('days', 30, type=int) + summary = server.stats_db.get_user_plugin_summary(user_id, days) + plugin_stats = server.stats_db.get_user_plugin_stats(user_id, days, limit=10) + success_rate = round( + (summary["success_calls"] / summary["total_calls"] * 100), + 2 + ) if summary["total_calls"] else 0.0 + + return jsonify({ + "success": True, + "data": { + "user_id": user_id, + "user_name": server.contact_manager.get_nickname(user_id) or user_id, + "days": days, + "summary": { + **summary, + "success_rate": success_rate, + }, + "plugin_stats": plugin_stats, + } + }) + except Exception as e: + logger.error(f"获取用户统计详情失败: {e}") + return jsonify({"success": False, "error": str(e)}), 500 + + @stats_bp.route('/api/plugin_stats') @login_required def api_plugin_stats(): diff --git a/admin/dashboard/templates/groups.html b/admin/dashboard/templates/groups.html index b67e3b9..748045b 100644 --- a/admin/dashboard/templates/groups.html +++ b/admin/dashboard/templates/groups.html @@ -35,6 +35,56 @@ + + +
+ +
+
{% endblock %} @@ -42,7 +92,7 @@ {% endblock %} diff --git a/admin/dashboard/templates/users.html b/admin/dashboard/templates/users.html index c025a51..b821608 100644 --- a/admin/dashboard/templates/users.html +++ b/admin/dashboard/templates/users.html @@ -21,14 +21,42 @@ + + +
+ +
+
{% endblock %} {% block scripts %} {% endblock %} diff --git a/db/stats_db.py b/db/stats_db.py index fb1c507..191fea4 100644 --- a/db/stats_db.py +++ b/db/stats_db.py @@ -608,3 +608,54 @@ class StatsDBOperator(BaseDBOperator): "plugin_count": int(result.get("plugin_count") or 0), "last_used_at": result.get("last_used_at") or "", } + + def get_user_plugin_stats(self, user_id: str, days: int = 30, limit: int = 10) -> List[Dict]: + """获取指定用户的插件调用统计""" + sql = """ + SELECT + plugin_name, + command, + SUM(total_calls) AS total_calls, + SUM(success_calls) AS success_calls, + SUM(failed_calls) AS failed_calls, + MIN(first_used_at) AS first_used_at, + MAX(last_used_at) AS last_used_at + FROM t_user_stats + WHERE user_id = %s + AND last_used_at >= DATE_SUB(NOW(), INTERVAL %s DAY) + GROUP BY plugin_name, command + ORDER BY total_calls DESC, last_used_at DESC + LIMIT %s + """ + rows = self.execute_query(sql, (user_id, days, limit)) or [] + for row in rows: + for key in ("first_used_at", "last_used_at"): + dt = row.get(key) + if isinstance(dt, datetime): + row[key] = dt.strftime("%Y-%m-%d %H:%M:%S") + return rows + + def get_user_plugin_summary(self, user_id: str, days: int = 30) -> Dict: + """获取指定用户的插件调用摘要""" + sql = """ + SELECT + SUM(total_calls) AS total_calls, + SUM(success_calls) AS success_calls, + SUM(failed_calls) AS failed_calls, + COUNT(DISTINCT plugin_name) AS plugin_count, + MAX(last_used_at) AS last_used_at + FROM t_user_stats + WHERE user_id = %s + AND last_used_at >= DATE_SUB(NOW(), INTERVAL %s DAY) + """ + result = self.execute_query(sql, (user_id, days), fetch_one=True) or {} + dt = result.get("last_used_at") + if isinstance(dt, datetime): + result["last_used_at"] = dt.strftime("%Y-%m-%d %H:%M:%S") + return { + "total_calls": int(result.get("total_calls") or 0), + "success_calls": int(result.get("success_calls") or 0), + "failed_calls": int(result.get("failed_calls") or 0), + "plugin_count": int(result.get("plugin_count") or 0), + "last_used_at": result.get("last_used_at") or "", + }