Files
YwxAppThink/app/backend/view/addon_monitor/index.html
T

394 lines
17 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>插件性能监控</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/assets/layui/css/layui.css">
<style>
body { background: #f2f3f5; padding: 15px; }
.stat-card {
background: #fff;
border-radius: 4px;
padding: 20px;
margin-bottom: 15px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.stat-card .value {
font-size: 28px;
font-weight: bold;
color: #1e9fff;
margin: 10px 0;
}
.stat-card .label {
color: #666;
font-size: 14px;
}
.stat-card.warning .value { color: #ffb800; }
.stat-card.danger .value { color: #ff5722; }
.stat-card.success .value { color: #5fb878; }
.chart-container {
background: #fff;
border-radius: 4px;
padding: 20px;
margin-bottom: 15px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
min-height: 400px;
}
.addon-list-item {
background: #fff;
border-radius: 4px;
padding: 15px;
margin-bottom: 10px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.addon-status {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 8px;
}
.addon-status.healthy { background: #5fb878; }
.addon-status.warning { background: #ffb800; }
.addon-status.danger { background: #ff5722; }
.performance-bar {
height: 8px;
background: #f0f0f0;
border-radius: 4px;
overflow: hidden;
margin-top: 5px;
}
.performance-bar-fill {
height: 100%;
background: #1e9fff;
transition: width 0.3s;
}
.performance-bar-fill.warning { background: #ffb800; }
.performance-bar-fill.danger { background: #ff5722; }
</style>
</head>
<body>
<div class="layui-fluid">
<!-- 统计卡片 -->
<div class="layui-row layui-col-space15">
<div class="layui-col-md3">
<div class="stat-card">
<div class="label">总插件数</div>
<div class="value" id="totaladdon">-</div>
</div>
</div>
<div class="layui-col-md3">
<div class="stat-card success">
<div class="label">健康插件</div>
<div class="value" id="healthyaddon">-</div>
</div>
</div>
<div class="layui-col-md3">
<div class="stat-card warning">
<div class="label">问题插件</div>
<div class="value" id="problematicaddon">-</div>
</div>
</div>
<div class="layui-col-md3">
<div class="stat-card">
<div class="label">平均执行时间</div>
<div class="value"><span id="avgExecutionTime">-</span> ms</div>
</div>
</div>
</div>
<!-- 性能图表 -->
<div class="layui-row layui-col-space15" style="margin-top: 15px;">
<div class="layui-col-md6">
<div class="chart-container">
<div style="margin-bottom: 15px;">
<button class="layui-btn layui-btn-sm layui-btn-primary" data-type="execution_time">执行时间</button>
<button class="layui-btn layui-btn-sm layui-btn-primary" data-type="memory_usage">内存使用</button>
<button class="layui-btn layui-btn-sm layui-btn-primary" data-type="success_rate">成功率</button>
<button class="layui-btn layui-btn-sm layui-btn-primary" data-type="call_count">调用次数</button>
</div>
<div id="performanceChart" style="height: 350px;"></div>
</div>
</div>
<div class="layui-col-md6">
<div class="chart-container">
<div style="margin-bottom: 15px;">
<span style="font-weight: bold;">插件性能列表</span>
<button class="layui-btn layui-btn-sm layui-btn-normal" id="refreshBtn" style="float: right;">
<i class="layui-icon layui-icon-refresh"></i> 刷新
</button>
</div>
<div id="addonList" style="height: 350px; overflow-y: auto;">
<div style="text-align: center; color: #999; padding-top: 100px;">
加载中...
</div>
</div>
</div>
</div>
</div>
<!-- 热重载统计 -->
<div class="layui-row layui-col-space15" style="margin-top: 15px;">
<div class="layui-col-md12">
<div class="stat-card">
<div style="margin-bottom: 15px;">
<span style="font-weight: bold;">热重载统计</span>
<button class="layui-btn layui-btn-sm layui-btn-danger" id="clearPerfBtn" style="float: right;">
<i class="layui-icon layui-icon-delete"></i> 清除性能数据
</button>
</div>
<div id="reloadStats">
<div style="text-align: center; color: #999; padding: 20px;">
加载中...
</div>
</div>
</div>
</div>
</div>
</div>
<script src="/assets/layui/layui.js"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<script>
layui.use(['jquery', 'layer', 'http'], function() {
var $ = layui.jquery;
var layer = layui.layer;
var http = layui.http;
var performanceChart = null;
var currentChartType = 'execution_time';
// 初始化图表
function initChart() {
performanceChart = echarts.init(document.getElementById('performanceChart'));
loadChartData(currentChartType);
}
// 加载图表数据
function loadChartData(type) {
currentChartType = type;
http.get('/backend/addon_monitor/chart', { type: type }).then(function(res) {
if (res.code === 0) {
updateChart(res.data.chart_data, type);
} else {
layer.msg('加载图表数据失败', {icon: 2});
}
}).catch(function() { layer.msg('请求失败', {icon: 2}); });
}
// 更新图表
function updateChart(data, type) {
var names = data.map(item => item.name);
var values = data.map(item => item.value);
var title = '';
var unit = '';
switch(type) {
case 'execution_time':
title = '执行时间 (ms)';
unit = 'ms';
break;
case 'memory_usage':
title = '内存使用 (KB)';
unit = 'KB';
break;
case 'success_rate':
title = '成功率 (%)';
unit = '%';
break;
case 'call_count':
title = '调用次数';
unit = '次';
break;
}
var option = {
title: {
text: title,
left: 'center'
},
tooltip: {
trigger: 'axis',
formatter: function(params) {
return params[0].name + ': ' + params[0].value + ' ' + unit;
}
},
xAxis: {
type: 'category',
data: names,
axisLabel: {
rotate: 45,
interval: 0
}
},
yAxis: {
type: 'value',
name: unit
},
series: [{
data: values,
type: 'bar',
itemStyle: {
color: '#1e9fff'
}
}]
};
performanceChart.setOption(option);
}
// 加载概览数据
function loadOverview() {
http.get('/backend/addon_monitor', { action: 'overview' }).then(function(res) {
if (res.code === 0) {
var data = res.data;
$('#totaladdon').text(data.total_addon);
$('#healthyaddon').text(data.healthy_addon);
$('#problematicaddon').text(data.problematic_addon);
$('#avgExecutionTime').text(data.avg_execution_time);
// 更新重载统计
updateReloadStats(data.reload_stats);
} else {
layer.msg('加载概览数据失败', {icon: 2});
}
}).catch(function() { layer.msg('请求失败', {icon: 2}); });
}
// 更新重载统计
function updateReloadStats(stats) {
if (!stats || stats.total_reloads === 0) {
$('#reloadStats').html('<div style="text-align: center; color: #999; padding: 20px;">暂无重载数据</div>');
return;
}
var html = '<div class="layui-row">';
html += '<div class="layui-col-md3"><div class="label">总重载次数</div><div class="value">' + stats.total_reloads + '</div></div>';
html += '<div class="layui-col-md3"><div class="label">成功次数</div><div class="value success">' + stats.successful_reloads + '</div></div>';
html += '<div class="layui-col-md3"><div class="label">失败次数</div><div class="value danger">' + stats.failed_reloads + '</div></div>';
html += '<div class="layui-col-md3"><div class="label">成功率</div><div class="value">' + (stats.total_reloads > 0 ? ((stats.successful_reloads / stats.total_reloads) * 100).toFixed(2) : 0) + '%</div></div>';
html += '</div>';
if (stats.addon && Object.keys(stats.addon).length > 0) {
html += '<div style="margin-top: 20px;"><strong>各插件重载状态:</strong></div>';
html += '<div style="margin-top: 10px;">';
for (var addon in stats.addon) {
var status = stats.addon[addon];
var statusClass = status.status === 'success' ? 'success' : 'danger';
var statusText = status.status === 'success' ? '成功' : '失败';
var lastReload = status.reload_time ? new Date(status.reload_time * 1000).toLocaleString() : '未重载';
html += '<div style="padding: 8px 0; border-bottom: 1px solid #f0f0f0;">';
html += '<span class="layui-badge layui-bg-' + statusClass + '">' + addon + '</span>';
html += ' <span style="color: #666;">' + lastReload + '</span>';
html += ' <span style="color: ' + (status.status === 'success' ? '#5fb878' : '#ff5722') + ';">' + statusText + '</span>';
html += '</div>';
}
html += '</div>';
}
$('#reloadStats').html(html);
}
// 加载插件列表
function loadAddonList() {
http.get('/backend/addon_monitor', { action: 'performance' }).then(function(res) {
if (res.code === 0) {
renderAddonList(res.data);
} else {
layer.msg('加载插件列表失败', {icon: 2});
}
}).catch(function() { layer.msg('请求失败', {icon: 2}); });
}
// 渲染插件列表
function renderAddonList(addon) {
if (!addon || Object.keys(addon).length === 0) {
$('#addonList').html('<div style="text-align: center; color: #999; padding-top: 100px;">暂无插件数据</div>');
return;
}
var html = '';
for (var addon in addon) {
var stats = addon[addon];
var statusClass = 'healthy';
var statusText = '健康';
if (stats.success_rate < 90 || stats.avg_execution_time > 1.0) {
statusClass = 'danger';
statusText = '异常';
} else if (stats.avg_execution_time > 0.5 || stats.success_rate < 95) {
statusClass = 'warning';
statusText = '警告';
}
var execTimePercent = Math.min((stats.avg_execution_time / 2.0) * 100, 100);
var execTimeClass = stats.avg_execution_time > 1.0 ? 'danger' : (stats.avg_execution_time > 0.5 ? 'warning' : '');
html += '<div class="addon-list-item">';
html += '<div style="display: flex; justify-content: space-between; align-items: center;">';
html += '<div><span class="addon-status ' + statusClass + '"></span><strong>' + addon + '</strong></div>';
html += '<span style="font-size: 12px; color: #666;">' + statusText + '</span>';
html += '</div>';
html += '<div style="margin-top: 10px; font-size: 12px; color: #666;">';
html += '<div>调用次数: ' + stats.total_calls + ' | 成功率: ' + stats.success_rate.toFixed(2) + '%</div>';
html += '<div style="margin-top: 5px;">平均执行时间: ' + (stats.avg_execution_time * 1000).toFixed(2) + ' ms</div>';
html += '<div class="performance-bar">';
html += '<div class="performance-bar-fill ' + execTimeClass + '" style="width: ' + execTimePercent + '%;"></div>';
html += '</div>';
html += '</div>';
html += '</div>';
}
$('#addonList').html(html);
}
// 绑定事件
$('button[data-type]').on('click', function() {
var type = $(this).data('type');
$('button[data-type]').removeClass('layui-btn-normal').addClass('layui-btn-primary');
$(this).removeClass('layui-btn-primary').addClass('layui-btn-normal');
loadChartData(type);
});
$('#refreshBtn').on('click', function() {
loadOverview();
loadAddonList();
loadChartData(currentChartType);
layer.msg('数据已刷新', {icon: 1});
});
$('#clearPerfBtn').on('click', function() {
layer.confirm('确定要清除所有性能数据吗?', function(index) {
http.get('/backend/addon_monitor', { action: 'clear_performance' }).then(function(res) {
layer.close(index);
if (res.code === 0) {
layer.msg('性能数据已清除', {icon: 1});
loadOverview();
loadAddonList();
} else {
layer.msg('清除失败', {icon: 2});
}
}).catch(function() { layer.msg('请求失败', {icon: 2}); });
});
});
// 初始化
initChart();
loadOverview();
loadAddonList();
// 定时刷新
setInterval(function() {
loadOverview();
}, 30000); // 30秒刷新一次
});
</script>
</body>
</html>