diff --git a/internal/handler/metrics.go b/internal/handler/metrics.go
index f4df1c8..9a41e81 100644
--- a/internal/handler/metrics.go
+++ b/internal/handler/metrics.go
@@ -2,7 +2,6 @@ package handler
import (
"encoding/json"
- "fmt"
"log"
"net/http"
"proxy-go/internal/metrics"
@@ -512,7 +511,7 @@ var metricsTemplate = `
历史数据
-
+
-
- 显示最近30天的数据
@@ -646,7 +639,7 @@ var metricsTemplate = `
// 更新状态码计
const statusCodesHtml = '
' +
- Object.entries(data.status_code_stats)
+ Object.entries(data.status_code_stats || {})
.sort((a, b) => a[0].localeCompare(b[0]))
.map(([status, count]) => {
const firstDigit = status.charAt(0);
@@ -664,7 +657,7 @@ var metricsTemplate = `
statusCodesContainer.innerHTML = statusCodesHtml;
// 更新热门路径
- const topPathsHtml = data.top_paths.map(path =>
+ const topPathsHtml = (data.top_paths || []).map(path =>
'
' +
'' + path.path + ' | ' +
'' + path.request_count + ' | ' +
@@ -676,7 +669,7 @@ var metricsTemplate = `
document.querySelector('#topPaths tbody').innerHTML = topPathsHtml;
// 更新最近请求
- const recentRequestsHtml = data.recent_requests.map(req =>
+ const recentRequestsHtml = (data.recent_requests || []).map(req =>
'
' +
'' + formatDate(req.Time) + ' | ' +
'' + req.Path + ' | ' +
@@ -689,7 +682,7 @@ var metricsTemplate = `
document.querySelector('#recentRequests tbody').innerHTML = recentRequestsHtml;
// 更新热门引用来源
- const topReferersHtml = data.top_referers.map(referer =>
+ const topReferersHtml = (data.top_referers || []).map(referer =>
'
' +
'' + referer.path + ' | ' +
'' + referer.request_count + ' | ' +
@@ -732,187 +725,6 @@ var metricsTemplate = `
.catch(error => showError(error.message));
}
- // 初始加载
- refreshMetrics();
-
- // 每5秒自动刷新
- setInterval(refreshMetrics, 5000);
-
- // 修改图表相关代码
- let currentCharts = {
- requests: null,
- errorRate: null,
- bytes: null
- };
-
- function loadHistoryData(hours) {
- const chartContainer = document.getElementById('historyChart');
- chartContainer.classList.add('loading');
-
- fetch('/metrics/history?hours=' + hours, {
- headers: {
- 'Authorization': 'Bearer ' + token
- }
- })
- .then(response => response.json())
- .then(data => {
- if (!Array.isArray(data)) {
- console.error('Invalid data format');
- return;
- }
-
- // 反转数据顺序,使时间从左到右
- data.reverse();
-
- const labels = data.map(m => {
- const date = new Date(m.timestamp);
- if (hours <= 24) {
- return date.toLocaleTimeString();
- } else if (hours <= 168) {
- return date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
- } else {
- return date.toLocaleDateString();
- }
- });
-
- const commonOptions = {
- responsive: true,
- maintainAspectRatio: false,
- plugins: {
- legend: {
- display: false
- },
- tooltip: {
- callbacks: {
- label: function(context) {
- let label = context.dataset.label || '';
- if (label) {
- label += ': ';
- }
- if (context.parsed.y !== null) {
- if (context.chart.canvas.id === 'bytesChart') {
- label += formatBytes(context.parsed.y * 1024 * 1024);
- } else if (context.chart.canvas.id === 'errorRateChart') {
- label += context.parsed.y.toFixed(2) + '%';
- } else {
- label += context.parsed.y.toLocaleString();
- }
- }
- return label;
- },
- title: function(tooltipItems) {
- const date = new Date(tooltipItems[0].label);
- return date.toLocaleString();
- }
- }
- }
- },
- scales: {
- x: {
- display: true,
- grid: {
- display: false
- },
- ticks: {
- maxRotation: 45,
- minRotation: 45,
- autoSkip: true,
- maxTicksLimit: 20
- }
- },
- y: {
- beginAtZero: true,
- grid: {
- drawBorder: false
- },
- ticks: {
- callback: function(value) {
- if (this.chart.canvas.id === 'bytesChart') {
- return formatBytes(value * 1024 * 1024);
- }
- return value;
- }
- }
- }
- },
- elements: {
- line: {
- tension: 0.4
- },
- point: {
- radius: 2,
- hitRadius: 10,
- hoverRadius: 5
- }
- },
- interaction: {
- mode: 'nearest',
- axis: 'x',
- intersect: false
- }
- };
-
- // 更新或创建图表
- updateChart('requestsChart', 'requests', labels, data, '请求数',
- m => m.total_requests, '#007bff', commonOptions);
- updateChart('errorRateChart', 'errorRate', labels, data, '错误率 (%)',
- m => m.error_rate * 100, '#dc3545', commonOptions);
- updateChart('bytesChart', 'bytes', labels, data, '流量 (MB)',
- m => m.total_bytes / (1024 * 1024), '#28a745', commonOptions);
- })
- .finally(() => {
- chartContainer.classList.remove('loading');
- });
- }
-
- function updateChart(canvasId, chartKey, labels, data, label, valueGetter, color, options) {
- // 保 canvas 元素存在
- const canvas = document.getElementById(canvasId);
- if (!canvas) {
- console.error('Canvas element ' + canvasId + ' not found');
- return;
- }
-
- // 如果存在旧图表,销毁它
- if (currentCharts[chartKey]) {
- currentCharts[chartKey].destroy();
- currentCharts[chartKey] = null;
- }
-
- const ctx = canvas.getContext('2d');
- const chartData = {
- labels: labels,
- datasets: [{
- label: label,
- data: data.map(valueGetter),
- borderColor: color,
- backgroundColor: color + '20',
- fill: true
- }]
- };
-
- // 创建新图表
- currentCharts[chartKey] = new Chart(ctx, {
- type: 'line',
- data: chartData,
- options: options
- });
- }
-
- // 时间范围按钮处理
- document.querySelectorAll('.time-btn').forEach(btn => {
- btn.addEventListener('click', function() {
- document.querySelectorAll('.time-btn').forEach(b => b.classList.remove('active'));
- this.classList.add('active');
- loadHistoryData(parseFloat(this.dataset.hours));
- });
- });
-
- // 初始加载历史数据
- document.addEventListener('DOMContentLoaded', function() {
- loadHistoryData(24);
- });
-
let refreshTimer;
function setupAutoRefresh() {
@@ -935,51 +747,10 @@ var metricsTemplate = `
}
document.addEventListener('DOMContentLoaded', function() {
- loadHistoryData(24);
+ refreshMetrics(); // 立即加载一次数据
setupAutoRefresh();
});
-
- function exportData() {
- const activeBtn = document.querySelector('.time-btn.active');
- const hours = parseInt(activeBtn.dataset.hours);
-
- fetch('/metrics/history?hours=' + hours, {
- headers: {
- 'Authorization': 'Bearer ' + token
- }
- })
- .then(response => response.json())
- .then(data => {
- const csv = convertToCSV(data);
- downloadCSV(csv, 'metrics_' + hours + 'h_' + new Date().toISOString() + '.csv');
- })
- .catch(error => showError('导出失败: ' + error.message));
- }
-
- function convertToCSV(data) {
- const headers = ['时间', '请求数', '错误数', '流量(MB)', '错误率(%)'];
- const rows = data.map(row => [
- row.timestamp,
- row.total_requests,
- row.total_errors,
- (row.total_bytes / (1024 * 1024)).toFixed(2),
- (row.error_rate * 100).toFixed(2)
- ]);
- return [headers, ...rows].map(row => row.join(',')).join('\n');
- }
-
- function downloadCSV(csv, filename) {
- const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
- const link = document.createElement('a');
- link.href = URL.createObjectURL(blob);
- link.download = filename;
- link.click();
- }
-
-
-
-