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
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:
@@ -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 }}>
|
||||
|
||||
Reference in New Issue
Block a user