bmad-method-mcp
Version:
Breakthrough Method of Agile AI-driven Development with Enhanced MCP Integration
451 lines (395 loc) • 15 kB
HTML
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BMad MCP Server Dashboard</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #0f172a;
color: #e2e8f0;
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.header {
text-align: center;
margin-bottom: 30px;
padding: 20px;
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
border-radius: 12px;
border: 1px solid #475569;
}
.header h1 {
color: #60a5fa;
margin-bottom: 8px;
font-size: 2.2rem;
}
.header p {
color: #94a3b8;
font-size: 1.1rem;
}
.status-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.status-card {
background: #1e293b;
padding: 20px;
border-radius: 12px;
border: 1px solid #475569;
transition: transform 0.2s ease, border-color 0.2s ease;
}
.status-card:hover {
transform: translateY(-2px);
border-color: #60a5fa;
}
.status-card h3 {
color: #60a5fa;
margin-bottom: 12px;
font-size: 1.2rem;
}
.status-value {
font-size: 2rem;
font-weight: bold;
margin: 8px 0;
}
.status-healthy { color: #10b981; }
.status-warning { color: #f59e0b; }
.status-error { color: #ef4444; }
.actions {
display: flex;
gap: 15px;
margin-bottom: 30px;
flex-wrap: wrap;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 1rem;
font-weight: 600;
transition: all 0.2s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: #3b82f6;
color: white;
}
.btn-primary:hover {
background: #2563eb;
transform: translateY(-1px);
}
.btn-danger {
background: #ef4444;
color: white;
}
.btn-danger:hover {
background: #dc2626;
transform: translateY(-1px);
}
.btn-secondary {
background: #475569;
color: #e2e8f0;
}
.btn-secondary:hover {
background: #64748b;
}
.logs-section {
background: #1e293b;
border-radius: 12px;
border: 1px solid #475569;
overflow: hidden;
}
.logs-header {
background: #334155;
padding: 15px 20px;
border-bottom: 1px solid #475569;
display: flex;
justify-content: between;
align-items: center;
}
.logs-header h3 {
color: #60a5fa;
margin: 0;
}
.logs-container {
height: 400px;
overflow-y: auto;
padding: 20px;
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
font-size: 14px;
background: #0f172a;
}
.log-entry {
margin-bottom: 8px;
padding: 4px 0;
border-left: 3px solid transparent;
padding-left: 8px;
}
.log-info { border-left-color: #3b82f6; color: #93c5fd; }
.log-warn { border-left-color: #f59e0b; color: #fbbf24; }
.log-error { border-left-color: #ef4444; color: #fca5a5; }
.log-debug { border-left-color: #8b5cf6; color: #c4b5fd; }
.log-timestamp {
color: #64748b;
font-size: 12px;
}
.footer {
text-align: center;
margin-top: 30px;
padding: 20px;
color: #64748b;
border-top: 1px solid #475569;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.status-indicator {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 8px;
}
.status-indicator.running {
background: #10b981;
animation: pulse 2s infinite;
}
.status-indicator.stopped {
background: #ef4444;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🚀 BMad MCP Server Dashboard</h1>
<p>Monitoring and Management Interface</p>
</div>
<div class="status-grid">
<div class="status-card">
<h3>🔗 Server Status</h3>
<div class="status-value status-healthy" id="server-status">
<span class="status-indicator running"></span>
Running
</div>
<p>MCP Server is active and responding</p>
</div>
<div class="status-card">
<h3>📊 Active Sessions</h3>
<div class="status-value" id="active-sessions">0</div>
<p>Connected AI agents</p>
</div>
<div class="status-card">
<h3>⚡ Tool Calls</h3>
<div class="status-value" id="tool-calls">0</div>
<p>Total MCP tool executions</p>
</div>
<div class="status-card">
<h3>🗄️ Database</h3>
<div class="status-value status-healthy" id="db-status">Connected</div>
<p>SQLite database status</p>
</div>
</div>
<div class="actions">
<button class="btn btn-primary" onclick="refreshStats()">
🔄 Refresh Stats
</button>
<button class="btn btn-secondary" onclick="exportLogs()">
📥 Export Logs
</button>
<button class="btn btn-secondary" onclick="clearLogs()">
🗑️ Clear Logs
</button>
<button class="btn btn-danger" onclick="shutdownServer()" id="shutdown-btn">
🛑 Shutdown Server
</button>
</div>
<div class="logs-section">
<div class="logs-header">
<h3>📝 Server Logs</h3>
<small>Real-time server activity</small>
</div>
<div class="logs-container" id="logs-container">
<div class="log-entry log-info">
<span class="log-timestamp">[Starting up...]</span>
Connecting to BMad MCP Server...
</div>
</div>
</div>
<div class="footer">
<p>BMad Method MCP Server Dashboard | Port: <span id="server-port">3001</span> |
PID: <span id="server-pid">---</span></p>
</div>
</div>
<script>
let logCount = 0;
let toolCallCount = 0;
let activeSessions = 0;
// Initialize dashboard
document.addEventListener('DOMContentLoaded', function() {
initializeDashboard();
startLogPolling();
startStatsPolling();
});
function initializeDashboard() {
// Get server info
fetch('/api/dashboard/info')
.then(response => response.json())
.then(data => {
document.getElementById('server-port').textContent = data.port || '3001';
document.getElementById('server-pid').textContent = data.pid || process.pid;
})
.catch(error => {
console.log('Dashboard API not available yet');
});
}
function startLogPolling() {
// Poll for new logs every 2 seconds
setInterval(fetchLogs, 2000);
}
function startStatsPolling() {
// Poll for stats every 5 seconds
setInterval(refreshStats, 5000);
}
function fetchLogs() {
fetch('/api/dashboard/logs')
.then(response => response.json())
.then(data => {
const logsContainer = document.getElementById('logs-container');
if (data.logs && data.logs.length > 0) {
// Clear existing logs if we have new ones
if (data.logs.length !== logCount) {
logsContainer.innerHTML = '';
data.logs.forEach(log => {
const logEntry = document.createElement('div');
logEntry.className = `log-entry log-${log.level}`;
logEntry.innerHTML = `
<span class="log-timestamp">[${log.timestamp}]</span>
${log.message}
`;
logsContainer.appendChild(logEntry);
});
// Scroll to bottom
logsContainer.scrollTop = logsContainer.scrollHeight;
logCount = data.logs.length;
}
}
})
.catch(error => {
// Dashboard not ready yet, add placeholder log
if (logCount === 0) {
addLogEntry('info', 'Waiting for MCP server connection...');
logCount = 1;
}
});
}
function refreshStats() {
fetch('/api/dashboard/stats')
.then(response => response.json())
.then(data => {
document.getElementById('active-sessions').textContent = data.activeSessions || 0;
document.getElementById('tool-calls').textContent = data.toolCalls || 0;
// Update server status
const serverStatus = document.getElementById('server-status');
if (data.healthy) {
serverStatus.innerHTML = '<span class="status-indicator running"></span>Running';
serverStatus.className = 'status-value status-healthy';
} else {
serverStatus.innerHTML = '<span class="status-indicator stopped"></span>Error';
serverStatus.className = 'status-value status-error';
}
})
.catch(error => {
console.log('Stats not available:', error);
});
}
function addLogEntry(level, message) {
const logsContainer = document.getElementById('logs-container');
const logEntry = document.createElement('div');
logEntry.className = `log-entry log-${level}`;
logEntry.innerHTML = `
<span class="log-timestamp">[${new Date().toLocaleTimeString()}]</span>
${message}
`;
logsContainer.appendChild(logEntry);
logsContainer.scrollTop = logsContainer.scrollHeight;
}
function exportLogs() {
fetch('/api/dashboard/logs/export')
.then(response => response.blob())
.then(blob => {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `bmad-mcp-logs-${new Date().toISOString().split('T')[0]}.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
})
.catch(error => {
alert('Export failed: ' + error.message);
});
}
function clearLogs() {
if (confirm('Clear all logs? This action cannot be undone.')) {
fetch('/api/dashboard/logs/clear', { method: 'POST' })
.then(response => response.json())
.then(data => {
document.getElementById('logs-container').innerHTML = '';
addLogEntry('info', 'Logs cleared');
logCount = 1;
})
.catch(error => {
alert('Clear failed: ' + error.message);
});
}
}
function shutdownServer() {
if (confirm('Shutdown BMad MCP Server? This will close all connections.')) {
const shutdownBtn = document.getElementById('shutdown-btn');
shutdownBtn.textContent = '🔄 Shutting down...';
shutdownBtn.disabled = true;
fetch('/api/dashboard/shutdown', { method: 'POST' })
.then(response => response.json())
.then(data => {
addLogEntry('warn', 'Server shutdown initiated');
// Update UI to show shutdown state
setTimeout(() => {
document.getElementById('server-status').innerHTML =
'<span class="status-indicator stopped"></span>Shutting down';
document.getElementById('server-status').className = 'status-value status-warning';
}, 1000);
// Close dashboard after delay
setTimeout(() => {
window.close();
}, 3000);
})
.catch(error => {
shutdownBtn.textContent = '🛑 Shutdown Server';
shutdownBtn.disabled = false;
alert('Shutdown failed: ' + error.message);
});
}
}
</script>
</body>
</html>