UNPKG

devflow-ai

Version:

Enterprise-grade AI agent orchestration with swarm management UI dashboard

680 lines (598 loc) 21.9 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>DevFlow Dashboard</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #333; min-height: 100vh; } .container { max-width: 1400px; margin: 0 auto; padding: 20px; } header { background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(10px); border-radius: 15px; padding: 20px 30px; margin-bottom: 30px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); display: flex; justify-content: space-between; align-items: center; } h1 { color: #667eea; font-size: 28px; display: flex; align-items: center; gap: 10px; } .status { display: flex; gap: 20px; align-items: center; } .status-indicator { display: flex; align-items: center; gap: 5px; padding: 8px 15px; background: #f0f0f0; border-radius: 20px; font-size: 14px; } .status-dot { width: 8px; height: 8px; border-radius: 50%; background: #4caf50; animation: pulse 2s infinite; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } .dashboard { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 20px; } .card { background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(10px); border-radius: 15px; padding: 20px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); transition: transform 0.3s ease, box-shadow 0.3s ease; } .card:hover { transform: translateY(-5px); box-shadow: 0 15px 40px rgba(0, 0, 0, 0.15); } .card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 2px solid #f0f0f0; } .card-title { font-size: 18px; font-weight: 600; color: #333; } .card-count { background: linear-gradient(135deg, #667eea, #764ba2); color: white; padding: 4px 12px; border-radius: 15px; font-size: 14px; font-weight: 600; } .agent-list, .task-list { list-style: none; max-height: 300px; overflow-y: auto; } .agent-item, .task-item { display: flex; justify-content: space-between; align-items: center; padding: 10px; margin-bottom: 8px; background: #f8f9fa; border-radius: 8px; transition: background 0.2s ease; } .agent-item:hover, .task-item:hover { background: #e9ecef; } .agent-name, .task-name { font-weight: 500; color: #495057; } .agent-status, .task-status { padding: 4px 10px; border-radius: 12px; font-size: 12px; font-weight: 600; text-transform: uppercase; } .status-active { background: #d4f4dd; color: #2e7d32; } .status-idle { background: #fff3cd; color: #856404; } .status-pending { background: #cce5ff; color: #004085; } .status-completed { background: #d1ecf1; color: #0c5460; } .metrics-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; } .metric { background: #f8f9fa; padding: 15px; border-radius: 8px; text-align: center; } .metric-value { font-size: 24px; font-weight: bold; color: #667eea; margin-bottom: 5px; } .metric-label { font-size: 12px; color: #6c757d; text-transform: uppercase; } .topology-view { min-height: 300px; background: #f8f9fa; border-radius: 8px; display: flex; align-items: center; justify-content: center; position: relative; overflow: hidden; } .node { position: absolute; width: 60px; height: 60px; background: linear-gradient(135deg, #667eea, #764ba2); border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); transition: transform 0.3s ease; } .node:hover { transform: scale(1.1); } .connection { position: absolute; height: 2px; background: #667eea; opacity: 0.3; transform-origin: left center; } .controls { display: flex; gap: 10px; margin-top: 20px; } .btn { padding: 10px 20px; border: none; border-radius: 8px; font-weight: 600; cursor: pointer; transition: all 0.3s ease; font-size: 14px; } .btn-primary { background: linear-gradient(135deg, #667eea, #764ba2); color: white; } .btn-primary:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); } .btn-secondary { background: #f8f9fa; color: #495057; border: 2px solid #dee2e6; } .btn-secondary:hover { background: #e9ecef; } .logs-container { background: #1e1e1e; color: #d4d4d4; padding: 15px; border-radius: 8px; font-family: 'Courier New', monospace; font-size: 12px; max-height: 200px; overflow-y: auto; } .log-entry { margin-bottom: 5px; white-space: pre-wrap; } .log-time { color: #858585; margin-right: 10px; } .log-level-info { color: #4ec9b0; } .log-level-warn { color: #ce9178; } .log-level-error { color: #f48771; } .empty-state { text-align: center; padding: 40px; color: #6c757d; } .empty-state-icon { font-size: 48px; margin-bottom: 10px; opacity: 0.3; } </style> </head> <body> <div class="container"> <header> <h1> 🌊 DevFlow Dashboard </h1> <div class="status"> <div class="status-indicator"> <span class="status-dot"></span> <span id="connection-status">Connected</span> </div> <div class="status-indicator"> <span>Swarms: </span> <strong id="swarm-count">0</strong> </div> <div class="status-indicator"> <span>Agents: </span> <strong id="agent-count">0</strong> </div> </div> </header> <div class="dashboard"> <!-- Swarm Topology --> <div class="card" style="grid-column: span 2;"> <div class="card-header"> <h2 class="card-title">🐝 Swarm Topology</h2> <span class="card-count" id="topology-count">0 nodes</span> </div> <div class="topology-view" id="topology-view"> <div class="empty-state"> <div class="empty-state-icon">🔗</div> <p>No active swarms</p> </div> </div> </div> <!-- Active Agents --> <div class="card"> <div class="card-header"> <h2 class="card-title">🤖 Active Agents</h2> <span class="card-count" id="active-agent-count">0</span> </div> <ul class="agent-list" id="agent-list"> <li class="empty-state"> <div class="empty-state-icon">🤖</div> <p>No active agents</p> </li> </ul> </div> <!-- Tasks --> <div class="card"> <div class="card-header"> <h2 class="card-title">📋 Tasks</h2> <span class="card-count" id="task-count">0</span> </div> <ul class="task-list" id="task-list"> <li class="empty-state"> <div class="empty-state-icon">📋</div> <p>No tasks</p> </li> </ul> </div> <!-- Performance Metrics --> <div class="card"> <div class="card-header"> <h2 class="card-title">📊 Performance</h2> </div> <div class="metrics-grid"> <div class="metric"> <div class="metric-value" id="cpu-usage">0%</div> <div class="metric-label">CPU Usage</div> </div> <div class="metric"> <div class="metric-value" id="memory-usage">0 MB</div> <div class="metric-label">Memory</div> </div> <div class="metric"> <div class="metric-value" id="task-throughput">0</div> <div class="metric-label">Tasks/min</div> </div> <div class="metric"> <div class="metric-value" id="response-time">0 ms</div> <div class="metric-label">Avg Response</div> </div> </div> </div> <!-- System Logs --> <div class="card"> <div class="card-header"> <h2 class="card-title">📜 System Logs</h2> </div> <div class="logs-container" id="logs-container"> <div class="log-entry"> <span class="log-time">[00:00:00]</span> <span class="log-level-info">INFO</span> Dashboard initialized </div> </div> </div> <!-- Controls --> <div class="card" style="grid-column: span 2;"> <div class="card-header"> <h2 class="card-title">🎮 Controls</h2> </div> <div class="controls"> <button class="btn btn-primary" onclick="createSwarm()">🚀 Create Swarm</button> <button class="btn btn-primary" onclick="spawnAgent()">🤖 Spawn Agent</button> <button class="btn btn-primary" onclick="createTask()">📋 Create Task</button> <button class="btn btn-secondary" onclick="clearLogs()">🗑️ Clear Logs</button> <button class="btn btn-secondary" onclick="exportMetrics()">📊 Export Metrics</button> </div> </div> </div> </div> <script> let ws; let agents = []; let tasks = []; let swarms = []; let metrics = {}; function connect() { ws = new WebSocket(`ws://${window.location.host}`); ws.onopen = () => { updateConnectionStatus('Connected'); addLog('INFO', 'Connected to DevFlow server'); }; ws.onclose = () => { updateConnectionStatus('Disconnected'); addLog('WARN', 'Disconnected from server'); setTimeout(connect, 3000); }; ws.onerror = (error) => { addLog('ERROR', `WebSocket error: ${error.message || 'Unknown error'}`); }; ws.onmessage = (event) => { const message = JSON.parse(event.data); handleMessage(message); }; } function handleMessage(message) { switch (message.type) { case 'init': initializeDashboard(message.data); break; case 'update': updateDashboard(message.data); break; case 'swarmCreated': addLog('INFO', `Swarm created: ${message.data.id}`); break; case 'taskCreated': addLog('INFO', `Task created: ${message.data.id}`); break; case 'agentStopped': addLog('INFO', `Agent stopped: ${message.data.agentId}`); break; case 'error': addLog('ERROR', message.error); break; } } function initializeDashboard(data) { agents = data.agents || []; tasks = data.tasks || []; swarms = data.swarms || []; metrics = data.metrics || {}; updateAgentList(); updateTaskList(); updateMetrics(); updateTopology(); updateCounts(); } function updateDashboard(data) { if (data.agents) agents = data.agents; if (data.tasks) tasks = data.tasks; if (data.metrics) metrics = data.metrics; updateAgentList(); updateTaskList(); updateMetrics(); updateCounts(); } function updateAgentList() { const list = document.getElementById('agent-list'); if (agents.length === 0) { list.innerHTML = '<li class="empty-state"><div class="empty-state-icon">🤖</div><p>No active agents</p></li>'; return; } list.innerHTML = agents.map(agent => ` <li class="agent-item"> <span class="agent-name">${agent.name || agent.id}</span> <span class="agent-status status-${agent.status || 'idle'}">${agent.status || 'idle'}</span> </li> `).join(''); } function updateTaskList() { const list = document.getElementById('task-list'); if (tasks.length === 0) { list.innerHTML = '<li class="empty-state"><div class="empty-state-icon">📋</div><p>No tasks</p></li>'; return; } list.innerHTML = tasks.map(task => ` <li class="task-item"> <span class="task-name">${task.name || task.id}</span> <span class="task-status status-${task.status || 'pending'}">${task.status || 'pending'}</span> </li> `).join(''); } function updateMetrics() { document.getElementById('cpu-usage').textContent = `${metrics.cpuUsage || 0}%`; document.getElementById('memory-usage').textContent = `${Math.round(metrics.memoryUsage / 1024 / 1024) || 0} MB`; document.getElementById('task-throughput').textContent = metrics.taskThroughput || 0; document.getElementById('response-time').textContent = `${metrics.avgResponseTime || 0} ms`; } function updateTopology() { const view = document.getElementById('topology-view'); if (agents.length === 0) { view.innerHTML = '<div class="empty-state"><div class="empty-state-icon">🔗</div><p>No active swarms</p></div>'; document.getElementById('topology-count').textContent = '0 nodes'; return; } // Simple circular topology visualization view.innerHTML = ''; const centerX = view.offsetWidth / 2; const centerY = view.offsetHeight / 2; const radius = Math.min(centerX, centerY) - 50; agents.forEach((agent, index) => { const angle = (index / agents.length) * 2 * Math.PI; const x = centerX + radius * Math.cos(angle) - 30; const y = centerY + radius * Math.sin(angle) - 30; const node = document.createElement('div'); node.className = 'node'; node.style.left = `${x}px`; node.style.top = `${y}px`; node.textContent = agent.type?.[0]?.toUpperCase() || 'A'; node.title = agent.name || agent.id; view.appendChild(node); }); document.getElementById('topology-count').textContent = `${agents.length} nodes`; } function updateCounts() { document.getElementById('swarm-count').textContent = swarms.length; document.getElementById('agent-count').textContent = agents.length; document.getElementById('active-agent-count').textContent = agents.filter(a => a.status === 'active').length; document.getElementById('task-count').textContent = tasks.length; } function updateConnectionStatus(status) { const element = document.getElementById('connection-status'); element.textContent = status; const dot = element.previousElementSibling; dot.style.background = status === 'Connected' ? '#4caf50' : '#f44336'; } function addLog(level, message) { const container = document.getElementById('logs-container'); const time = new Date().toLocaleTimeString(); const entry = document.createElement('div'); entry.className = 'log-entry'; entry.innerHTML = ` <span class="log-time">[${time}]</span> <span class="log-level-${level.toLowerCase()}">${level}</span> ${message} `; container.appendChild(entry); container.scrollTop = container.scrollHeight; // Keep only last 100 logs while (container.children.length > 100) { container.removeChild(container.firstChild); } } function createSwarm() { if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'createSwarm', data: { topology: 'mesh', maxAgents: 5 } })); } } function spawnAgent() { if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'spawnAgent', data: { type: 'worker', capabilities: ['task-execution'] } })); } } function createTask() { const taskName = prompt('Enter task description:'); if (taskName && ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'createTask', data: { name: taskName, priority: 'normal' } })); } } function clearLogs() { const container = document.getElementById('logs-container'); container.innerHTML = '<div class="log-entry"><span class="log-time">[00:00:00]</span><span class="log-level-info">INFO</span> Logs cleared</div>'; } function exportMetrics() { const data = { timestamp: new Date().toISOString(), agents, tasks, metrics }; const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `devflow-metrics-${Date.now()}.json`; a.click(); URL.revokeObjectURL(url); addLog('INFO', 'Metrics exported'); } // Initialize connection connect(); </script> </body> </html>