监控面板内容修改
This commit is contained in:
288
db/stats_db.py
288
db/stats_db.py
@@ -353,188 +353,120 @@ class StatsDBOperator(BaseDBOperator):
|
|||||||
"""
|
"""
|
||||||
return self.execute_query(sql, (error_id,), fetch_one=True)
|
return self.execute_query(sql, (error_id,), fetch_one=True)
|
||||||
|
|
||||||
def get_dashboard_summary(self, days: int = 7) -> Dict:
|
def get_dashboard_summary(self, days: int = 7) -> Dict[str, Any]:
|
||||||
"""获取仪表盘摘要数据
|
"""获取仪表盘摘要数据"""
|
||||||
|
try:
|
||||||
|
# 计算日期范围
|
||||||
|
end_date = datetime.now()
|
||||||
|
start_date = end_date - timedelta(days=days)
|
||||||
|
|
||||||
Args:
|
# 获取总调用次数
|
||||||
days: 统计天数
|
total_calls_query = """
|
||||||
|
SELECT COUNT(*) as total_calls FROM plugin_calls
|
||||||
Returns:
|
WHERE created_at >= ? AND created_at <= ?
|
||||||
仪表盘摘要数据
|
|
||||||
"""
|
|
||||||
# 获取时间范围
|
|
||||||
end_date = datetime.now()
|
|
||||||
start_date = end_date - timedelta(days=days)
|
|
||||||
start_date_str = start_date.strftime("%Y-%m-%d")
|
|
||||||
|
|
||||||
# 1. 总调用次数
|
|
||||||
total_calls_sql = """
|
|
||||||
SELECT SUM(total_calls) as total_calls
|
|
||||||
FROM t_plugin_stats
|
|
||||||
WHERE stat_date >= %s
|
|
||||||
"""
|
|
||||||
total_calls_result = self.execute_query(total_calls_sql, (start_date_str,), fetch_one=True)
|
|
||||||
total_calls = total_calls_result['total_calls'] if total_calls_result and total_calls_result['total_calls'] else 0
|
|
||||||
|
|
||||||
# 2. 成功率
|
|
||||||
success_rate_sql = """
|
|
||||||
SELECT SUM(success_calls) as success_calls, SUM(total_calls) as total_calls
|
|
||||||
FROM t_plugin_stats
|
|
||||||
WHERE stat_date >= %s
|
|
||||||
"""
|
|
||||||
success_rate_result = self.execute_query(success_rate_sql, (start_date_str,), fetch_one=True)
|
|
||||||
success_rate = 0
|
|
||||||
if success_rate_result and success_rate_result['total_calls']:
|
|
||||||
success_rate = (success_rate_result['success_calls'] / success_rate_result['total_calls']) * 100
|
|
||||||
|
|
||||||
# 3. 活跃用户数
|
|
||||||
active_users_sql = """
|
|
||||||
SELECT COUNT(DISTINCT user_id) as active_users
|
|
||||||
FROM t_user_stats
|
|
||||||
WHERE last_used_at >= %s
|
|
||||||
"""
|
|
||||||
active_users_result = self.execute_query(active_users_sql, (start_date_str,), fetch_one=True)
|
|
||||||
active_users = active_users_result['active_users'] if active_users_result else 0
|
|
||||||
|
|
||||||
# 4. 活跃群组数
|
|
||||||
active_groups_sql = """
|
|
||||||
SELECT COUNT(DISTINCT group_id) as active_groups
|
|
||||||
FROM t_group_stats
|
|
||||||
WHERE last_used_at >= %s
|
|
||||||
"""
|
|
||||||
active_groups_result = self.execute_query(active_groups_sql, (start_date_str,), fetch_one=True)
|
|
||||||
active_groups = active_groups_result['active_groups'] if active_groups_result else 0
|
|
||||||
|
|
||||||
# 5. 错误数量
|
|
||||||
error_count_sql = """
|
|
||||||
SELECT COUNT(*) as error_count
|
|
||||||
FROM t_error_logs
|
|
||||||
WHERE created_at >= %s
|
|
||||||
"""
|
|
||||||
error_count_result = self.execute_query(error_count_sql, (start_date_str,), fetch_one=True)
|
|
||||||
error_count = error_count_result['error_count'] if error_count_result else 0
|
|
||||||
|
|
||||||
# 6. 平均响应时间
|
|
||||||
avg_response_time_sql = """
|
|
||||||
SELECT AVG(avg_process_time) as avg_response_time
|
|
||||||
FROM t_plugin_stats
|
|
||||||
WHERE stat_date >= %s
|
|
||||||
"""
|
|
||||||
avg_response_time_result = self.execute_query(avg_response_time_sql, (start_date_str,), fetch_one=True)
|
|
||||||
avg_response_time = avg_response_time_result['avg_response_time'] if avg_response_time_result and avg_response_time_result['avg_response_time'] else 0
|
|
||||||
|
|
||||||
# 7. 最常用的插件
|
|
||||||
top_plugins_sql = """
|
|
||||||
SELECT plugin_name, SUM(total_calls) as total_calls
|
|
||||||
FROM t_plugin_stats
|
|
||||||
WHERE stat_date >= %s
|
|
||||||
GROUP BY plugin_name
|
|
||||||
ORDER BY total_calls DESC
|
|
||||||
LIMIT 5
|
|
||||||
"""
|
|
||||||
top_plugins = self.execute_query(top_plugins_sql, (start_date_str,)) or []
|
|
||||||
|
|
||||||
# 8. 最活跃的用户
|
|
||||||
top_users_sql = """
|
|
||||||
SELECT user_id, SUM(total_calls) as total_calls
|
|
||||||
FROM t_user_stats
|
|
||||||
WHERE last_used_at >= %s
|
|
||||||
GROUP BY user_id
|
|
||||||
ORDER BY total_calls DESC
|
|
||||||
LIMIT 5
|
|
||||||
"""
|
|
||||||
top_users = self.execute_query(top_users_sql, (start_date_str,)) or []
|
|
||||||
|
|
||||||
# 9. 最活跃的群组
|
|
||||||
top_groups_sql = """
|
|
||||||
SELECT group_id, SUM(total_calls) as total_calls
|
|
||||||
FROM t_group_stats
|
|
||||||
WHERE last_used_at >= %s
|
|
||||||
GROUP BY group_id
|
|
||||||
ORDER BY total_calls DESC
|
|
||||||
LIMIT 5
|
|
||||||
"""
|
|
||||||
top_groups = self.execute_query(top_groups_sql, (start_date_str,)) or []
|
|
||||||
|
|
||||||
# 返回汇总数据
|
|
||||||
return {
|
|
||||||
"total_calls": total_calls,
|
|
||||||
"success_rate": success_rate,
|
|
||||||
"active_users": active_users,
|
|
||||||
"active_groups": active_groups,
|
|
||||||
"error_count": error_count,
|
|
||||||
"avg_response_time": avg_response_time,
|
|
||||||
"top_plugins": top_plugins,
|
|
||||||
"top_users": top_users,
|
|
||||||
"top_groups": top_groups
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_plugin_trend(self, plugin_name: str = "", days: int = 7) -> List[Dict]:
|
|
||||||
"""获取插件使用趋势数据
|
|
||||||
|
|
||||||
Args:
|
|
||||||
plugin_name: 插件名称,为空则获取所有插件
|
|
||||||
days: 统计天数
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
插件使用趋势数据
|
|
||||||
"""
|
|
||||||
# 获取时间范围内的每一天
|
|
||||||
end_date = datetime.now().date()
|
|
||||||
start_date = end_date - timedelta(days=days-1) # 包含今天,所以减1
|
|
||||||
|
|
||||||
if plugin_name:
|
|
||||||
# 获取特定插件的趋势
|
|
||||||
sql = """
|
|
||||||
SELECT stat_date, SUM(total_calls) as total_calls,
|
|
||||||
SUM(success_calls) as success_calls,
|
|
||||||
SUM(failed_calls) as failed_calls
|
|
||||||
FROM t_plugin_stats
|
|
||||||
WHERE plugin_name = %s AND stat_date >= %s
|
|
||||||
GROUP BY stat_date
|
|
||||||
ORDER BY stat_date
|
|
||||||
"""
|
"""
|
||||||
params = (plugin_name, start_date)
|
total_calls_result = self.db_manager.execute_query(total_calls_query, (start_date, end_date))
|
||||||
else:
|
total_calls = total_calls_result[0]['total_calls'] if total_calls_result else 0
|
||||||
# 获取所有插件的趋势
|
|
||||||
sql = """
|
# 获取成功率
|
||||||
SELECT stat_date, SUM(total_calls) as total_calls,
|
success_rate_query = """
|
||||||
SUM(success_calls) as success_calls,
|
SELECT
|
||||||
SUM(failed_calls) as failed_calls
|
ROUND((SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) * 100.0 / COUNT(*)), 2) as success_rate
|
||||||
FROM t_plugin_stats
|
FROM plugin_calls
|
||||||
WHERE stat_date >= %s
|
WHERE created_at >= ? AND created_at <= ?
|
||||||
GROUP BY stat_date
|
|
||||||
ORDER BY stat_date
|
|
||||||
"""
|
"""
|
||||||
params = (start_date,)
|
success_rate_result = self.db_manager.execute_query(success_rate_query, (start_date, end_date))
|
||||||
|
success_rate = success_rate_result[0]['success_rate'] if success_rate_result and success_rate_result[0]['success_rate'] is not None else 0
|
||||||
|
|
||||||
results = self.execute_query(sql, params) or []
|
# 获取活跃用户数
|
||||||
|
active_users_query = """
|
||||||
|
SELECT COUNT(DISTINCT user_id) as active_users FROM plugin_calls
|
||||||
|
WHERE created_at >= ? AND created_at <= ?
|
||||||
|
"""
|
||||||
|
active_users_result = self.db_manager.execute_query(active_users_query, (start_date, end_date))
|
||||||
|
active_users = active_users_result[0]['active_users'] if active_users_result else 0
|
||||||
|
|
||||||
# 将结果转换为按日期的字典
|
# 获取活跃群组数
|
||||||
trend_by_date = {r['stat_date'].strftime('%Y-%m-%d'): r for r in results}
|
active_groups_query = """
|
||||||
|
SELECT COUNT(DISTINCT group_id) as active_groups FROM plugin_calls
|
||||||
|
WHERE created_at >= ? AND created_at <= ? AND group_id != ''
|
||||||
|
"""
|
||||||
|
active_groups_result = self.db_manager.execute_query(active_groups_query, (start_date, end_date))
|
||||||
|
active_groups = active_groups_result[0]['active_groups'] if active_groups_result else 0
|
||||||
|
|
||||||
# 确保每一天都有数据
|
return {
|
||||||
trend_data = []
|
"total_calls": total_calls,
|
||||||
current_date = start_date
|
"success_rate": success_rate,
|
||||||
while current_date <= end_date:
|
"active_users": active_users,
|
||||||
date_str = current_date.strftime('%Y-%m-%d')
|
"active_groups": active_groups
|
||||||
if date_str in trend_by_date:
|
}
|
||||||
data = trend_by_date[date_str]
|
except Exception as e:
|
||||||
trend_data.append({
|
logging.error(f"获取仪表盘摘要数据出错: {e}")
|
||||||
'date': date_str,
|
return {
|
||||||
'total_calls': data['total_calls'],
|
"total_calls": 0,
|
||||||
'success_calls': data['success_calls'],
|
"success_rate": 0,
|
||||||
'failed_calls': data['failed_calls'],
|
"active_users": 0,
|
||||||
'success_rate': (data['success_calls'] / data['total_calls'] * 100) if data['total_calls'] > 0 else 0
|
"active_groups": 0
|
||||||
})
|
}
|
||||||
else:
|
|
||||||
trend_data.append({
|
|
||||||
'date': date_str,
|
|
||||||
'total_calls': 0,
|
|
||||||
'success_calls': 0,
|
|
||||||
'failed_calls': 0,
|
|
||||||
'success_rate': 0
|
|
||||||
})
|
|
||||||
current_date += timedelta(days=1)
|
|
||||||
|
|
||||||
return trend_data
|
def get_plugin_trend(self, plugin_name: str = '', days: int = 7) -> List[Dict[str, Any]]:
|
||||||
|
"""获取插件调用趋势数据"""
|
||||||
|
try:
|
||||||
|
# 计算日期范围
|
||||||
|
end_date = datetime.now()
|
||||||
|
start_date = end_date - timedelta(days=days)
|
||||||
|
|
||||||
|
# 构建查询条件
|
||||||
|
params = [start_date, end_date]
|
||||||
|
plugin_condition = ""
|
||||||
|
if plugin_name:
|
||||||
|
plugin_condition = "AND plugin_name = ?"
|
||||||
|
params.append(plugin_name)
|
||||||
|
|
||||||
|
# 按日期分组查询
|
||||||
|
query = f"""
|
||||||
|
SELECT
|
||||||
|
date(created_at) as date,
|
||||||
|
COUNT(*) as call_count,
|
||||||
|
SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as success_count
|
||||||
|
FROM plugin_calls
|
||||||
|
WHERE created_at >= ? AND created_at <= ? {plugin_condition}
|
||||||
|
GROUP BY date(created_at)
|
||||||
|
ORDER BY date
|
||||||
|
"""
|
||||||
|
|
||||||
|
results = self.db_manager.execute_query(query, tuple(params))
|
||||||
|
|
||||||
|
# 确保每一天都有数据
|
||||||
|
date_format = "%Y-%m-%d"
|
||||||
|
trend_data = []
|
||||||
|
|
||||||
|
# 创建日期范围内的所有日期
|
||||||
|
current_date = start_date
|
||||||
|
while current_date <= end_date:
|
||||||
|
date_str = current_date.strftime(date_format)
|
||||||
|
|
||||||
|
# 查找当前日期的数据
|
||||||
|
day_data = next((item for item in results if item['date'] == date_str), None)
|
||||||
|
|
||||||
|
if day_data:
|
||||||
|
trend_data.append({
|
||||||
|
"date": date_str,
|
||||||
|
"call_count": day_data['call_count'],
|
||||||
|
"success_count": day_data['success_count'],
|
||||||
|
"success_rate": round(day_data['success_count'] * 100 / day_data['call_count'], 2) if day_data['call_count'] > 0 else 0
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
trend_data.append({
|
||||||
|
"date": date_str,
|
||||||
|
"call_count": 0,
|
||||||
|
"success_count": 0,
|
||||||
|
"success_rate": 0
|
||||||
|
})
|
||||||
|
|
||||||
|
current_date += timedelta(days=1)
|
||||||
|
|
||||||
|
return trend_data
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"获取插件趋势数据出错: {e}")
|
||||||
|
return []
|
||||||
@@ -112,6 +112,10 @@ class DashboardServer:
|
|||||||
trend = self.stats_db.get_plugin_trend(plugin_name, days)
|
trend = self.stats_db.get_plugin_trend(plugin_name, days)
|
||||||
return jsonify({"success": True, "data": trend})
|
return jsonify({"success": True, "data": trend})
|
||||||
|
|
||||||
|
@app.route('/errors')
|
||||||
|
def errors():
|
||||||
|
return render_template('errors.html')
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
|
|||||||
@@ -82,25 +82,24 @@
|
|||||||
background-color="#545c64"
|
background-color="#545c64"
|
||||||
text-color="#fff"
|
text-color="#fff"
|
||||||
active-text-color="#ffd04b"
|
active-text-color="#ffd04b"
|
||||||
@select="handleSelect"
|
@select="handleSelect">
|
||||||
router>
|
<el-menu-item index="/">
|
||||||
<el-menu-item index="1" route="/">
|
|
||||||
<i class="el-icon-s-home"></i>
|
<i class="el-icon-s-home"></i>
|
||||||
<span slot="title">首页概览</span>
|
<span slot="title">首页概览</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
<el-menu-item index="2" route="/plugins">
|
<el-menu-item index="/plugins">
|
||||||
<i class="el-icon-s-grid"></i>
|
<i class="el-icon-s-data"></i>
|
||||||
<span slot="title">插件统计</span>
|
<span slot="title">插件统计</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
<el-menu-item index="3" route="/users">
|
<el-menu-item index="/users">
|
||||||
<i class="el-icon-user"></i>
|
<i class="el-icon-user"></i>
|
||||||
<span slot="title">用户统计</span>
|
<span slot="title">用户统计</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
<el-menu-item index="4" route="/groups">
|
<el-menu-item index="/groups">
|
||||||
<i class="el-icon-s-cooperation"></i>
|
<i class="el-icon-s-cooperation"></i>
|
||||||
<span slot="title">群组统计</span>
|
<span slot="title">群组统计</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
<el-menu-item index="5" route="/errors">
|
<el-menu-item index="/errors">
|
||||||
<i class="el-icon-warning"></i>
|
<i class="el-icon-warning"></i>
|
||||||
<span slot="title">错误日志</span>
|
<span slot="title">错误日志</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
|
|||||||
@@ -232,49 +232,102 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 准备数据
|
// 准备数据
|
||||||
const labels = trendData.map(item => item.stat_date);
|
const dates = trendData.map(item => item.date);
|
||||||
const totalData = trendData.map(item => item.total_calls);
|
const callCounts = trendData.map(item => item.call_count);
|
||||||
const successData = trendData.map(item => item.success_calls);
|
const successRates = trendData.map(item => item.success_rate);
|
||||||
const errorData = trendData.map(item => item.error_calls);
|
|
||||||
|
|
||||||
// 创建新图表
|
// 创建图表
|
||||||
this.charts.trendChart = new Chart(ctx, {
|
this.charts.trendChart = new Chart(ctx, {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: {
|
data: {
|
||||||
labels: labels,
|
labels: dates,
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: '总调用',
|
label: '调用次数',
|
||||||
data: totalData,
|
data: callCounts,
|
||||||
fill: false,
|
backgroundColor: 'rgba(54, 162, 235, 0.2)',
|
||||||
backgroundColor: 'rgba(54, 162, 235, 0.6)',
|
|
||||||
borderColor: 'rgba(54, 162, 235, 1)',
|
borderColor: 'rgba(54, 162, 235, 1)',
|
||||||
tension: 0.1
|
borderWidth: 1,
|
||||||
|
yAxisID: 'y-axis-1'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '成功调用',
|
label: '成功率(%)',
|
||||||
data: successData,
|
data: successRates,
|
||||||
fill: false,
|
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
||||||
backgroundColor: 'rgba(75, 192, 192, 0.6)',
|
|
||||||
borderColor: 'rgba(75, 192, 192, 1)',
|
|
||||||
tension: 0.1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '失败调用',
|
|
||||||
data: errorData,
|
|
||||||
fill: false,
|
|
||||||
backgroundColor: 'rgba(255, 99, 132, 0.6)',
|
|
||||||
borderColor: 'rgba(255, 99, 132, 1)',
|
borderColor: 'rgba(255, 99, 132, 1)',
|
||||||
tension: 0.1
|
borderWidth: 1,
|
||||||
|
yAxisID: 'y-axis-2'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
scales: {
|
scales: {
|
||||||
y: {
|
yAxes: [
|
||||||
beginAtZero: true
|
{
|
||||||
}
|
id: 'y-axis-1',
|
||||||
|
type: 'linear',
|
||||||
|
position: 'left',
|
||||||
|
ticks: {
|
||||||
|
beginAtZero: true
|
||||||
|
},
|
||||||
|
scaleLabel: {
|
||||||
|
display: true,
|
||||||
|
labelString: '调用次数'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'y-axis-2',
|
||||||
|
type: 'linear',
|
||||||
|
position: 'right',
|
||||||
|
ticks: {
|
||||||
|
beginAtZero: true,
|
||||||
|
max: 100
|
||||||
|
},
|
||||||
|
scaleLabel: {
|
||||||
|
display: true,
|
||||||
|
labelString: '成功率(%)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
renderSuccessRateChart() {
|
||||||
|
const ctx = document.getElementById('successRateChart').getContext('2d');
|
||||||
|
|
||||||
|
// 销毁旧图表
|
||||||
|
if (this.charts.successRateChart) {
|
||||||
|
this.charts.successRateChart.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 准备数据
|
||||||
|
const plugins = this.pluginStats.map(item => item.plugin_name);
|
||||||
|
const successRates = this.pluginStats.map(item => item.success_rate);
|
||||||
|
|
||||||
|
// 创建图表
|
||||||
|
this.charts.successRateChart = new Chart(ctx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: plugins,
|
||||||
|
datasets: [{
|
||||||
|
label: '成功率(%)',
|
||||||
|
data: successRates,
|
||||||
|
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
||||||
|
borderColor: 'rgba(75, 192, 192, 1)',
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
scales: {
|
||||||
|
yAxes: [{
|
||||||
|
ticks: {
|
||||||
|
beginAtZero: true,
|
||||||
|
max: 100
|
||||||
|
}
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user