// 执行历史页面JavaScript
class HistoryManager {
constructor() {
this.currentTaskPage = 1;
this.currentLogsPage = 1;
this.pageSize = 20;
this.charts = {};
this.searchParams = {
taskType: '',
taskStatus: '',
startDate: '',
endDate: ''
};
this.logSearchParams = {
level: '',
category: '',
keyword: ''
};
this.init();
}
async init() {
console.log('初始化执行历史页面...');
// 绑定事件
this.bindEvents();
// 初始化图表
this.initCharts();
// 加载初始数据
await this.loadTaskHistory();
await this.loadSystemLogs();
await this.loadStatistics();
console.log('执行历史页面初始化完成');
}
bindEvents() {
// 任务历史筛选
document.getElementById('taskFilterForm').addEventListener('submit', (e) => {
e.preventDefault();
this.searchParams.taskType = document.getElementById('taskTypeFilter').value;
this.searchParams.taskStatus = document.getElementById('taskStatusFilter').value;
this.searchParams.startDate = document.getElementById('startDate').value;
this.searchParams.endDate = document.getElementById('endDate').value;
this.currentTaskPage = 1;
this.loadTaskHistory();
});
// 系统日志筛选
document.getElementById('logFilterForm').addEventListener('submit', (e) => {
e.preventDefault();
this.logSearchParams.level = document.getElementById('logLevelFilter').value;
this.logSearchParams.category = document.getElementById('logCategoryFilter').value;
this.logSearchParams.keyword = document.getElementById('logSearchKeyword').value;
this.currentLogsPage = 1;
this.loadSystemLogs();
});
// 选项卡切换事件
document.getElementById('stats-tab').addEventListener('shown.bs.tab', () => {
this.loadStatistics();
this.updateCharts();
});
}
initCharts() {
// 任务趋势图
const taskTrendCtx = document.getElementById('taskTrendChart');
if (taskTrendCtx) {
this.charts.taskTrend = new Chart(taskTrendCtx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: '成功任务',
data: [],
borderColor: '#28a745',
backgroundColor: 'rgba(40, 167, 69, 0.1)',
tension: 0.4
}, {
label: '失败任务',
data: [],
borderColor: '#dc3545',
backgroundColor: 'rgba(220, 53, 69, 0.1)',
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'top',
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
precision: 0
}
}
}
}
});
}
// 日志级别分布图
const logLevelCtx = document.getElementById('logLevelChart');
if (logLevelCtx) {
this.charts.logLevel = new Chart(logLevelCtx, {
type: 'doughnut',
data: {
labels: ['错误', '警告', '信息', '调试'],
datasets: [{
data: [0, 0, 0, 0],
backgroundColor: ['#dc3545', '#ffc107', '#17a2b8', '#6c757d']
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
}
}
}
});
}
}
async loadTaskHistory() {
try {
this.showTaskLoading(true);
// 构建查询参数
const params = new URLSearchParams({
limit: this.pageSize,
offset: (this.currentTaskPage - 1) * this.pageSize
});
if (this.searchParams.taskType) params.append('taskType', this.searchParams.taskType);
if (this.searchParams.taskStatus) params.append('status', this.searchParams.taskStatus);
const response = await fetch(`/api/history?${params}`);
const result = await response.json();
if (result.success) {
this.renderTaskHistoryTable(result.data);
this.renderTaskPagination(result.pagination);
} else {
this.showAlert('加载任务历史失败: ' + result.error, 'danger');
}
} catch (error) {
console.error('加载任务历史失败:', error);
this.showAlert('加载任务历史失败: ' + error.message, 'danger');
} finally {
this.showTaskLoading(false);
}
}
async loadSystemLogs() {
try {
this.showLogsLoading(true);
// 构建查询参数
const params = new URLSearchParams({
limit: this.pageSize,
offset: (this.currentLogsPage - 1) * this.pageSize
});
if (this.logSearchParams.level) params.append('level', this.logSearchParams.level);
if (this.logSearchParams.category) params.append('category', this.logSearchParams.category);
let response;
if (this.logSearchParams.keyword) {
response = await fetch(`/api/history/logs/search?keyword=${encodeURIComponent(this.logSearchParams.keyword)}&limit=${this.pageSize}`);
} else {
response = await fetch(`/api/history/logs/system?${params}`);
}
const result = await response.json();
if (result.success) {
this.renderSystemLogsTable(result.data);
this.renderLogsPagination(result.pagination);
} else {
this.showAlert('加载系统日志失败: ' + result.error, 'danger');
}
} catch (error) {
console.error('加载系统日志失败:', error);
this.showAlert('加载系统日志失败: ' + error.message, 'danger');
} finally {
this.showLogsLoading(false);
}
}
async loadStatistics() {
try {
// 加载任务统计
const taskStatsResponse = await fetch('/api/history/stats/summary');
const taskStatsResult = await taskStatsResponse.json();
if (taskStatsResult.success) {
this.updateTaskStatistics(taskStatsResult.data.summary);
this.renderTaskStats(taskStatsResult.data);
}
// 加载日志统计
const logStatsResponse = await fetch('/api/history/logs/stats');
const logStatsResult = await logStatsResponse.json();
if (logStatsResult.success) {
this.updateLogStatistics(logStatsResult.data.summary);
this.renderLogStats(logStatsResult.data);
}
} catch (error) {
console.error('加载统计数据失败:', error);
this.showAlert('加载统计数据失败: ' + error.message, 'danger');
}
}
renderTaskHistoryTable(tasks) {
const tbody = document.getElementById('taskHistoryTableBody');
if (tasks.length === 0) {
tbody.innerHTML = `
|
暂无任务历史
|
`;
return;
}
tbody.innerHTML = tasks.map(task => `
|
${this.getTaskTypeLabel(task.task_type)}
|
${task.task_name} |
${this.getStatusText(task.status)}
|
${this.formatDateTime(task.start_time)}
|
${task.end_time ? this.formatDateTime(task.end_time) : '-'}
|
${task.duration ? this.formatDuration(task.duration) : '-'}
|
${task.result_summary || '-'}
|
|
`).join('');
}
renderSystemLogsTable(logs) {
const tbody = document.getElementById('systemLogsTableBody');
if (logs.length === 0) {
tbody.innerHTML = `
|
暂无系统日志
|
`;
return;
}
tbody.innerHTML = logs.map(log => `
|
${this.formatDateTime(log.timestamp)}
|
${log.level.toUpperCase()}
|
${log.category || '-'}
|
${log.message}
|
${log.source}
|
`).join('');
}
renderTaskPagination(pagination) {
const paginationElement = document.getElementById('taskPagination');
this.renderPagination(paginationElement, pagination, this.currentTaskPage, (page) => {
this.currentTaskPage = page;
this.loadTaskHistory();
});
}
renderLogsPagination(pagination) {
const paginationElement = document.getElementById('logsPagination');
this.renderPagination(paginationElement, pagination, this.currentLogsPage, (page) => {
this.currentLogsPage = page;
this.loadSystemLogs();
});
}
renderPagination(container, pagination, currentPage, onPageChange) {
const totalPages = Math.ceil(pagination.total / pagination.limit);
if (totalPages <= 1) {
container.innerHTML = '';
return;
}
let paginationHTML = '';
// 上一页
paginationHTML += `
`;
// 页码
const startPage = Math.max(1, currentPage - 2);
const endPage = Math.min(totalPages, currentPage + 2);
for (let i = startPage; i <= endPage; i++) {
paginationHTML += `
${i}
`;
}
// 下一页
paginationHTML += `
`;
container.innerHTML = paginationHTML;
}
updateTaskStatistics(stats) {
document.getElementById('totalTasks').textContent = stats.total;
document.getElementById('successTasks').textContent = stats.success;
document.getElementById('failedTasks').textContent = stats.failed;
document.getElementById('successRate').textContent = stats.success_rate + '%';
}
updateLogStatistics(stats) {
// 更新日志级别图表
if (this.charts.logLevel) {
this.charts.logLevel.data.datasets[0].data = [
stats.error,
stats.warning,
stats.info,
stats.debug
];
this.charts.logLevel.update();
}
}
renderTaskStats(data) {
const content = document.getElementById('taskStatsContent');
if (!data.daily || data.daily.length === 0) {
content.innerHTML = '暂无数据
';
return;
}
let html = '';
html += '| 日期 | 总任务 | 成功 | 失败 | 成功率 |
';
data.daily.slice(0, 7).forEach(day => {
const successRate = day.total > 0 ? ((day.success / day.total) * 100).toFixed(1) : '0';
html += `
| ${this.formatDate(day.date)} |
${day.total} |
${day.success} |
${day.failed} |
${successRate}% |
`;
});
html += '
';
content.innerHTML = html;
}
renderLogStats(data) {
const content = document.getElementById('logStatsContent');
const summary = data.summary;
let html = '';
html += '
总日志数:' + summary.total + '
';
html += '
';
html += '
警告:' + summary.warning + '
';
html += '
';
html += '
';
content.innerHTML = html;
}
async updateCharts() {
try {
// 加载每日任务统计
const response = await fetch('/api/dashboard/charts/tasks');
const result = await response.json();
if (result.success && this.charts.taskTrend) {
const labels = result.data.map(item => this.formatDate(item.date));
const successData = result.data.map(item => item.scrape_success + item.validation_success);
const failedData = result.data.map(item => item.scrape_failed + item.validation_failed);
this.charts.taskTrend.data.labels = labels;
this.charts.taskTrend.data.datasets[0].data = successData;
this.charts.taskTrend.data.datasets[1].data = failedData;
this.charts.taskTrend.update();
}
} catch (error) {
console.error('更新图表失败:', error);
}
}
async showTaskDetail(taskId) {
try {
const response = await fetch(`/api/history/${taskId}`);
const result = await response.json();
if (result.success) {
const task = result.data;
const content = document.getElementById('taskDetailContent');
content.innerHTML = `
任务类型:
${this.getTaskTypeLabel(task.task_type)}
任务名称:
${task.task_name}
状态:
${this.getStatusText(task.status)}
执行时长:
${task.duration ? this.formatDuration(task.duration) : '-'}
开始时间:
${this.formatDateTime(task.start_time)}
结束时间:
${task.end_time ? this.formatDateTime(task.end_time) : '-'}
结果摘要:
${task.result_summary || '暂无摘要'}
${task.error_message ? `
错误信息:
${task.error_message}
` : ''}
${task.details ? `
详细信息:
${JSON.stringify(task.details, null, 2)}
` : ''}
`;
// 显示模态框
const modal = new bootstrap.Modal(document.getElementById('taskDetailModal'));
modal.show();
} else {
this.showAlert('获取任务详情失败: ' + result.error, 'danger');
}
} catch (error) {
console.error('获取任务详情失败:', error);
this.showAlert('获取任务详情失败: ' + error.message, 'danger');
}
}
async exportTaskHistory() {
try {
const params = new URLSearchParams();
if (this.searchParams.taskType) params.append('taskType', this.searchParams.taskType);
if (this.searchParams.taskStatus) params.append('status', this.searchParams.taskStatus);
const response = await fetch(`/api/history/export?${params}&format=csv`);
if (response.ok) {
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `task_history_${new Date().toISOString().slice(0, 10)}.csv`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
this.showAlert('任务历史导出成功', 'success');
} else {
this.showAlert('导出失败', 'danger');
}
} catch (error) {
console.error('导出任务历史失败:', error);
this.showAlert('导出任务历史失败: ' + error.message, 'danger');
}
}
async cleanupLogs() {
if (!confirm('确定要清理30天前的历史记录吗?此操作不可恢复。')) {
return;
}
try {
const response = await fetch('/api/history/cleanup', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ days: 30, type: 'all' })
});
const result = await response.json();
if (result.success) {
this.showAlert(result.message, 'success');
await this.loadSystemLogs();
await this.loadStatistics();
} else {
this.showAlert('清理失败: ' + result.error, 'danger');
}
} catch (error) {
console.error('清理历史记录失败:', error);
this.showAlert('清理历史记录失败: ' + error.message, 'danger');
}
}
// 工具函数
getTaskTypeLabel(type) {
const labels = {
'scrape': '抓取任务',
'validation': '验证任务',
'health_check': '健康检查'
};
return labels[type] || type;
}
getStatusClass(status) {
const classes = {
'success': 'bg-success',
'failed': 'bg-danger',
'running': 'bg-warning',
'pending': 'bg-secondary'
};
return classes[status] || 'bg-secondary';
}
getStatusIcon(status) {
const icons = {
'success': 'check-circle',
'failed': 'x-circle',
'running': 'clock',
'pending': 'pause-circle'
};
return icons[status] || 'question-circle';
}
getStatusText(status) {
const texts = {
'success': '成功',
'failed': '失败',
'running': '运行中',
'pending': '等待中'
};
return texts[status] || status;
}
getLogLevelClass(level) {
const classes = {
'error': 'bg-danger',
'warning': 'bg-warning',
'info': 'bg-info',
'debug': 'bg-secondary'
};
return classes[level] || 'bg-secondary';
}
formatDateTime(dateString) {
const date = new Date(dateString);
return date.toLocaleString('zh-CN');
}
formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' });
}
formatDuration(ms) {
if (!ms) return '-';
if (ms < 1000) {
return ms + 'ms';
} else if (ms < 60000) {
return (ms / 1000).toFixed(1) + 's';
} else {
return (ms / 60000).toFixed(1) + 'min';
}
}
showTaskLoading(show) {
const tbody = document.getElementById('taskHistoryTableBody');
const spinner = tbody.querySelector('.spinner-border');
if (spinner) {
spinner.parentElement.style.display = show ? 'block' : 'none';
}
}
showLogsLoading(show) {
const tbody = document.getElementById('systemLogsTableBody');
const spinner = tbody.querySelector('.spinner-border');
if (spinner) {
spinner.parentElement.style.display = show ? 'block' : 'none';
}
}
showAlert(message, type = 'info') {
const alertContainer = document.getElementById('alertContainer');
if (!alertContainer) return;
const alertId = 'alert-' + Date.now();
const alertHtml = `
${message}
`;
alertContainer.insertAdjacentHTML('beforeend', alertHtml);
// 自动移除提示
setTimeout(() => {
const alertElement = document.getElementById(alertId);
if (alertElement) {
alertElement.remove();
}
}, 5000);
}
getAlertIcon(type) {
const icons = {
'success': 'check-circle-fill',
'danger': 'exclamation-triangle-fill',
'warning': 'exclamation-triangle-fill',
'info': 'info-circle-fill'
};
return icons[type] || 'info-circle-fill';
}
goToTaskPage(page) {
this.currentTaskPage = page;
this.loadTaskHistory();
}
}
// 全局函数(供HTML调用)
let historyManager;
async function refreshHistory() {
if (historyManager) {
await Promise.all([
historyManager.loadTaskHistory(),
historyManager.loadSystemLogs(),
historyManager.loadStatistics()
]);
historyManager.showAlert('历史数据已刷新', 'info');
}
}
async function exportTaskHistory() {
if (historyManager) {
await historyManager.exportTaskHistory();
}
}
async function cleanupLogs() {
if (historyManager) {
await historyManager.cleanupLogs();
}
}
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', () => {
historyManager = new HistoryManager();
});