// 执行历史页面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 += ` `; }); html += '
    日期总任务成功失败成功率
    ${this.formatDate(day.date)} ${day.total} ${day.success} ${day.failed} ${successRate}%
    '; content.innerHTML = html; } renderLogStats(data) { const content = document.getElementById('logStatsContent'); const summary = data.summary; let html = '
    '; html += '
    总日志数:
    ' + summary.total + '
    '; html += '
    错误:
    ' + summary.error + '
    '; html += '
    警告:
    ' + summary.warning + '
    '; html += '
    信息:
    ' + summary.info + '
    '; 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 = ` `; 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(); });