← back to Dear Bubbe Nextjs
archived/tests/monitoring-dashboard.html
356 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dear Bubbe - Health Monitoring Dashboard</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
min-height: 100vh;
padding: 20px;
}
.dashboard {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
}
.header p {
font-size: 1.1rem;
opacity: 0.9;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 15px;
padding: 25px;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.stat-title {
font-size: 1.2rem;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 10px;
}
.status-indicator {
width: 12px;
height: 12px;
border-radius: 50%;
display: inline-block;
}
.status-online { background: #10B981; }
.status-warning { background: #F59E0B; }
.status-error { background: #EF4444; }
.stat-value {
font-size: 2rem;
font-weight: bold;
margin-bottom: 10px;
}
.stat-detail {
font-size: 0.9rem;
opacity: 0.8;
}
.logs-section {
background: rgba(0, 0, 0, 0.3);
border-radius: 15px;
padding: 25px;
margin-bottom: 20px;
}
.logs-header {
display: flex;
justify-content: between;
align-items: center;
margin-bottom: 15px;
}
.logs-content {
background: #1a1a1a;
border-radius: 8px;
padding: 15px;
font-family: 'Courier New', monospace;
max-height: 400px;
overflow-y: auto;
font-size: 0.85rem;
line-height: 1.4;
}
.log-line {
margin-bottom: 2px;
padding: 2px 0;
}
.log-pass { color: #10B981; }
.log-warn { color: #F59E0B; }
.log-error { color: #EF4444; }
.log-info { color: #60A5FA; }
.refresh-btn {
background: rgba(255, 255, 255, 0.2);
border: none;
color: white;
padding: 10px 20px;
border-radius: 8px;
cursor: pointer;
font-size: 0.9rem;
transition: all 0.3s ease;
}
.refresh-btn:hover {
background: rgba(255, 255, 255, 0.3);
}
.footer {
text-align: center;
margin-top: 30px;
opacity: 0.8;
}
@media (max-width: 768px) {
.header h1 {
font-size: 2rem;
}
.stats-grid {
grid-template-columns: 1fr;
}
.stat-card {
padding: 20px;
}
}
</style>
</head>
<body>
<div class="dashboard">
<div class="header">
<h1>👵 Dear Bubbe Health Monitor</h1>
<p>24/7 monitoring to keep Bubbe kvetching without interruption</p>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-title">
<span class="status-indicator" id="pm2-indicator"></span>
PM2 Process Status
</div>
<div class="stat-value" id="pm2-status">Loading...</div>
<div class="stat-detail" id="pm2-detail">Checking process health</div>
</div>
<div class="stat-card">
<div class="stat-title">
<span class="status-indicator" id="api-indicator"></span>
API Response Time
</div>
<div class="stat-value" id="api-response">Loading...</div>
<div class="stat-detail" id="api-detail">Last checked: Never</div>
</div>
<div class="stat-card">
<div class="stat-title">
<span class="status-indicator" id="voice-indicator"></span>
Voice API Status
</div>
<div class="stat-value" id="voice-status">Loading...</div>
<div class="stat-detail" id="voice-detail">ElevenLabs integration</div>
</div>
<div class="stat-card">
<div class="stat-title">
<span class="status-indicator" id="memory-indicator"></span>
Memory Usage
</div>
<div class="stat-value" id="memory-usage">Loading...</div>
<div class="stat-detail" id="memory-detail">Current consumption</div>
</div>
<div class="stat-card">
<div class="stat-title">
<span class="status-indicator" id="uptime-indicator"></span>
System Uptime
</div>
<div class="stat-value" id="uptime-value">Loading...</div>
<div class="stat-detail" id="uptime-detail">Since last restart</div>
</div>
<div class="stat-card">
<div class="stat-title">
<span class="status-indicator" id="health-indicator"></span>
Overall Health Score
</div>
<div class="stat-value" id="health-score">Loading...</div>
<div class="stat-detail" id="health-detail">Last check results</div>
</div>
</div>
<div class="logs-section">
<div class="logs-header">
<h3>Recent Health Check Logs</h3>
<button class="refresh-btn" onclick="refreshLogs()">🔄 Refresh Logs</button>
</div>
<div class="logs-content" id="logs-content">
Loading recent health check logs...
</div>
</div>
<div class="footer">
<p>Auto-refreshes every 30 seconds • Health checks run every 5 minutes</p>
<p>Last updated: <span id="last-update">Loading...</span></p>
</div>
</div>
<script>
// Update timestamps
function updateTimestamp() {
document.getElementById('last-update').textContent = new Date().toLocaleString();
}
// Fetch and display logs
async function refreshLogs() {
try {
const response = await fetch('/api/health-logs');
const logs = await response.text();
const logsContent = document.getElementById('logs-content');
const lines = logs.split('\n').slice(-50); // Last 50 lines
logsContent.innerHTML = lines
.filter(line => line.trim())
.map(line => {
let className = 'log-info';
if (line.includes('✅') || line.includes('PASS')) className = 'log-pass';
else if (line.includes('⚠️') || line.includes('WARN')) className = 'log-warn';
else if (line.includes('❌') || line.includes('FAIL') || line.includes('ERROR')) className = 'log-error';
return `<div class="log-line ${className}">${escapeHtml(line)}</div>`;
})
.join('');
// Auto-scroll to bottom
logsContent.scrollTop = logsContent.scrollHeight;
} catch (error) {
console.error('Failed to fetch logs:', error);
document.getElementById('logs-content').innerHTML =
'<div class="log-line log-error">Failed to load logs. Check if health monitoring is running.</div>';
}
}
// Fetch system stats
async function refreshStats() {
try {
// PM2 Status
const pm2Response = await fetch('/api/pm2-status');
const pm2Data = await pm2Response.json();
updateStatusCard('pm2', pm2Data.status, pm2Data.details);
// API Response Time
const apiStart = Date.now();
const apiResponse = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: 'ping', userId: 'monitor', mode: 'bubbe' })
});
const apiTime = Date.now() - apiStart;
updateStatusCard('api', apiResponse.ok ? 'online' : 'error', `${apiTime}ms response`);
// Voice API
const voiceStart = Date.now();
const voiceResponse = await fetch('/api/bubbe-voice', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: 'test', voiceId: '21m00Tcm4TlvDq8ikWAM' })
});
const voiceTime = Date.now() - voiceStart;
updateStatusCard('voice', voiceResponse.ok ? 'online' : 'warning', `${voiceTime}ms response`);
// Memory and other stats would require server endpoints
} catch (error) {
console.error('Failed to fetch stats:', error);
}
}
// Update status card
function updateStatusCard(type, status, details) {
const indicator = document.getElementById(`${type}-indicator`);
const statusElement = document.getElementById(`${type}-status`);
const detailElement = document.getElementById(`${type}-detail`);
// Update indicator color
indicator.className = 'status-indicator';
if (status === 'online') indicator.classList.add('status-online');
else if (status === 'warning') indicator.classList.add('status-warning');
else indicator.classList.add('status-error');
// Update status text
if (type === 'api' && status === 'online') {
statusElement.textContent = details;
detailElement.textContent = 'Chat API responding';
} else if (type === 'voice' && status === 'online') {
statusElement.textContent = details;
detailElement.textContent = 'ElevenLabs responding';
} else {
statusElement.textContent = status.toUpperCase();
detailElement.textContent = details;
}
}
// Escape HTML
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Initialize dashboard
function init() {
updateTimestamp();
refreshLogs();
refreshStats();
// Set up auto-refresh
setInterval(refreshLogs, 30000); // Every 30 seconds
setInterval(refreshStats, 45000); // Every 45 seconds
setInterval(updateTimestamp, 1000); // Every second
}
// Start dashboard when page loads
window.addEventListener('load', init);
</script>
</body>
</html>