Skip to main content
Glama
dashboard.html12.1 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>KafkaIQ Dashboard</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; padding: 20px; } .container { max-width: 1400px; margin: 0 auto; } .header { background: rgba(255, 255, 255, 0.95); padding: 30px; border-radius: 20px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); margin-bottom: 30px; backdrop-filter: blur(10px); } .header h1 { color: #667eea; font-size: 2.5em; margin-bottom: 10px; display: flex; align-items: center; gap: 15px; } .header p { color: #666; font-size: 1.1em; } .status-badge { display: inline-block; padding: 8px 20px; background: #10b981; color: white; border-radius: 20px; font-size: 0.9em; font-weight: bold; animation: pulse 2s infinite; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } } .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin-bottom: 30px; } .card { background: rgba(255, 255, 255, 0.95); padding: 25px; border-radius: 15px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); 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.3); } .card h3 { color: #667eea; margin-bottom: 15px; font-size: 1.3em; display: flex; align-items: center; gap: 10px; } .metric { font-size: 2.5em; font-weight: bold; color: #333; margin: 10px 0; } .metric-label { color: #666; font-size: 0.9em; text-transform: uppercase; letter-spacing: 1px; } .topic-list { list-style: none; } .topic-item { padding: 12px; background: #f8f9fa; margin: 8px 0; border-radius: 8px; border-left: 4px solid #667eea; display: flex; justify-content: space-between; align-items: center; transition: background 0.3s ease; } .topic-item:hover { background: #e9ecef; } .topic-name { font-weight: 600; color: #333; } .topic-partitions { color: #666; font-size: 0.9em; } .consumer-group { background: #f8f9fa; padding: 15px; margin: 10px 0; border-radius: 10px; border-left: 4px solid #10b981; } .consumer-group-name { font-weight: bold; color: #333; margin-bottom: 10px; } .lag-info { display: flex; justify-content: space-between; align-items: center; margin-top: 8px; padding: 8px; background: white; border-radius: 6px; } .lag-value { font-weight: bold; font-size: 1.2em; } .lag-ok { color: #10b981; } .lag-warn { color: #f59e0b; } .lag-critical { color: #ef4444; } .refresh-btn { background: #667eea; color: white; border: none; padding: 12px 30px; border-radius: 8px; font-size: 1em; font-weight: 600; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4); } .refresh-btn:hover { background: #5568d3; transform: translateY(-2px); box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6); } .refresh-btn:active { transform: translateY(0); } .loading { text-align: center; padding: 40px; color: #666; font-size: 1.2em; } .error { background: #fee; color: #c33; padding: 15px; border-radius: 8px; border-left: 4px solid #c33; margin: 10px 0; } .icon { font-size: 1.5em; } .auto-refresh { display: flex; align-items: center; gap: 10px; margin-top: 15px; } .auto-refresh input { width: 20px; height: 20px; cursor: pointer; } .timestamp { color: #999; font-size: 0.9em; margin-top: 10px; } </style> </head> <body> <div class="container"> <div class="header"> <h1> <span class="icon">🧠</span> KafkaIQ Dashboard <span class="status-badge" id="status">● LIVE</span> </h1> <p>Real-time Kafka Cluster Monitoring & Management</p> <div style="margin-top: 20px;"> <button class="refresh-btn" onclick="loadData()">🔄 Refresh Data</button> <div class="auto-refresh"> <input type="checkbox" id="autoRefresh" onchange="toggleAutoRefresh()"> <label for="autoRefresh">Auto-refresh every 5 seconds</label> </div> <div class="timestamp" id="lastUpdate"></div> </div> </div> <div class="grid"> <div class="card"> <h3><span class="icon">🖥️</span> Cluster Info</h3> <div class="metric-label">Cluster ID</div> <div id="clusterId" style="font-size: 1em; color: #667eea; font-weight: bold; margin: 10px 0;">Loading...</div> <div class="metric-label">Active Brokers</div> <div class="metric" id="brokerCount">-</div> </div> <div class="card"> <h3><span class="icon">📊</span> Topics</h3> <div class="metric-label">Total Topics</div> <div class="metric" id="topicCount">-</div> <div class="metric-label">Total Partitions</div> <div class="metric" id="partitionCount">-</div> </div> <div class="card"> <h3><span class="icon">👥</span> Consumer Groups</h3> <div class="metric-label">Active Groups</div> <div class="metric" id="groupCount">-</div> </div> </div> <div class="grid"> <div class="card"> <h3><span class="icon">📝</span> Topic List</h3> <ul class="topic-list" id="topicList"> <li class="loading">Loading topics...</li> </ul> </div> <div class="card"> <h3><span class="icon">⚡</span> Consumer Lag Monitor</h3> <div id="consumerLag"> <div class="loading">Loading consumer lag data...</div> </div> </div> </div> </div> <script> let autoRefreshInterval = null; async function loadData() { const timestamp = new Date().toLocaleString(); document.getElementById('lastUpdate').textContent = `Last updated: ${timestamp}`; try { // Simulate API calls - In production, these would call the MCP server // For now, we'll use the Python backend to get real data const response = await fetch('/api/dashboard-data'); const data = await response.json(); // Update cluster info document.getElementById('clusterId').textContent = data.cluster.cluster_id || 'N/A'; document.getElementById('brokerCount').textContent = data.cluster.broker_count || 0; document.getElementById('topicCount').textContent = data.topics.length; document.getElementById('partitionCount').textContent = data.cluster.total_partitions || 0; document.getElementById('groupCount').textContent = data.consumer_groups.length; // Update topic list const topicList = document.getElementById('topicList'); topicList.innerHTML = ''; data.topics.forEach(topic => { const li = document.createElement('li'); li.className = 'topic-item'; li.innerHTML = ` <span class="topic-name">${topic.name}</span> <span class="topic-partitions">${topic.partitions} partitions</span> `; topicList.appendChild(li); }); // Update consumer lag const consumerLagDiv = document.getElementById('consumerLag'); consumerLagDiv.innerHTML = ''; if (data.consumer_groups.length === 0) { consumerLagDiv.innerHTML = '<div class="loading">No active consumer groups</div>'; } else { data.consumer_groups.forEach(group => { const groupDiv = document.createElement('div'); groupDiv.className = 'consumer-group'; let lagHtml = `<div class="consumer-group-name">📦 ${group.name}</div>`; group.topics.forEach(topic => { const lagClass = topic.lag < 1000 ? 'lag-ok' : topic.lag < 10000 ? 'lag-warn' : 'lag-critical'; lagHtml += ` <div class="lag-info"> <span>${topic.name}</span> <span class="lag-value ${lagClass}">${topic.lag.toLocaleString()} msgs</span> </div> `; }); groupDiv.innerHTML = lagHtml; consumerLagDiv.appendChild(groupDiv); }); } } catch (error) { console.error('Error loading data:', error); document.getElementById('status').textContent = '● ERROR'; document.getElementById('status').style.background = '#ef4444'; } } function toggleAutoRefresh() { const checkbox = document.getElementById('autoRefresh'); if (checkbox.checked) { autoRefreshInterval = setInterval(loadData, 5000); console.log('Auto-refresh enabled'); } else { if (autoRefreshInterval) { clearInterval(autoRefreshInterval); autoRefreshInterval = null; } console.log('Auto-refresh disabled'); } } // Load data on page load window.addEventListener('load', loadData); </script> </body> </html>

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/ojhaayush03/kafka_mcp'

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