Files
ETB/etb-dashboard/src/components/Dashboard/OverviewDashboard.tsx
Iliyan Angelov 6b247e5b9f Updates
2025-09-19 11:58:53 +03:00

586 lines
18 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import {
Box,
Card,
CardContent,
Typography,
LinearProgress,
Chip,
List,
ListItem,
ListItemText,
ListItemIcon,
Avatar,
IconButton,
Alert,
Button,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Badge,
Tabs,
Tab,
Paper,
Grid,
} from '@mui/material';
import {
BugReport as IncidentIcon,
Schedule as SLIcon,
Security as SecurityIcon,
Warning as WarningIcon,
CheckCircle as CheckCircleIcon,
Error as ErrorIcon,
TrendingUp as TrendingUpIcon,
TrendingDown as TrendingDownIcon,
Refresh as RefreshIcon,
Notifications as NotificationsIcon,
Dashboard as DashboardIcon,
ViewModule as ModuleIcon,
} from '@mui/icons-material';
import { useAuth } from '../../contexts/AuthContext';
import { useNavigate } from 'react-router-dom';
import apiService from '../../services/api';
import { Incident, Alert as AlertType, OnCallAssignment } from '../../types';
import ModuleOverviewCards from './ModuleOverviewCards';
interface DashboardStats {
totalIncidents: number;
openIncidents: number;
resolvedIncidents: number;
criticalIncidents: number;
slaBreaches: number;
activeAlerts: number;
systemHealth: number;
mttr: number;
mtta: number;
}
const OverviewDashboard: React.FC = () => {
const { user } = useAuth();
const navigate = useNavigate();
const [activeTab, setActiveTab] = useState(0);
const [stats, setStats] = useState<DashboardStats>({
totalIncidents: 0,
openIncidents: 0,
resolvedIncidents: 0,
criticalIncidents: 0,
slaBreaches: 0,
activeAlerts: 0,
systemHealth: 100,
mttr: 0,
mtta: 0,
});
const [recentIncidents, setRecentIncidents] = useState<Incident[]>([]);
const [recentAlerts, setRecentAlerts] = useState<AlertType[]>([]);
const [currentOnCall, setCurrentOnCall] = useState<OnCallAssignment | null>(null);
// const [systemMetrics] = useState<SystemMetric[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
loadDashboardData();
}, []);
const loadDashboardData = async () => {
try {
setIsLoading(true);
setError(null);
// Load incidents
const incidentsResponse = await apiService.getIncidents({ page_size: 10 });
setRecentIncidents(incidentsResponse.results);
// Load alerts
const alertsResponse = await apiService.getAlerts({ page_size: 5 });
setRecentAlerts(alertsResponse.results);
// Load current on-call
const onCallResponse = await apiService.getCurrentOnCall();
setCurrentOnCall(onCallResponse);
// Load system metrics
// const metricsResponse = await apiService.getSystemMetrics();
// setSystemMetrics(metricsResponse); // Commented out as it's not used
// Calculate stats
const totalIncidents = incidentsResponse.count;
const openIncidents = incidentsResponse.results.filter(i => i.status === 'OPEN' || i.status === 'IN_PROGRESS').length;
const resolvedIncidents = incidentsResponse.results.filter(i => i.status === 'RESOLVED' || i.status === 'CLOSED').length;
const criticalIncidents = incidentsResponse.results.filter(i => i.severity === 'CRITICAL' || i.severity === 'EMERGENCY').length;
const activeAlerts = alertsResponse.results.filter(a => a.status === 'TRIGGERED').length;
setStats({
totalIncidents,
openIncidents,
resolvedIncidents,
criticalIncidents,
slaBreaches: 0, // This would come from SLA API
activeAlerts,
systemHealth: 95, // This would be calculated from health checks
mttr: 2.5, // This would come from analytics
mtta: 0.5, // This would come from analytics
});
} catch (error) {
console.error('Failed to load dashboard data:', error);
setError('Failed to load dashboard data');
} finally {
setIsLoading(false);
}
};
const getSeverityColor = (severity: string) => {
switch (severity) {
case 'CRITICAL':
case 'EMERGENCY':
return 'error';
case 'HIGH':
return 'warning';
case 'MEDIUM':
return 'info';
case 'LOW':
return 'success';
default:
return 'default';
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'OPEN':
return 'error';
case 'IN_PROGRESS':
return 'warning';
case 'RESOLVED':
return 'success';
case 'CLOSED':
return 'default';
default:
return 'default';
}
};
const formatTime = (timestamp: string) => {
return new Date(timestamp).toLocaleString();
};
const handleModuleClick = (moduleId: string) => {
// Navigate to the specific module page
const moduleRoutes: { [key: string]: string } = {
incident_intelligence: '/incidents',
security: '/security',
monitoring: '/monitoring',
sla_oncall: '/sla',
automation_orchestration: '/automation',
collaboration_war_rooms: '/collaboration',
analytics_predictive_insights: '/analytics',
knowledge_learning: '/knowledge',
compliance_governance: '/compliance',
};
const route = moduleRoutes[moduleId];
if (route) {
navigate(route);
}
};
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
setActiveTab(newValue);
};
const StatCard: React.FC<{
title: string;
value: string | number;
icon: React.ReactNode;
color: string;
trend?: 'up' | 'down' | 'neutral';
trendValue?: string;
}> = ({ title, value, icon, color, trend, trendValue }) => (
<Card>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Box>
<Typography color="textSecondary" gutterBottom variant="h6">
{title}
</Typography>
<Typography variant="h4" component="div" color={color}>
{value}
</Typography>
{trend && trendValue && (
<Box display="flex" alignItems="center" mt={1}>
{trend === 'up' ? (
<TrendingUpIcon color="error" fontSize="small" />
) : trend === 'down' ? (
<TrendingDownIcon color="success" fontSize="small" />
) : null}
<Typography variant="caption" color="textSecondary" sx={{ ml: 0.5 }}>
{trendValue}
</Typography>
</Box>
)}
</Box>
<Avatar sx={{ bgcolor: `${color}.main` }}>
{icon}
</Avatar>
</Box>
</CardContent>
</Card>
);
if (isLoading) {
return (
<Box>
<LinearProgress />
<Typography variant="h6" sx={{ mt: 2 }}>
Loading dashboard...
</Typography>
</Box>
);
}
if (error) {
return (
<Alert severity="error" action={
<Button color="inherit" size="small" onClick={loadDashboardData}>
Retry
</Button>
}>
{error}
</Alert>
);
}
return (
<Box>
{/* Welcome Section */}
<Box mb={3}>
<Typography variant="h4" gutterBottom>
Welcome back, {user?.first_name || user?.username}!
</Typography>
<Typography variant="subtitle1" color="textSecondary">
Enterprise Incident Management Dashboard - Real-time system overview
</Typography>
</Box>
{/* Dashboard Tabs */}
<Paper sx={{ mb: 3 }}>
<Tabs
value={activeTab}
onChange={handleTabChange}
indicatorColor="primary"
textColor="primary"
>
<Tab
icon={<DashboardIcon />}
label="Dashboard Overview"
iconPosition="start"
/>
<Tab
icon={<ModuleIcon />}
label="Module Status"
iconPosition="start"
/>
</Tabs>
</Paper>
{/* Tab Content */}
{activeTab === 0 && (
<>
{/* Stats Cards */}
<Grid container spacing={3} mb={3}>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Total Incidents"
value={stats.totalIncidents}
icon={<IncidentIcon />}
color="primary"
trend="up"
trendValue="+12% from last week"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Open Incidents"
value={stats.openIncidents}
icon={<WarningIcon />}
color="warning"
trend="down"
trendValue="-5% from yesterday"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Critical Issues"
value={stats.criticalIncidents}
icon={<ErrorIcon />}
color="error"
trend="neutral"
trendValue="No change"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="System Health"
value={`${stats.systemHealth}%`}
icon={<CheckCircleIcon />}
color="success"
trend="up"
trendValue="+2% from last hour"
/>
</Grid>
</Grid>
{/* Performance Metrics */}
<Grid container spacing={3} mb={3}>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="MTTR (hours)"
value={stats.mttr}
icon={<SLIcon />}
color="info"
trend="down"
trendValue="-0.5h from last week"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="MTTA (hours)"
value={stats.mtta}
icon={<NotificationsIcon />}
color="secondary"
trend="down"
trendValue="-0.2h from last week"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Active Alerts"
value={stats.activeAlerts}
icon={<SecurityIcon />}
color="warning"
trend="up"
trendValue="+3 from yesterday"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="SLA Breaches"
value={stats.slaBreaches}
icon={<ErrorIcon />}
color="error"
trend="down"
trendValue="-1 from last week"
/>
</Grid>
</Grid>
{/* Main Content Grid */}
<Grid container spacing={3}>
{/* Recent Incidents */}
<Grid size={{ xs: 12, md: 8 }}>
<Card>
<CardContent>
<Box display="flex" justifyContent="space-between" alignItems="center" mb={2}>
<Typography variant="h6">
Recent Incidents
</Typography>
<IconButton onClick={loadDashboardData}>
<RefreshIcon />
</IconButton>
</Box>
<TableContainer>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>Title</TableCell>
<TableCell>Severity</TableCell>
<TableCell>Status</TableCell>
<TableCell>Assigned To</TableCell>
<TableCell>Created</TableCell>
</TableRow>
</TableHead>
<TableBody>
{recentIncidents.map((incident) => (
<TableRow key={incident.id}>
<TableCell>
<Typography variant="body2" noWrap>
{incident.title}
</Typography>
</TableCell>
<TableCell>
<Chip
label={incident.severity}
color={getSeverityColor(incident.severity) as any}
size="small"
/>
</TableCell>
<TableCell>
<Chip
label={incident.status}
color={getStatusColor(incident.status) as any}
size="small"
/>
</TableCell>
<TableCell>
{incident.assigned_to ? (
<Box display="flex" alignItems="center">
<Avatar sx={{ width: 24, height: 24, mr: 1 }}>
{incident.assigned_to.first_name?.[0]}
</Avatar>
<Typography variant="body2">
{incident.assigned_to.first_name} {incident.assigned_to.last_name}
</Typography>
</Box>
) : (
<Typography variant="body2" color="textSecondary">
Unassigned
</Typography>
)}
</TableCell>
<TableCell>
<Typography variant="body2">
{formatTime(incident.created_at)}
</Typography>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</Grid>
{/* Sidebar */}
<Grid size={{ xs: 12, md: 4 }}>
{/* Current On-Call */}
{currentOnCall && (
<Card sx={{ mb: 3 }}>
<CardContent>
<Typography variant="h6" gutterBottom>
Current On-Call
</Typography>
<Box display="flex" alignItems="center">
<Avatar sx={{ mr: 2 }}>
{currentOnCall.user.first_name?.[0]}
</Avatar>
<Box>
<Typography variant="subtitle1">
{currentOnCall.user.first_name} {currentOnCall.user.last_name}
</Typography>
<Typography variant="body2" color="textSecondary">
{currentOnCall.rotation.team_name}
</Typography>
<Typography variant="caption" color="textSecondary">
Until {formatTime(currentOnCall.end_time)}
</Typography>
</Box>
</Box>
</CardContent>
</Card>
)}
{/* Recent Alerts */}
<Card sx={{ mb: 3 }}>
<CardContent>
<Typography variant="h6" gutterBottom>
Recent Alerts
</Typography>
<List dense>
{recentAlerts.map((alert) => (
<ListItem key={alert.id}>
<ListItemIcon>
<Badge color="error" variant="dot">
<NotificationsIcon />
</Badge>
</ListItemIcon>
<ListItemText
primary={alert.title}
secondary={
<Box>
<Typography variant="caption" display="block">
{formatTime(alert.triggered_at)}
</Typography>
<Chip
label={alert.severity}
color={getSeverityColor(alert.severity) as any}
size="small"
/>
</Box>
}
/>
</ListItem>
))}
</List>
</CardContent>
</Card>
{/* System Status */}
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
System Status
</Typography>
<Box mb={2}>
<Box display="flex" justifyContent="space-between" mb={1}>
<Typography variant="body2">Overall Health</Typography>
<Typography variant="body2">{stats.systemHealth}%</Typography>
</Box>
<LinearProgress
variant="determinate"
value={stats.systemHealth}
color={stats.systemHealth > 90 ? 'success' : stats.systemHealth > 70 ? 'warning' : 'error'}
/>
</Box>
<List dense>
<ListItem>
<ListItemIcon>
<CheckCircleIcon color="success" />
</ListItemIcon>
<ListItemText
primary="API Services"
secondary="Operational"
/>
</ListItem>
<ListItem>
<ListItemIcon>
<CheckCircleIcon color="success" />
</ListItemIcon>
<ListItemText
primary="Database"
secondary="Operational"
/>
</ListItem>
<ListItem>
<ListItemIcon>
<WarningIcon color="warning" />
</ListItemIcon>
<ListItemText
primary="Monitoring"
secondary="Degraded"
/>
</ListItem>
</List>
</CardContent>
</Card>
</Grid>
</Grid>
</>
)}
{/* Module Status Tab */}
{activeTab === 1 && (
<ModuleOverviewCards onModuleClick={handleModuleClick} />
)}
</Box>
);
};
export default OverviewDashboard;