Skip to main content
Glama
monitor.js14.9 kB
// 任务执行监控系统 - 实时监控页面功能 class Monitor { constructor() { this.planUuid = this.getPlanUuidFromUrl(); this.plan = null; this.tasks = []; this.logs = []; this.refreshInterval = null; this.logRefreshInterval = null; this.isExecuting = false; this.init(); } async init() { await this.loadPlan(); await this.loadTasks(); this.bindEvents(); this.startAutoRefresh(); this.initializeLogs(); } getPlanUuidFromUrl() { const urlParams = new URLSearchParams(window.location.search); return urlParams.get('plan'); } async loadPlan() { if (!this.planUuid) { document.getElementById('monitor-title').textContent = '全局监控'; document.getElementById('breadcrumb-plan-name').textContent = '全局监控'; return; } try { const response = await fetch('/api/test-plans'); const plans = await response.json(); this.plan = plans.find(p => p.uuid === this.planUuid); if (this.plan) { document.getElementById('monitor-title').textContent = `${this.plan.name} - 实时监控`; document.getElementById('breadcrumb-plan-name').textContent = this.plan.name; } } catch (error) { console.error('加载计划信息失败:', error); } } async loadTasks() { try { let tasks = []; if (this.planUuid) { // 加载特定计划的任务 const response = await fetch(`/api/test-plans?uuid=${this.planUuid}`); tasks = await response.json(); } else { // 加载所有任务 const plansResponse = await fetch('/api/test-plans'); const plans = await plansResponse.json(); for (const plan of plans) { const tasksResponse = await fetch(`/api/test-plans?uuid=${plan.uuid}`); const planTasks = await tasksResponse.json(); tasks = tasks.concat(planTasks.map(task => ({ ...task, planName: plan.name }))); } } this.tasks = tasks; this.updateProgress(); this.updateStats(); } catch (error) { console.error('加载任务失败:', error); this.showError('加载任务失败'); } } updateProgress() { const stats = this.calculateStats(); const progressPercentage = stats.total > 0 ? Math.round((stats.completed / stats.total) * 100) : 0; // 更新进度条 const progressBar = document.getElementById('progress-bar'); progressBar.style.width = `${progressPercentage}%`; // 更新进度文本 document.getElementById('progress-text').textContent = `${stats.completed}/${stats.total} (${progressPercentage}%)`; // 更新进度详情 document.getElementById('progress-completed').textContent = stats.completed; document.getElementById('progress-running').textContent = stats.running; document.getElementById('progress-failed').textContent = stats.failed; document.getElementById('progress-pending').textContent = stats.pending; // 更新执行状态 this.isExecuting = stats.running > 0; const statusElement = document.getElementById('execution-status'); if (this.isExecuting) { statusElement.textContent = '执行中'; statusElement.className = 'status-badge status-running'; } else if (stats.completed === stats.total && stats.total > 0) { statusElement.textContent = '已完成'; statusElement.className = 'status-badge status-completed'; } else { statusElement.textContent = '待执行'; statusElement.className = 'status-badge status-pending'; } } updateStats() { const stats = this.calculateStats(); // 更新统计图表 this.updateChart(stats); // 更新统计摘要 document.getElementById('stat-total').textContent = stats.total; document.getElementById('stat-success-rate').textContent = stats.total > 0 ? `${Math.round((stats.completed / stats.total) * 100)}%` : '0%'; document.getElementById('stat-avg-response').textContent = this.calculateAverageResponseTime() + 'ms'; document.getElementById('stat-error-rate').textContent = stats.total > 0 ? `${Math.round((stats.failed / stats.total) * 100)}%` : '0%'; } calculateStats() { const stats = { total: this.tasks.length, completed: 0, running: 0, failed: 0, pending: 0 }; this.tasks.forEach(task => { switch (task.status) { case 'completed': stats.completed++; break; case 'running': stats.running++; break; case 'failed': case 'error': stats.failed++; break; default: stats.pending++; } }); return stats; } calculateAverageResponseTime() { const completedTasks = this.tasks.filter(task => task.status === 'completed' && task.response_time); if (completedTasks.length === 0) return 0; const totalTime = completedTasks.reduce((sum, task) => sum + (task.response_time || 0), 0); return Math.round(totalTime / completedTasks.length); } updateChart(stats) { const maxHeight = 100; const maxValue = Math.max(stats.completed, stats.failed, stats.pending, 1); // 更新图表条 const successBar = document.getElementById('chart-success'); const errorBar = document.getElementById('chart-error'); const pendingBar = document.getElementById('chart-pending'); successBar.style.height = `${(stats.completed / maxValue) * maxHeight}px`; errorBar.style.height = `${(stats.failed / maxValue) * maxHeight}px`; pendingBar.style.height = `${(stats.pending / maxValue) * maxHeight}px`; // 更新标签 document.getElementById('chart-success-label').textContent = stats.completed; document.getElementById('chart-error-label').textContent = stats.failed; document.getElementById('chart-pending-label').textContent = stats.pending; } initializeLogs() { this.addLog('info', '监控系统已启动'); if (this.planUuid) { this.addLog('info', `开始监控计划: ${this.plan ? this.plan.name : this.planUuid}`); } else { this.addLog('info', '开始全局监控'); } // 开始日志刷新 this.logRefreshInterval = setInterval(() => { this.checkForUpdates(); }, 5000); } addLog(type, message) { const timestamp = new Date().toLocaleTimeString('zh-CN'); const log = { type, message, timestamp }; this.logs.unshift(log); // 限制日志数量 if (this.logs.length > 100) { this.logs = this.logs.slice(0, 100); } this.updateLogsDisplay(); } updateLogsDisplay() { const container = document.getElementById('logs-container'); container.innerHTML = ''; this.logs.forEach(log => { const logEntry = document.createElement('div'); logEntry.className = `log-entry ${log.type}`; logEntry.innerHTML = ` <span class="log-time">${log.timestamp}</span> <span class="log-message">${this.escapeHtml(log.message)}</span> `; container.appendChild(logEntry); }); // 滚动到最新日志 container.scrollTop = 0; } async checkForUpdates() { const previousTasksCount = this.tasks.length; const previousStats = this.calculateStats(); await this.loadTasks(); const currentStats = this.calculateStats(); // 检查状态变化并添加日志 if (currentStats.completed > previousStats.completed) { const diff = currentStats.completed - previousStats.completed; this.addLog('success', `${diff} 个任务执行完成`); } if (currentStats.failed > previousStats.failed) { const diff = currentStats.failed - previousStats.failed; this.addLog('error', `${diff} 个任务执行失败`); } if (currentStats.running > previousStats.running) { const diff = currentStats.running - previousStats.running; this.addLog('info', `${diff} 个任务开始执行`); } // 检查执行状态变化 if (!this.isExecuting && currentStats.running > 0) { this.addLog('info', '开始执行任务'); } else if (this.isExecuting && currentStats.running === 0) { if (currentStats.completed === currentStats.total && currentStats.total > 0) { this.addLog('success', '所有任务执行完成'); } else { this.addLog('info', '任务执行已停止'); } } } bindEvents() { // 返回按钮 document.getElementById('back-btn').addEventListener('click', () => { if (this.planUuid) { window.location.href = `/plan/${this.planUuid}`; } else { window.location.href = '/'; } }); // 刷新按钮 document.getElementById('refresh-btn').addEventListener('click', () => { this.refresh(); }); // 开始执行按钮 document.getElementById('start-btn').addEventListener('click', () => { this.startExecution(); }); // 停止执行按钮 document.getElementById('stop-btn').addEventListener('click', () => { this.stopExecution(); }); // 清空日志按钮 document.getElementById('clear-logs-btn').addEventListener('click', () => { this.clearLogs(); }); } async refresh() { const refreshBtn = document.getElementById('refresh-btn'); const originalText = refreshBtn.textContent; refreshBtn.textContent = '刷新中...'; refreshBtn.disabled = true; try { await this.loadTasks(); this.addLog('info', '数据已刷新'); } catch (error) { this.addLog('error', '刷新失败'); } finally { refreshBtn.textContent = originalText; refreshBtn.disabled = false; } } async startExecution() { if (!this.planUuid) { this.showError('请选择特定计划进行执行'); return; } const startBtn = document.getElementById('start-btn'); const originalText = startBtn.textContent; startBtn.textContent = '启动中...'; startBtn.disabled = true; try { const response = await fetch(`/api/test-plans/${this.planUuid}/execute`, { method: 'POST' }); if (response.ok) { this.addLog('success', '计划执行已开始'); // 立即刷新数据 setTimeout(() => { this.loadTasks(); }, 1000); } else { throw new Error('执行失败'); } } catch (error) { console.error('启动执行失败:', error); this.addLog('error', '启动执行失败'); } finally { startBtn.textContent = originalText; startBtn.disabled = false; } } stopExecution() { // 这里可以添加停止执行的逻辑 this.addLog('warning', '停止执行功能待实现'); } clearLogs() { this.logs = []; this.updateLogsDisplay(); this.addLog('info', '日志已清空'); } startAutoRefresh() { // 每10秒自动刷新一次 this.refreshInterval = setInterval(() => { this.loadTasks(); }, 10000); } stopAutoRefresh() { if (this.refreshInterval) { clearInterval(this.refreshInterval); this.refreshInterval = null; } if (this.logRefreshInterval) { clearInterval(this.logRefreshInterval); this.logRefreshInterval = null; } } escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } showSuccess(message) { this.showToast(message, 'success'); } showError(message) { this.showToast(message, 'error'); } showToast(message, type = 'info') { // 创建toast元素 const toast = document.createElement('div'); toast.className = `toast toast-${type}`; toast.style.cssText = ` position: fixed; top: 20px; right: 20px; padding: 1rem 1.5rem; border-radius: 8px; color: white; font-weight: bold; z-index: 1000; animation: slideIn 0.3s ease; `; if (type === 'success') { toast.style.backgroundColor = '#10b981'; } else if (type === 'error') { toast.style.backgroundColor = '#ef4444'; } else { toast.style.backgroundColor = '#3b82f6'; } toast.textContent = message; document.body.appendChild(toast); // 3秒后自动移除 setTimeout(() => { toast.style.animation = 'slideOut 0.3s ease'; setTimeout(() => { if (toast.parentNode) { toast.parentNode.removeChild(toast); } }, 300); }, 3000); } destroy() { this.stopAutoRefresh(); } } // 初始化监控页面 let monitor; document.addEventListener('DOMContentLoaded', () => { monitor = new Monitor(); }); // 页面卸载时清理 window.addEventListener('beforeunload', () => { if (monitor) { monitor.destroy(); } });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/FactrueSolin/api-test-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server