393 lines
14 KiB
JavaScript
393 lines
14 KiB
JavaScript
|
|
/**
|
|||
|
|
* 系统监控服务
|
|||
|
|
* 处理系统性能指标获取和展示
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
// 日志自动刷新相关变量
|
|||
|
|
let logRefreshTimer = null;
|
|||
|
|
let logCountdownTimer = null;
|
|||
|
|
let logCurrentInterval = 10;
|
|||
|
|
let logFullContent = '';
|
|||
|
|
|
|||
|
|
// 设置自动刷新间隔
|
|||
|
|
function setRefreshInterval() {
|
|||
|
|
const select = document.getElementById('logRefreshInterval');
|
|||
|
|
const countdownEl = document.getElementById('logRefreshCountdown');
|
|||
|
|
const secondsEl = document.getElementById('countdownNumber');
|
|||
|
|
|
|||
|
|
if (!select) return;
|
|||
|
|
|
|||
|
|
logCurrentInterval = parseInt(select.value) || 10;
|
|||
|
|
|
|||
|
|
// 清除之前的定时器
|
|||
|
|
if (logRefreshTimer) {
|
|||
|
|
clearInterval(logRefreshTimer);
|
|||
|
|
logRefreshTimer = null;
|
|||
|
|
}
|
|||
|
|
if (logCountdownTimer) {
|
|||
|
|
clearInterval(logCountdownTimer);
|
|||
|
|
logCountdownTimer = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果选择关闭,不显示倒计时
|
|||
|
|
if (select.value === '0') {
|
|||
|
|
countdownEl.classList.add('hidden');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 显示倒计时
|
|||
|
|
countdownEl.classList.remove('hidden');
|
|||
|
|
secondsEl.textContent = logCurrentInterval;
|
|||
|
|
|
|||
|
|
// 启动倒计时
|
|||
|
|
let countdown = logCurrentInterval;
|
|||
|
|
logCountdownTimer = setInterval(() => {
|
|||
|
|
countdown--;
|
|||
|
|
if (countdown <= 0) {
|
|||
|
|
countdown = logCurrentInterval;
|
|||
|
|
}
|
|||
|
|
secondsEl.textContent = countdown;
|
|||
|
|
}, 1000);
|
|||
|
|
|
|||
|
|
// 启动自动刷新
|
|||
|
|
logRefreshTimer = setInterval(() => {
|
|||
|
|
if (typeof refreshLogs === 'function') {
|
|||
|
|
refreshLogs();
|
|||
|
|
}
|
|||
|
|
}, logCurrentInterval * 1000);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取系统性能监控数据
|
|||
|
|
async function fetchSystemMetrics() {
|
|||
|
|
try {
|
|||
|
|
const response = await fetch(`${window.API_BASE}/health`);
|
|||
|
|
const result = await response.json();
|
|||
|
|
if (result.code === 0 && result.data) {
|
|||
|
|
const data = result.data;
|
|||
|
|
// 更新CPU使用率
|
|||
|
|
const cpuEl = document.getElementById('cpuUsage');
|
|||
|
|
if (cpuEl && data.cpu_percent !== undefined) {
|
|||
|
|
cpuEl.textContent = data.cpu_percent;
|
|||
|
|
cpuEl.className = data.cpu_percent > 80 ? 'text-red-500 font-medium' : '';
|
|||
|
|
}
|
|||
|
|
// 更新内存使用率
|
|||
|
|
const memEl = document.getElementById('memUsage');
|
|||
|
|
if (memEl && data.memory_percent !== undefined) {
|
|||
|
|
memEl.textContent = data.memory_percent;
|
|||
|
|
memEl.className = data.memory_percent > 80 ? 'text-red-500 font-medium' : '';
|
|||
|
|
}
|
|||
|
|
// 更新磁盘使用率
|
|||
|
|
const diskEl = document.getElementById('diskUsage');
|
|||
|
|
if (diskEl && data.disk_percent !== undefined) {
|
|||
|
|
diskEl.textContent = data.disk_percent;
|
|||
|
|
diskEl.className = data.disk_percent > 80 ? 'text-red-500 font-medium' : '';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取系统监控数据失败:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 停止日志自动刷新(离开页面时调用)
|
|||
|
|
function stopLogAutoRefresh() {
|
|||
|
|
if (logRefreshTimer) {
|
|||
|
|
clearInterval(logRefreshTimer);
|
|||
|
|
logRefreshTimer = null;
|
|||
|
|
}
|
|||
|
|
if (logCountdownTimer) {
|
|||
|
|
clearInterval(logCountdownTimer);
|
|||
|
|
logCountdownTimer = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 刷新日志
|
|||
|
|
function refreshLogs() {
|
|||
|
|
if (typeof switchLogTab === 'function') {
|
|||
|
|
const currentLogTab = window.currentLogTab || 'system';
|
|||
|
|
if (currentLogTab === 'system') {
|
|||
|
|
if (typeof loadLogFiles === 'function' && document.getElementById('logTypeSelect')?.value) {
|
|||
|
|
loadSelectedLog();
|
|||
|
|
} else {
|
|||
|
|
loadLogFiles();
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
loadTrainingLogFiles();
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
loadLogFiles();
|
|||
|
|
}
|
|||
|
|
// 重置倒计时
|
|||
|
|
const select = document.getElementById('logRefreshInterval');
|
|||
|
|
const secondsEl = document.getElementById('countdownNumber');
|
|||
|
|
if (select && select.value !== '0' && secondsEl) {
|
|||
|
|
secondsEl.textContent = logCurrentInterval;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 滚动到日志底部
|
|||
|
|
function scrollToLogBottom() {
|
|||
|
|
const logContent = document.getElementById('logContent');
|
|||
|
|
if (logContent) {
|
|||
|
|
logContent.scrollTop = logContent.scrollHeight;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 当前日志类型:system 或 training
|
|||
|
|
window.currentLogTab = 'system';
|
|||
|
|
|
|||
|
|
// 切换日志类型标签
|
|||
|
|
function switchLogTab(tab) {
|
|||
|
|
window.currentLogTab = tab;
|
|||
|
|
const systemTab = document.getElementById('logTabSystem');
|
|||
|
|
const trainingTab = document.getElementById('logTabTraining');
|
|||
|
|
const systemOptions = document.getElementById('systemLogOptions');
|
|||
|
|
const trainingOptions = document.getElementById('trainingLogOptions');
|
|||
|
|
|
|||
|
|
if (tab === 'system') {
|
|||
|
|
systemTab.className = 'px-4 py-1.5 text-sm rounded-md transition-colors bg-white shadow-sm text-primary';
|
|||
|
|
trainingTab.className = 'px-4 py-1.5 text-sm rounded-md transition-colors text-gray-600 hover:text-gray-800';
|
|||
|
|
systemOptions.classList.remove('hidden');
|
|||
|
|
trainingOptions.classList.add('hidden');
|
|||
|
|
loadLogFiles();
|
|||
|
|
} else {
|
|||
|
|
trainingTab.className = 'px-4 py-1.5 text-sm rounded-md transition-colors bg-white shadow-sm text-primary';
|
|||
|
|
systemTab.className = 'px-4 py-1.5 text-sm rounded-md transition-colors text-gray-600 hover:text-gray-800';
|
|||
|
|
trainingOptions.classList.remove('hidden');
|
|||
|
|
systemOptions.classList.add('hidden');
|
|||
|
|
loadTrainingLogFiles();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 初始化日志查看器
|
|||
|
|
function initLogViewer() {
|
|||
|
|
const datePicker = document.getElementById('logDatePicker');
|
|||
|
|
if (datePicker) {
|
|||
|
|
const today = new Date().toISOString().split('T')[0];
|
|||
|
|
datePicker.value = today;
|
|||
|
|
}
|
|||
|
|
// 加载默认日志类型
|
|||
|
|
loadLogFiles();
|
|||
|
|
// 启动自动刷新
|
|||
|
|
setRefreshInterval();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 加载训练日志文件列表
|
|||
|
|
async function loadTrainingLogFiles() {
|
|||
|
|
const logSelect = document.getElementById('trainingLogSelect');
|
|||
|
|
if (!logSelect) return;
|
|||
|
|
|
|||
|
|
logSelect.innerHTML = '<option value="">加载中...</option>';
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const response = await fetch(`${window.API_BASE}/training-log-files`);
|
|||
|
|
const result = await response.json();
|
|||
|
|
|
|||
|
|
if (result.code === 0 && result.data) {
|
|||
|
|
logSelect.innerHTML = '<option value="">请选择训练日志</option>';
|
|||
|
|
result.data.forEach(log => {
|
|||
|
|
const option = document.createElement('option');
|
|||
|
|
option.value = log.file;
|
|||
|
|
option.textContent = `${log.name} (PID: ${log.pid}, ${log.date}, ${log.size})`;
|
|||
|
|
logSelect.appendChild(option);
|
|||
|
|
});
|
|||
|
|
// 如果有日志文件,自动加载第一个
|
|||
|
|
if (result.data.length > 0) {
|
|||
|
|
logSelect.value = result.data[0].file;
|
|||
|
|
loadSelectedTrainingLog();
|
|||
|
|
} else {
|
|||
|
|
document.getElementById('logContent').textContent = '暂无训练日志';
|
|||
|
|
document.getElementById('logFileInfo').textContent = '无训练日志';
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
logSelect.innerHTML = '<option value="">暂无训练日志</option>';
|
|||
|
|
document.getElementById('logContent').textContent = '暂无训练日志';
|
|||
|
|
document.getElementById('logFileInfo').textContent = '无训练日志';
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('加载训练日志列表失败:', error);
|
|||
|
|
logSelect.innerHTML = '<option value="">加载失败</option>';
|
|||
|
|
document.getElementById('logContent').textContent = '加载训练日志列表失败: ' + error.message;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 加载选中的训练日志
|
|||
|
|
async function loadSelectedTrainingLog() {
|
|||
|
|
const logSelect = document.getElementById('trainingLogSelect');
|
|||
|
|
const logFile = logSelect.value;
|
|||
|
|
const logContent = document.getElementById('logContent');
|
|||
|
|
const logFileInfo = document.getElementById('logFileInfo');
|
|||
|
|
|
|||
|
|
if (!logFile) {
|
|||
|
|
logContent.textContent = '请选择训练日志';
|
|||
|
|
logFileInfo.textContent = '无训练日志';
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
logContent.textContent = '加载中...';
|
|||
|
|
logFileInfo.textContent = '加载中...';
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const response = await fetch(`${window.API_BASE}/training-log-content?file=${encodeURIComponent(logFile)}`);
|
|||
|
|
const result = await response.json();
|
|||
|
|
|
|||
|
|
if (result.code === 0 && result.data) {
|
|||
|
|
logFullContent = result.data.content || '';
|
|||
|
|
logContent.textContent = logFullContent || '(空日志)';
|
|||
|
|
logFileInfo.textContent = result.data.file + ' (' + result.data.size + ')';
|
|||
|
|
// 清空搜索
|
|||
|
|
document.getElementById('logSearchInput').value = '';
|
|||
|
|
document.getElementById('logMatchCount').textContent = '';
|
|||
|
|
// 滚动到底部
|
|||
|
|
scrollToLogBottom();
|
|||
|
|
} else {
|
|||
|
|
logContent.textContent = '加载失败: ' + (result.message || '未知错误');
|
|||
|
|
logFileInfo.textContent = '加载失败';
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('加载训练日志内容失败:', error);
|
|||
|
|
logContent.textContent = '加载失败: ' + error.message;
|
|||
|
|
logFileInfo.textContent = '加载失败';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 加载日志文件列表
|
|||
|
|
async function loadLogFiles() {
|
|||
|
|
const datePicker = document.getElementById('logDatePicker');
|
|||
|
|
const logTypeSelect = document.getElementById('logTypeSelect');
|
|||
|
|
const selectedDate = datePicker ? datePicker.value : new Date().toISOString().split('T')[0];
|
|||
|
|
|
|||
|
|
if (!logTypeSelect) return;
|
|||
|
|
logTypeSelect.innerHTML = '<option value="">加载中...</option>';
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const response = await fetch(`${window.API_BASE}/log-files?date=${selectedDate}`);
|
|||
|
|
const result = await response.json();
|
|||
|
|
|
|||
|
|
if (result.code === 0 && result.data) {
|
|||
|
|
logTypeSelect.innerHTML = '<option value="">请选择日志文件</option>';
|
|||
|
|
result.data.forEach(log => {
|
|||
|
|
const option = document.createElement('option');
|
|||
|
|
option.value = log.file;
|
|||
|
|
option.textContent = log.name + ' (' + log.size + ')';
|
|||
|
|
logTypeSelect.appendChild(option);
|
|||
|
|
});
|
|||
|
|
// 如果有日志文件,自动加载第一个
|
|||
|
|
if (result.data.length > 0) {
|
|||
|
|
logTypeSelect.value = result.data[0].file;
|
|||
|
|
loadSelectedLog();
|
|||
|
|
} else {
|
|||
|
|
logTypeSelect.innerHTML = '<option value="">暂无日志文件</option>';
|
|||
|
|
document.getElementById('logContent').textContent = '该日期暂无日志文件';
|
|||
|
|
document.getElementById('logFileInfo').textContent = '无日志文件';
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
logTypeSelect.innerHTML = '<option value="">暂无日志文件</option>';
|
|||
|
|
document.getElementById('logContent').textContent = '该日期暂无日志文件';
|
|||
|
|
document.getElementById('logFileInfo').textContent = '无日志文件';
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('加载日志文件列表失败:', error);
|
|||
|
|
logTypeSelect.innerHTML = '<option value="">加载失败</option>';
|
|||
|
|
document.getElementById('logContent').textContent = '加载日志文件列表失败: ' + error.message;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 加载选中的日志
|
|||
|
|
async function loadSelectedLog() {
|
|||
|
|
const logTypeSelect = document.getElementById('logTypeSelect');
|
|||
|
|
const logFile = logTypeSelect.value;
|
|||
|
|
const logContent = document.getElementById('logContent');
|
|||
|
|
const logFileInfo = document.getElementById('logFileInfo');
|
|||
|
|
|
|||
|
|
if (!logFile) {
|
|||
|
|
logContent.textContent = '请选择日志文件';
|
|||
|
|
logFileInfo.textContent = '无日志文件';
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
logContent.textContent = '加载中...';
|
|||
|
|
logFileInfo.textContent = '加载中...';
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const response = await fetch(`${window.API_BASE}/log-content?file=${encodeURIComponent(logFile)}`);
|
|||
|
|
const result = await response.json();
|
|||
|
|
|
|||
|
|
if (result.code === 0) {
|
|||
|
|
logFullContent = result.data.content || '';
|
|||
|
|
logContent.textContent = logFullContent || '(空日志)';
|
|||
|
|
logFileInfo.textContent = result.data.file + ' (' + result.data.size + ')';
|
|||
|
|
// 清空搜索框和匹配计数
|
|||
|
|
document.getElementById('logSearchInput').value = '';
|
|||
|
|
document.getElementById('logMatchCount').textContent = '';
|
|||
|
|
// 滚动到最底部
|
|||
|
|
scrollToLogBottom();
|
|||
|
|
} else {
|
|||
|
|
logContent.textContent = '加载失败: ' + (result.message || '未知错误');
|
|||
|
|
logFileInfo.textContent = '加载失败';
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('加载日志内容失败:', error);
|
|||
|
|
logContent.textContent = '加载日志内容失败: ' + error.message;
|
|||
|
|
logFileInfo.textContent = '加载失败';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 过滤日志内容
|
|||
|
|
function filterLogContent() {
|
|||
|
|
const searchInput = document.getElementById('logSearchInput');
|
|||
|
|
const matchCount = document.getElementById('logMatchCount');
|
|||
|
|
const logContent = document.getElementById('logContent');
|
|||
|
|
|
|||
|
|
if (!searchInput || !matchCount || !logContent) return;
|
|||
|
|
|
|||
|
|
const keyword = searchInput.value.trim();
|
|||
|
|
|
|||
|
|
if (!keyword) {
|
|||
|
|
logContent.textContent = logFullContent || '(空日志)';
|
|||
|
|
matchCount.textContent = '';
|
|||
|
|
scrollToLogBottom();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const lines = logFullContent.split('\n');
|
|||
|
|
const matchingLines = lines.filter(line => line.toLowerCase().includes(keyword.toLowerCase()));
|
|||
|
|
|
|||
|
|
if (matchingLines.length > 0) {
|
|||
|
|
logContent.textContent = matchingLines.join('\n');
|
|||
|
|
matchCount.textContent = `(${matchingLines.length}条匹配)`;
|
|||
|
|
// 滚动到最底部查看最新匹配
|
|||
|
|
scrollToLogBottom();
|
|||
|
|
} else {
|
|||
|
|
logContent.textContent = '未找到匹配的日志';
|
|||
|
|
matchCount.textContent = '(0条匹配)';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 清空日志内容显示
|
|||
|
|
function clearLogContent() {
|
|||
|
|
document.getElementById('logContent').textContent = '日志内容将在这里显示...';
|
|||
|
|
document.getElementById('logFileInfo').textContent = '请选择日志文件';
|
|||
|
|
const logTypeSelect = document.getElementById('logTypeSelect');
|
|||
|
|
if (logTypeSelect) logTypeSelect.value = '';
|
|||
|
|
document.getElementById('logSearchInput').value = '';
|
|||
|
|
document.getElementById('logMatchCount').textContent = '';
|
|||
|
|
logFullContent = '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 导出服务函数
|
|||
|
|
window.SystemService = {
|
|||
|
|
fetchSystemMetrics,
|
|||
|
|
setRefreshInterval,
|
|||
|
|
stopLogAutoRefresh,
|
|||
|
|
refreshLogs,
|
|||
|
|
scrollToLogBottom,
|
|||
|
|
switchLogTab,
|
|||
|
|
initLogViewer,
|
|||
|
|
loadTrainingLogFiles,
|
|||
|
|
loadSelectedTrainingLog,
|
|||
|
|
loadLogFiles,
|
|||
|
|
loadSelectedLog,
|
|||
|
|
filterLogContent,
|
|||
|
|
clearLogContent
|
|||
|
|
};
|