feat: Add real-time system health monitoring to admin dashboard
Some checks failed
ParentFlow CI/CD Pipeline / Backend Tests (push) Has been cancelled
ParentFlow CI/CD Pipeline / Frontend Tests (push) Has been cancelled
ParentFlow CI/CD Pipeline / Security Scanning (push) Has been cancelled
ParentFlow CI/CD Pipeline / Build Docker Images (map[context:maternal-app/maternal-app-backend dockerfile:Dockerfile.production name:backend]) (push) Has been cancelled
ParentFlow CI/CD Pipeline / Build Docker Images (map[context:maternal-web dockerfile:Dockerfile.production name:frontend]) (push) Has been cancelled
ParentFlow CI/CD Pipeline / Deploy to Development (push) Has been cancelled
ParentFlow CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / Lint and Test (push) Has been cancelled
CI/CD Pipeline / E2E Tests (push) Has been cancelled
CI/CD Pipeline / Build Application (push) Has been cancelled

Backend:
- Added getSystemHealth method to DashboardService
- Checks PostgreSQL connection and response time
- Monitors database connections, active queries, and database size
- Tracks API uptime and database connection pool stats
- Attempts to fetch recent error logs from admin_audit_logs table

Frontend:
- Removed deprecated MUI Grid import
- Replaced all Grid components with CSS Grid layouts
- Connected health page to real backend API at /admin/dashboard/health
- Removed all mock data
- Real-time metrics update every 30 seconds

Health metrics now show:
- Service status (Backend API, PostgreSQL)
- Database connections and active queries
- Database size
- API uptime in hours
- Recent system events/errors

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Andrei
2025-10-08 11:16:56 +00:00
parent c9f68076b8
commit 95d22fe633
3 changed files with 208 additions and 211 deletions

View File

@@ -6,7 +6,6 @@ import {
Card,
CardContent,
Typography,
Grid,
LinearProgress,
Chip,
Table,
@@ -77,143 +76,11 @@ export default function HealthPage() {
const fetchHealthData = async () => {
try {
setLoading(true);
// API calls would go here
// const response = await apiClient.get('/admin/health');
// Using mock data for development
setServices([
{
name: 'Backend API',
status: 'healthy',
responseTime: 45,
uptime: 99.98,
lastCheck: new Date().toISOString(),
},
{
name: 'PostgreSQL Database',
status: 'healthy',
responseTime: 12,
uptime: 99.99,
lastCheck: new Date().toISOString(),
},
{
name: 'Redis Cache',
status: 'healthy',
responseTime: 3,
uptime: 100,
lastCheck: new Date().toISOString(),
},
{
name: 'MongoDB',
status: 'healthy',
responseTime: 18,
uptime: 99.95,
lastCheck: new Date().toISOString(),
},
{
name: 'MinIO Storage',
status: 'degraded',
responseTime: 250,
uptime: 98.5,
lastCheck: new Date().toISOString(),
},
{
name: 'WebSocket Server',
status: 'healthy',
responseTime: 8,
uptime: 99.97,
lastCheck: new Date().toISOString(),
},
]);
setMetrics([
{
name: 'CPU Usage',
value: 45,
max: 100,
unit: '%',
status: 'normal',
},
{
name: 'Memory Usage',
value: 3.2,
max: 8,
unit: 'GB',
status: 'normal',
},
{
name: 'Disk Usage',
value: 42,
max: 100,
unit: 'GB',
status: 'normal',
},
{
name: 'Network I/O',
value: 125,
max: 1000,
unit: 'Mbps',
status: 'normal',
},
{
name: 'Database Connections',
value: 85,
max: 100,
unit: '',
status: 'warning',
},
{
name: 'Redis Memory',
value: 450,
max: 512,
unit: 'MB',
status: 'warning',
},
]);
setErrorLogs([
{
id: '1',
timestamp: new Date(Date.now() - 5 * 60000).toISOString(),
severity: 'warning',
service: 'MinIO',
message: 'High response time detected',
count: 3,
},
{
id: '2',
timestamp: new Date(Date.now() - 15 * 60000).toISOString(),
severity: 'error',
service: 'Backend API',
message: 'Rate limit exceeded for IP 192.168.1.105',
count: 12,
},
{
id: '3',
timestamp: new Date(Date.now() - 30 * 60000).toISOString(),
severity: 'info',
service: 'Database',
message: 'Automatic vacuum completed',
count: 1,
},
{
id: '4',
timestamp: new Date(Date.now() - 45 * 60000).toISOString(),
severity: 'warning',
service: 'Redis',
message: 'Memory usage above 85%',
count: 2,
},
{
id: '5',
timestamp: new Date(Date.now() - 60 * 60000).toISOString(),
severity: 'info',
service: 'System',
message: 'Daily backup completed successfully',
count: 1,
},
]);
const response = await apiClient.get('/admin/dashboard/health');
setServices(response.services || []);
setMetrics(response.metrics || []);
setErrorLogs(response.errorLogs || []);
setLastRefresh(new Date());
} catch (error) {
console.error('Failed to fetch health data:', error);
@@ -333,93 +200,89 @@ export default function HealthPage() {
<Typography variant="h6" gutterBottom sx={{ mt: 4, mb: 2 }}>
Service Status
</Typography>
<Grid container spacing={2} sx={{ mb: 4 }}>
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: 2, mb: 4 }}>
{services.map((service) => (
<Grid item xs={12} sm={6} md={4} key={service.name}>
<Card>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Typography variant="subtitle1" sx={{ fontWeight: 500 }}>
{service.name}
<Card key={service.name}>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Typography variant="subtitle1" sx={{ fontWeight: 500 }}>
{service.name}
</Typography>
<Chip
label={service.status}
color={getStatusColor(service.status) as any}
size="small"
icon={getStatusIcon(service.status) as any}
/>
</Box>
<Box sx={{ mt: 2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="body2" color="text.secondary">
Response Time
</Typography>
<Typography variant="body2">
{service.responseTime}ms
</Typography>
<Chip
label={service.status}
color={getStatusColor(service.status) as any}
size="small"
icon={getStatusIcon(service.status) as any}
/>
</Box>
<Box sx={{ mt: 2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="body2" color="text.secondary">
Response Time
</Typography>
<Typography variant="body2">
{service.responseTime}ms
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="body2" color="text.secondary">
Uptime
</Typography>
<Typography variant="body2">
{service.uptime}%
</Typography>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="body2" color="text.secondary">
Uptime
</Typography>
<Typography variant="body2">
{service.uptime}%
</Typography>
</Box>
</CardContent>
</Card>
</Grid>
</Box>
</CardContent>
</Card>
))}
</Grid>
</Box>
{/* System Metrics */}
<Typography variant="h6" gutterBottom sx={{ mt: 4, mb: 2 }}>
System Metrics
</Typography>
<Grid container spacing={3} sx={{ mb: 4 }}>
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: 3, mb: 4 }}>
{metrics.map((metric) => (
<Grid item xs={12} sm={6} md={4} key={metric.name}>
<Paper sx={{ p: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" color="text.secondary">
{metric.name}
</Typography>
{metric.status !== 'normal' && (
<Chip
label={metric.status}
color={getStatusColor(metric.status) as any}
size="small"
/>
)}
</Box>
<Typography variant="h5" sx={{ mb: 1 }}>
{metric.value}{metric.unit}
<Typography variant="body2" component="span" color="text.secondary">
{' '}/ {metric.max}{metric.unit}
</Typography>
<Paper key={metric.name} sx={{ p: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" color="text.secondary">
{metric.name}
</Typography>
<LinearProgress
variant="determinate"
value={(metric.value / metric.max) * 100}
sx={{
height: 8,
borderRadius: 1,
backgroundColor: 'grey.200',
'& .MuiLinearProgress-bar': {
backgroundColor:
metric.status === 'critical'
? 'error.main'
: metric.status === 'warning'
? 'warning.main'
: 'success.main',
},
}}
/>
</Paper>
</Grid>
{metric.status !== 'normal' && (
<Chip
label={metric.status}
color={getStatusColor(metric.status) as any}
size="small"
/>
)}
</Box>
<Typography variant="h5" sx={{ mb: 1 }}>
{metric.value}{metric.unit}
<Typography variant="body2" component="span" color="text.secondary">
{' '}/ {metric.max}{metric.unit}
</Typography>
</Typography>
<LinearProgress
variant="determinate"
value={(metric.value / metric.max) * 100}
sx={{
height: 8,
borderRadius: 1,
backgroundColor: 'grey.200',
'& .MuiLinearProgress-bar': {
backgroundColor:
metric.status === 'critical'
? 'error.main'
: metric.status === 'warning'
? 'warning.main'
: 'success.main',
},
}}
/>
</Paper>
))}
</Grid>
</Box>
{/* Recent Error Logs */}
<Typography variant="h6" gutterBottom sx={{ mt: 4, mb: 2 }}>