← back to Dear Bubbe Admin
frontend/src/components/MonitoringPage.js
380 lines
import React, { useState, useEffect } from 'react'
import axios from 'axios'
const MonitoringPage = () => {
const [liveData, setLiveData] = useState({})
const [bubbeHealth, setBubbeHealth] = useState({})
const [loading, setLoading] = useState(true)
const [autoRefresh, setAutoRefresh] = useState(true)
useEffect(() => {
fetchLiveData()
checkBubbeHealth()
let interval
if (autoRefresh) {
interval = setInterval(() => {
fetchLiveData()
checkBubbeHealth()
}, 10000) // Refresh every 10 seconds
}
return () => {
if (interval) clearInterval(interval)
}
}, [autoRefresh])
const fetchLiveData = async () => {
try {
const response = await axios.get('/api/monitoring/dashboard')
setLiveData(response.data)
setLoading(false)
} catch (error) {
console.error('Failed to fetch live data:', error)
}
}
const checkBubbeHealth = async () => {
try {
const response = await axios.get('/api/monitoring/bubbe-health')
setBubbeHealth(response.data)
} catch (error) {
console.error('Failed to check Bubbe health:', error)
}
}
if (loading) {
return (
<div style={{ display: 'flex', justifyContent: 'center', padding: '40px' }}>
<div className="loading-spinner"></div>
<span style={{ marginLeft: '12px' }}>Loading monitoring data...</span>
</div>
)
}
return (
<div>
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '24px'
}}>
<h2>Live Monitoring</h2>
<div style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>
<label style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<input
type="checkbox"
checked={autoRefresh}
onChange={(e) => setAutoRefresh(e.target.checked)}
/>
Auto-refresh (10s)
</label>
<button onClick={() => { fetchLiveData(); checkBubbeHealth(); }} className="btn btn-primary">
Refresh Now
</button>
</div>
</div>
{/* Health Status Banner */}
<div className={`card ${bubbeHealth.overall === 'healthy' ? 'success' : bubbeHealth.overall === 'degraded' ? 'warning' : 'critical'}`} style={{ marginBottom: '24px' }}>
<div className="card-content">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<h3 style={{ margin: 0, fontSize: '18px' }}>
{bubbeHealth.overall === 'healthy' ? '✅' : bubbeHealth.overall === 'degraded' ? '⚠️' : '🚨'}
Dear Bubbe API Status: {bubbeHealth.overall || 'Unknown'}
</h3>
<div style={{ fontSize: '14px', marginTop: '4px', opacity: 0.8 }}>
{bubbeHealth.api?.responseTime && `Response time: ${bubbeHealth.api.responseTime}ms`}
{bubbeHealth.pm2?.status && ` • PM2 Status: ${bubbeHealth.pm2.status}`}
</div>
</div>
<div style={{ fontSize: '12px', opacity: 0.7 }}>
Last checked: {bubbeHealth.timestamp ? new Date(bubbeHealth.timestamp).toLocaleTimeString() : 'Unknown'}
</div>
</div>
</div>
</div>
{/* Real-time Stats */}
<div className="stat-cards" style={{ marginBottom: '24px' }}>
<div className="stat-card">
<div className="stat-label">Active Users (5 min)</div>
<div className="stat-value-large" style={{ color: '#3b82f6' }}>
{liveData.userActivity?.activeUsers || 0}
</div>
<div style={{ fontSize: '12px', color: '#64748b', marginTop: '4px' }}>
{liveData.userActivity?.activeSessions || 0} sessions
</div>
</div>
<div className="stat-card">
<div className="stat-label">Conversations/Hour</div>
<div className="stat-value-large" style={{ color: '#10b981' }}>
{liveData.conversations?.lastHour || 0}
</div>
<div style={{ fontSize: '12px', color: '#64748b', marginTop: '4px' }}>
{liveData.conversations?.today || 0} today
</div>
</div>
<div className="stat-card critical">
<div className="stat-label">Active Alerts</div>
<div className="stat-value-large" style={{ color: '#ef4444' }}>
{liveData.alerts?.today || 0}
</div>
<div style={{ fontSize: '12px', color: '#64748b', marginTop: '4px' }}>
{liveData.alerts?.critical || 0} critical
</div>
</div>
<div className="stat-card">
<div className="stat-label">System Load</div>
<div className="stat-value-large" style={{
color: liveData.system?.cpuUsage > 80 ? '#ef4444' :
liveData.system?.cpuUsage > 60 ? '#f59e0b' : '#10b981'
}}>
{liveData.system?.cpuUsage || 0}%
</div>
<div style={{ fontSize: '12px', color: '#64748b', marginTop: '4px' }}>
CPU usage
</div>
</div>
</div>
{/* Detailed Monitoring */}
<div className="dashboard-grid">
{/* System Resources */}
<div className="card">
<div className="card-header">
<h3 className="card-title">System Resources</h3>
</div>
<div className="card-content">
<div style={{ display: 'grid', gap: '16px' }}>
<div>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
<span>Memory Usage</span>
<span style={{ fontWeight: '600' }}>{liveData.system?.memoryUsage || 0}%</span>
</div>
<div style={{
background: '#e2e8f0',
height: '8px',
borderRadius: '4px',
overflow: 'hidden'
}}>
<div style={{
width: `${liveData.system?.memoryUsage || 0}%`,
height: '100%',
background: liveData.system?.memoryUsage > 80 ? '#ef4444' :
liveData.system?.memoryUsage > 60 ? '#f59e0b' : '#10b981',
transition: 'width 0.3s ease'
}}></div>
</div>
</div>
<div>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
<span>CPU Usage</span>
<span style={{ fontWeight: '600' }}>{liveData.system?.cpuUsage || 0}%</span>
</div>
<div style={{
background: '#e2e8f0',
height: '8px',
borderRadius: '4px',
overflow: 'hidden'
}}>
<div style={{
width: `${liveData.system?.cpuUsage || 0}%`,
height: '100%',
background: liveData.system?.cpuUsage > 80 ? '#ef4444' :
liveData.system?.cpuUsage > 60 ? '#f59e0b' : '#10b981',
transition: 'width 0.3s ease'
}}></div>
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px', marginTop: '16px' }}>
<div style={{ textAlign: 'center' }}>
<div style={{ fontSize: '20px', fontWeight: '600', color: '#1e293b' }}>
{liveData.system?.processMemoryMB || 0}
</div>
<div style={{ fontSize: '12px', color: '#64748b' }}>Process Memory (MB)</div>
</div>
<div style={{ textAlign: 'center' }}>
<div style={{ fontSize: '20px', fontWeight: '600', color: '#1e293b' }}>
{liveData.system?.loadAverage?.toFixed(2) || '0.00'}
</div>
<div style={{ fontSize: '12px', color: '#64748b' }}>Load Average</div>
</div>
</div>
</div>
</div>
</div>
{/* API Health */}
<div className="card">
<div className="card-header">
<h3 className="card-title">API Health Check</h3>
</div>
<div className="card-content">
<div style={{ display: 'grid', gap: '12px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span>API Status:</span>
<span style={{
padding: '4px 8px',
borderRadius: '4px',
fontSize: '12px',
fontWeight: '600',
background: bubbeHealth.api?.status === 'healthy' ? '#dcfce7' : '#fee2e2',
color: bubbeHealth.api?.status === 'healthy' ? '#16a34a' : '#dc2626'
}}>
{bubbeHealth.api?.status || 'Unknown'}
</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span>Response Time:</span>
<span style={{ fontWeight: '600' }}>
{bubbeHealth.api?.responseTime || 0}ms
</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span>PM2 Status:</span>
<span style={{
padding: '4px 8px',
borderRadius: '4px',
fontSize: '12px',
fontWeight: '600',
background: bubbeHealth.pm2?.status === 'online' ? '#dcfce7' : '#fee2e2',
color: bubbeHealth.pm2?.status === 'online' ? '#16a34a' : '#dc2626'
}}>
{bubbeHealth.pm2?.status || 'Unknown'}
</span>
</div>
{bubbeHealth.api?.data && (
<div style={{
marginTop: '16px',
padding: '12px',
background: '#f8fafc',
borderRadius: '6px',
fontSize: '12px'
}}>
<strong>API Info:</strong><br />
Uptime: {bubbeHealth.api.data.uptime ? (bubbeHealth.api.data.uptime / 3600).toFixed(1) + 'h' : 'Unknown'}<br />
Memory: {bubbeHealth.api.data.memory ? Math.round(bubbeHealth.api.data.memory.rss / 1024 / 1024) + 'MB' : 'Unknown'}
</div>
)}
</div>
</div>
</div>
{/* Live Activity */}
<div className="card">
<div className="card-header">
<h3 className="card-title">Live Activity</h3>
</div>
<div className="card-content">
<div style={{ display: 'grid', gap: '12px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>New Users Today:</span>
<strong style={{ color: '#10b981' }}>{liveData.userActivity?.newUsersToday || 0}</strong>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>Blocked Users:</span>
<strong style={{ color: '#ef4444' }}>{liveData.userActivity?.blockedUsers || 0}</strong>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>Total Users:</span>
<strong>{liveData.userActivity?.totalUsers || 0}</strong>
</div>
<hr style={{ margin: '12px 0', border: 'none', borderTop: '1px solid #e2e8f0' }} />
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>Conversations Today:</span>
<strong style={{ color: '#3b82f6' }}>{liveData.conversations?.today || 0}</strong>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>Last Hour:</span>
<strong>{liveData.conversations?.lastHour || 0}</strong>
</div>
</div>
</div>
</div>
{/* Quick Actions */}
<div className="card">
<div className="card-header">
<h3 className="card-title">Quick Actions</h3>
</div>
<div className="card-content">
<div style={{ display: 'grid', gap: '12px' }}>
<button
onClick={() => window.open('/alerts', '_blank')}
className="btn btn-danger btn-small"
>
🚨 View Critical Alerts
</button>
<button
onClick={() => window.open('/users?blocked=true', '_blank')}
className="btn btn-secondary btn-small"
>
🚫 View Blocked Users
</button>
<button
onClick={() => {
fetchLiveData()
checkBubbeHealth()
}}
className="btn btn-primary btn-small"
>
🔄 Force Refresh
</button>
<button
onClick={() => window.open('http://45.61.58.125:3011', '_blank')}
className="btn btn-secondary btn-small"
>
🌐 Open Dear Bubbe
</button>
</div>
</div>
</div>
</div>
{/* Status Footer */}
<div style={{
marginTop: '24px',
padding: '16px',
background: '#f8fafc',
borderRadius: '8px',
border: '1px solid #e2e8f0',
textAlign: 'center',
fontSize: '14px',
color: '#64748b'
}}>
{autoRefresh && (
<div>
🔄 Auto-refreshing every 10 seconds •
Last update: {liveData.timestamp ? new Date(liveData.timestamp).toLocaleTimeString() : 'Unknown'}
</div>
)}
<div style={{ marginTop: '8px' }}>
Monitoring {liveData.userActivity?.activeUsers || 0} active users •
System uptime: {liveData.system?.uptime ? Math.floor(liveData.system.uptime / 3600) + 'h' : 'Unknown'}
</div>
</div>
</div>
)
}
export default MonitoringPage