chore: 重写初始提交(清空历史,整理后全量提交)
This commit is contained in:
@@ -0,0 +1,394 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user