Skip to main content
Glama
dashboard.js11.4 kB
// 任务执行监控系统 - 首页功能 class Dashboard { constructor() { this.stats = { totalPlans: 0, totalTasks: 0, completionRate: 0, runningTasks: 0 }; this.plans = []; this.refreshInterval = null; this.init(); } async init() { await this.loadStats(); await this.loadPlans(); this.bindEvents(); this.startAutoRefresh(); } async loadStats() { try { // 获取所有测试计划 const plansResponse = await fetch('/api/test-plans'); const plansResult = await plansResponse.json(); // 处理API返回的数据格式 const plans = plansResult.data || []; let totalTasks = 0; let completedTasks = 0; let runningTasks = 0; // 统计任务数据 for (const plan of plans) { const tasksResponse = await fetch(`/api/test-plans?uuid=${plan.uuid}`); const tasksResult = await tasksResponse.json(); const tasks = tasksResult.data || []; totalTasks += tasks.length; tasks.forEach(task => { if (task.status === 'completed') { completedTasks++; } else if (task.status === 'running') { runningTasks++; } }); } this.stats = { totalPlans: plans.length, totalTasks: totalTasks, completionRate: totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0, runningTasks: runningTasks }; this.updateStatsDisplay(); } catch (error) { console.error('加载统计数据失败:', error); this.showError('加载统计数据失败'); } } async loadPlans() { try { const response = await fetch('/api/test-plans'); const result = await response.json(); this.plans = result.data || []; this.updatePlansTable(); } catch (error) { console.error('加载测试计划失败:', error); this.showError('加载测试计划失败'); } } updateStatsDisplay() { const totalPlansEl = document.getElementById('total-plans'); const totalTasksEl = document.getElementById('total-tasks'); const completionRateEl = document.getElementById('completion-rate'); const runningTasksEl = document.getElementById('running-tasks'); if (totalPlansEl) totalPlansEl.textContent = this.stats.totalPlans; if (totalTasksEl) totalTasksEl.textContent = this.stats.totalTasks; if (completionRateEl) completionRateEl.textContent = `${this.stats.completionRate}%`; if (runningTasksEl) runningTasksEl.textContent = this.stats.runningTasks; } updatePlansTable() { const tbody = document.getElementById('plans-table-body'); if (!tbody) { console.error('找不到plans-table-body元素'); return; } tbody.innerHTML = ''; if (this.plans.length === 0) { tbody.innerHTML = ` <tr> <td colspan="5" style="text-align: center; padding: 2rem; opacity: 0.7;"> 暂无测试计划 </td> </tr> `; return; } this.plans.forEach(plan => { const row = document.createElement('tr'); row.innerHTML = ` <td> <div style="font-weight: bold;">${this.escapeHtml(plan.name)}</div> <div style="font-size: 0.875rem; opacity: 0.7;">${this.escapeHtml(plan.description || '')}</div> </td> <td> <span class="status-badge status-${plan.status || 'pending'}"> ${this.getStatusText(plan.status)} </span> </td> <td>${plan.task_count || 0}</td> <td>${this.formatDate(plan.created_at)}</td> <td> <div style="display: flex; gap: 0.5rem;"> <button class="btn btn-sm" onclick="dashboard.viewPlan('${plan.uuid}')"> 查看详情 </button> <button class="btn btn-sm btn-secondary" onclick="dashboard.goToMonitor('${plan.uuid}')"> 实时监控 </button> </div> </td> `; tbody.appendChild(row); }); } bindEvents() { // 刷新按钮 const refreshBtn = document.getElementById('refresh-btn'); if (refreshBtn) { refreshBtn.addEventListener('click', () => { this.refresh(); }); } // 实时监控按钮 const monitorBtn = document.getElementById('monitor-btn'); if (monitorBtn) { monitorBtn.addEventListener('click', () => { window.location.href = '/monitor'; }); } } async refresh() { const refreshBtn = document.getElementById('refresh-btn'); const originalText = refreshBtn.textContent; refreshBtn.textContent = '刷新中...'; refreshBtn.disabled = true; try { await this.loadStats(); await this.loadPlans(); this.showSuccess('数据已刷新'); } catch (error) { this.showError('刷新失败'); } finally { refreshBtn.textContent = originalText; refreshBtn.disabled = false; } } startAutoRefresh() { // 每30秒自动刷新一次 this.refreshInterval = setInterval(() => { this.loadStats(); this.loadPlans(); }, 30000); } stopAutoRefresh() { if (this.refreshInterval) { clearInterval(this.refreshInterval); this.refreshInterval = null; } } viewPlan(uuid) { window.location.href = `/plan/${uuid}`; } goToMonitor(uuid) { window.location.href = `/monitor?plan=${uuid}`; } getStatusText(status) { const statusMap = { 'pending': '待执行', 'running': '执行中', 'completed': '已完成', 'failed': '失败', 'paused': '已暂停' }; return statusMap[status] || '未知'; } formatDate(dateString) { if (!dateString) return '-'; const date = new Date(dateString); return date.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); } 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(); } } // 添加动画样式 const style = document.createElement('style'); style.textContent = ` @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes slideOut { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } } .status-badge { display: inline-block; padding: 0.25rem 0.75rem; border-radius: 9999px; font-size: 0.75rem; font-weight: bold; text-transform: uppercase; } .status-pending { background: #f3f4f6; color: #6b7280; } .status-running { background: #dbeafe; color: #1e40af; } .status-completed { background: #dcfce7; color: #166534; } .status-failed { background: #fee2e2; color: #991b1b; } .status-paused { background: #fef3c7; color: #92400e; } .btn-sm { padding: 0.375rem 0.75rem; font-size: 0.875rem; } `; document.head.appendChild(style); // 初始化仪表板 let dashboard; // 等待DOM完全加载 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initDashboard); } else { initDashboard(); } function initDashboard() { console.log('开始初始化仪表板...'); console.log('当前DOM状态:', document.readyState); // 确保所有必要的元素都存在 const requiredElements = [ 'total-plans', 'total-tasks', 'completion-rate', 'running-tasks', 'plans-table-body', 'refresh-btn', 'monitor-btn' ]; console.log('检查必要元素...'); requiredElements.forEach(id => { const element = document.getElementById(id); console.log(`元素 ${id}:`, element ? '存在' : '不存在'); }); const missingElements = requiredElements.filter(id => !document.getElementById(id)); if (missingElements.length > 0) { console.error('缺少必要的HTML元素:', missingElements); console.log('页面HTML:', document.documentElement.outerHTML.substring(0, 1000)); } try { dashboard = new Dashboard(); console.log('仪表板初始化成功'); } catch (error) { console.error('仪表板初始化失败:', error); } } // 页面卸载时清理 window.addEventListener('beforeunload', () => { if (dashboard) { dashboard.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