Updates
This commit is contained in:
585
etb-dashboard/src/components/Dashboard/OverviewDashboard.tsx
Normal file
585
etb-dashboard/src/components/Dashboard/OverviewDashboard.tsx
Normal file
@@ -0,0 +1,585 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user