实现日志收集功能

This commit is contained in:
Alex Yang
2025-12-05 00:03:44 +08:00
parent 057a2ea9ee
commit 52155cb77c
13 changed files with 7811 additions and 139 deletions

View File

@@ -360,7 +360,7 @@
</div>
</div>
</div>
<br>
<!-- 进程信息展示 -->
<div id="processInfoContainer" class="mt-8">
<h3 class="text-lg font-semibold text-gray-900 mb-4">进程信息</h3>
@@ -478,8 +478,8 @@
</div>
</div>
<!-- 系统日志 图表 -->
<div id="logChartContainer" class="chart-container h-80 hidden">
<!-- 系统日志 信息 -->
<div id="logInfoContainer" class="mt-8 hidden">
<h3 class="text-lg font-semibold text-gray-900 mb-4">系统日志</h3>
<div class="overflow-x-auto">
<table id="logTable" class="min-w-full bg-white rounded-lg overflow-hidden shadow-md">
@@ -496,6 +496,8 @@
</tbody>
</table>
</div>
<!-- 日志信息分页容器 -->
<div id="logPaginationContainer" class="mt-4"></div>
</div>
<!-- 缩放控件已移动到各个图表容器内 -->

View File

@@ -7,6 +7,7 @@ let state = {
customStartTime: '',
customEndTime: '',
currentInterval: '3m', // 固定10分钟区间
currentDeviceID: '', // 当前选中的服务器ID
historyMetrics: {}, // 存储历史指标数据
autoRefreshEnabled: false, // 自动刷新开关状态,默认关闭
lastMetricsUpdate: null, // 记录上次指标更新时间
@@ -101,12 +102,22 @@ function updateAutoRefreshInterval() {
// 初始化自定义时间范围
function initCustomTimeRange() {
const now = new Date();
// 默认显示过去24小时
const twentyFourHoursAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
// 默认显示过去1小时
const oneHourAgo = new Date(now.getTime() - 1 * 60 * 60 * 1000);
// 直接使用ISO字符串包含完整的时区信息
state.customStartTime = twentyFourHoursAgo.toISOString();
state.customStartTime = oneHourAgo.toISOString();
state.customEndTime = now.toISOString();
// 更新日期选择器输入框的值
const startTimeInput = document.getElementById('customStartTime');
const endTimeInput = document.getElementById('customEndTime');
if (startTimeInput && endTimeInput) {
// 将ISO字符串转换为datetime-local格式YYYY-MM-DDTHH:MM
startTimeInput.value = oneHourAgo.toISOString().slice(0, 16);
endTimeInput.value = now.toISOString().slice(0, 16);
}
}
// 页面切换
@@ -125,9 +136,13 @@ function switchPage() {
if (hash === '#servers') {
showContent('serversContent');
loadAllServers();
// 清除当前设备ID避免在服务器列表页显示特定服务器的数据
state.currentDeviceID = '';
} else if (hash === '#devices') {
showContent('devicesContent');
loadDeviceManagementList();
// 清除当前设备ID
state.currentDeviceID = '';
} else if (hash === '#serverMonitor' || hash.startsWith('#serverMonitor/')) {
showContent('serverMonitorContent');
@@ -137,6 +152,9 @@ function switchPage() {
deviceId = hash.split('/')[1];
}
// 直接设置当前设备ID确保loadMetrics能使用正确的设备ID
state.currentDeviceID = deviceId;
// 加载服务器信息
if (deviceId) {
loadServerInfo(deviceId);
@@ -146,6 +164,8 @@ function switchPage() {
} else {
showContent('homeContent');
loadHomeData();
// 清除当前设备ID
state.currentDeviceID = '';
}
}
@@ -733,7 +753,7 @@ function initDetailedCharts() {
},
zoom: {
wheel: {
enabled: true,
enabled: true
},
pinch: {
enabled: true
@@ -802,7 +822,7 @@ function initDetailedCharts() {
},
zoom: {
wheel: {
enabled: true,
enabled: true
},
pinch: {
enabled: true
@@ -872,7 +892,7 @@ function initDetailedCharts() {
},
zoom: {
wheel: {
enabled: true,
enabled: true
},
pinch: {
enabled: true
@@ -950,11 +970,10 @@ function initDetailedCharts() {
label: function(context) {
const label = context.dataset.label || '';
const value = context.parsed.y;
const rawValue = context.raw.y;
// 第二行显示实际值
return [
`${label}: ${value} MB`,
`实际值: ${rawValue} MB`
`实际值: ${value} MB`
];
}
}
@@ -1043,11 +1062,10 @@ function initDetailedCharts() {
label: function(context) {
const label = context.dataset.label || '';
const value = context.parsed.y;
const rawValue = context.raw.y;
// 第二行显示实际值
return [
`${label}: ${value} MB/s`,
`实际值: ${rawValue} MB/s`
`实际值: ${value} MB/s`
];
}
}
@@ -1196,7 +1214,18 @@ async function fetchMetric(metricType, aggregation = 'average') {
}
const data = await response.json();
return data.data;
// 确保返回的数据是数组格式
if (metricType === 'disk') {
// 磁盘数据可能是对象格式,需要特殊处理
return data.data || {};
} else if (metricType === 'network') {
// 网络数据可能是对象格式,需要特殊处理
return data.data || {};
} else {
// 其他数据应该是数组格式
return Array.isArray(data.data) ? data.data : [];
}
}
// 格式化时间,统一格式避免图表误解
@@ -1259,6 +1288,9 @@ async function loadMetrics() {
statusIndicator.className = 'w-2 h-2 bg-red-500 rounded-full animate-pulse';
lastRefreshTime.textContent = `上次刷新: 失败`;
}
// 即使发生错误,也要尝试初始化图表,避免页面空白
initDetailedCharts();
}
}
@@ -1450,6 +1482,9 @@ function handleWebSocketMessage(message) {
// 加载服务器信息
async function loadServerInfo(deviceId) {
try {
// 将设备ID存储到全局状态
state.currentDeviceID = deviceId;
const response = await fetch(`${API_BASE_URL}/devices/${deviceId}`);
if (!response.ok) {
throw new Error('Failed to fetch server info');
@@ -1464,6 +1499,9 @@ async function loadServerInfo(deviceId) {
}
} catch (error) {
console.error('Failed to load server info:', error);
// 即使请求失败也要将设备ID存储到全局状态
state.currentDeviceID = deviceId;
// 使用模拟数据
const serverInfoDisplay = document.getElementById('serverInfoDisplay');
if (serverInfoDisplay) {
@@ -1475,6 +1513,12 @@ async function loadServerInfo(deviceId) {
// 处理指标更新
function handleMetricsUpdate(message) {
const { device_id, metrics } = message;
// 只处理当前选中设备的WebSocket消息
if (state.currentDeviceID && device_id !== state.currentDeviceID) {
return;
}
// 格式化数据确保updateStatusCards函数能正确处理
const formattedMetrics = {
cpu: metrics.cpu,
@@ -1855,12 +1899,18 @@ function updateHistoryMetrics(metrics) {
// 更新图表数据
function updateCharts(cpuData, memoryData, diskData, networkData) {
// 确保数据格式正确
const safeCpuData = Array.isArray(cpuData) ? cpuData : [];
const safeMemoryData = Array.isArray(memoryData) ? memoryData : [];
const safeDiskData = typeof diskData === 'object' && diskData !== null ? diskData : {};
const safeNetworkData = typeof networkData === 'object' && networkData !== null ? networkData : {};
// 保存待处理的图表更新数据
state.pendingChartUpdate = {
cpuData,
memoryData,
diskData,
networkData
cpuData: safeCpuData,
memoryData: safeMemoryData,
diskData: safeDiskData,
networkData: safeNetworkData
};
// 使用节流机制更新图表
@@ -2619,18 +2669,7 @@ function bindEvents() {
});
}
// 初始化自定义时间输入框
const now = new Date();
const startTimeInput = document.getElementById('customStartTime');
const endTimeInput = document.getElementById('customEndTime');
if (startTimeInput && endTimeInput) {
// 默认显示过去24小时
const twentyFourHoursAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
// 显示本地时间格式YYYY-MM-DDTHH:MM
startTimeInput.value = twentyFourHoursAgo.toISOString().slice(0, 16);
endTimeInput.value = now.toISOString().slice(0, 16);
}
// 缩放控件事件处理
const zoomOutBtn = document.getElementById('zoomOutBtn');
@@ -2722,27 +2761,49 @@ async function loadNetworkInterfaces() {
// 更新当前时间范围显示
const updateTimeRangeDisplay = () => {
let displayText = '';
switch(state.currentTimeRange) {
case '30m':
displayText = '过去30分钟';
break;
case '1h':
displayText = '过去1小时';
break;
case '2h':
displayText = '过去2小时';
break;
case '6h':
displayText = '过去6小时';
break;
case '12h':
displayText = '过去12小时';
break;
case '24h':
displayText = '过去24小时';
break;
default:
displayText = '自定义时间范围';
// 计算实际的开始时间和结束时间
let startTime, endTime;
if (state.customStartTime && state.customEndTime) {
// 使用自定义时间范围
startTime = new Date(state.customStartTime);
endTime = new Date(state.customEndTime);
displayText = `${formatTime(state.customStartTime)}${formatTime(state.customEndTime)}`;
} else {
// 使用预设时间范围
const now = new Date();
endTime = now;
// 根据预设时间范围计算开始时间
switch(state.currentTimeRange) {
case '30m':
startTime = new Date(now.getTime() - 30 * 60 * 1000);
displayText = `${formatTime(startTime.toISOString())}${formatTime(endTime.toISOString())}`;
break;
case '1h':
startTime = new Date(now.getTime() - 60 * 60 * 1000);
displayText = `${formatTime(startTime.toISOString())}${formatTime(endTime.toISOString())}`;
break;
case '2h':
startTime = new Date(now.getTime() - 2 * 60 * 60 * 1000);
displayText = `${formatTime(startTime.toISOString())}${formatTime(endTime.toISOString())}`;
break;
case '6h':
startTime = new Date(now.getTime() - 6 * 60 * 60 * 1000);
displayText = `${formatTime(startTime.toISOString())}${formatTime(endTime.toISOString())}`;
break;
case '12h':
startTime = new Date(now.getTime() - 12 * 60 * 60 * 1000);
displayText = `${formatTime(startTime.toISOString())}${formatTime(endTime.toISOString())}`;
break;
case '24h':
startTime = new Date(now.getTime() - 24 * 60 * 60 * 1000);
displayText = `${formatTime(startTime.toISOString())}${formatTime(endTime.toISOString())}`;
break;
default:
displayText = '自定义时间范围';
}
}
// 更新所有图表的时间范围显示
@@ -2897,11 +2958,15 @@ function initChartTabs() {
const activeContainer = document.getElementById(`${tabId}ChartContainer`);
if (activeContainer) {
activeContainer.classList.remove('hidden');
console.log(`Shown chart container: ${tabId}ChartContainer`);
} else {
console.error(`Chart container not found: ${tabId}ChartContainer`);
}
// 显示/隐藏进程信息磁盘详细信息
// 显示/隐藏进程信息磁盘详细信息和系统日志
const processInfoContainer = document.getElementById('processInfoContainer');
const diskDetailsContainer = document.getElementById('diskDetailsContainer');
const logInfoContainer = document.getElementById('logInfoContainer');
// 隐藏所有附加信息容器
if (processInfoContainer) {
@@ -2910,6 +2975,9 @@ function initChartTabs() {
if (diskDetailsContainer) {
diskDetailsContainer.classList.add('hidden');
}
if (logInfoContainer) {
logInfoContainer.classList.add('hidden');
}
// 根据选项卡显示相应的附加信息
if (tabId === 'cpu') {
@@ -2926,6 +2994,13 @@ function initChartTabs() {
// 加载磁盘详细信息
loadDiskDetails();
}
} else if (tabId === 'logs') {
// 显示系统日志
if (logInfoContainer) {
logInfoContainer.classList.remove('hidden');
// 加载系统日志
loadSystemLogs();
}
}
// 显示/隐藏网卡选择下拉框
@@ -2941,15 +3016,64 @@ function initChartTabs() {
});
});
// 初始状态:根据当前选中的选项卡显示/隐藏网卡选择下拉框
// 初始状态:根据当前选中的选项卡显示/隐藏网卡选择下拉框和加载数据
const activeTab = document.querySelector('.chart-tab.active');
const interfaceContainer = document.getElementById('interfaceSelectorContainer');
if (activeTab && interfaceContainer) {
if (activeTab) {
const tabId = activeTab.dataset.tab;
if (tabId === 'network' || tabId === 'speed') {
interfaceContainer.classList.remove('hidden');
// 显示当前选中的图表容器
const activeContainer = document.getElementById(`${tabId}ChartContainer`);
if (activeContainer) {
activeContainer.classList.remove('hidden');
} else {
interfaceContainer.classList.add('hidden');
console.error(`Initial chart container not found: ${tabId}ChartContainer`);
}
// 显示/隐藏进程信息、磁盘详细信息和系统日志
const processInfoContainer = document.getElementById('processInfoContainer');
const diskDetailsContainer = document.getElementById('diskDetailsContainer');
const logInfoContainer = document.getElementById('logInfoContainer');
// 隐藏所有附加信息容器
if (processInfoContainer) {
processInfoContainer.classList.add('hidden');
}
if (diskDetailsContainer) {
diskDetailsContainer.classList.add('hidden');
}
if (logInfoContainer) {
logInfoContainer.classList.add('hidden');
}
// 根据选项卡显示相应的附加信息
if (tabId === 'cpu') {
// 显示进程信息
if (processInfoContainer) {
processInfoContainer.classList.remove('hidden');
}
loadProcessInfo();
} else if (tabId === 'disk') {
// 显示磁盘详细信息
if (diskDetailsContainer) {
diskDetailsContainer.classList.remove('hidden');
}
loadDiskDetails();
} else if (tabId === 'logs') {
// 显示系统日志
if (logInfoContainer) {
logInfoContainer.classList.remove('hidden');
}
loadSystemLogs();
}
// 显示/隐藏网卡选择下拉框
if (interfaceContainer) {
if (tabId === 'network' || tabId === 'speed') {
interfaceContainer.classList.remove('hidden');
} else {
interfaceContainer.classList.add('hidden');
}
}
}
}
@@ -2974,7 +3098,18 @@ let processPagination = {
itemsPerPage: 5,
totalItems: 0,
totalPages: 0,
allProcesses: []
allProcesses: [],
lastDeviceID: '' // 上次请求数据的设备ID
};
// 系统日志分页状态
let logPagination = {
currentPage: 1,
itemsPerPage: 5,
totalItems: 0,
totalPages: 0,
allLogs: [],
lastDeviceID: '' // 上次请求数据的设备ID
};
// 加载进程信息
@@ -2991,7 +3126,16 @@ async function loadProcessInfo(page = 1) {
params.append('device_id', state.currentDeviceID);
}
// 如果是第一次加载,获取全部数据
// 检查设备ID是否变化
if (processPagination.lastDeviceID !== state.currentDeviceID) {
// 设备ID变化清空旧数据
processPagination.allProcesses = [];
processPagination.totalItems = 0;
processPagination.totalPages = 0;
processPagination.currentPage = 1;
}
// 如果是第一次加载或设备ID变化获取全部数据
if (processPagination.allProcesses.length === 0) {
// 发送请求
const response = await fetch(`${API_BASE_URL}/metrics/processes?${params.toString()}`);
@@ -3003,6 +3147,8 @@ async function loadProcessInfo(page = 1) {
processPagination.allProcesses = data.data || [];
processPagination.totalItems = processPagination.allProcesses.length;
processPagination.totalPages = Math.ceil(processPagination.totalItems / processPagination.itemsPerPage);
// 更新上次请求数据的设备ID
processPagination.lastDeviceID = state.currentDeviceID;
}
// 更新当前页码
@@ -3072,7 +3218,8 @@ let diskPagination = {
itemsPerPage: 5,
totalItems: 0,
totalPages: 0,
allDisks: []
allDisks: [],
lastDeviceID: '' // 上次请求数据的设备ID
};
// 创建分页控件
@@ -3221,7 +3368,16 @@ async function loadDiskDetails(page = 1) {
params.append('device_id', state.currentDeviceID);
}
// 如果是第一次加载,获取全部数据
// 检查设备ID是否变化
if (diskPagination.lastDeviceID !== state.currentDeviceID) {
// 设备ID变化清空旧数据
diskPagination.allDisks = [];
diskPagination.totalItems = 0;
diskPagination.totalPages = 0;
diskPagination.currentPage = 1;
}
// 如果是第一次加载或设备ID变化获取全部数据
if (diskPagination.allDisks.length === 0) {
// 发送请求
const response = await fetch(`${API_BASE_URL}/metrics/disk_details?${params.toString()}`);
@@ -3233,6 +3389,8 @@ async function loadDiskDetails(page = 1) {
diskPagination.allDisks = data.data || [];
diskPagination.totalItems = diskPagination.allDisks.length;
diskPagination.totalPages = Math.ceil(diskPagination.totalItems / diskPagination.itemsPerPage);
// 更新上次请求数据的设备ID
diskPagination.lastDeviceID = state.currentDeviceID;
}
// 更新当前页码
@@ -3319,9 +3477,15 @@ async function loadDiskDetails(page = 1) {
}
// 加载系统日志
async function loadSystemLogs() {
async function loadSystemLogs(page = 1) {
console.log('loadSystemLogs function called');
const logTableBody = document.getElementById('logTableBody');
if (!logTableBody) return;
const logPaginationContainer = document.getElementById('logPaginationContainer');
if (!logTableBody) {
console.error('logTableBody element not found');
return;
}
try {
// 构建查询参数
@@ -3330,46 +3494,86 @@ async function loadSystemLogs() {
params.append('device_id', state.currentDeviceID);
}
// 发送请求
const response = await fetch(`${API_BASE_URL}/metrics/logs?${params.toString()}`);
if (!response.ok) {
throw new Error('Failed to fetch system logs');
// 检查设备ID是否变化
if (logPagination.lastDeviceID !== state.currentDeviceID) {
// 设备ID变化清空旧数据
logPagination.allLogs = [];
logPagination.totalItems = 0;
logPagination.totalPages = 0;
logPagination.currentPage = 1;
}
const data = await response.json();
const logs = data.data;
// 如果是第一次加载或设备ID变化获取全部数据
if (logPagination.allLogs.length === 0) {
console.log('Fetching logs from:', `${API_BASE_URL}/metrics/logs?${params.toString()}`);
// 发送请求
const response = await fetch(`${API_BASE_URL}/metrics/logs?${params.toString()}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
logPagination.allLogs = data.data || [];
logPagination.totalItems = logPagination.allLogs.length;
logPagination.totalPages = Math.ceil(logPagination.totalItems / logPagination.itemsPerPage);
// 更新上次请求数据的设备ID
logPagination.lastDeviceID = state.currentDeviceID;
}
// 更新当前页码
logPagination.currentPage = page;
// 清空表格
logTableBody.innerHTML = '';
if (logs.length === 0) {
if (logPagination.totalItems === 0) {
// 没有日志数据,显示提示
logTableBody.innerHTML = `
<tr>
<td colspan="4" class="px-6 py-4 text-center text-gray-500">暂无系统日志</td>
</tr>
`;
// 隐藏分页控件
if (logPaginationContainer) {
logPaginationContainer.innerHTML = '';
}
return;
}
// 计算分页数据
const startIndex = (logPagination.currentPage - 1) * logPagination.itemsPerPage;
const endIndex = Math.min(startIndex + logPagination.itemsPerPage, logPagination.totalItems);
const paginatedLogs = logPagination.allLogs.slice(startIndex, endIndex);
// 填充表格数据
logs.forEach((log, index) => {
paginatedLogs.forEach((log, index) => {
const row = document.createElement('tr');
row.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${index + 1}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${startIndex + index + 1}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 font-medium">${log.source || 'System'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${log.time || new Date().toLocaleString()}</td>
<td class="px-6 py-4 whitespace-normal text-sm text-gray-500">${log.message || 'No message'}</td>
`;
logTableBody.appendChild(row);
});
// 创建分页控件
if (logPaginationContainer) {
createPaginationControls(logPaginationContainer, logPagination, loadSystemLogs);
}
} catch (error) {
console.error('Error loading system logs:', error);
logTableBody.innerHTML = `
<tr>
<td colspan="4" class="px-6 py-4 text-center text-red-500">加载系统日志失败</td>
<td colspan="4" class="px-6 py-4 text-center text-red-500">加载系统日志失败: ${error.message}</td>
</tr>
`;
if (logPaginationContainer) {
logPaginationContainer.innerHTML = '';
}
}
}
@@ -3379,11 +3583,56 @@ function handleHashChange() {
if (hash === '#serverMonitor' || hash.startsWith('#serverMonitor/')) {
// 延迟一下确保DOM已经渲染完成
setTimeout(() => {
loadMetrics();
// 加载进程信息如果当前是CPU选项卡
// 加载当前选项卡对应的数据
const activeTab = document.querySelector('.chart-tab.active');
if (activeTab && activeTab.dataset.tab === 'cpu') {
loadProcessInfo();
if (activeTab) {
const tabId = activeTab.dataset.tab;
// 显示/隐藏附加信息容器
const processInfoContainer = document.getElementById('processInfoContainer');
const diskDetailsContainer = document.getElementById('diskDetailsContainer');
const logInfoContainer = document.getElementById('logInfoContainer');
// 隐藏所有附加信息容器
if (processInfoContainer) {
processInfoContainer.classList.add('hidden');
}
if (diskDetailsContainer) {
diskDetailsContainer.classList.add('hidden');
}
if (logInfoContainer) {
logInfoContainer.classList.add('hidden');
}
if (tabId === 'logs') {
// 显示系统日志
if (logInfoContainer) {
logInfoContainer.classList.remove('hidden');
}
// 加载系统日志
loadSystemLogs();
} else {
// 加载其他监控数据
loadMetrics();
// 根据选项卡加载附加信息
if (tabId === 'cpu') {
// 显示进程信息
if (processInfoContainer) {
processInfoContainer.classList.remove('hidden');
}
loadProcessInfo();
} else if (tabId === 'disk') {
// 显示磁盘详细信息
if (diskDetailsContainer) {
diskDetailsContainer.classList.remove('hidden');
}
loadDiskDetails();
}
}
} else {
// 如果没有找到激活的选项卡默认加载metrics
loadMetrics();
}
}, 300);
}