subscription/assets/js/admin-logs.js

501 lines
15 KiB
JavaScript
Raw Permalink 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.

/**
* 日志管理界面JavaScript
*
* 处理日志页面的交互功能
*/
(function($) {
'use strict';
/**
* 日志管理器
*/
var LogManager = {
/**
* 初始化
*/
init: function() {
this.bindEvents();
this.setupAutoRefresh();
},
/**
* 绑定事件
*/
bindEvents: function() {
// 刷新日志
$('#refresh-logs').on('click', this.refreshLogs.bind(this));
// 清空日志
$('#clear-logs').on('click', this.clearLogs.bind(this));
// 下载日志
$('#download-logs').on('click', this.downloadLogs.bind(this));
// 日志行点击展开详情
$(document).on('click', '.log-table tbody tr', this.toggleLogDetails.bind(this));
// 实时搜索
$('input[name="search"]').on('input', this.debounce(this.performSearch.bind(this), 500));
},
/**
* 刷新日志
*/
refreshLogs: function() {
location.reload();
},
/**
* 清空日志
*/
clearLogs: function() {
if (!confirm(yoone_logs_params.i18n.confirm_clear)) {
return;
}
var $button = $('#clear-logs');
var originalText = $button.text();
$button.text(yoone_logs_params.i18n.clearing).prop('disabled', true);
$.ajax({
url: yoone_logs_params.ajax_url,
type: 'POST',
data: {
action: 'yoone_clear_logs',
nonce: yoone_logs_params.nonce
},
success: function(response) {
if (response.success) {
location.reload();
} else {
alert(response.data.message || '清空失败');
}
},
error: function() {
alert('请求失败,请重试');
},
complete: function() {
$button.text(originalText).prop('disabled', false);
}
});
},
/**
* 下载日志
*/
downloadLogs: function() {
var $button = $('#download-logs');
var originalText = $button.text();
$button.text(yoone_logs_params.i18n.downloading).prop('disabled', true);
// 创建隐藏的下载链接
var downloadUrl = yoone_logs_params.ajax_url + '?action=yoone_download_logs&nonce=' + yoone_logs_params.nonce;
var $link = $('<a>').attr({
href: downloadUrl,
download: 'yoone-subscriptions-' + new Date().toISOString().split('T')[0] + '.log'
}).appendTo('body');
$link[0].click();
$link.remove();
setTimeout(function() {
$button.text(originalText).prop('disabled', false);
}, 2000);
},
/**
* 切换日志详情显示
*/
toggleLogDetails: function(e) {
var $row = $(e.currentTarget);
var $detailRow = $row.next('.log-detail-row');
if ($detailRow.length) {
$detailRow.toggle();
return;
}
// 创建详情行
var context = $row.find('.log-context').text();
if (!context) {
return;
}
var $newDetailRow = $('<tr class="log-detail-row">').html(
'<td colspan="4"><div class="log-detail">' +
'<h4>详细信息:</h4>' +
'<pre>' + this.formatJSON(context) + '</pre>' +
'</div></td>'
);
$row.after($newDetailRow);
},
/**
* 格式化JSON
*/
formatJSON: function(jsonString) {
try {
var obj = JSON.parse(jsonString);
return JSON.stringify(obj, null, 2);
} catch (e) {
return jsonString;
}
},
/**
* 执行搜索
*/
performSearch: function() {
var searchTerm = $('input[name="search"]').val();
var $rows = $('.log-table tbody tr:not(.log-detail-row)');
if (!searchTerm) {
$rows.show();
return;
}
$rows.each(function() {
var $row = $(this);
var text = $row.text().toLowerCase();
var match = text.indexOf(searchTerm.toLowerCase()) !== -1;
$row.toggle(match);
// 隐藏对应的详情行
var $detailRow = $row.next('.log-detail-row');
if ($detailRow.length) {
$detailRow.toggle(match);
}
});
},
/**
* 设置自动刷新
*/
setupAutoRefresh: function() {
// 每30秒自动刷新一次仅在错误标签页
if (this.getCurrentTab() === 'error') {
setInterval(function() {
if (document.visibilityState === 'visible') {
location.reload();
}
}, 30000);
}
},
/**
* 获取当前标签页
*/
getCurrentTab: function() {
var urlParams = new URLSearchParams(window.location.search);
return urlParams.get('tab') || 'recent';
},
/**
* 防抖函数
*/
debounce: function(func, wait) {
var timeout;
return function executedFunction() {
var context = this;
var args = arguments;
var later = function() {
timeout = null;
func.apply(context, args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
};
/**
* 日志统计图表
*/
var LogChart = {
/**
* 初始化图表
*/
init: function() {
this.createLevelChart();
this.createTimelineChart();
},
/**
* 创建级别分布图表
*/
createLevelChart: function() {
var $container = $('#log-level-chart');
if (!$container.length) {
return;
}
// 统计各级别日志数量
var levels = {
error: 0,
warning: 0,
info: 0,
debug: 0
};
$('.log-level').each(function() {
var level = $(this).text().toLowerCase();
if (levels.hasOwnProperty(level)) {
levels[level]++;
}
});
// 创建简单的条形图
var total = Object.values(levels).reduce((a, b) => a + b, 0);
var html = '<div class="log-chart-title">日志级别分布</div>';
Object.keys(levels).forEach(function(level) {
var count = levels[level];
var percentage = total > 0 ? (count / total * 100).toFixed(1) : 0;
html += '<div class="chart-bar">' +
'<span class="bar-label">' + level + '</span>' +
'<div class="bar-container">' +
'<div class="bar-fill ' + level + '" style="width: ' + percentage + '%"></div>' +
'</div>' +
'<span class="bar-value">' + count + ' (' + percentage + '%)</span>' +
'</div>';
});
$container.html(html);
},
/**
* 创建时间线图表
*/
createTimelineChart: function() {
var $container = $('#log-timeline-chart');
if (!$container.length) {
return;
}
// 按小时统计日志数量
var hourlyStats = {};
var now = new Date();
// 初始化最近24小时
for (var i = 23; i >= 0; i--) {
var hour = new Date(now.getTime() - i * 60 * 60 * 1000);
var key = hour.getHours().toString().padStart(2, '0') + ':00';
hourlyStats[key] = 0;
}
// 统计实际日志
$('.log-table tbody tr:not(.log-detail-row)').each(function() {
var timestamp = $(this).find('td:first').text();
var date = new Date(timestamp);
var hour = date.getHours().toString().padStart(2, '0') + ':00';
if (hourlyStats.hasOwnProperty(hour)) {
hourlyStats[hour]++;
}
});
// 创建时间线图表
var maxCount = Math.max(...Object.values(hourlyStats));
var html = '<div class="log-chart-title">24小时日志趋势</div><div class="timeline-chart">';
Object.keys(hourlyStats).forEach(function(hour) {
var count = hourlyStats[hour];
var height = maxCount > 0 ? (count / maxCount * 100) : 0;
html += '<div class="timeline-bar" title="' + hour + ': ' + count + ' 条日志">' +
'<div class="timeline-fill" style="height: ' + height + '%"></div>' +
'<span class="timeline-label">' + hour + '</span>' +
'</div>';
});
html += '</div>';
$container.html(html);
}
};
/**
* 日志导出功能
*/
var LogExporter = {
/**
* 导出为CSV
*/
exportCSV: function() {
var csv = 'Timestamp,Level,Message,Context\n';
$('.log-table tbody tr:not(.log-detail-row)').each(function() {
var $row = $(this);
var cells = $row.find('td').map(function() {
return '"' + $(this).text().replace(/"/g, '""') + '"';
}).get();
csv += cells.join(',') + '\n';
});
this.downloadFile(csv, 'yoone-logs-' + new Date().toISOString().split('T')[0] + '.csv', 'text/csv');
},
/**
* 导出为JSON
*/
exportJSON: function() {
var logs = [];
$('.log-table tbody tr:not(.log-detail-row)').each(function() {
var $row = $(this);
var $cells = $row.find('td');
logs.push({
timestamp: $cells.eq(0).text(),
level: $cells.eq(1).text(),
message: $cells.eq(2).text(),
context: $cells.eq(3).text()
});
});
var json = JSON.stringify(logs, null, 2);
this.downloadFile(json, 'yoone-logs-' + new Date().toISOString().split('T')[0] + '.json', 'application/json');
},
/**
* 下载文件
*/
downloadFile: function(content, filename, contentType) {
var blob = new Blob([content], { type: contentType });
var url = window.URL.createObjectURL(blob);
var $link = $('<a>').attr({
href: url,
download: filename
}).appendTo('body');
$link[0].click();
$link.remove();
window.URL.revokeObjectURL(url);
}
};
// 文档就绪时初始化
$(document).ready(function() {
LogManager.init();
LogChart.init();
// 添加导出按钮事件
$(document).on('click', '#export-csv', LogExporter.exportCSV.bind(LogExporter));
$(document).on('click', '#export-json', LogExporter.exportJSON.bind(LogExporter));
});
// 添加样式
$('<style>').text(`
.log-detail {
background: #f9f9f9;
padding: 15px;
border-radius: 3px;
margin: 10px 0;
}
.log-detail h4 {
margin: 0 0 10px 0;
color: #333;
}
.log-detail pre {
background: #fff;
padding: 10px;
border: 1px solid #ddd;
border-radius: 3px;
overflow-x: auto;
font-size: 12px;
line-height: 1.4;
}
.log-table tbody tr:hover {
background-color: #f5f5f5;
cursor: pointer;
}
.log-chart-title {
font-weight: bold;
margin-bottom: 15px;
color: #333;
}
.chart-bar {
display: flex;
align-items: center;
margin-bottom: 8px;
}
.bar-label {
width: 80px;
font-size: 12px;
text-transform: uppercase;
}
.bar-container {
flex: 1;
height: 20px;
background: #f0f0f0;
border-radius: 10px;
margin: 0 10px;
overflow: hidden;
}
.bar-fill {
height: 100%;
border-radius: 10px;
transition: width 0.3s ease;
}
.bar-fill.error { background: #dc3545; }
.bar-fill.warning { background: #ffc107; }
.bar-fill.info { background: #17a2b8; }
.bar-fill.debug { background: #6c757d; }
.bar-value {
font-size: 12px;
color: #666;
min-width: 80px;
}
.timeline-chart {
display: flex;
align-items: end;
height: 100px;
gap: 2px;
padding: 10px 0;
}
.timeline-bar {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
}
.timeline-fill {
width: 100%;
background: #007cba;
border-radius: 2px 2px 0 0;
transition: height 0.3s ease;
min-height: 2px;
}
.timeline-label {
font-size: 10px;
color: #666;
margin-top: 5px;
transform: rotate(-45deg);
}
`).appendTo('head');
})(jQuery);