Files
abot/admin/dashboard/templates/index.html
2025-03-27 12:27:16 +08:00

547 lines
23 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}首页概览 - 机器人管理后台{% endblock %}
{% block content %}
<!-- 首页概览 -->
<div>
<el-row :gutter="20">
<el-col :span="4">
<el-card shadow="hover" class="stats-card">
<div slot="header">
<span>总调用次数</span>
</div>
<div style="font-size: 24px; text-align: center;">
{% raw %}{{ totalCalls }}{% endraw %}
</div>
</el-card>
</el-col>
<el-col :span="4">
<el-card shadow="hover" class="stats-card">
<div slot="header">
<span>成功率</span>
</div>
<div style="font-size: 24px; text-align: center;">
{% raw %}{{ successRate.toFixed(2) }}{% endraw %}%
</div>
</el-card>
</el-col>
<el-col :span="4">
<el-card shadow="hover" class="stats-card">
<div slot="header">
<span>活跃用户数</span>
</div>
<div style="font-size: 24px; text-align: center;">
{% raw %}{{ activeUsers }}{% endraw %}
</div>
</el-card>
</el-col>
<el-col :span="4">
<el-card shadow="hover" class="stats-card">
<div slot="header">
<span>活跃群组数</span>
</div>
<div style="font-size: 24px; text-align: center;">
{% raw %}{{ activeGroups }}{% endraw %}
</div>
</el-card>
</el-col>
<el-col :span="4">
<el-card shadow="hover" class="stats-card">
<div slot="header">
<span>平均响应时间</span>
</div>
<div style="font-size: 24px; text-align: center;">
{% raw %}{{ avgResponseTime.toFixed(2) }}{% endraw %} ms
</div>
</el-card>
</el-col>
</el-row>
<!-- 添加热门用户、群组和插件 -->
<el-row :gutter="20" style="margin-top: 20px;">
<el-col :span="8">
<el-card shadow="hover">
<div slot="header">
<span>热门用户</span>
</div>
<el-table :data="topUsers" style="width: 100%">
<!-- 修改将用户ID改为用户信息使用固定像素宽度 -->
<el-table-column label="用户信息" min-width="180">
<template slot-scope="scope">
{% raw %}{{ scope.row.user_name || scope.row.user_id }} ({{ scope.row.user_id }}){% endraw %}
</template>
</el-table-column>
<el-table-column prop="total_calls" label="调用次数" width="80" align="center"></el-table-column>
</el-table>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="hover">
<div slot="header">
<span>热门群组</span>
</div>
<el-table :data="topGroups" style="width: 100%">
<!-- 修改将群组ID改为群组信息使用固定像素宽度 -->
<el-table-column label="群组信息" min-width="180">
<template slot-scope="scope">
{% raw %}{{ scope.row.group_name || scope.row.group_id }} ({{ scope.row.group_id }}){% endraw %}
</template>
</el-table-column>
<el-table-column prop="total_calls" label="调用次数" width="80" align="center"></el-table-column>
</el-table>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="hover">
<div slot="header">
<span>热门插件</span>
</div>
<el-table :data="topPlugins" style="width: 100%">
<el-table-column prop="plugin_name" label="插件名称" min-width="180"></el-table-column>
<el-table-column prop="total_calls" label="调用次数" width="80" align="center"></el-table-column>
</el-table>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<div class="chart-container">
<h3>插件使用排行</h3>
<canvas id="pluginChart" width="400" height="200"></canvas>
</div>
</el-col>
<el-col :span="12">
<div class="chart-container">
<h3>成功率分析</h3>
<canvas id="successRateChart" width="400" height="200"></canvas>
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<div class="chart-container">
<h3>使用趋势</h3>
<canvas id="trendChart" width="800" height="200"></canvas>
</div>
</el-col>
</el-row>
</div>
<!-- 在适当位置添加系统信息卡片 -->
<div class="col-lg-4">
<div class="card">
<div class="card-header">
<h5 class="card-title">系统状态</h5>
</div>
<div class="card-body">
<!-- 将原来的系统状态卡片替换为以下内容 -->
<el-row :gutter="20" style="margin-top: 20px;">
<el-col :span="24">
<el-card shadow="hover">
<div slot="header">
<span>系统状态</span>
</div>
<el-row :gutter="20">
<el-col :span="8">
<div class="mb-3">
<div class="d-flex justify-content-between mb-1">
<span>操作系统:</span>
<span id="system-os">加载中...</span>
</div>
<div class="d-flex justify-content-between mb-1">
<span>Python版本:</span>
<span id="system-python">加载中...</span>
</div>
<div class="d-flex justify-content-between mb-1">
<span>运行时间:</span>
<span id="system-uptime">加载中...</span>
</div>
</div>
</el-col>
<el-col :span="16">
<div class="mb-3">
<div class="d-flex justify-content-between mb-1">
<span>CPU使用率:</span>
<span id="system-cpu">0%</span>
</div>
<div class="progress mb-2" style="height: 8px;">
<div id="cpu-progress" class="progress-bar bg-success" role="progressbar" style="width: 0%"></div>
</div>
<div class="d-flex justify-content-between mb-1">
<span>内存使用率:</span>
<span id="system-memory">0%</span>
</div>
<div class="progress mb-2" style="height: 8px;">
<div id="memory-progress" class="progress-bar bg-success" role="progressbar" style="width: 0%"></div>
</div>
<div class="d-flex justify-content-between mb-1">
<span>磁盘使用率:</span>
<span id="system-disk">0%</span>
</div>
<div class="progress mb-2" style="height: 8px;">
<div id="disk-progress" class="progress-bar bg-success" role="progressbar" style="width: 0%"></div>
</div>
</div>
</el-col>
</el-row>
<div class="text-muted small text-end" id="system-update-time">最后更新: -</div>
</el-card>
</el-col>
</el-row>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
new Vue({
el: '#app',
mixins: [baseApp],
data() {
return {
totalCalls: 0,
successRate: 0,
activeUsers: 0,
activeGroups: 0,
avgResponseTime: 0, // 确保这里初始化为数字
topUsers: [],
topGroups: [],
topPlugins: [],
pluginStats: [],
charts: {}, // 添加charts对象
// 添加系统信息相关数据
systemInfo: {
os: '加载中...',
os_version: '',
python_version: '加载中...',
cpu_usage: 0,
memory_usage: 0,
disk_usage: 0,
uptime: 0,
timestamp: '-'
}
}
},
mounted() {
this.currentView = '1';
this.loadData();
// 添加加载系统信息
this.loadSystemInfo();
// 设置定时刷新系统信息每30秒
this.systemInfoTimer = setInterval(this.loadSystemInfo, 30000);
},
beforeDestroy() {
// 组件销毁前清除定时器
if (this.systemInfoTimer) {
clearInterval(this.systemInfoTimer);
}
},
methods: {
loadData() {
const days = parseInt(this.timeRange);
this.loadDashboardSummary(days);
this.loadPluginStats(days);
this.loadPluginTrend(days);
},
// 添加加载系统信息的方法
loadSystemInfo() {
axios.get('/api/system_info')
.then(response => {
if (response.data.success) {
this.systemInfo = response.data.data;
this.updateSystemInfoUI();
}
})
.catch(error => {
console.error('加载系统信息出错:', error);
});
},
// 更新系统信息UI
updateSystemInfoUI() {
// 更新系统信息显示
document.getElementById('system-os').textContent =
`${this.systemInfo.os} ${this.systemInfo.os_version}`;
document.getElementById('system-python').textContent =
this.systemInfo.python_version;
document.getElementById('system-uptime').textContent =
this.formatUptime(this.systemInfo.uptime);
// 更新资源使用率
document.getElementById('system-cpu').textContent =
`${this.systemInfo.cpu_usage}%`;
document.getElementById('system-memory').textContent =
`${this.systemInfo.memory_usage}%`;
document.getElementById('system-disk').textContent =
`${this.systemInfo.disk_usage}%`;
// 更新进度条
document.getElementById('cpu-progress').style.width =
`${this.systemInfo.cpu_usage}%`;
document.getElementById('memory-progress').style.width =
`${this.systemInfo.memory_usage}%`;
document.getElementById('disk-progress').style.width =
`${this.systemInfo.disk_usage}%`;
// 设置进度条颜色
this.setProgressColor('cpu-progress', this.systemInfo.cpu_usage);
this.setProgressColor('memory-progress', this.systemInfo.memory_usage);
this.setProgressColor('disk-progress', this.systemInfo.disk_usage);
// 更新时间戳
document.getElementById('system-update-time').textContent =
`最后更新: ${this.systemInfo.timestamp}`;
},
// 设置进度条颜色
setProgressColor(elementId, value) {
const element = document.getElementById(elementId);
element.classList.remove('bg-success', 'bg-warning', 'bg-danger');
if (value > 70) {
element.classList.add('bg-danger'); // 红色 (高负载)
} else if (value > 50) {
element.classList.add('bg-warning'); // 黄色 (中等负载)
} else {
element.classList.add('bg-success'); // 绿色 (低负载)
}
},
// 格式化运行时间
formatUptime(seconds) {
const days = Math.floor(seconds / 86400);
const hours = Math.floor((seconds % 86400) / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
let result = '';
if (days > 0) result += days + '天 ';
if (hours > 0 || days > 0) result += hours + '小时 ';
result += minutes + '分钟';
return result;
},
loadDashboardSummary(days) {
axios.get(`/api/dashboard_summary?days=${days}`)
.then(response => {
if (response.data.success) {
const data = response.data.data;
this.totalCalls = parseInt(data.total_calls) || 0;
this.successRate = parseFloat(data.success_rate) || 0;
this.activeUsers = data.active_users || 0;
this.activeGroups = data.active_groups || 0;
this.avgResponseTime = parseFloat(data.avg_response_time) || 0;
this.topUsers = data.top_users || [];
this.topGroups = data.top_groups || [];
this.topPlugins = data.top_plugins || [];
}
})
.catch(error => {
console.error('加载仪表盘摘要数据出错:', error);
this.$message.error('加载仪表盘摘要数据出错');
});
},
loadPluginStats(days) {
axios.get(`/api/plugin_stats?days=${days}`)
.then(response => {
if (response.data.success) {
this.pluginStats = response.data.data || [];
this.$nextTick(() => {
this.renderPluginChart();
this.renderSuccessRateChart();
});
}
})
.catch(error => {
console.error('加载插件统计数据出错:', error);
this.$message.error('加载插件统计数据出错');
});
},
loadPluginTrend(days) {
axios.get(`/api/plugin_trend?days=${days}`)
.then(response => {
if (response.data.success) {
const trendData = response.data.data || [];
this.$nextTick(() => {
this.renderTrendChart(trendData);
});
}
})
.catch(error => {
console.error('加载插件趋势数据出错:', error);
this.$message.error('加载插件趋势数据出错');
});
},
renderPluginChart() {
const ctx = document.getElementById('pluginChart').getContext('2d');
// 销毁旧图表
if (this.charts.pluginChart) {
this.charts.pluginChart.destroy();
}
// 准备数据
const sortedData = [...this.pluginStats].sort((a, b) => b.total_calls - a.total_calls).slice(0, 10);
// 修改这里:将插件名称和指令组合作为标签
const labels = sortedData.map(item => `${item.plugin_name}(${item.command})`);
const data = sortedData.map(item => item.total_calls);
// 创建新图表
this.charts.pluginChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: '调用次数',
data: data,
backgroundColor: 'rgba(54, 162, 235, 0.6)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true
}
}
}
});
},
renderSuccessRateChart() {
const ctx = document.getElementById('successRateChart').getContext('2d');
// 销毁旧图表
if (this.charts.successRateChart) {
this.charts.successRateChart.destroy();
}
// 准备数据
const sortedData = [...this.pluginStats]
.filter(item => item.total_calls > 0)
.sort((a, b) => (b.success_calls / b.total_calls) - (a.success_calls / a.total_calls))
.slice(0, 10);
// 修改这里:将插件名称和指令组合作为标签
const labels = sortedData.map(item => `${item.plugin_name}(${item.command})`);
const data = sortedData.map(item => (item.success_calls / item.total_calls) * 100);
// 创建新图表
this.charts.successRateChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: '成功率(%)',
data: data,
backgroundColor: 'rgba(75, 192, 192, 0.6)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true,
max: 100
}
}
}
});
},
renderTrendChart(trendData) {
const ctx = document.getElementById('trendChart').getContext('2d');
// 销毁旧图表
if (this.charts && this.charts.trendChart) {
this.charts.trendChart.destroy();
}
// 确保charts对象存在
if (!this.charts) {
this.charts = {};
}
// 准备数据 - 修改字段名匹配和数据类型转换
const labels = trendData.map(item => item.date); // 使用date而不是stat_date
const totalData = trendData.map(item => parseInt(item.total_calls) || 0); // 确保转换为数字
const successData = trendData.map(item => parseInt(item.success_calls) || 0);
const errorData = trendData.map(item => parseInt(item.failed_calls) || 0); // 使用failed_calls而不是error_calls
console.log('处理后的趋势数据:', { labels, totalData, successData, errorData });
// 创建新图表
this.charts.trendChart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: '总调用',
data: totalData,
fill: false,
backgroundColor: 'rgba(54, 162, 235, 0.6)',
borderColor: 'rgba(54, 162, 235, 1)',
tension: 0.1
},
{
label: '成功调用',
data: successData,
fill: false,
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)',
tension: 0.1
}
]
},
options: {
responsive: true,
maintainAspectRatio: true,
scales: {
y: {
beginAtZero: true
}
}
}
});
}
}
});
</script>
{% endblock %}
{% block styles %}
<style>
.stats-card {
margin-bottom: 15px;
height: 120px;
}
.chart-container {
margin-bottom: 20px;
padding: 10px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.chart-container h3 {
margin-top: 0;
margin-bottom: 10px;
font-size: 16px;
color: #606266;
}
</style>
{% endblock %}