This commit is contained in:
Iliyan Angelov
2025-09-19 11:58:53 +03:00
parent 306b20e24a
commit 6b247e5b9f
11423 changed files with 1500615 additions and 778 deletions

1
etb-dashboard/.env Normal file
View File

@@ -0,0 +1 @@
REACT_APP_API_URL=http://localhost:8000

23
etb-dashboard/.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

46
etb-dashboard/README.md Normal file
View File

@@ -0,0 +1,46 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.\
You will also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).

18422
etb-dashboard/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,63 @@
{
"name": "etb-dashboard",
"version": "0.1.0",
"private": true,
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@heroicons/react": "^2.2.0",
"@mui/icons-material": "^7.3.2",
"@mui/material": "^7.3.2",
"@mui/x-data-grid": "^8.11.3",
"@mui/x-date-pickers": "^8.11.3",
"@tanstack/react-query": "^5.89.0",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.8.0",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.126",
"@types/qrcode.react": "^1.0.5",
"@types/react": "^19.1.13",
"@types/react-dom": "^19.1.9",
"@types/react-router-dom": "^5.3.3",
"axios": "^1.12.2",
"qrcode.react": "^4.2.0",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-router-dom": "^7.9.1",
"react-scripts": "5.0.1",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@types/axios": "^0.9.36",
"autoprefixer": "^10.4.21",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.17"
}
}

View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@@ -0,0 +1,38 @@
<svg width="200" height="60" viewBox="0 0 200 60" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- Background Shield -->
<defs>
<linearGradient id="shieldGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#1e3c72;stop-opacity:1" />
<stop offset="100%" style="stop-color:#2a5298;stop-opacity:1" />
</linearGradient>
<linearGradient id="textGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#1e3c72;stop-opacity:1" />
<stop offset="100%" style="stop-color:#2a5298;stop-opacity:1" />
</linearGradient>
</defs>
<!-- Shield Icon -->
<path d="M20 10 L20 25 C20 30 25 35 30 35 L30 40 C30 45 35 50 40 50 L40 45 C45 45 50 40 50 35 L50 10 Z"
fill="url(#shieldGradient)"
stroke="#ffffff"
stroke-width="2"/>
<!-- Security Symbol -->
<circle cx="35" cy="25" r="8" fill="#ffffff" opacity="0.9"/>
<path d="M32 25 L34 27 L38 23" stroke="#1e3c72" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
<!-- ETB Text -->
<text x="70" y="25" font-family="Arial, sans-serif" font-size="24" font-weight="bold" fill="url(#textGradient)">
ETB Security
</text>
<!-- Subtitle -->
<text x="70" y="40" font-family="Arial, sans-serif" font-size="12" fill="#666666">
Enterprise Threat & Breach Management
</text>
<!-- Decorative Elements -->
<circle cx="180" cy="15" r="3" fill="#1e3c72" opacity="0.3"/>
<circle cx="185" cy="25" r="2" fill="#2a5298" opacity="0.4"/>
<circle cx="175" cy="35" r="2.5" fill="#1e3c72" opacity="0.2"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

38
etb-dashboard/src/App.css Normal file
View File

@@ -0,0 +1,38 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View File

@@ -0,0 +1,9 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

341
etb-dashboard/src/App.tsx Normal file
View File

@@ -0,0 +1,341 @@
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import { CssBaseline } from '@mui/material';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
// Contexts
import { AuthProvider } from './contexts/AuthContext';
// Components
import LoginPage from './components/Login/LoginPage';
import DashboardLayout from './components/Dashboard/DashboardLayout';
import OverviewDashboard from './components/Dashboard/OverviewDashboard';
import IncidentIntelligenceDashboard from './components/Dashboard/IncidentIntelligenceDashboard';
import SecurityDashboard from './components/Dashboard/SecurityDashboard';
import MonitoringDashboard from './components/Dashboard/MonitoringDashboard';
import SLAOnCallDashboard from './components/Dashboard/SLAOnCallDashboard';
import AutomationDashboard from './components/Dashboard/AutomationDashboard';
import CollaborationDashboard from './components/Dashboard/CollaborationDashboard';
import AnalyticsDashboard from './components/Dashboard/AnalyticsDashboard';
import KnowledgeDashboard from './components/Dashboard/KnowledgeDashboard';
import ComplianceDashboard from './components/Dashboard/ComplianceDashboard';
import UserManagementDashboard from './components/Dashboard/UserManagementDashboard';
// Create a custom theme
const theme = createTheme({
palette: {
mode: 'light',
primary: {
main: '#1976d2',
light: '#42a5f5',
dark: '#1565c0',
},
secondary: {
main: '#dc004e',
light: '#ff5983',
dark: '#9a0036',
},
background: {
default: '#f5f5f5',
paper: '#ffffff',
},
},
typography: {
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
h1: {
fontSize: '2.5rem',
fontWeight: 600,
},
h2: {
fontSize: '2rem',
fontWeight: 600,
},
h3: {
fontSize: '1.75rem',
fontWeight: 600,
},
h4: {
fontSize: '1.5rem',
fontWeight: 600,
},
h5: {
fontSize: '1.25rem',
fontWeight: 600,
},
h6: {
fontSize: '1rem',
fontWeight: 600,
},
},
components: {
MuiButton: {
styleOverrides: {
root: {
textTransform: 'none',
borderRadius: 8,
},
},
},
MuiCard: {
styleOverrides: {
root: {
borderRadius: 12,
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
},
},
},
MuiPaper: {
styleOverrides: {
root: {
borderRadius: 8,
},
},
},
},
});
// Create a client for React Query
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 1,
refetchOnWindowFocus: false,
staleTime: 5 * 60 * 1000, // 5 minutes
},
},
});
// Protected Route Component
interface ProtectedRouteProps {
children: React.ReactNode;
}
const ProtectedRoute: React.FC<ProtectedRouteProps> = ({ children }) => {
const token = localStorage.getItem('auth_token');
if (!token) {
return <Navigate to="/login" replace />;
}
return <>{children}</>;
};
// Placeholder components for other routes
const IncidentsPage: React.FC = () => (
<IncidentIntelligenceDashboard onNavigateToModule={(moduleId) => {
// Handle navigation to other modules
console.log('Navigate to module:', moduleId);
}} />
);
const MonitoringPage: React.FC = () => (
<MonitoringDashboard onNavigateToModule={(moduleId) => {
// Handle navigation to other modules
console.log('Navigate to module:', moduleId);
}} />
);
const SLAPage: React.FC = () => (
<SLAOnCallDashboard onNavigateToModule={(moduleId) => {
// Handle navigation to other modules
console.log('Navigate to module:', moduleId);
}} />
);
const SecurityPage: React.FC = () => (
<SecurityDashboard onNavigateToModule={(moduleId) => {
// Handle navigation to other modules
console.log('Navigate to module:', moduleId);
}} />
);
const AutomationPage: React.FC = () => (
<AutomationDashboard onNavigateToModule={(moduleId) => {
// Handle navigation to other modules
console.log('Navigate to module:', moduleId);
}} />
);
const CollaborationPage: React.FC = () => (
<CollaborationDashboard onNavigateToModule={(moduleId) => {
// Handle navigation to other modules
console.log('Navigate to module:', moduleId);
}} />
);
const AnalyticsPage: React.FC = () => (
<AnalyticsDashboard onNavigateToModule={(moduleId) => {
// Handle navigation to other modules
console.log('Navigate to module:', moduleId);
}} />
);
const KnowledgePage: React.FC = () => (
<KnowledgeDashboard onNavigateToModule={(moduleId) => {
// Handle navigation to other modules
console.log('Navigate to module:', moduleId);
}} />
);
const CompliancePage: React.FC = () => (
<ComplianceDashboard onNavigateToModule={(moduleId) => {
// Handle navigation to other modules
console.log('Navigate to module:', moduleId);
}} />
);
const UserManagementPage: React.FC = () => (
<UserManagementDashboard onNavigateToModule={(moduleId) => {
// Handle navigation to other modules
console.log('Navigate to module:', moduleId);
}} />
);
const App: React.FC = () => {
return (
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={theme}>
<CssBaseline />
<AuthProvider>
<Router>
<Routes>
{/* Public Routes */}
<Route path="/login" element={<LoginPage />} />
{/* Protected Routes */}
<Route
path="/dashboard"
element={
<ProtectedRoute>
<DashboardLayout>
<OverviewDashboard />
</DashboardLayout>
</ProtectedRoute>
}
/>
<Route
path="/incidents"
element={
<ProtectedRoute>
<DashboardLayout>
<IncidentsPage />
</DashboardLayout>
</ProtectedRoute>
}
/>
<Route
path="/monitoring"
element={
<ProtectedRoute>
<DashboardLayout>
<MonitoringPage />
</DashboardLayout>
</ProtectedRoute>
}
/>
<Route
path="/sla"
element={
<ProtectedRoute>
<DashboardLayout>
<SLAPage />
</DashboardLayout>
</ProtectedRoute>
}
/>
<Route
path="/security"
element={
<ProtectedRoute>
<DashboardLayout>
<SecurityPage />
</DashboardLayout>
</ProtectedRoute>
}
/>
<Route
path="/automation"
element={
<ProtectedRoute>
<DashboardLayout>
<AutomationPage />
</DashboardLayout>
</ProtectedRoute>
}
/>
<Route
path="/collaboration"
element={
<ProtectedRoute>
<DashboardLayout>
<CollaborationPage />
</DashboardLayout>
</ProtectedRoute>
}
/>
<Route
path="/analytics"
element={
<ProtectedRoute>
<DashboardLayout>
<AnalyticsPage />
</DashboardLayout>
</ProtectedRoute>
}
/>
<Route
path="/knowledge"
element={
<ProtectedRoute>
<DashboardLayout>
<KnowledgePage />
</DashboardLayout>
</ProtectedRoute>
}
/>
<Route
path="/compliance"
element={
<ProtectedRoute>
<DashboardLayout>
<CompliancePage />
</DashboardLayout>
</ProtectedRoute>
}
/>
<Route
path="/user-management"
element={
<ProtectedRoute>
<DashboardLayout>
<UserManagementPage />
</DashboardLayout>
</ProtectedRoute>
}
/>
{/* Default redirect */}
<Route path="/" element={<Navigate to="/dashboard" replace />} />
{/* Catch all route */}
<Route path="*" element={<Navigate to="/dashboard" replace />} />
</Routes>
</Router>
</AuthProvider>
</ThemeProvider>
</QueryClientProvider>
);
};
export default App;

View File

@@ -0,0 +1,861 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Card,
CardContent,
Typography,
Grid,
Chip,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
IconButton,
Button,
LinearProgress,
Alert,
Avatar,
Tabs,
Tab,
} from '@mui/material';
import {
TrendingUp as TrendingUpIcon,
TrendingDown as TrendingDownIcon,
Refresh as RefreshIcon,
Settings as SettingsIcon,
Timeline as TimelineIcon,
Psychology as PsychologyIcon,
Insights as InsightsIcon,
Warning as WarningIcon,
Speed as SpeedIcon,
Download as DownloadIcon,
} from '@mui/icons-material';
interface AnalyticsDashboardProps {
onNavigateToModule: (moduleId: string) => void;
}
interface AnalyticsStats {
totalIncidents: number;
avgResolutionTime: number;
avgResponseTime: number;
automationSuccessRate: number;
predictionAccuracy: number;
anomalyDetections: number;
costSavings: number;
trendPrediction: 'UP' | 'DOWN' | 'STABLE';
}
interface PredictionModel {
id: string;
name: string;
description: string;
type: 'INCIDENT_PREDICTION' | 'SEVERITY_PREDICTION' | 'RESOLUTION_TIME' | 'ANOMALY_DETECTION';
accuracy: number;
lastTrained: string;
status: 'ACTIVE' | 'TRAINING' | 'INACTIVE';
predictionsToday: number;
correctPredictions: number;
}
interface AnomalyDetection {
id: string;
timestamp: string;
type: 'PERFORMANCE' | 'SECURITY' | 'BEHAVIORAL' | 'SYSTEM';
severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
description: string;
confidence: number;
status: 'NEW' | 'INVESTIGATING' | 'RESOLVED' | 'FALSE_POSITIVE';
relatedIncident?: string;
affectedSystems: string[];
}
interface CostImpactAnalysis {
id: string;
incidentId: string;
incidentTitle: string;
estimatedCost: number;
actualCost: number;
costCategory: 'DOWNTIME' | 'RESOURCE' | 'REPUTATION' | 'COMPLIANCE';
analysisDate: string;
mitigationActions: string[];
}
interface TrendAnalysis {
metric: string;
currentValue: number;
previousValue: number;
changePercent: number;
trend: 'UP' | 'DOWN' | 'STABLE';
timeframe: string;
prediction: number;
}
const AnalyticsDashboard: React.FC<AnalyticsDashboardProps> = ({ onNavigateToModule }) => {
const [activeTab, setActiveTab] = useState(0);
const [stats, setStats] = useState<AnalyticsStats>({
totalIncidents: 0,
avgResolutionTime: 0,
avgResponseTime: 0,
automationSuccessRate: 0,
predictionAccuracy: 0,
anomalyDetections: 0,
costSavings: 0,
trendPrediction: 'STABLE',
});
const [predictionModels, setPredictionModels] = useState<PredictionModel[]>([]);
const [anomalyDetections, setAnomalyDetections] = useState<AnomalyDetection[]>([]);
const [costAnalyses, setCostAnalyses] = useState<CostImpactAnalysis[]>([]);
const [trendAnalyses, setTrendAnalyses] = useState<TrendAnalysis[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
loadAnalyticsData();
}, []);
const loadAnalyticsData = async () => {
try {
setIsLoading(true);
setError(null);
// Mock data - replace with actual API calls
setStats({
totalIncidents: 156,
avgResolutionTime: 2.3,
avgResponseTime: 0.8,
automationSuccessRate: 94.2,
predictionAccuracy: 91.8,
anomalyDetections: 23,
costSavings: 125000,
trendPrediction: 'DOWN',
});
setPredictionModels([
{
id: '1',
name: 'Incident Severity Predictor',
description: 'Predicts incident severity based on initial symptoms and patterns',
type: 'SEVERITY_PREDICTION',
accuracy: 94.2,
lastTrained: '2024-01-15T06:00:00Z',
status: 'ACTIVE',
predictionsToday: 45,
correctPredictions: 42,
},
{
id: '2',
name: 'Resolution Time Estimator',
description: 'Estimates time to resolution based on incident characteristics',
type: 'RESOLUTION_TIME',
accuracy: 87.6,
lastTrained: '2024-01-14T18:00:00Z',
status: 'ACTIVE',
predictionsToday: 38,
correctPredictions: 33,
},
{
id: '3',
name: 'Anomaly Detection Engine',
description: 'Detects unusual patterns in system behavior and performance',
type: 'ANOMALY_DETECTION',
accuracy: 92.1,
lastTrained: '2024-01-15T12:00:00Z',
status: 'TRAINING',
predictionsToday: 67,
correctPredictions: 62,
},
{
id: '4',
name: 'Incident Correlation Engine',
description: 'Identifies related incidents and potential cascading effects',
type: 'INCIDENT_PREDICTION',
accuracy: 89.3,
lastTrained: '2024-01-13T15:00:00Z',
status: 'ACTIVE',
predictionsToday: 23,
correctPredictions: 20,
},
]);
setAnomalyDetections([
{
id: '1',
timestamp: '2024-01-15T10:30:00Z',
type: 'PERFORMANCE',
severity: 'HIGH',
description: 'Unusual CPU spike detected on database servers',
confidence: 92.5,
status: 'INVESTIGATING',
affectedSystems: ['Database Cluster', 'API Gateway'],
},
{
id: '2',
timestamp: '2024-01-15T09:45:00Z',
type: 'SECURITY',
severity: 'MEDIUM',
description: 'Anomalous login patterns detected',
confidence: 78.3,
status: 'NEW',
relatedIncident: 'INC-003',
affectedSystems: ['Authentication Service'],
},
{
id: '3',
timestamp: '2024-01-15T08:20:00Z',
type: 'BEHAVIORAL',
severity: 'LOW',
description: 'Unusual API usage patterns detected',
confidence: 65.2,
status: 'FALSE_POSITIVE',
affectedSystems: ['User Service', 'API Gateway'],
},
]);
setCostAnalyses([
{
id: '1',
incidentId: 'INC-001',
incidentTitle: 'Database Connection Timeout',
estimatedCost: 15000,
actualCost: 12000,
costCategory: 'DOWNTIME',
analysisDate: '2024-01-15T11:00:00Z',
mitigationActions: ['Automated failover', 'Cache optimization'],
},
{
id: '2',
incidentId: 'INC-002',
incidentTitle: 'API Gateway Error',
estimatedCost: 8000,
actualCost: 6500,
costCategory: 'RESOURCE',
analysisDate: '2024-01-15T10:30:00Z',
mitigationActions: ['Load balancing', 'Circuit breaker'],
},
{
id: '3',
incidentId: 'INC-003',
incidentTitle: 'Security Incident',
estimatedCost: 25000,
actualCost: 18000,
costCategory: 'REPUTATION',
analysisDate: '2024-01-15T09:15:00Z',
mitigationActions: ['Immediate containment', 'Security audit'],
},
]);
setTrendAnalyses([
{
metric: 'Incident Volume',
currentValue: 45,
previousValue: 52,
changePercent: -13.5,
trend: 'DOWN',
timeframe: 'Last 7 days',
prediction: 42,
},
{
metric: 'Resolution Time',
currentValue: 2.3,
previousValue: 2.8,
changePercent: -17.9,
trend: 'DOWN',
timeframe: 'Last 7 days',
prediction: 2.1,
},
{
metric: 'Automation Success Rate',
currentValue: 94.2,
previousValue: 91.8,
changePercent: 2.6,
trend: 'UP',
timeframe: 'Last 7 days',
prediction: 95.1,
},
{
metric: 'System Uptime',
currentValue: 99.2,
previousValue: 98.9,
changePercent: 0.3,
trend: 'UP',
timeframe: 'Last 7 days',
prediction: 99.4,
},
]);
} catch (error) {
console.error('Failed to load analytics data:', error);
setError('Failed to load analytics data');
} finally {
setIsLoading(false);
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'ACTIVE':
case 'RESOLVED':
return 'success';
case 'TRAINING':
case 'INVESTIGATING':
return 'warning';
case 'INACTIVE':
case 'FALSE_POSITIVE':
return 'default';
case 'NEW':
return 'info';
default:
return 'default';
}
};
const getSeverityColor = (severity: string) => {
switch (severity) {
case 'CRITICAL':
return 'error';
case 'HIGH':
return 'warning';
case 'MEDIUM':
return 'info';
case 'LOW':
return 'success';
default:
return 'default';
}
};
const getTrendIcon = (trend: string) => {
switch (trend) {
case 'UP':
return <TrendingUpIcon color="success" />;
case 'DOWN':
return <TrendingDownIcon color="error" />;
case 'STABLE':
return <TimelineIcon color="info" />;
default:
return <TimelineIcon />;
}
};
const formatTime = (timestamp: string) => {
return new Date(timestamp).toLocaleString();
};
const formatCurrency = (amount: number) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 0,
}).format(amount);
};
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;
subtitle?: string;
}> = ({ title, value, icon, color, trend, trendValue, subtitle }) => (
<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>
{subtitle && (
<Typography variant="body2" color="textSecondary">
{subtitle}
</Typography>
)}
{trend && trendValue && (
<Box display="flex" alignItems="center" mt={1}>
{trend === 'up' ? (
<TrendingUpIcon color="success" fontSize="small" />
) : trend === 'down' ? (
<TrendingDownIcon color="error" 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 Analytics Dashboard...
</Typography>
</Box>
);
}
if (error) {
return (
<Alert severity="error" action={
<Button color="inherit" size="small" onClick={loadAnalyticsData}>
Retry
</Button>
}>
{error}
</Alert>
);
}
return (
<Box>
{/* Header */}
<Box display="flex" justifyContent="space-between" alignItems="center" mb={3}>
<Box>
<Typography variant="h4" gutterBottom>
Analytics & Predictive Insights
</Typography>
<Typography variant="subtitle1" color="textSecondary">
AI-powered analytics, predictions, and intelligent insights
</Typography>
</Box>
<Box>
<Button
variant="outlined"
startIcon={<DownloadIcon />}
sx={{ mr: 2 }}
>
Export Report
</Button>
<IconButton onClick={loadAnalyticsData}>
<RefreshIcon />
</IconButton>
<IconButton>
<SettingsIcon />
</IconButton>
</Box>
</Box>
{/* Analytics Status Alert */}
<Alert
severity={stats.predictionAccuracy > 90 ? 'success' : stats.predictionAccuracy > 80 ? 'warning' : 'error'}
sx={{ mb: 3 }}
icon={stats.predictionAccuracy > 90 ? <PsychologyIcon /> : <WarningIcon />}
>
<Typography variant="h6">
AI Prediction Accuracy: {stats.predictionAccuracy}%
</Typography>
<Typography variant="body2">
{stats.predictionAccuracy > 90 ? 'Excellent' : stats.predictionAccuracy > 80 ? 'Good' : 'Needs improvement'} AI model performance.
{stats.anomalyDetections > 0 && ` ${stats.anomalyDetections} anomalies detected today.`}
</Typography>
</Alert>
{/* Tabs */}
<Paper sx={{ mb: 3 }}>
<Tabs value={activeTab} onChange={handleTabChange} indicatorColor="primary" textColor="primary">
<Tab label="Overview" />
<Tab label="AI Models" />
<Tab label="Anomaly Detection" />
<Tab label="Cost Analysis" />
<Tab label="Trends" />
</Tabs>
</Paper>
{/* Tab Content */}
{activeTab === 0 && (
<>
{/* Stats Cards */}
<Grid container spacing={3} mb={3}>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Prediction Accuracy"
value={`${stats.predictionAccuracy}%`}
icon={<PsychologyIcon />}
color="primary"
trend="up"
trendValue="+2% this week"
subtitle="AI model performance"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Anomalies Detected"
value={stats.anomalyDetections}
icon={<WarningIcon />}
color="warning"
trend="down"
trendValue="-3 from yesterday"
subtitle="Today's detections"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Cost Savings"
value={formatCurrency(stats.costSavings)}
icon={<InsightsIcon />}
color="success"
trend="up"
trendValue="+15% this month"
subtitle="Through automation"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Avg Resolution Time"
value={`${stats.avgResolutionTime}h`}
icon={<SpeedIcon />}
color="info"
trend="down"
trendValue="-0.5h this week"
subtitle="Performance improvement"
/>
</Grid>
</Grid>
{/* Trend Analysis */}
<Card sx={{ mb: 3 }}>
<CardContent>
<Typography variant="h6" gutterBottom>
Key Performance Trends
</Typography>
<Grid container spacing={2}>
{trendAnalyses.map((trend, index) => (
<Grid size={{ xs: 12, sm: 6, md: 3 }} key={index}>
<Paper sx={{ p: 2, textAlign: 'center' }}>
<Box display="flex" alignItems="center" justifyContent="center" mb={1}>
{getTrendIcon(trend.trend)}
<Typography variant="h6" sx={{ ml: 1 }}>
{trend.currentValue}
</Typography>
</Box>
<Typography variant="body2" color="textSecondary">
{trend.metric}
</Typography>
<Typography
variant="caption"
color={trend.changePercent > 0 ? 'success.main' : trend.changePercent < 0 ? 'error.main' : 'textSecondary'}
>
{trend.changePercent > 0 ? '+' : ''}{trend.changePercent.toFixed(1)}%
</Typography>
</Paper>
</Grid>
))}
</Grid>
</CardContent>
</Card>
</>
)}
{/* AI Models Tab */}
{activeTab === 1 && (
<>
<Typography variant="h5" gutterBottom>
AI Prediction Models
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Monitor and manage machine learning models for incident prediction
</Typography>
<Grid container spacing={3} sx={{ mt: 2 }}>
{predictionModels.map((model) => (
<Grid size={{ xs: 12, md: 6 }} key={model.id}>
<Card>
<CardContent>
<Box display="flex" justifyContent="space-between" alignItems="start" mb={2}>
<Typography variant="h6">
{model.name}
</Typography>
<Chip
label={model.status}
color={getStatusColor(model.status) as any}
size="small"
/>
</Box>
<Typography variant="body2" color="textSecondary" paragraph>
{model.description}
</Typography>
<Box mb={2}>
<Typography variant="body2" fontWeight="bold">
Model Performance:
</Typography>
<Box display="flex" alignItems="center" mt={1}>
<LinearProgress
variant="determinate"
value={model.accuracy}
color={model.accuracy > 90 ? 'success' : model.accuracy > 80 ? 'warning' : 'error'}
sx={{ flexGrow: 1, mr: 2 }}
/>
<Typography variant="body2">
{model.accuracy}%
</Typography>
</Box>
</Box>
<Box display="flex" justifyContent="space-between" mb={2}>
<Typography variant="caption">
Predictions Today: {model.predictionsToday}
</Typography>
<Typography variant="caption">
Correct: {model.correctPredictions}
</Typography>
</Box>
<Typography variant="caption" color="textSecondary" display="block">
Last Trained: {formatTime(model.lastTrained)}
</Typography>
<Button
variant="outlined"
size="small"
sx={{ mt: 2 }}
fullWidth
>
View Model Details
</Button>
</CardContent>
</Card>
</Grid>
))}
</Grid>
</>
)}
{/* Anomaly Detection Tab */}
{activeTab === 2 && (
<>
<Typography variant="h5" gutterBottom>
Anomaly Detection
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
AI-detected anomalies and unusual patterns in system behavior
</Typography>
<Card sx={{ mt: 3 }}>
<CardContent>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Timestamp</TableCell>
<TableCell>Type</TableCell>
<TableCell>Severity</TableCell>
<TableCell>Description</TableCell>
<TableCell>Confidence</TableCell>
<TableCell>Status</TableCell>
<TableCell>Affected Systems</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{anomalyDetections.map((anomaly) => (
<TableRow key={anomaly.id}>
<TableCell>
<Typography variant="body2">
{formatTime(anomaly.timestamp)}
</Typography>
</TableCell>
<TableCell>
<Chip label={anomaly.type} size="small" />
</TableCell>
<TableCell>
<Chip
label={anomaly.severity}
color={getSeverityColor(anomaly.severity) as any}
size="small"
/>
</TableCell>
<TableCell>
<Typography variant="body2">
{anomaly.description}
</Typography>
{anomaly.relatedIncident && (
<Typography variant="caption" color="primary">
Related: {anomaly.relatedIncident}
</Typography>
)}
</TableCell>
<TableCell>
<Box display="flex" alignItems="center">
<LinearProgress
variant="determinate"
value={anomaly.confidence}
color={anomaly.confidence > 80 ? 'success' : anomaly.confidence > 60 ? 'warning' : 'error'}
sx={{ width: 60, mr: 1 }}
/>
<Typography variant="caption">
{anomaly.confidence}%
</Typography>
</Box>
</TableCell>
<TableCell>
<Chip
label={anomaly.status}
color={getStatusColor(anomaly.status) as any}
size="small"
/>
</TableCell>
<TableCell>
<Typography variant="body2">
{anomaly.affectedSystems.join(', ')}
</Typography>
</TableCell>
<TableCell>
<Button size="small" variant="outlined">
Investigate
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
{/* Cost Analysis Tab */}
{activeTab === 3 && (
<>
<Typography variant="h5" gutterBottom>
Cost Impact Analysis
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Financial impact analysis of incidents and cost savings through automation
</Typography>
<Card sx={{ mt: 3 }}>
<CardContent>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Incident</TableCell>
<TableCell>Cost Category</TableCell>
<TableCell>Estimated Cost</TableCell>
<TableCell>Actual Cost</TableCell>
<TableCell>Savings</TableCell>
<TableCell>Analysis Date</TableCell>
<TableCell>Mitigation Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{costAnalyses.map((analysis) => (
<TableRow key={analysis.id}>
<TableCell>
<Typography variant="body2" fontWeight="bold">
{analysis.incidentId}
</Typography>
<Typography variant="caption" color="textSecondary">
{analysis.incidentTitle}
</Typography>
</TableCell>
<TableCell>
<Chip label={analysis.costCategory} size="small" />
</TableCell>
<TableCell>
<Typography variant="body2" color="error">
{formatCurrency(analysis.estimatedCost)}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2" color="warning">
{formatCurrency(analysis.actualCost)}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2" color="success" fontWeight="bold">
{formatCurrency(analysis.estimatedCost - analysis.actualCost)}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{formatTime(analysis.analysisDate)}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{analysis.mitigationActions.join(', ')}
</Typography>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
{/* Trends Tab */}
{activeTab === 4 && (
<>
<Typography variant="h5" gutterBottom>
Trend Analysis & Forecasting
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Historical trends and predictive forecasting for key metrics
</Typography>
<Grid container spacing={3} sx={{ mt: 2 }}>
{trendAnalyses.map((trend, index) => (
<Grid size={{ xs: 12, md: 6 }} key={index}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
{trend.metric}
</Typography>
<Box display="flex" alignItems="center" justifyContent="space-between" mb={2}>
<Typography variant="h4" color="primary">
{trend.currentValue}
</Typography>
<Box display="flex" alignItems="center">
{getTrendIcon(trend.trend)}
<Typography
variant="body2"
sx={{ ml: 1 }}
color={trend.changePercent > 0 ? 'success.main' : trend.changePercent < 0 ? 'error.main' : 'textSecondary'}
>
{trend.changePercent > 0 ? '+' : ''}{trend.changePercent.toFixed(1)}%
</Typography>
</Box>
</Box>
<Box mb={2}>
<Typography variant="body2" color="textSecondary">
Previous: {trend.previousValue} ({trend.timeframe})
</Typography>
</Box>
<Box>
<Typography variant="body2" color="textSecondary">
Prediction: {trend.prediction}
</Typography>
</Box>
</CardContent>
</Card>
</Grid>
))}
</Grid>
</>
)}
</Box>
);
};
export default AnalyticsDashboard;

View File

@@ -0,0 +1,972 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Card,
CardContent,
Typography,
Grid,
Chip,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
IconButton,
Button,
LinearProgress,
Alert,
Avatar,
List,
ListItem,
ListItemText,
ListItemIcon,
Tabs,
Tab,
Switch,
FormControlLabel,
} from '@mui/material';
import {
PlayArrow as PlayIcon,
Stop as StopIcon,
CheckCircle as CheckCircleIcon,
Error as ErrorIcon,
Warning as WarningIcon,
TrendingUp as TrendingUpIcon,
TrendingDown as TrendingDownIcon,
Refresh as RefreshIcon,
Settings as SettingsIcon,
Build as BuildIcon,
Speed as SpeedIcon,
} from '@mui/icons-material';
interface AutomationDashboardProps {
onNavigateToModule: (moduleId: string) => void;
}
interface AutomationStats {
totalRunbooks: number;
activeExecutions: number;
successfulExecutions: number;
failedExecutions: number;
successRate: number;
avgExecutionTime: number;
maintenanceWindows: number;
scheduledTasks: number;
}
interface Runbook {
id: string;
name: string;
description: string;
category: string;
status: 'ACTIVE' | 'INACTIVE' | 'DRAFT';
executionCount: number;
successRate: number;
avgExecutionTime: number;
lastExecuted: string;
triggers: string[];
steps: number;
}
interface AutomationExecution {
id: string;
runbookName: string;
status: 'RUNNING' | 'COMPLETED' | 'FAILED' | 'CANCELLED';
startedAt: string;
completedAt?: string;
executionTime?: number;
triggeredBy: string;
incidentId?: string;
progress: number;
currentStep: string;
errorMessage?: string;
}
interface MaintenanceWindow {
id: string;
name: string;
description: string;
startTime: string;
endTime: string;
status: 'SCHEDULED' | 'ACTIVE' | 'COMPLETED' | 'CANCELLED';
affectedServices: string[];
suppressIncidents: boolean;
suppressAlerts: boolean;
}
interface ScheduledTask {
id: string;
name: string;
description: string;
schedule: string;
nextRun: string;
status: 'ACTIVE' | 'PAUSED' | 'ERROR';
lastRun?: string;
lastStatus?: 'SUCCESS' | 'FAILED';
runbookId: string;
}
const AutomationDashboard: React.FC<AutomationDashboardProps> = ({ onNavigateToModule }) => {
const [activeTab, setActiveTab] = useState(0);
const [stats, setStats] = useState<AutomationStats>({
totalRunbooks: 0,
activeExecutions: 0,
successfulExecutions: 0,
failedExecutions: 0,
successRate: 0,
avgExecutionTime: 0,
maintenanceWindows: 0,
scheduledTasks: 0,
});
const [runbooks, setRunbooks] = useState<Runbook[]>([]);
const [executions, setExecutions] = useState<AutomationExecution[]>([]);
const [maintenanceWindows, setMaintenanceWindows] = useState<MaintenanceWindow[]>([]);
const [scheduledTasks, setScheduledTasks] = useState<ScheduledTask[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
loadAutomationData();
}, []);
const loadAutomationData = async () => {
try {
setIsLoading(true);
setError(null);
// Mock data - replace with actual API calls
setStats({
totalRunbooks: 45,
activeExecutions: 3,
successfulExecutions: 234,
failedExecutions: 12,
successRate: 95.1,
avgExecutionTime: 2.3,
maintenanceWindows: 8,
scheduledTasks: 23,
});
setRunbooks([
{
id: '1',
name: 'Database Restart Procedure',
description: 'Automated database restart with health checks',
category: 'Database',
status: 'ACTIVE',
executionCount: 45,
successRate: 97.8,
avgExecutionTime: 3.2,
lastExecuted: '2024-01-15T09:30:00Z',
triggers: ['Incident', 'Manual', 'Schedule'],
steps: 8,
},
{
id: '2',
name: 'Cache Clear and Rebuild',
description: 'Clear Redis cache and rebuild from database',
category: 'Cache',
status: 'ACTIVE',
executionCount: 23,
successRate: 91.3,
avgExecutionTime: 1.8,
lastExecuted: '2024-01-15T08:15:00Z',
triggers: ['Incident', 'Manual'],
steps: 5,
},
{
id: '3',
name: 'Load Balancer Failover',
description: 'Automated failover to backup load balancer',
category: 'Infrastructure',
status: 'ACTIVE',
executionCount: 12,
successRate: 100,
avgExecutionTime: 4.5,
lastExecuted: '2024-01-14T16:45:00Z',
triggers: ['Incident'],
steps: 12,
},
]);
setExecutions([
{
id: '1',
runbookName: 'Database Restart Procedure',
status: 'RUNNING',
startedAt: '2024-01-15T10:25:00Z',
triggeredBy: 'John Doe',
incidentId: 'INC-001',
progress: 65,
currentStep: 'Restarting database service',
},
{
id: '2',
runbookName: 'Cache Clear and Rebuild',
status: 'COMPLETED',
startedAt: '2024-01-15T10:15:00Z',
completedAt: '2024-01-15T10:18:00Z',
executionTime: 3,
triggeredBy: 'System',
incidentId: 'INC-002',
progress: 100,
currentStep: 'Completed',
},
{
id: '3',
runbookName: 'Load Balancer Failover',
status: 'FAILED',
startedAt: '2024-01-15T09:45:00Z',
completedAt: '2024-01-15T09:50:00Z',
executionTime: 5,
triggeredBy: 'Jane Smith',
incidentId: 'INC-003',
progress: 40,
currentStep: 'Failed at backup verification',
errorMessage: 'Backup load balancer unreachable',
},
]);
setMaintenanceWindows([
{
id: '1',
name: 'Database Maintenance',
description: 'Scheduled database optimization and cleanup',
startTime: '2024-01-16T02:00:00Z',
endTime: '2024-01-16T04:00:00Z',
status: 'SCHEDULED',
affectedServices: ['Database', 'API Gateway', 'User Service'],
suppressIncidents: true,
suppressAlerts: true,
},
{
id: '2',
name: 'Infrastructure Updates',
description: 'Server OS updates and security patches',
startTime: '2024-01-15T14:00:00Z',
endTime: '2024-01-15T16:00:00Z',
status: 'ACTIVE',
affectedServices: ['Web Servers', 'Load Balancers'],
suppressIncidents: false,
suppressAlerts: true,
},
]);
setScheduledTasks([
{
id: '1',
name: 'Daily Health Check',
description: 'Comprehensive system health check',
schedule: '0 6 * * *',
nextRun: '2024-01-16T06:00:00Z',
status: 'ACTIVE',
lastRun: '2024-01-15T06:00:00Z',
lastStatus: 'SUCCESS',
runbookId: 'health-check-001',
},
{
id: '2',
name: 'Weekly Report Generation',
description: 'Generate weekly incident and performance reports',
schedule: '0 9 * * 1',
nextRun: '2024-01-22T09:00:00Z',
status: 'ACTIVE',
lastRun: '2024-01-15T09:00:00Z',
lastStatus: 'SUCCESS',
runbookId: 'report-gen-001',
},
{
id: '3',
name: 'Cache Cleanup',
description: 'Clean up expired cache entries',
schedule: '0 */4 * * *',
nextRun: '2024-01-15T14:00:00Z',
status: 'ERROR',
lastRun: '2024-01-15T10:00:00Z',
lastStatus: 'FAILED',
runbookId: 'cache-cleanup-001',
},
]);
} catch (error) {
console.error('Failed to load automation data:', error);
setError('Failed to load automation data');
} finally {
setIsLoading(false);
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'COMPLETED':
case 'SUCCESS':
case 'ACTIVE':
return 'success';
case 'RUNNING':
case 'SCHEDULED':
return 'info';
case 'FAILED':
case 'ERROR':
case 'CANCELLED':
return 'error';
case 'PAUSED':
case 'DRAFT':
return 'warning';
default:
return 'default';
}
};
const getExecutionStatusIcon = (status: string) => {
switch (status) {
case 'RUNNING':
return <PlayIcon color="info" />;
case 'COMPLETED':
return <CheckCircleIcon color="success" />;
case 'FAILED':
return <ErrorIcon color="error" />;
case 'CANCELLED':
return <StopIcon color="warning" />;
default:
return <PlayIcon />;
}
};
const formatTime = (timestamp: string) => {
return new Date(timestamp).toLocaleString();
};
const formatDuration = (minutes: number) => {
const hours = Math.floor(minutes / 60);
const mins = Math.round(minutes % 60);
return hours > 0 ? `${hours}h ${mins}m` : `${mins}m`;
};
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;
subtitle?: string;
}> = ({ title, value, icon, color, trend, trendValue, subtitle }) => (
<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>
{subtitle && (
<Typography variant="body2" color="textSecondary">
{subtitle}
</Typography>
)}
{trend && trendValue && (
<Box display="flex" alignItems="center" mt={1}>
{trend === 'up' ? (
<TrendingUpIcon color="success" fontSize="small" />
) : trend === 'down' ? (
<TrendingDownIcon color="error" 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 Automation Dashboard...
</Typography>
</Box>
);
}
if (error) {
return (
<Alert severity="error" action={
<Button color="inherit" size="small" onClick={loadAutomationData}>
Retry
</Button>
}>
{error}
</Alert>
);
}
return (
<Box>
{/* Header */}
<Box display="flex" justifyContent="space-between" alignItems="center" mb={3}>
<Box>
<Typography variant="h4" gutterBottom>
Automation & Orchestration
</Typography>
<Typography variant="subtitle1" color="textSecondary">
Intelligent automation and runbook management
</Typography>
</Box>
<Box>
<Button
variant="contained"
startIcon={<BuildIcon />}
sx={{ mr: 2 }}
>
Create Runbook
</Button>
<IconButton onClick={loadAutomationData}>
<RefreshIcon />
</IconButton>
<IconButton>
<SettingsIcon />
</IconButton>
</Box>
</Box>
{/* Automation Status Alert */}
<Alert
severity={stats.successRate > 95 ? 'success' : stats.successRate > 85 ? 'warning' : 'error'}
sx={{ mb: 3 }}
icon={stats.successRate > 95 ? <CheckCircleIcon /> : <WarningIcon />}
>
<Typography variant="h6">
Automation Success Rate: {stats.successRate}%
</Typography>
<Typography variant="body2">
{stats.successRate > 95 ? 'Excellent' : stats.successRate > 85 ? 'Good' : 'Needs improvement'} automation performance.
{stats.activeExecutions > 0 && ` ${stats.activeExecutions} executions currently running.`}
</Typography>
</Alert>
{/* Tabs */}
<Paper sx={{ mb: 3 }}>
<Tabs value={activeTab} onChange={handleTabChange} indicatorColor="primary" textColor="primary">
<Tab label="Overview" />
<Tab label="Runbooks" />
<Tab label="Executions" />
<Tab label="Maintenance" />
<Tab label="Scheduled Tasks" />
</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 Runbooks"
value={stats.totalRunbooks}
icon={<BuildIcon />}
color="primary"
trend="up"
trendValue="+3 this week"
subtitle={`${runbooks.filter(r => r.status === 'ACTIVE').length} active`}
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Success Rate"
value={`${stats.successRate}%`}
icon={<CheckCircleIcon />}
color="success"
trend="up"
trendValue="+2% this month"
subtitle="Automation reliability"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Active Executions"
value={stats.activeExecutions}
icon={<PlayIcon />}
color="info"
trend="neutral"
trendValue="No change"
subtitle="Currently running"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Avg Execution Time"
value={`${stats.avgExecutionTime}m`}
icon={<SpeedIcon />}
color="secondary"
trend="down"
trendValue="-0.5m this week"
subtitle="Performance improvement"
/>
</Grid>
</Grid>
{/* Current Executions */}
<Card sx={{ mb: 3 }}>
<CardContent>
<Typography variant="h6" gutterBottom>
Active Executions
</Typography>
{executions.filter(e => e.status === 'RUNNING').length > 0 ? (
<List>
{executions.filter(e => e.status === 'RUNNING').map((execution) => (
<ListItem key={execution.id}>
<ListItemIcon>
{getExecutionStatusIcon(execution.status)}
</ListItemIcon>
<ListItemText
primary={
<Box display="flex" alignItems="center">
<Typography variant="body1" sx={{ mr: 2 }}>
{execution.runbookName}
</Typography>
<Chip
label={`Step ${Math.floor(execution.progress / 10)} of ${Math.floor(100 / 10)}`}
size="small"
color="info"
/>
</Box>
}
secondary={
<Box>
<Typography variant="body2" color="textSecondary">
{execution.currentStep}
</Typography>
<LinearProgress
variant="determinate"
value={execution.progress}
sx={{ mt: 1, height: 6 }}
/>
</Box>
}
/>
</ListItem>
))}
</List>
) : (
<Typography variant="body2" color="textSecondary">
No active executions
</Typography>
)}
</CardContent>
</Card>
{/* Recent Executions */}
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Recent Executions
</Typography>
<TableContainer>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>Runbook</TableCell>
<TableCell>Status</TableCell>
<TableCell>Triggered By</TableCell>
<TableCell>Duration</TableCell>
<TableCell>Incident</TableCell>
<TableCell>Started</TableCell>
</TableRow>
</TableHead>
<TableBody>
{executions.slice(0, 5).map((execution) => (
<TableRow key={execution.id}>
<TableCell>
<Typography variant="body2">
{execution.runbookName}
</Typography>
</TableCell>
<TableCell>
<Box display="flex" alignItems="center">
{getExecutionStatusIcon(execution.status)}
<Chip
label={execution.status}
color={getStatusColor(execution.status) as any}
size="small"
sx={{ ml: 1 }}
/>
</Box>
</TableCell>
<TableCell>
<Typography variant="body2">
{execution.triggeredBy}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{execution.executionTime ? formatDuration(execution.executionTime) : 'Running...'}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{execution.incidentId || '-'}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{formatTime(execution.startedAt)}
</Typography>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
{/* Runbooks Tab */}
{activeTab === 1 && (
<>
<Typography variant="h5" gutterBottom>
Runbook Library
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Manage and monitor automation runbooks
</Typography>
<Grid container spacing={3} sx={{ mt: 2 }}>
{runbooks.map((runbook) => (
<Grid size={{ xs: 12, md: 6, lg: 4 }} key={runbook.id}>
<Card>
<CardContent>
<Box display="flex" justifyContent="space-between" alignItems="start" mb={2}>
<Typography variant="h6">
{runbook.name}
</Typography>
<Chip
label={runbook.status}
color={getStatusColor(runbook.status) as any}
size="small"
/>
</Box>
<Typography variant="body2" color="textSecondary" paragraph>
{runbook.description}
</Typography>
<Box mb={2}>
<Chip label={runbook.category} size="small" sx={{ mr: 1 }} />
<Typography variant="caption" color="textSecondary">
{runbook.steps} steps
</Typography>
</Box>
<Box display="flex" justifyContent="space-between" mb={2}>
<Typography variant="caption">
Executions: {runbook.executionCount}
</Typography>
<Typography variant="caption">
Success Rate: {runbook.successRate}%
</Typography>
</Box>
<Box display="flex" justifyContent="space-between" mb={2}>
<Typography variant="caption">
Avg Time: {formatDuration(runbook.avgExecutionTime)}
</Typography>
<Typography variant="caption">
Last Run: {formatTime(runbook.lastExecuted)}
</Typography>
</Box>
<Box mb={2}>
<Typography variant="caption" color="textSecondary" display="block">
Triggers:
</Typography>
{runbook.triggers.map((trigger, index) => (
<Chip
key={index}
label={trigger}
size="small"
variant="outlined"
sx={{ mr: 0.5, mt: 0.5 }}
/>
))}
</Box>
<Button
variant="outlined"
size="small"
startIcon={<PlayIcon />}
fullWidth
>
Execute
</Button>
</CardContent>
</Card>
</Grid>
))}
</Grid>
</>
)}
{/* Executions Tab */}
{activeTab === 2 && (
<>
<Typography variant="h5" gutterBottom>
Execution History
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Detailed execution logs and performance metrics
</Typography>
<Card sx={{ mt: 3 }}>
<CardContent>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Runbook</TableCell>
<TableCell>Status</TableCell>
<TableCell>Progress</TableCell>
<TableCell>Triggered By</TableCell>
<TableCell>Incident</TableCell>
<TableCell>Duration</TableCell>
<TableCell>Started</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{executions.map((execution) => (
<TableRow key={execution.id}>
<TableCell>
<Typography variant="body2" fontWeight="bold">
{execution.runbookName}
</Typography>
</TableCell>
<TableCell>
<Box display="flex" alignItems="center">
{getExecutionStatusIcon(execution.status)}
<Chip
label={execution.status}
color={getStatusColor(execution.status) as any}
size="small"
sx={{ ml: 1 }}
/>
</Box>
</TableCell>
<TableCell>
<Box display="flex" alignItems="center">
<LinearProgress
variant="determinate"
value={execution.progress}
color={execution.status === 'COMPLETED' ? 'success' : execution.status === 'FAILED' ? 'error' : 'primary'}
sx={{ width: 60, mr: 1 }}
/>
<Typography variant="caption">
{execution.progress}%
</Typography>
</Box>
</TableCell>
<TableCell>
<Typography variant="body2">
{execution.triggeredBy}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{execution.incidentId || '-'}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{execution.executionTime ? formatDuration(execution.executionTime) : 'Running...'}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{formatTime(execution.startedAt)}
</Typography>
</TableCell>
<TableCell>
<Button size="small" variant="outlined">
View Logs
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
{/* Maintenance Windows Tab */}
{activeTab === 3 && (
<>
<Typography variant="h5" gutterBottom>
Maintenance Windows
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Schedule and manage system maintenance windows
</Typography>
<Grid container spacing={3} sx={{ mt: 2 }}>
{maintenanceWindows.map((window) => (
<Grid size={{ xs: 12, md: 6 }} key={window.id}>
<Card>
<CardContent>
<Box display="flex" justifyContent="space-between" alignItems="start" mb={2}>
<Typography variant="h6">
{window.name}
</Typography>
<Chip
label={window.status}
color={getStatusColor(window.status) as any}
size="small"
/>
</Box>
<Typography variant="body2" color="textSecondary" paragraph>
{window.description}
</Typography>
<Box mb={2}>
<Typography variant="body2" fontWeight="bold">
Schedule:
</Typography>
<Typography variant="body2">
{formatTime(window.startTime)} - {formatTime(window.endTime)}
</Typography>
</Box>
<Box mb={2}>
<Typography variant="body2" fontWeight="bold">
Affected Services:
</Typography>
<Box sx={{ mt: 1 }}>
{window.affectedServices.map((service, index) => (
<Chip
key={index}
label={service}
size="small"
sx={{ mr: 0.5, mb: 0.5 }}
/>
))}
</Box>
</Box>
<Box display="flex" justifyContent="space-between">
<FormControlLabel
control={<Switch checked={window.suppressIncidents} />}
label="Suppress Incidents"
/>
<FormControlLabel
control={<Switch checked={window.suppressAlerts} />}
label="Suppress Alerts"
/>
</Box>
</CardContent>
</Card>
</Grid>
))}
</Grid>
</>
)}
{/* Scheduled Tasks Tab */}
{activeTab === 4 && (
<>
<Typography variant="h5" gutterBottom>
Scheduled Tasks
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Manage automated scheduled tasks and cron jobs
</Typography>
<Card sx={{ mt: 3 }}>
<CardContent>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Task Name</TableCell>
<TableCell>Schedule</TableCell>
<TableCell>Status</TableCell>
<TableCell>Next Run</TableCell>
<TableCell>Last Run</TableCell>
<TableCell>Last Status</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{scheduledTasks.map((task) => (
<TableRow key={task.id}>
<TableCell>
<Typography variant="body2" fontWeight="bold">
{task.name}
</Typography>
<Typography variant="caption" color="textSecondary">
{task.description}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2" fontFamily="monospace">
{task.schedule}
</Typography>
</TableCell>
<TableCell>
<Chip
label={task.status}
color={getStatusColor(task.status) as any}
size="small"
/>
</TableCell>
<TableCell>
<Typography variant="body2">
{formatTime(task.nextRun)}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{task.lastRun ? formatTime(task.lastRun) : '-'}
</Typography>
</TableCell>
<TableCell>
{task.lastStatus ? (
<Chip
label={task.lastStatus}
color={getStatusColor(task.lastStatus) as any}
size="small"
/>
) : (
<Typography variant="body2" color="textSecondary">
-
</Typography>
)}
</TableCell>
<TableCell>
<Button size="small" variant="outlined">
Configure
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
</Box>
);
};
export default AutomationDashboard;

View File

@@ -0,0 +1,882 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Card,
CardContent,
Typography,
Grid,
Chip,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
IconButton,
Button,
LinearProgress,
Alert,
Avatar,
Tabs,
Tab,
Switch,
FormControlLabel,
Stack,
} from '@mui/material';
import {
Group as GroupIcon,
Message as MessageIcon,
PersonAdd as PersonAddIcon,
Refresh as RefreshIcon,
Settings as SettingsIcon,
CheckCircle as CheckCircleIcon,
TrendingUp as TrendingUpIcon,
TrendingDown as TrendingDownIcon,
BugReport as BugReportIcon,
Visibility as VisibilityIcon,
Lock as LockIcon,
Public as PublicIcon,
} from '@mui/icons-material';
interface CollaborationDashboardProps {
onNavigateToModule: (moduleId: string) => void;
}
interface CollaborationStats {
totalWarRooms: number;
activeWarRooms: number;
totalParticipants: number;
activeParticipants: number;
messagesToday: number;
avgResponseTime: number;
incidentsResolved: number;
knowledgeItemsCreated: number;
}
interface WarRoom {
id: string;
name: string;
description: string;
incidentId: string;
incidentTitle: string;
status: 'ACTIVE' | 'ARCHIVED' | 'CLOSED';
privacyLevel: 'PUBLIC' | 'PRIVATE' | 'RESTRICTED';
participants: number;
maxParticipants: number;
createdBy: string;
createdAt: string;
lastActivity: string;
messageCount: number;
integrations: string[];
}
interface WarRoomParticipant {
id: string;
name: string;
email: string;
role: 'INCIDENT_COMMANDER' | 'DEPUTY' | 'COMMUNICATIONS' | 'PLANNING' | 'OPERATIONS' | 'LOGISTICS' | 'FINANCE' | 'PARTICIPANT';
status: 'ONLINE' | 'AWAY' | 'OFFLINE';
joinedAt: string;
lastSeen: string;
messageCount: number;
avatar?: string;
}
interface WarRoomMessage {
id: string;
participantId: string;
participantName: string;
content: string;
timestamp: string;
type: 'TEXT' | 'IMAGE' | 'FILE' | 'COMMAND' | 'SYSTEM';
isEdited: boolean;
reactions: { [emoji: string]: number };
attachments?: string[];
}
interface ChatCommand {
id: string;
name: string;
description: string;
usage: string;
category: 'INCIDENT' | 'AUTOMATION' | 'INFO' | 'UTILITY';
isEnabled: boolean;
usageCount: number;
}
const CollaborationDashboard: React.FC<CollaborationDashboardProps> = ({ onNavigateToModule }) => {
const [activeTab, setActiveTab] = useState(0);
const [stats, setStats] = useState<CollaborationStats>({
totalWarRooms: 0,
activeWarRooms: 0,
totalParticipants: 0,
activeParticipants: 0,
messagesToday: 0,
avgResponseTime: 0,
incidentsResolved: 0,
knowledgeItemsCreated: 0,
});
const [warRooms, setWarRooms] = useState<WarRoom[]>([]);
const [participants, setParticipants] = useState<WarRoomParticipant[]>([]);
const [, setMessages] = useState<WarRoomMessage[]>([]);
const [chatCommands, setChatCommands] = useState<ChatCommand[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
loadCollaborationData();
}, []);
const loadCollaborationData = async () => {
try {
setIsLoading(true);
setError(null);
// Mock data - replace with actual API calls
setStats({
totalWarRooms: 23,
activeWarRooms: 5,
totalParticipants: 156,
activeParticipants: 42,
messagesToday: 1247,
avgResponseTime: 0.8,
incidentsResolved: 18,
knowledgeItemsCreated: 12,
});
setWarRooms([
{
id: '1',
name: 'Database Outage Response',
description: 'Critical database connectivity issues affecting multiple services',
incidentId: 'INC-001',
incidentTitle: 'Database Connection Timeout',
status: 'ACTIVE',
privacyLevel: 'PRIVATE',
participants: 8,
maxParticipants: 12,
createdBy: 'John Doe',
createdAt: '2024-01-15T10:00:00Z',
lastActivity: '2024-01-15T11:25:00Z',
messageCount: 47,
integrations: ['Slack', 'Teams', 'Jira'],
},
{
id: '2',
name: 'API Gateway Investigation',
description: 'Investigation of API gateway performance degradation',
incidentId: 'INC-002',
incidentTitle: 'API Gateway Error',
status: 'ACTIVE',
privacyLevel: 'RESTRICTED',
participants: 5,
maxParticipants: 8,
createdBy: 'Jane Smith',
createdAt: '2024-01-15T09:30:00Z',
lastActivity: '2024-01-15T11:20:00Z',
messageCount: 32,
integrations: ['Slack', 'PagerDuty'],
},
{
id: '3',
name: 'Security Incident Response',
description: 'Response to potential security breach investigation',
incidentId: 'INC-003',
incidentTitle: 'Security Incident',
status: 'ACTIVE',
privacyLevel: 'RESTRICTED',
participants: 6,
maxParticipants: 10,
createdBy: 'Mike Johnson',
createdAt: '2024-01-15T08:45:00Z',
lastActivity: '2024-01-15T11:15:00Z',
messageCount: 28,
integrations: ['Teams', 'Security Tools'],
},
]);
setParticipants([
{
id: '1',
name: 'John Doe',
email: 'john.doe@company.com',
role: 'INCIDENT_COMMANDER',
status: 'ONLINE',
joinedAt: '2024-01-15T10:00:00Z',
lastSeen: '2024-01-15T11:25:00Z',
messageCount: 15,
},
{
id: '2',
name: 'Jane Smith',
email: 'jane.smith@company.com',
role: 'DEPUTY',
status: 'ONLINE',
joinedAt: '2024-01-15T10:05:00Z',
lastSeen: '2024-01-15T11:24:00Z',
messageCount: 12,
},
{
id: '3',
name: 'Mike Johnson',
email: 'mike.johnson@company.com',
role: 'OPERATIONS',
status: 'AWAY',
joinedAt: '2024-01-15T10:10:00Z',
lastSeen: '2024-01-15T11:20:00Z',
messageCount: 8,
},
{
id: '4',
name: 'Sarah Wilson',
email: 'sarah.wilson@company.com',
role: 'COMMUNICATIONS',
status: 'ONLINE',
joinedAt: '2024-01-15T10:15:00Z',
lastSeen: '2024-01-15T11:25:00Z',
messageCount: 6,
},
]);
setMessages([
{
id: '1',
participantId: '1',
participantName: 'John Doe',
content: 'All hands on deck. We have a critical database connectivity issue affecting multiple services.',
timestamp: '2024-01-15T10:00:00Z',
type: 'TEXT',
isEdited: false,
reactions: { '👍': 5, '🔥': 2 },
},
{
id: '2',
participantId: '2',
participantName: 'Jane Smith',
content: 'I\'m checking the database cluster status now. Initial reports show connection timeouts.',
timestamp: '2024-01-15T10:02:00Z',
type: 'TEXT',
isEdited: false,
reactions: { '👀': 3 },
},
{
id: '3',
participantId: '1',
participantName: 'John Doe',
content: '/status db-cluster-1',
timestamp: '2024-01-15T10:03:00Z',
type: 'COMMAND',
isEdited: false,
reactions: {},
},
{
id: '4',
participantId: '3',
participantName: 'Mike Johnson',
content: 'Database cluster is showing 40% connection failures. Primary node appears to be overloaded.',
timestamp: '2024-01-15T10:05:00Z',
type: 'TEXT',
isEdited: false,
reactions: { '😰': 4, '👍': 2 },
},
]);
setChatCommands([
{
id: '1',
name: 'status',
description: 'Get status of a service or component',
usage: '/status <service-name>',
category: 'INFO',
isEnabled: true,
usageCount: 45,
},
{
id: '2',
name: 'escalate',
description: 'Escalate incident to next level',
usage: '/escalate <reason>',
category: 'INCIDENT',
isEnabled: true,
usageCount: 12,
},
{
id: '3',
name: 'runbook',
description: 'Execute a runbook or automation',
usage: '/runbook <runbook-name>',
category: 'AUTOMATION',
isEnabled: true,
usageCount: 23,
},
{
id: '4',
name: 'notify',
description: 'Send notification to stakeholders',
usage: '/notify <message>',
category: 'UTILITY',
isEnabled: true,
usageCount: 18,
},
]);
} catch (error) {
console.error('Failed to load collaboration data:', error);
setError('Failed to load collaboration data');
} finally {
setIsLoading(false);
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'ACTIVE':
case 'ONLINE':
return 'success';
case 'AWAY':
return 'warning';
case 'OFFLINE':
case 'ARCHIVED':
return 'default';
case 'CLOSED':
return 'error';
default:
return 'default';
}
};
const getPrivacyIcon = (privacyLevel: string) => {
switch (privacyLevel) {
case 'PUBLIC':
return <PublicIcon color="success" />;
case 'PRIVATE':
return <VisibilityIcon color="warning" />;
case 'RESTRICTED':
return <LockIcon color="error" />;
default:
return <VisibilityIcon />;
}
};
const getRoleColor = (role: string) => {
switch (role) {
case 'INCIDENT_COMMANDER':
return 'error';
case 'DEPUTY':
return 'warning';
case 'COMMUNICATIONS':
case 'PLANNING':
case 'OPERATIONS':
case 'LOGISTICS':
case 'FINANCE':
return 'info';
default:
return 'default';
}
};
const formatTime = (timestamp: string) => {
return new Date(timestamp).toLocaleString();
};
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;
subtitle?: string;
}> = ({ title, value, icon, color, trend, trendValue, subtitle }) => (
<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>
{subtitle && (
<Typography variant="body2" color="textSecondary">
{subtitle}
</Typography>
)}
{trend && trendValue && (
<Box display="flex" alignItems="center" mt={1}>
{trend === 'up' ? (
<TrendingUpIcon color="success" fontSize="small" />
) : trend === 'down' ? (
<TrendingDownIcon color="error" 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 Collaboration Dashboard...
</Typography>
</Box>
);
}
if (error) {
return (
<Alert severity="error" action={
<Button color="inherit" size="small" onClick={loadCollaborationData}>
Retry
</Button>
}>
{error}
</Alert>
);
}
return (
<Box>
{/* Header */}
<Box display="flex" justifyContent="space-between" alignItems="center" mb={3}>
<Box>
<Typography variant="h4" gutterBottom>
War Rooms & Collaboration
</Typography>
<Typography variant="subtitle1" color="textSecondary">
Real-time incident collaboration and communication
</Typography>
</Box>
<Box>
<Button
variant="contained"
startIcon={<GroupIcon />}
sx={{ mr: 2 }}
>
Create War Room
</Button>
<IconButton onClick={loadCollaborationData}>
<RefreshIcon />
</IconButton>
<IconButton>
<SettingsIcon />
</IconButton>
</Box>
</Box>
{/* Collaboration Status Alert */}
<Alert
severity={stats.activeWarRooms > 0 ? 'info' : 'success'}
sx={{ mb: 3 }}
icon={stats.activeWarRooms > 0 ? <GroupIcon /> : <CheckCircleIcon />}
>
<Typography variant="h6">
Active War Rooms: {stats.activeWarRooms}
</Typography>
<Typography variant="body2">
{stats.activeWarRooms > 0 ? 'Currently managing incidents with active collaboration rooms.' : 'No active war rooms. All incidents resolved or in normal operations.'}
{stats.activeParticipants > 0 && ` ${stats.activeParticipants} participants currently online.`}
</Typography>
</Alert>
{/* Tabs */}
<Paper sx={{ mb: 3 }}>
<Tabs value={activeTab} onChange={handleTabChange} indicatorColor="primary" textColor="primary">
<Tab label="Overview" />
<Tab label="War Rooms" />
<Tab label="Participants" />
<Tab label="Chat Commands" />
</Tabs>
</Paper>
{/* Tab Content */}
{activeTab === 0 && (
<>
{/* Stats Cards */}
<Grid container spacing={3} mb={3}>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Active War Rooms"
value={stats.activeWarRooms}
icon={<GroupIcon />}
color="primary"
trend="up"
trendValue="+2 this week"
subtitle={`${stats.totalWarRooms} total rooms`}
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Active Participants"
value={stats.activeParticipants}
icon={<PersonAddIcon />}
color="success"
trend="up"
trendValue="+8 today"
subtitle={`${stats.totalParticipants} total users`}
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Messages Today"
value={stats.messagesToday}
icon={<MessageIcon />}
color="info"
trend="up"
trendValue="+156 from yesterday"
subtitle="Real-time collaboration"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Incidents Resolved"
value={stats.incidentsResolved}
icon={<CheckCircleIcon />}
color="success"
trend="up"
trendValue="+3 this week"
subtitle="Through collaboration"
/>
</Grid>
</Grid>
{/* Active War Rooms */}
<Card sx={{ mb: 3 }}>
<CardContent>
<Typography variant="h6" gutterBottom>
Active War Rooms
</Typography>
<Grid container spacing={2}>
{warRooms.filter(room => room.status === 'ACTIVE').map((room) => (
<Grid size={{ xs: 12, md: 4 }} key={room.id}>
<Paper sx={{ p: 2, border: '1px solid', borderColor: 'primary.main' }}>
<Box display="flex" justifyContent="space-between" alignItems="start" mb={2}>
<Typography variant="h6">
{room.name}
</Typography>
<Box display="flex" alignItems="center">
{getPrivacyIcon(room.privacyLevel)}
<Chip
label={room.status}
color={getStatusColor(room.status) as any}
size="small"
sx={{ ml: 1 }}
/>
</Box>
</Box>
<Typography variant="body2" color="textSecondary" paragraph>
{room.description}
</Typography>
<Box display="flex" justifyContent="space-between" mb={2}>
<Typography variant="caption">
<BugReportIcon fontSize="small" sx={{ mr: 0.5, verticalAlign: 'middle' }} />
{room.incidentId}
</Typography>
<Typography variant="caption">
<GroupIcon fontSize="small" sx={{ mr: 0.5, verticalAlign: 'middle' }} />
{room.participants}/{room.maxParticipants}
</Typography>
</Box>
<Box display="flex" justifyContent="space-between" mb={2}>
<Typography variant="caption">
<MessageIcon fontSize="small" sx={{ mr: 0.5, verticalAlign: 'middle' }} />
{room.messageCount} messages
</Typography>
<Typography variant="caption">
Last activity: {formatTime(room.lastActivity)}
</Typography>
</Box>
<Box>
<Typography variant="caption" color="textSecondary" display="block">
Integrations:
</Typography>
<Stack direction="row" spacing={0.5} sx={{ mt: 0.5 }}>
{room.integrations.map((integration, index) => (
<Chip
key={index}
label={integration}
size="small"
variant="outlined"
/>
))}
</Stack>
</Box>
</Paper>
</Grid>
))}
</Grid>
</CardContent>
</Card>
</>
)}
{/* War Rooms Tab */}
{activeTab === 1 && (
<>
<Typography variant="h5" gutterBottom>
War Room Management
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Manage incident collaboration rooms and communications
</Typography>
<Card sx={{ mt: 3 }}>
<CardContent>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>War Room</TableCell>
<TableCell>Incident</TableCell>
<TableCell>Status</TableCell>
<TableCell>Privacy</TableCell>
<TableCell>Participants</TableCell>
<TableCell>Messages</TableCell>
<TableCell>Last Activity</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{warRooms.map((room) => (
<TableRow key={room.id}>
<TableCell>
<Box>
<Typography variant="body2" fontWeight="bold">
{room.name}
</Typography>
<Typography variant="caption" color="textSecondary">
{room.description}
</Typography>
</Box>
</TableCell>
<TableCell>
<Typography variant="body2">
{room.incidentId}
</Typography>
<Typography variant="caption" color="textSecondary">
{room.incidentTitle}
</Typography>
</TableCell>
<TableCell>
<Chip
label={room.status}
color={getStatusColor(room.status) as any}
size="small"
/>
</TableCell>
<TableCell>
<Box display="flex" alignItems="center">
{getPrivacyIcon(room.privacyLevel)}
<Typography variant="body2" sx={{ ml: 1 }}>
{room.privacyLevel}
</Typography>
</Box>
</TableCell>
<TableCell>
<Typography variant="body2">
{room.participants}/{room.maxParticipants}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{room.messageCount}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{formatTime(room.lastActivity)}
</Typography>
</TableCell>
<TableCell>
<Button size="small" variant="outlined">
Join
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
{/* Participants Tab */}
{activeTab === 2 && (
<>
<Typography variant="h5" gutterBottom>
War Room Participants
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Monitor participant activity and engagement
</Typography>
<Card sx={{ mt: 3 }}>
<CardContent>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Participant</TableCell>
<TableCell>Role</TableCell>
<TableCell>Status</TableCell>
<TableCell>Messages</TableCell>
<TableCell>Joined</TableCell>
<TableCell>Last Seen</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{participants.map((participant) => (
<TableRow key={participant.id}>
<TableCell>
<Box display="flex" alignItems="center">
<Avatar sx={{ mr: 2 }}>
{participant.name.split(' ').map(n => n[0]).join('')}
</Avatar>
<Box>
<Typography variant="body2" fontWeight="bold">
{participant.name}
</Typography>
<Typography variant="caption" color="textSecondary">
{participant.email}
</Typography>
</Box>
</Box>
</TableCell>
<TableCell>
<Chip
label={participant.role.replace('_', ' ')}
color={getRoleColor(participant.role) as any}
size="small"
/>
</TableCell>
<TableCell>
<Box display="flex" alignItems="center">
<Box
sx={{
width: 8,
height: 8,
borderRadius: '50%',
bgcolor: participant.status === 'ONLINE' ? 'success.main' :
participant.status === 'AWAY' ? 'warning.main' : 'grey.400',
mr: 1
}}
/>
<Typography variant="body2">
{participant.status}
</Typography>
</Box>
</TableCell>
<TableCell>
<Typography variant="body2">
{participant.messageCount}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{formatTime(participant.joinedAt)}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{formatTime(participant.lastSeen)}
</Typography>
</TableCell>
<TableCell>
<Button size="small" variant="outlined">
Message
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
{/* Chat Commands Tab */}
{activeTab === 3 && (
<>
<Typography variant="h5" gutterBottom>
Chat Commands & Automation
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Manage chat commands and automated responses
</Typography>
<Grid container spacing={3} sx={{ mt: 2 }}>
{chatCommands.map((command) => (
<Grid size={{ xs: 12, sm: 6, md: 4 }} key={command.id}>
<Card>
<CardContent>
<Box display="flex" justifyContent="space-between" alignItems="start" mb={2}>
<Typography variant="h6">
/{command.name}
</Typography>
<Chip
label={command.category}
size="small"
color="info"
/>
</Box>
<Typography variant="body2" color="textSecondary" paragraph>
{command.description}
</Typography>
<Box mb={2}>
<Typography variant="body2" fontWeight="bold">
Usage:
</Typography>
<Typography variant="body2" fontFamily="monospace" sx={{ bgcolor: 'grey.100', p: 1, borderRadius: 1 }}>
{command.usage}
</Typography>
</Box>
<Box display="flex" justifyContent="space-between" alignItems="center" mb={2}>
<Typography variant="caption">
Used {command.usageCount} times
</Typography>
<FormControlLabel
control={<Switch checked={command.isEnabled} />}
label="Enabled"
/>
</Box>
<Button
variant="outlined"
size="small"
fullWidth
>
Configure
</Button>
</CardContent>
</Card>
</Grid>
))}
</Grid>
</>
)}
</Box>
);
};
export default CollaborationDashboard;

View File

@@ -0,0 +1,935 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Card,
CardContent,
Typography,
Grid,
Chip,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
IconButton,
Button,
LinearProgress,
Alert,
Avatar,
Tabs,
Tab,
} from '@mui/material';
import {
Gavel as GavelIcon,
TrendingUp as TrendingUpIcon,
TrendingDown as TrendingDownIcon,
Refresh as RefreshIcon,
Settings as SettingsIcon,
Policy as PolicyIcon,
VerifiedUser as VerifiedUserIcon,
Warning as WarningIcon,
Assessment as AssessmentIcon,
} from '@mui/icons-material';
interface ComplianceDashboardProps {
onNavigateToModule: (moduleId: string) => void;
}
interface ComplianceStats {
totalPolicies: number;
activePolicies: number;
complianceScore: number;
violationsFound: number;
violationsResolved: number;
auditScore: number;
lastAuditDate: string;
nextAuditDate: string;
}
interface CompliancePolicy {
id: string;
name: string;
description: string;
category: 'SECURITY' | 'DATA_PROTECTION' | 'INCIDENT_RESPONSE' | 'ACCESS_CONTROL' | 'AUDIT';
framework: 'SOX' | 'HIPAA' | 'GDPR' | 'PCI-DSS' | 'ISO27001' | 'NIST';
status: 'ACTIVE' | 'DRAFT' | 'REVIEW' | 'ARCHIVED';
lastUpdated: string;
owner: string;
complianceLevel: number;
violations: number;
}
interface ComplianceViolation {
id: string;
policyId: string;
policyName: string;
description: string;
severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
status: 'OPEN' | 'INVESTIGATING' | 'RESOLVED' | 'ACCEPTED_RISK';
discoveredAt: string;
resolvedAt?: string;
assignedTo: string;
framework: string;
remediationSteps: string[];
}
interface AuditFinding {
id: string;
auditId: string;
auditName: string;
finding: string;
category: 'COMPLIANCE' | 'SECURITY' | 'PROCESS' | 'TECHNICAL';
severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
status: 'OPEN' | 'IN_PROGRESS' | 'RESOLVED' | 'ACCEPTED';
discoveredAt: string;
dueDate: string;
assignedTo: string;
framework: string;
}
interface ComplianceReport {
id: string;
name: string;
type: 'MONTHLY' | 'QUARTERLY' | 'ANNUAL' | 'AUDIT';
framework: string;
generatedAt: string;
score: number;
status: 'GENERATED' | 'REVIEW' | 'APPROVED' | 'PUBLISHED';
findings: number;
violations: number;
recommendations: number;
}
const ComplianceDashboard: React.FC<ComplianceDashboardProps> = ({ onNavigateToModule }) => {
const [activeTab, setActiveTab] = useState(0);
const [stats, setStats] = useState<ComplianceStats>({
totalPolicies: 0,
activePolicies: 0,
complianceScore: 0,
violationsFound: 0,
violationsResolved: 0,
auditScore: 0,
lastAuditDate: '',
nextAuditDate: '',
});
const [policies, setPolicies] = useState<CompliancePolicy[]>([]);
const [violations, setViolations] = useState<ComplianceViolation[]>([]);
const [auditFindings, setAuditFindings] = useState<AuditFinding[]>([]);
const [reports, setReports] = useState<ComplianceReport[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
loadComplianceData();
}, []);
const loadComplianceData = async () => {
try {
setIsLoading(true);
setError(null);
// Mock data - replace with actual API calls
setStats({
totalPolicies: 45,
activePolicies: 42,
complianceScore: 94,
violationsFound: 8,
violationsResolved: 23,
auditScore: 87,
lastAuditDate: '2024-01-01T00:00:00Z',
nextAuditDate: '2024-04-01T00:00:00Z',
});
setPolicies([
{
id: '1',
name: 'Data Classification and Handling',
description: 'Policy for classifying and handling sensitive data according to GDPR requirements',
category: 'DATA_PROTECTION',
framework: 'GDPR',
status: 'ACTIVE',
lastUpdated: '2024-01-15T10:30:00Z',
owner: 'Data Protection Officer',
complianceLevel: 95,
violations: 2,
},
{
id: '2',
name: 'Incident Response Procedures',
description: 'Standardized procedures for responding to security incidents',
category: 'INCIDENT_RESPONSE',
framework: 'ISO27001',
status: 'ACTIVE',
lastUpdated: '2024-01-14T15:20:00Z',
owner: 'Security Team',
complianceLevel: 89,
violations: 1,
},
{
id: '3',
name: 'Access Control Management',
description: 'Controls for managing user access to systems and data',
category: 'ACCESS_CONTROL',
framework: 'SOX',
status: 'ACTIVE',
lastUpdated: '2024-01-13T09:15:00Z',
owner: 'IT Security',
complianceLevel: 92,
violations: 3,
},
]);
setViolations([
{
id: '1',
policyId: '1',
policyName: 'Data Classification and Handling',
description: 'Personal data stored without proper encryption',
severity: 'HIGH',
status: 'INVESTIGATING',
discoveredAt: '2024-01-15T10:00:00Z',
assignedTo: 'Data Protection Officer',
framework: 'GDPR',
remediationSteps: ['Encrypt personal data', 'Review data storage practices', 'Update classification procedures'],
},
{
id: '2',
policyId: '2',
policyName: 'Incident Response Procedures',
description: 'Incident response time exceeded SLA requirements',
severity: 'MEDIUM',
status: 'OPEN',
discoveredAt: '2024-01-14T16:30:00Z',
assignedTo: 'Incident Response Team',
framework: 'ISO27001',
remediationSteps: ['Review response procedures', 'Update escalation matrix', 'Conduct team training'],
},
{
id: '3',
policyId: '3',
policyName: 'Access Control Management',
description: 'Orphaned user accounts found in active directory',
severity: 'LOW',
status: 'RESOLVED',
discoveredAt: '2024-01-13T14:20:00Z',
resolvedAt: '2024-01-14T10:15:00Z',
assignedTo: 'IT Administrator',
framework: 'SOX',
remediationSteps: ['Remove orphaned accounts', 'Implement automated cleanup', 'Review access provisioning'],
},
]);
setAuditFindings([
{
id: '1',
auditId: 'AUD-2024-001',
auditName: 'Q1 2024 Compliance Audit',
finding: 'Insufficient logging of data access events',
category: 'COMPLIANCE',
severity: 'HIGH',
status: 'IN_PROGRESS',
discoveredAt: '2024-01-10T00:00:00Z',
dueDate: '2024-02-15T00:00:00Z',
assignedTo: 'Security Team',
framework: 'GDPR',
},
{
id: '2',
auditId: 'AUD-2024-001',
auditName: 'Q1 2024 Compliance Audit',
finding: 'Missing documentation for incident response procedures',
category: 'PROCESS',
severity: 'MEDIUM',
status: 'OPEN',
discoveredAt: '2024-01-10T00:00:00Z',
dueDate: '2024-02-28T00:00:00Z',
assignedTo: 'Process Owner',
framework: 'ISO27001',
},
]);
setReports([
{
id: '1',
name: 'Q1 2024 Compliance Report',
type: 'QUARTERLY',
framework: 'GDPR',
generatedAt: '2024-01-15T12:00:00Z',
score: 94,
status: 'GENERATED',
findings: 5,
violations: 2,
recommendations: 8,
},
{
id: '2',
name: 'Annual Security Audit Report',
type: 'ANNUAL',
framework: 'ISO27001',
generatedAt: '2024-01-01T00:00:00Z',
score: 87,
status: 'APPROVED',
findings: 12,
violations: 3,
recommendations: 15,
},
]);
} catch (error) {
console.error('Failed to load compliance data:', error);
setError('Failed to load compliance data');
} finally {
setIsLoading(false);
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'ACTIVE':
case 'RESOLVED':
case 'APPROVED':
case 'PUBLISHED':
return 'success';
case 'DRAFT':
case 'IN_PROGRESS':
case 'REVIEW':
return 'warning';
case 'OPEN':
case 'GENERATED':
return 'info';
case 'ARCHIVED':
case 'ACCEPTED_RISK':
case 'ACCEPTED':
return 'default';
default:
return 'default';
}
};
const getSeverityColor = (severity: string) => {
switch (severity) {
case 'CRITICAL':
return 'error';
case 'HIGH':
return 'warning';
case 'MEDIUM':
return 'info';
case 'LOW':
return 'success';
default:
return 'default';
}
};
const getFrameworkColor = (framework: string) => {
switch (framework) {
case 'SOX':
return 'primary';
case 'HIPAA':
return 'secondary';
case 'GDPR':
return 'success';
case 'PCI-DSS':
return 'warning';
case 'ISO27001':
return 'info';
case 'NIST':
return 'error';
default:
return 'default';
}
};
const formatTime = (timestamp: string) => {
return new Date(timestamp).toLocaleString();
};
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;
subtitle?: string;
}> = ({ title, value, icon, color, trend, trendValue, subtitle }) => (
<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>
{subtitle && (
<Typography variant="body2" color="textSecondary">
{subtitle}
</Typography>
)}
{trend && trendValue && (
<Box display="flex" alignItems="center" mt={1}>
{trend === 'up' ? (
<TrendingUpIcon color="success" fontSize="small" />
) : trend === 'down' ? (
<TrendingDownIcon color="error" 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 Compliance Dashboard...
</Typography>
</Box>
);
}
if (error) {
return (
<Alert severity="error" action={
<Button color="inherit" size="small" onClick={loadComplianceData}>
Retry
</Button>
}>
{error}
</Alert>
);
}
return (
<Box>
{/* Header */}
<Box display="flex" justifyContent="space-between" alignItems="center" mb={3}>
<Box>
<Typography variant="h4" gutterBottom>
Compliance & Governance
</Typography>
<Typography variant="subtitle1" color="textSecondary">
Regulatory compliance management and governance framework
</Typography>
</Box>
<Box>
<Button
variant="contained"
startIcon={<PolicyIcon />}
sx={{ mr: 2 }}
>
Create Policy
</Button>
<IconButton onClick={loadComplianceData}>
<RefreshIcon />
</IconButton>
<IconButton>
<SettingsIcon />
</IconButton>
</Box>
</Box>
{/* Compliance Status Alert */}
<Alert
severity={stats.complianceScore > 90 ? 'success' : stats.complianceScore > 75 ? 'warning' : 'error'}
sx={{ mb: 3 }}
icon={stats.complianceScore > 90 ? <VerifiedUserIcon /> : <WarningIcon />}
>
<Typography variant="h6">
Overall Compliance Score: {stats.complianceScore}%
</Typography>
<Typography variant="body2">
{stats.complianceScore > 90 ? 'Excellent compliance posture' : stats.complianceScore > 75 ? 'Good compliance with room for improvement' : 'Compliance issues require immediate attention'}.
{stats.violationsFound > 0 && ` ${stats.violationsFound} active violations need attention.`}
</Typography>
</Alert>
{/* Tabs */}
<Paper sx={{ mb: 3 }}>
<Tabs value={activeTab} onChange={handleTabChange} indicatorColor="primary" textColor="primary">
<Tab label="Overview" />
<Tab label="Policies" />
<Tab label="Violations" />
<Tab label="Audit Findings" />
<Tab label="Reports" />
</Tabs>
</Paper>
{/* Tab Content */}
{activeTab === 0 && (
<>
{/* Stats Cards */}
<Grid container spacing={3} mb={3}>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Compliance Score"
value={`${stats.complianceScore}%`}
icon={<AssessmentIcon />}
color="primary"
trend="up"
trendValue="+3% this quarter"
subtitle="Overall compliance"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Active Policies"
value={stats.activePolicies}
icon={<PolicyIcon />}
color="success"
trend="up"
trendValue="+2 this month"
subtitle={`${stats.totalPolicies} total policies`}
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Violations Found"
value={stats.violationsFound}
icon={<WarningIcon />}
color="warning"
trend="down"
trendValue="-2 this week"
subtitle={`${stats.violationsResolved} resolved`}
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Audit Score"
value={`${stats.auditScore}%`}
icon={<GavelIcon />}
color="info"
trend="up"
trendValue="+5% this year"
subtitle="Last audit performance"
/>
</Grid>
</Grid>
{/* Compliance Frameworks */}
<Card sx={{ mb: 3 }}>
<CardContent>
<Typography variant="h6" gutterBottom>
Compliance Framework Status
</Typography>
<Grid container spacing={2}>
{['SOX', 'HIPAA', 'GDPR', 'PCI-DSS', 'ISO27001'].map((framework) => (
<Grid size={{ xs: 12, sm: 6, md: 2.4 }} key={framework}>
<Paper sx={{ p: 2, textAlign: 'center' }}>
<Typography variant="h6" color="primary">
{framework}
</Typography>
<Typography variant="h4" sx={{ my: 1 }}>
{Math.floor(Math.random() * 20) + 80}%
</Typography>
<Chip
label="Compliant"
color="success"
size="small"
/>
</Paper>
</Grid>
))}
</Grid>
</CardContent>
</Card>
</>
)}
{/* Policies Tab */}
{activeTab === 1 && (
<>
<Typography variant="h5" gutterBottom>
Compliance Policies
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Manage compliance policies and procedures
</Typography>
<Card sx={{ mt: 3 }}>
<CardContent>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Policy Name</TableCell>
<TableCell>Category</TableCell>
<TableCell>Framework</TableCell>
<TableCell>Status</TableCell>
<TableCell>Compliance Level</TableCell>
<TableCell>Violations</TableCell>
<TableCell>Owner</TableCell>
<TableCell>Last Updated</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{policies.map((policy) => (
<TableRow key={policy.id}>
<TableCell>
<Typography variant="body2" fontWeight="bold">
{policy.name}
</Typography>
<Typography variant="caption" color="textSecondary">
{policy.description}
</Typography>
</TableCell>
<TableCell>
<Chip label={policy.category.replace('_', ' ')} size="small" />
</TableCell>
<TableCell>
<Chip
label={policy.framework}
color={getFrameworkColor(policy.framework) as any}
size="small"
/>
</TableCell>
<TableCell>
<Chip
label={policy.status}
color={getStatusColor(policy.status) as any}
size="small"
/>
</TableCell>
<TableCell>
<Box display="flex" alignItems="center">
<LinearProgress
variant="determinate"
value={policy.complianceLevel}
color={policy.complianceLevel > 90 ? 'success' : policy.complianceLevel > 75 ? 'warning' : 'error'}
sx={{ width: 60, mr: 1 }}
/>
<Typography variant="caption">
{policy.complianceLevel}%
</Typography>
</Box>
</TableCell>
<TableCell>
<Typography variant="body2">
{policy.violations}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{policy.owner}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{formatTime(policy.lastUpdated)}
</Typography>
</TableCell>
<TableCell>
<Button size="small" variant="outlined">
View
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
{/* Violations Tab */}
{activeTab === 2 && (
<>
<Typography variant="h5" gutterBottom>
Compliance Violations
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Track and manage compliance violations and remediation
</Typography>
<Card sx={{ mt: 3 }}>
<CardContent>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Violation</TableCell>
<TableCell>Policy</TableCell>
<TableCell>Severity</TableCell>
<TableCell>Status</TableCell>
<TableCell>Framework</TableCell>
<TableCell>Assigned To</TableCell>
<TableCell>Discovered</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{violations.map((violation) => (
<TableRow key={violation.id}>
<TableCell>
<Typography variant="body2" fontWeight="bold">
{violation.description}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{violation.policyName}
</Typography>
</TableCell>
<TableCell>
<Chip
label={violation.severity}
color={getSeverityColor(violation.severity) as any}
size="small"
/>
</TableCell>
<TableCell>
<Chip
label={violation.status}
color={getStatusColor(violation.status) as any}
size="small"
/>
</TableCell>
<TableCell>
<Chip
label={violation.framework}
color={getFrameworkColor(violation.framework) as any}
size="small"
/>
</TableCell>
<TableCell>
<Typography variant="body2">
{violation.assignedTo}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{formatTime(violation.discoveredAt)}
</Typography>
{violation.resolvedAt && (
<Typography variant="caption" color="success" display="block">
Resolved: {formatTime(violation.resolvedAt)}
</Typography>
)}
</TableCell>
<TableCell>
<Button size="small" variant="outlined">
View Details
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
{/* Audit Findings Tab */}
{activeTab === 3 && (
<>
<Typography variant="h5" gutterBottom>
Audit Findings
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Track audit findings and remediation progress
</Typography>
<Card sx={{ mt: 3 }}>
<CardContent>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Finding</TableCell>
<TableCell>Audit</TableCell>
<TableCell>Category</TableCell>
<TableCell>Severity</TableCell>
<TableCell>Status</TableCell>
<TableCell>Framework</TableCell>
<TableCell>Assigned To</TableCell>
<TableCell>Due Date</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{auditFindings.map((finding) => (
<TableRow key={finding.id}>
<TableCell>
<Typography variant="body2" fontWeight="bold">
{finding.finding}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{finding.auditName}
</Typography>
<Typography variant="caption" color="textSecondary">
{finding.auditId}
</Typography>
</TableCell>
<TableCell>
<Chip label={finding.category} size="small" />
</TableCell>
<TableCell>
<Chip
label={finding.severity}
color={getSeverityColor(finding.severity) as any}
size="small"
/>
</TableCell>
<TableCell>
<Chip
label={finding.status}
color={getStatusColor(finding.status) as any}
size="small"
/>
</TableCell>
<TableCell>
<Chip
label={finding.framework}
color={getFrameworkColor(finding.framework) as any}
size="small"
/>
</TableCell>
<TableCell>
<Typography variant="body2">
{finding.assignedTo}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{formatTime(finding.dueDate)}
</Typography>
</TableCell>
<TableCell>
<Button size="small" variant="outlined">
Update Status
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
{/* Reports Tab */}
{activeTab === 4 && (
<>
<Typography variant="h5" gutterBottom>
Compliance Reports
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Generate and manage compliance reports
</Typography>
<Card sx={{ mt: 3 }}>
<CardContent>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Report Name</TableCell>
<TableCell>Type</TableCell>
<TableCell>Framework</TableCell>
<TableCell>Score</TableCell>
<TableCell>Status</TableCell>
<TableCell>Findings</TableCell>
<TableCell>Violations</TableCell>
<TableCell>Generated</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{reports.map((report) => (
<TableRow key={report.id}>
<TableCell>
<Typography variant="body2" fontWeight="bold">
{report.name}
</Typography>
</TableCell>
<TableCell>
<Chip label={report.type} size="small" />
</TableCell>
<TableCell>
<Chip
label={report.framework}
color={getFrameworkColor(report.framework) as any}
size="small"
/>
</TableCell>
<TableCell>
<Box display="flex" alignItems="center">
<LinearProgress
variant="determinate"
value={report.score}
color={report.score > 90 ? 'success' : report.score > 75 ? 'warning' : 'error'}
sx={{ width: 60, mr: 1 }}
/>
<Typography variant="caption">
{report.score}%
</Typography>
</Box>
</TableCell>
<TableCell>
<Chip
label={report.status}
color={getStatusColor(report.status) as any}
size="small"
/>
</TableCell>
<TableCell>
<Typography variant="body2">
{report.findings}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{report.violations}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{formatTime(report.generatedAt)}
</Typography>
</TableCell>
<TableCell>
<Button size="small" variant="outlined">
View Report
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
</Box>
);
};
export default ComplianceDashboard;

View File

@@ -0,0 +1,524 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Drawer,
AppBar,
Toolbar,
List,
Typography,
Divider,
IconButton,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText,
Avatar,
Menu,
MenuItem,
Badge,
Chip,
Paper,
Tooltip,
} from '@mui/material';
import {
Menu as MenuIcon,
Dashboard as DashboardIcon,
Security as SecurityIcon,
BugReport as IncidentIcon,
Schedule as SLIcon,
Monitor as MonitorIcon,
Group as CollaborationIcon,
AutoFixHigh as AutomationIcon,
Assessment as AnalyticsIcon,
School as KnowledgeIcon,
Gavel as ComplianceIcon,
AdminPanelSettings as UserManagementIcon,
Notifications as NotificationsIcon,
AccountCircle as ProfileIcon,
Settings as SettingsIcon,
Logout as LogoutIcon,
ChevronLeft as ChevronLeftIcon,
Warning as WarningIcon,
CheckCircle as CheckCircleIcon,
Error as ErrorIcon,
Info as InfoIcon,
} from '@mui/icons-material';
import { useAuth } from '../../contexts/AuthContext';
import { useNavigate, useLocation } from 'react-router-dom';
import { NavigationItem } from '../../types';
const drawerWidth = 240;
interface DashboardLayoutProps {
children: React.ReactNode;
}
const DashboardLayout: React.FC<DashboardLayoutProps> = ({ children }) => {
const { user, logout, isAuthenticated } = useAuth();
const navigate = useNavigate();
const location = useLocation();
const [mobileOpen, setMobileOpen] = useState(false);
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [systemStatus, setSystemStatus] = useState<any>(null);
const [notifications, setNotifications] = useState<any[]>([]);
// Navigation items based on user permissions
const navigationItems: NavigationItem[] = [
{
id: 'dashboard',
label: 'Dashboard',
icon: 'Dashboard',
path: '/dashboard',
permissions: [],
},
{
id: 'incidents',
label: 'Incidents',
icon: 'BugReport',
path: '/incidents',
permissions: ['view_incident'],
},
{
id: 'monitoring',
label: 'Monitoring',
icon: 'Monitor',
path: '/monitoring',
permissions: ['view_monitoringdashboard', 'view_alert', 'view_healthcheck'],
},
{
id: 'sla',
label: 'SLA & On-Call',
icon: 'Schedule',
path: '/sla',
permissions: ['view_sladefinition', 'view_slainstance', 'view_oncallassignment'],
},
{
id: 'security',
label: 'Security',
icon: 'Security',
path: '/security',
permissions: ['view_user', 'view_role', 'view_auditlog', 'view_securityevent'],
clearance_level: 2,
},
{
id: 'automation',
label: 'Automation',
icon: 'AutoFixHigh',
path: '/automation',
permissions: ['view_runbook', 'view_autoremediation', 'view_integration'],
},
{
id: 'collaboration',
label: 'War Rooms',
icon: 'Group',
path: '/collaboration',
permissions: ['view_warroom', 'view_warroommessage', 'view_conferencebridge'],
},
{
id: 'analytics',
label: 'Analytics',
icon: 'Assessment',
path: '/analytics',
permissions: ['view_kpimetric', 'view_anomalydetection', 'view_dashboardconfiguration'],
},
{
id: 'knowledge',
label: 'Knowledge',
icon: 'School',
path: '/knowledge',
permissions: ['view_knowledgebasearticle', 'view_postmortem', 'view_learningpattern'],
},
{
id: 'compliance',
label: 'Compliance',
icon: 'Gavel',
path: '/compliance',
permissions: ['view_compliancereport', 'view_regulatoryframework', 'view_legalhold'],
clearance_level: 3,
},
{
id: 'user-management',
label: 'User Management',
icon: 'AdminPanelSettings',
path: '/user-management',
permissions: ['view_user', 'change_user'],
clearance_level: 5,
},
];
const iconMap: { [key: string]: React.ComponentType } = {
Dashboard: DashboardIcon,
BugReport: IncidentIcon,
Monitor: MonitorIcon,
Schedule: SLIcon,
Security: SecurityIcon,
AutoFixHigh: AutomationIcon,
Group: CollaborationIcon,
Assessment: AnalyticsIcon,
School: KnowledgeIcon,
Gavel: ComplianceIcon,
AdminPanelSettings: UserManagementIcon,
};
useEffect(() => {
if (!isAuthenticated) {
navigate('/login');
}
}, [isAuthenticated, navigate]);
useEffect(() => {
// Load system status and notifications
loadSystemStatus();
loadNotifications();
}, []);
const loadSystemStatus = async () => {
try {
// This would be an API call to get system status
setSystemStatus({
status: 'OPERATIONAL',
message: 'All systems operational',
last_updated: new Date().toISOString(),
});
} catch (error) {
console.error('Failed to load system status:', error);
}
};
const loadNotifications = async () => {
try {
// This would be an API call to get notifications
setNotifications([
{
id: '1',
type: 'warning',
title: 'High CPU Usage',
message: 'Server CPU usage is above 80%',
timestamp: new Date().toISOString(),
},
{
id: '2',
type: 'info',
title: 'Scheduled Maintenance',
message: 'System maintenance scheduled for tonight',
timestamp: new Date().toISOString(),
},
]);
} catch (error) {
console.error('Failed to load notifications:', error);
}
};
const handleDrawerToggle = () => {
setMobileOpen(!mobileOpen);
};
const handleProfileMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const handleProfileMenuClose = () => {
setAnchorEl(null);
};
const handleLogout = async () => {
try {
await logout();
navigate('/login');
} catch (error) {
console.error('Logout failed:', error);
}
};
const handleNavigation = (path: string) => {
navigate(path);
setMobileOpen(false);
};
const canAccessItem = (item: NavigationItem): boolean => {
if (!user) {
console.log('No user found, denying access to:', item.label);
return false;
}
console.log('Checking access for:', item.label, {
user,
item,
userRoles: user.roles,
userPermissions: user.roles?.flatMap(role => role.permissions.map(perm => perm.codename)) || [],
clearanceLevel: user.clearance_level?.level,
isSuperuser: user.is_superuser,
isStaff: user.is_staff
});
// Superusers and staff bypass all permission checks
if (user.is_superuser || user.is_staff) {
console.log(`Access granted for ${item.label}: superuser/staff bypass`);
return true;
}
// Check clearance level
if (item.clearance_level && user.clearance_level) {
if (user.clearance_level.level < item.clearance_level) {
console.log(`Access denied for ${item.label}: insufficient clearance level (${user.clearance_level.level} < ${item.clearance_level})`);
return false;
}
}
// Check permissions
if (item.permissions && item.permissions.length > 0) {
// For superusers, we assume they have all permissions
if (user.is_superuser) {
console.log(`Permission granted for ${item.label}: superuser has all permissions`);
return true;
}
const userPermissions = user.roles?.flatMap(role =>
role.permissions.map(perm => perm.codename)
) || [];
const hasPermission = item.permissions.some(permission =>
userPermissions.includes(permission)
);
console.log(`Permission check for ${item.label}:`, {
required: item.permissions,
userHas: userPermissions,
hasPermission
});
return hasPermission;
}
console.log(`Access granted for ${item.label}: no restrictions`);
return true;
};
const getStatusColor = (status: string) => {
switch (status) {
case 'OPERATIONAL': return 'success';
case 'DEGRADED': return 'warning';
case 'PARTIAL_OUTAGE': return 'error';
case 'MAJOR_OUTAGE': return 'error';
default: return 'default';
}
};
const getStatusIcon = (status: string) => {
switch (status) {
case 'OPERATIONAL': return <CheckCircleIcon />;
case 'DEGRADED': return <WarningIcon />;
case 'PARTIAL_OUTAGE': return <ErrorIcon />;
case 'MAJOR_OUTAGE': return <ErrorIcon />;
default: return <InfoIcon />;
}
};
const drawer = (
<Box>
<Toolbar>
<Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1 }}>
ETB Dashboard
</Typography>
<IconButton
color="inherit"
aria-label="close drawer"
edge="end"
onClick={handleDrawerToggle}
sx={{ display: { sm: 'none' } }}
>
<ChevronLeftIcon />
</IconButton>
</Toolbar>
<Divider />
{/* System Status */}
{systemStatus && (
<Paper sx={{ m: 2, p: 2 }}>
<Box display="flex" alignItems="center" sx={{ mb: 1 }}>
{getStatusIcon(systemStatus.status)}
<Typography variant="subtitle2" sx={{ ml: 1 }}>
System Status
</Typography>
</Box>
<Chip
label={systemStatus.status}
color={getStatusColor(systemStatus.status) as any}
size="small"
sx={{ mb: 1 }}
/>
<Typography variant="caption" display="block" color="text.secondary">
{systemStatus.message}
</Typography>
</Paper>
)}
<Divider />
{/* Navigation */}
<List>
{navigationItems
.filter(canAccessItem)
.map((item) => {
const IconComponent = iconMap[item.icon];
const isActive = location.pathname === item.path;
return (
<ListItem key={item.id} disablePadding>
<ListItemButton
selected={isActive}
onClick={() => handleNavigation(item.path)}
sx={{
'&.Mui-selected': {
backgroundColor: 'primary.main',
color: 'primary.contrastText',
'&:hover': {
backgroundColor: 'primary.dark',
},
},
}}
>
<ListItemIcon sx={{ color: isActive ? 'inherit' : undefined }}>
{IconComponent && <IconComponent />}
</ListItemIcon>
<ListItemText primary={item.label} />
</ListItemButton>
</ListItem>
);
})}
</List>
</Box>
);
return (
<Box sx={{ display: 'flex' }}>
<AppBar
position="fixed"
sx={{
width: { sm: `calc(100% - ${drawerWidth}px)` },
ml: { sm: `${drawerWidth}px` },
}}
>
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
edge="start"
onClick={handleDrawerToggle}
sx={{ mr: 2, display: { sm: 'none' } }}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1 }}>
{navigationItems.find(item => item.path === location.pathname)?.label || 'Dashboard'}
</Typography>
{/* Notifications */}
<Tooltip title="Notifications">
<IconButton color="inherit">
<Badge badgeContent={notifications.length} color="error">
<NotificationsIcon />
</Badge>
</IconButton>
</Tooltip>
{/* User Profile */}
<Tooltip title="User Profile">
<IconButton
size="large"
edge="end"
aria-label="account of current user"
aria-controls="primary-search-account-menu"
aria-haspopup="true"
onClick={handleProfileMenuOpen}
color="inherit"
>
<Avatar sx={{ width: 32, height: 32 }}>
{user?.first_name?.[0]}{user?.last_name?.[0]}
</Avatar>
</IconButton>
</Tooltip>
</Toolbar>
</AppBar>
<Menu
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
keepMounted
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
open={Boolean(anchorEl)}
onClose={handleProfileMenuClose}
>
<MenuItem onClick={handleProfileMenuClose}>
<ListItemIcon>
<ProfileIcon fontSize="small" />
</ListItemIcon>
<ListItemText>Profile</ListItemText>
</MenuItem>
<MenuItem onClick={handleProfileMenuClose}>
<ListItemIcon>
<SettingsIcon fontSize="small" />
</ListItemIcon>
<ListItemText>Settings</ListItemText>
</MenuItem>
<Divider />
<MenuItem onClick={handleLogout}>
<ListItemIcon>
<LogoutIcon fontSize="small" />
</ListItemIcon>
<ListItemText>Logout</ListItemText>
</MenuItem>
</Menu>
<Box
component="nav"
sx={{ width: { sm: drawerWidth }, flexShrink: { sm: 0 } }}
aria-label="mailbox folders"
>
<Drawer
variant="temporary"
open={mobileOpen}
onClose={handleDrawerToggle}
ModalProps={{
keepMounted: true,
}}
sx={{
display: { xs: 'block', sm: 'none' },
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth },
}}
>
{drawer}
</Drawer>
<Drawer
variant="permanent"
sx={{
display: { xs: 'none', sm: 'block' },
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth },
}}
open
>
{drawer}
</Drawer>
</Box>
<Box
component="main"
sx={{
flexGrow: 1,
p: 3,
width: { sm: `calc(100% - ${drawerWidth}px)` },
}}
>
<Toolbar />
{children}
</Box>
</Box>
);
};
export default DashboardLayout;

View File

@@ -0,0 +1,850 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Card,
CardContent,
Typography,
Grid,
Chip,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
IconButton,
Button,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
TextField,
Select,
MenuItem,
FormControl,
InputLabel,
LinearProgress,
Alert,
Avatar,
List,
ListItem,
ListItemText,
ListItemIcon,
Tabs,
Tab,
} from '@mui/material';
import {
BugReport as IncidentIcon,
Add as AddIcon,
Refresh as RefreshIcon,
FilterList as FilterIcon,
TrendingUp as TrendingUpIcon,
TrendingDown as TrendingDownIcon,
CheckCircle as CheckCircleIcon,
Warning as WarningIcon,
Error as ErrorIcon,
Schedule as ScheduleIcon,
Person as PersonIcon,
Security as SecurityIcon,
AutoFixHigh as AutoFixIcon,
Assessment as AssessmentIcon,
Timeline as TimelineIcon,
} from '@mui/icons-material';
import apiService from '../../services/api';
import { Incident } from '../../types';
interface IncidentIntelligenceDashboardProps {
onNavigateToModule: (moduleId: string) => void;
}
interface IncidentStats {
total: number;
open: number;
inProgress: number;
resolved: number;
critical: number;
high: number;
medium: number;
low: number;
aiProcessed: number;
automationTriggered: number;
avgResolutionTime: number;
avgResponseTime: number;
}
interface AIMetrics {
classificationAccuracy: number;
severityPredictionAccuracy: number;
duplicateDetectionAccuracy: number;
correlationAccuracy: number;
processingTime: number;
}
const IncidentIntelligenceDashboard: React.FC<IncidentIntelligenceDashboardProps> = ({
onNavigateToModule
}) => {
const [activeTab, setActiveTab] = useState(0);
const [incidents, setIncidents] = useState<Incident[]>([]);
const [stats, setStats] = useState<IncidentStats>({
total: 0,
open: 0,
inProgress: 0,
resolved: 0,
critical: 0,
high: 0,
medium: 0,
low: 0,
aiProcessed: 0,
automationTriggered: 0,
avgResolutionTime: 0,
avgResponseTime: 0,
});
const [aiMetrics, setAiMetrics] = useState<AIMetrics>({
classificationAccuracy: 0,
severityPredictionAccuracy: 0,
duplicateDetectionAccuracy: 0,
correlationAccuracy: 0,
processingTime: 0,
});
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [createDialogOpen, setCreateDialogOpen] = useState(false);
const [, setFilterDialogOpen] = useState(false);
useEffect(() => {
loadDashboardData();
}, []);
const loadDashboardData = async () => {
try {
setIsLoading(true);
setError(null);
// Load incidents
const incidentsResponse = await apiService.getIncidents({ page_size: 50 });
setIncidents(incidentsResponse.results);
// Calculate stats from incidents
const total = incidentsResponse.count;
const open = incidentsResponse.results.filter(i => i.status === 'OPEN').length;
const inProgress = incidentsResponse.results.filter(i => i.status === 'IN_PROGRESS').length;
const resolved = incidentsResponse.results.filter(i => i.status === 'RESOLVED' || i.status === 'CLOSED').length;
const critical = incidentsResponse.results.filter(i => i.severity === 'CRITICAL' || i.severity === 'EMERGENCY').length;
const high = incidentsResponse.results.filter(i => i.severity === 'HIGH').length;
const medium = incidentsResponse.results.filter(i => i.severity === 'MEDIUM').length;
const low = incidentsResponse.results.filter(i => i.severity === 'LOW').length;
const aiProcessed = incidentsResponse.results.filter(i => i.ai_processed).length;
const automationTriggered = incidentsResponse.results.filter(i => i.ai_processed).length;
setStats({
total,
open,
inProgress,
resolved,
critical,
high,
medium,
low,
aiProcessed,
automationTriggered,
avgResolutionTime: 2.5, // Mock data
avgResponseTime: 0.8, // Mock data
});
// Mock AI metrics
setAiMetrics({
classificationAccuracy: 94.2,
severityPredictionAccuracy: 91.8,
duplicateDetectionAccuracy: 96.5,
correlationAccuracy: 88.7,
processingTime: 1.2,
});
} catch (error) {
console.error('Failed to load incident intelligence data:', error);
setError('Failed to load incident intelligence 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 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;
subtitle?: string;
}> = ({ title, value, icon, color, trend, trendValue, subtitle }) => (
<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>
{subtitle && (
<Typography variant="body2" color="textSecondary">
{subtitle}
</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>
);
const AIMetricCard: React.FC<{
title: string;
value: number;
icon: React.ReactNode;
color: string;
unit?: string;
}> = ({ title, value, icon, color, unit = '%' }) => (
<Card>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between" mb={2}>
<Typography variant="h6" color="textSecondary">
{title}
</Typography>
<Avatar sx={{ bgcolor: `${color}.main` }}>
{icon}
</Avatar>
</Box>
<Typography variant="h4" component="div" color={color}>
{value}{unit}
</Typography>
<LinearProgress
variant="determinate"
value={value}
color={color as any}
sx={{ mt: 1, height: 8, borderRadius: 4 }}
/>
</CardContent>
</Card>
);
if (isLoading) {
return (
<Box>
<LinearProgress />
<Typography variant="h6" sx={{ mt: 2 }}>
Loading Incident Intelligence Dashboard...
</Typography>
</Box>
);
}
if (error) {
return (
<Alert severity="error" action={
<Button color="inherit" size="small" onClick={loadDashboardData}>
Retry
</Button>
}>
{error}
</Alert>
);
}
return (
<Box>
{/* Header */}
<Box display="flex" justifyContent="space-between" alignItems="center" mb={3}>
<Box>
<Typography variant="h4" gutterBottom>
Incident Intelligence
</Typography>
<Typography variant="subtitle1" color="textSecondary">
AI-powered incident management and analysis
</Typography>
</Box>
<Box>
<Button
variant="contained"
startIcon={<AddIcon />}
onClick={() => setCreateDialogOpen(true)}
sx={{ mr: 2 }}
>
Create Incident
</Button>
<IconButton onClick={loadDashboardData}>
<RefreshIcon />
</IconButton>
<IconButton onClick={() => setFilterDialogOpen(true)}>
<FilterIcon />
</IconButton>
</Box>
</Box>
{/* Tabs */}
<Paper sx={{ mb: 3 }}>
<Tabs value={activeTab} onChange={handleTabChange} indicatorColor="primary" textColor="primary">
<Tab label="Overview" />
<Tab label="AI Analytics" />
<Tab label="Automation" />
<Tab label="Correlations" />
</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.total}
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.open}
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.critical}
icon={<ErrorIcon />}
color="error"
trend="neutral"
trendValue="No change"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="AI Processed"
value={stats.aiProcessed}
icon={<AssessmentIcon />}
color="info"
trend="up"
trendValue="+8% from last week"
subtitle={`${Math.round((stats.aiProcessed / stats.total) * 100)}% of total`}
/>
</Grid>
</Grid>
{/* Performance Metrics */}
<Grid container spacing={3} mb={3}>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Avg Resolution Time"
value={`${stats.avgResolutionTime}h`}
icon={<ScheduleIcon />}
color="secondary"
trend="down"
trendValue="-0.5h from last week"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Avg Response Time"
value={`${stats.avgResponseTime}h`}
icon={<PersonIcon />}
color="success"
trend="down"
trendValue="-0.2h from last week"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Automation Triggered"
value={stats.automationTriggered}
icon={<AutoFixIcon />}
color="warning"
trend="up"
trendValue="+3 from yesterday"
subtitle={`${Math.round((stats.automationTriggered / stats.total) * 100)}% of total`}
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Security Clearance"
value="Level 2"
icon={<SecurityIcon />}
color="info"
trend="neutral"
trendValue="Required for 12 incidents"
/>
</Grid>
</Grid>
{/* Recent Incidents */}
<Card>
<CardContent>
<Box display="flex" justifyContent="space-between" alignItems="center" mb={2}>
<Typography variant="h6">
Recent Incidents
</Typography>
<Button
variant="outlined"
size="small"
onClick={() => onNavigateToModule('incident_intelligence')}
>
View All
</Button>
</Box>
<TableContainer>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>Title</TableCell>
<TableCell>Severity</TableCell>
<TableCell>Status</TableCell>
<TableCell>AI Confidence</TableCell>
<TableCell>Assigned To</TableCell>
<TableCell>Created</TableCell>
</TableRow>
</TableHead>
<TableBody>
{incidents.slice(0, 10).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.classification_confidence ? (
<Box display="flex" alignItems="center">
<LinearProgress
variant="determinate"
value={incident.classification_confidence * 100}
sx={{ width: 60, mr: 1 }}
/>
<Typography variant="caption">
{Math.round(incident.classification_confidence * 100)}%
</Typography>
</Box>
) : (
<Typography variant="caption" color="textSecondary">
Not processed
</Typography>
)}
</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>
</>
)}
{/* AI Analytics Tab */}
{activeTab === 1 && (
<>
<Typography variant="h5" gutterBottom>
AI Performance Metrics
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Machine learning model performance and accuracy metrics
</Typography>
<Grid container spacing={3} mb={3}>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<AIMetricCard
title="Classification Accuracy"
value={aiMetrics.classificationAccuracy}
icon={<AssessmentIcon />}
color="success"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<AIMetricCard
title="Severity Prediction"
value={aiMetrics.severityPredictionAccuracy}
icon={<WarningIcon />}
color="warning"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<AIMetricCard
title="Duplicate Detection"
value={aiMetrics.duplicateDetectionAccuracy}
icon={<CheckCircleIcon />}
color="info"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<AIMetricCard
title="Correlation Accuracy"
value={aiMetrics.correlationAccuracy}
icon={<TimelineIcon />}
color="secondary"
/>
</Grid>
</Grid>
<Grid container spacing={3}>
<Grid size={{ xs: 12, md: 6 }}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
AI Processing Stats
</Typography>
<List>
<ListItem>
<ListItemIcon>
<AssessmentIcon />
</ListItemIcon>
<ListItemText
primary="Average Processing Time"
secondary={`${aiMetrics.processingTime}s per incident`}
/>
</ListItem>
<ListItem>
<ListItemIcon>
<CheckCircleIcon />
</ListItemIcon>
<ListItemText
primary="Successful Classifications"
secondary={`${stats.aiProcessed} of ${stats.total} incidents`}
/>
</ListItem>
<ListItem>
<ListItemIcon>
<WarningIcon />
</ListItemIcon>
<ListItemText
primary="Manual Overrides"
secondary="12 classifications reviewed"
/>
</ListItem>
</List>
</CardContent>
</Card>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Model Performance Trends
</Typography>
<Typography variant="body2" color="textSecondary" paragraph>
AI models are performing above target thresholds across all metrics.
Classification accuracy has improved by 2.3% this month.
</Typography>
<Button
variant="outlined"
onClick={() => onNavigateToModule('analytics_predictive_insights')}
>
View Detailed Analytics
</Button>
</CardContent>
</Card>
</Grid>
</Grid>
</>
)}
{/* Automation Tab */}
{activeTab === 2 && (
<>
<Typography variant="h5" gutterBottom>
Automation & Orchestration
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Automated incident response and runbook execution
</Typography>
<Grid container spacing={3}>
<Grid size={{ xs: 12, md: 6 }}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Automation Statistics
</Typography>
<List>
<ListItem>
<ListItemIcon>
<AutoFixIcon />
</ListItemIcon>
<ListItemText
primary="Automation Triggered"
secondary={`${stats.automationTriggered} times`}
/>
</ListItem>
<ListItem>
<ListItemIcon>
<CheckCircleIcon />
</ListItemIcon>
<ListItemText
primary="Successful Executions"
secondary="89% success rate"
/>
</ListItem>
<ListItem>
<ListItemIcon>
<ScheduleIcon />
</ListItemIcon>
<ListItemText
primary="Average Execution Time"
secondary="2.3 minutes"
/>
</ListItem>
</List>
</CardContent>
</Card>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Runbook Suggestions
</Typography>
<Typography variant="body2" color="textSecondary" paragraph>
AI has suggested runbooks for {stats.automationTriggered} incidents this week.
Automation has reduced manual intervention by 34%.
</Typography>
<Button
variant="outlined"
onClick={() => onNavigateToModule('automation_orchestration')}
>
Manage Automation
</Button>
</CardContent>
</Card>
</Grid>
</Grid>
</>
)}
{/* Correlations Tab */}
{activeTab === 3 && (
<>
<Typography variant="h5" gutterBottom>
Incident Correlations
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
AI-detected patterns and relationships between incidents
</Typography>
<Grid container spacing={3}>
<Grid size={{ xs: 12, md: 6 }}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Detected Patterns
</Typography>
<List>
<ListItem>
<ListItemIcon>
<TimelineIcon />
</ListItemIcon>
<ListItemText
primary="Service Dependencies"
secondary="3 patterns identified"
/>
</ListItem>
<ListItem>
<ListItemIcon>
<WarningIcon />
</ListItemIcon>
<ListItemText
primary="Recurring Issues"
secondary="7 recurring patterns"
/>
</ListItem>
<ListItem>
<ListItemIcon>
<AssessmentIcon />
</ListItemIcon>
<ListItemText
primary="Cascade Effects"
secondary="2 cascade patterns"
/>
</ListItem>
</List>
</CardContent>
</Card>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Correlation Accuracy
</Typography>
<Typography variant="body2" color="textSecondary" paragraph>
AI correlation engine has achieved {aiMetrics.correlationAccuracy}% accuracy
in identifying related incidents. This helps reduce duplicate work and
identify root causes faster.
</Typography>
<Button
variant="outlined"
onClick={() => onNavigateToModule('analytics_predictive_insights')}
>
View Correlation Details
</Button>
</CardContent>
</Card>
</Grid>
</Grid>
</>
)}
{/* Create Incident Dialog */}
<Dialog open={createDialogOpen} onClose={() => setCreateDialogOpen(false)} maxWidth="md" fullWidth>
<DialogTitle>Create New Incident</DialogTitle>
<DialogContent>
<TextField
autoFocus
margin="dense"
label="Incident Title"
fullWidth
variant="outlined"
sx={{ mb: 2 }}
/>
<TextField
margin="dense"
label="Description"
fullWidth
multiline
rows={4}
variant="outlined"
sx={{ mb: 2 }}
/>
<Grid container spacing={2}>
<Grid size={{ xs: 6 }}>
<FormControl fullWidth>
<InputLabel>Severity</InputLabel>
<Select label="Severity">
<MenuItem value="LOW">Low</MenuItem>
<MenuItem value="MEDIUM">Medium</MenuItem>
<MenuItem value="HIGH">High</MenuItem>
<MenuItem value="CRITICAL">Critical</MenuItem>
<MenuItem value="EMERGENCY">Emergency</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid size={{ xs: 6 }}>
<FormControl fullWidth>
<InputLabel>Priority</InputLabel>
<Select label="Priority">
<MenuItem value="P4">P4 - Low</MenuItem>
<MenuItem value="P3">P3 - Medium</MenuItem>
<MenuItem value="P2">P2 - High</MenuItem>
<MenuItem value="P1">P1 - Critical</MenuItem>
</Select>
</FormControl>
</Grid>
</Grid>
</DialogContent>
<DialogActions>
<Button onClick={() => setCreateDialogOpen(false)}>Cancel</Button>
<Button variant="contained" onClick={() => setCreateDialogOpen(false)}>
Create Incident
</Button>
</DialogActions>
</Dialog>
</Box>
);
};
export default IncidentIntelligenceDashboard;

View File

@@ -0,0 +1,972 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Card,
CardContent,
Typography,
Grid,
Chip,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
IconButton,
Button,
LinearProgress,
Alert,
Avatar,
List,
ListItem,
ListItemText,
ListItemIcon,
Tabs,
Tab,
Rating,
} from '@mui/material';
import {
Article as ArticleIcon,
TrendingUp as TrendingUpIcon,
TrendingDown as TrendingDownIcon,
Refresh as RefreshIcon,
Settings as SettingsIcon,
Upload as UploadIcon,
Warning as WarningIcon,
CheckCircle as CheckCircleIcon,
Visibility as VisibilityIcon,
ThumbUp as ThumbUpIcon,
Edit as EditIcon,
Share as ShareIcon,
Bookmark as BookmarkIcon,
} from '@mui/icons-material';
interface KnowledgeDashboardProps {
onNavigateToModule: (moduleId: string) => void;
}
interface KnowledgeStats {
totalArticles: number;
publishedArticles: number;
draftArticles: number;
totalViews: number;
totalLikes: number;
avgRating: number;
searchQueries: number;
knowledgeGaps: number;
}
interface KnowledgeArticle {
id: string;
title: string;
description: string;
category: string;
tags: string[];
author: string;
status: 'PUBLISHED' | 'DRAFT' | 'REVIEW' | 'ARCHIVED';
views: number;
likes: number;
rating: number;
lastUpdated: string;
wordCount: number;
readingTime: number;
isBookmarked: boolean;
}
interface LearningModule {
id: string;
name: string;
description: string;
category: 'INCIDENT_RESPONSE' | 'AUTOMATION' | 'SECURITY' | 'MONITORING' | 'GENERAL';
difficulty: 'BEGINNER' | 'INTERMEDIATE' | 'ADVANCED' | 'EXPERT';
duration: number;
completionRate: number;
rating: number;
enrolledUsers: number;
lastUpdated: string;
prerequisites: string[];
skills: string[];
}
interface KnowledgeGap {
id: string;
title: string;
description: string;
category: string;
priority: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
requestedBy: string;
requestedAt: string;
status: 'OPEN' | 'IN_PROGRESS' | 'RESOLVED' | 'CLOSED';
relatedIncidents: string[];
estimatedEffort: string;
}
interface SearchQuery {
id: string;
query: string;
timestamp: string;
resultsFound: number;
user: string;
category: string;
successRate: number;
}
const KnowledgeDashboard: React.FC<KnowledgeDashboardProps> = ({ onNavigateToModule }) => {
const [activeTab, setActiveTab] = useState(0);
const [stats, setStats] = useState<KnowledgeStats>({
totalArticles: 0,
publishedArticles: 0,
draftArticles: 0,
totalViews: 0,
totalLikes: 0,
avgRating: 0,
searchQueries: 0,
knowledgeGaps: 0,
});
const [articles, setArticles] = useState<KnowledgeArticle[]>([]);
const [learningModules, setLearningModules] = useState<LearningModule[]>([]);
const [knowledgeGaps, setKnowledgeGaps] = useState<KnowledgeGap[]>([]);
const [searchQueries, setSearchQueries] = useState<SearchQuery[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
loadKnowledgeData();
}, []);
const loadKnowledgeData = async () => {
try {
setIsLoading(true);
setError(null);
// Mock data - replace with actual API calls
setStats({
totalArticles: 234,
publishedArticles: 189,
draftArticles: 45,
totalViews: 15420,
totalLikes: 892,
avgRating: 4.2,
searchQueries: 567,
knowledgeGaps: 12,
});
setArticles([
{
id: '1',
title: 'Database Performance Optimization Guide',
description: 'Comprehensive guide to optimizing database performance and troubleshooting common issues',
category: 'Database',
tags: ['performance', 'optimization', 'troubleshooting'],
author: 'John Doe',
status: 'PUBLISHED',
views: 245,
likes: 18,
rating: 4.5,
lastUpdated: '2024-01-15T10:30:00Z',
wordCount: 2500,
readingTime: 12,
isBookmarked: true,
},
{
id: '2',
title: 'Incident Response Best Practices',
description: 'Step-by-step guide for effective incident response and management',
category: 'Incident Management',
tags: ['incident-response', 'best-practices', 'process'],
author: 'Jane Smith',
status: 'PUBLISHED',
views: 189,
likes: 15,
rating: 4.8,
lastUpdated: '2024-01-14T15:20:00Z',
wordCount: 3200,
readingTime: 16,
isBookmarked: false,
},
{
id: '3',
title: 'API Gateway Configuration',
description: 'How to configure and optimize API gateway settings for better performance',
category: 'Infrastructure',
tags: ['api-gateway', 'configuration', 'performance'],
author: 'Mike Johnson',
status: 'DRAFT',
views: 0,
likes: 0,
rating: 0,
lastUpdated: '2024-01-15T09:15:00Z',
wordCount: 1800,
readingTime: 9,
isBookmarked: false,
},
]);
setLearningModules([
{
id: '1',
name: 'Advanced Incident Response',
description: 'Master the art of incident response with advanced techniques and tools',
category: 'INCIDENT_RESPONSE',
difficulty: 'ADVANCED',
duration: 180,
completionRate: 78,
rating: 4.6,
enrolledUsers: 45,
lastUpdated: '2024-01-15T08:00:00Z',
prerequisites: ['Basic Incident Response', 'Communication Skills'],
skills: ['Incident Command', 'Team Coordination', 'Root Cause Analysis'],
},
{
id: '2',
name: 'Automation Fundamentals',
description: 'Learn the basics of automation and orchestration in incident management',
category: 'AUTOMATION',
difficulty: 'BEGINNER',
duration: 120,
completionRate: 92,
rating: 4.3,
enrolledUsers: 67,
lastUpdated: '2024-01-14T16:30:00Z',
prerequisites: [],
skills: ['Runbook Creation', 'Workflow Design', 'Tool Integration'],
},
{
id: '3',
name: 'Security Incident Handling',
description: 'Specialized training for handling security-related incidents',
category: 'SECURITY',
difficulty: 'EXPERT',
duration: 240,
completionRate: 65,
rating: 4.7,
enrolledUsers: 23,
lastUpdated: '2024-01-13T14:15:00Z',
prerequisites: ['Security Fundamentals', 'Incident Response'],
skills: ['Threat Analysis', 'Forensic Investigation', 'Compliance'],
},
]);
setKnowledgeGaps([
{
id: '1',
title: 'Kubernetes Troubleshooting Guide',
description: 'Need comprehensive guide for troubleshooting Kubernetes cluster issues',
category: 'Infrastructure',
priority: 'HIGH',
requestedBy: 'Sarah Wilson',
requestedAt: '2024-01-15T11:00:00Z',
status: 'OPEN',
relatedIncidents: ['INC-001', 'INC-005'],
estimatedEffort: '2 weeks',
},
{
id: '2',
title: 'Microservices Communication Patterns',
description: 'Documentation needed for microservices communication best practices',
category: 'Architecture',
priority: 'MEDIUM',
requestedBy: 'David Brown',
requestedAt: '2024-01-14T16:45:00Z',
status: 'IN_PROGRESS',
relatedIncidents: ['INC-003'],
estimatedEffort: '1 week',
},
{
id: '3',
title: 'Cloud Cost Optimization',
description: 'Guide for optimizing cloud costs and resource utilization',
category: 'Finance',
priority: 'LOW',
requestedBy: 'Lisa Chen',
requestedAt: '2024-01-13T09:30:00Z',
status: 'OPEN',
relatedIncidents: [],
estimatedEffort: '3 weeks',
},
]);
setSearchQueries([
{
id: '1',
query: 'database connection timeout',
timestamp: '2024-01-15T11:25:00Z',
resultsFound: 12,
user: 'John Doe',
category: 'Database',
successRate: 85,
},
{
id: '2',
query: 'API gateway configuration',
timestamp: '2024-01-15T10:45:00Z',
resultsFound: 8,
user: 'Mike Johnson',
category: 'Infrastructure',
successRate: 92,
},
{
id: '3',
query: 'incident response checklist',
timestamp: '2024-01-15T09:30:00Z',
resultsFound: 15,
user: 'Jane Smith',
category: 'Incident Management',
successRate: 78,
},
]);
} catch (error) {
console.error('Failed to load knowledge data:', error);
setError('Failed to load knowledge data');
} finally {
setIsLoading(false);
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'PUBLISHED':
case 'RESOLVED':
return 'success';
case 'DRAFT':
case 'IN_PROGRESS':
return 'warning';
case 'REVIEW':
case 'OPEN':
return 'info';
case 'ARCHIVED':
case 'CLOSED':
return 'default';
default:
return 'default';
}
};
const getPriorityColor = (priority: string) => {
switch (priority) {
case 'CRITICAL':
return 'error';
case 'HIGH':
return 'warning';
case 'MEDIUM':
return 'info';
case 'LOW':
return 'success';
default:
return 'default';
}
};
const getDifficultyColor = (difficulty: string) => {
switch (difficulty) {
case 'EXPERT':
return 'error';
case 'ADVANCED':
return 'warning';
case 'INTERMEDIATE':
return 'info';
case 'BEGINNER':
return 'success';
default:
return 'default';
}
};
const formatTime = (timestamp: string) => {
return new Date(timestamp).toLocaleString();
};
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;
subtitle?: string;
}> = ({ title, value, icon, color, trend, trendValue, subtitle }) => (
<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>
{subtitle && (
<Typography variant="body2" color="textSecondary">
{subtitle}
</Typography>
)}
{trend && trendValue && (
<Box display="flex" alignItems="center" mt={1}>
{trend === 'up' ? (
<TrendingUpIcon color="success" fontSize="small" />
) : trend === 'down' ? (
<TrendingDownIcon color="error" 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 Knowledge Dashboard...
</Typography>
</Box>
);
}
if (error) {
return (
<Alert severity="error" action={
<Button color="inherit" size="small" onClick={loadKnowledgeData}>
Retry
</Button>
}>
{error}
</Alert>
);
}
return (
<Box>
{/* Header */}
<Box display="flex" justifyContent="space-between" alignItems="center" mb={3}>
<Box>
<Typography variant="h4" gutterBottom>
Knowledge & Learning
</Typography>
<Typography variant="subtitle1" color="textSecondary">
Centralized knowledge base and learning management system
</Typography>
</Box>
<Box>
<Button
variant="contained"
startIcon={<UploadIcon />}
sx={{ mr: 2 }}
>
Add Article
</Button>
<IconButton onClick={loadKnowledgeData}>
<RefreshIcon />
</IconButton>
<IconButton>
<SettingsIcon />
</IconButton>
</Box>
</Box>
{/* Knowledge Status Alert */}
<Alert
severity={stats.knowledgeGaps > 10 ? 'warning' : 'success'}
sx={{ mb: 3 }}
icon={stats.knowledgeGaps > 10 ? <WarningIcon /> : <CheckCircleIcon />}
>
<Typography variant="h6">
Knowledge Base Status: {stats.publishedArticles} Articles Published
</Typography>
<Typography variant="body2">
{stats.knowledgeGaps > 10 ? 'High number of knowledge gaps identified. Consider prioritizing content creation.' : 'Knowledge base is well-maintained with comprehensive coverage.'}
{stats.totalViews > 0 && ` ${stats.totalViews} total views this month.`}
</Typography>
</Alert>
{/* Tabs */}
<Paper sx={{ mb: 3 }}>
<Tabs value={activeTab} onChange={handleTabChange} indicatorColor="primary" textColor="primary">
<Tab label="Overview" />
<Tab label="Knowledge Base" />
<Tab label="Learning Modules" />
<Tab label="Knowledge Gaps" />
<Tab label="Search Analytics" />
</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 Articles"
value={stats.totalArticles}
icon={<ArticleIcon />}
color="primary"
trend="up"
trendValue="+12 this week"
subtitle={`${stats.publishedArticles} published`}
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Total Views"
value={stats.totalViews}
icon={<VisibilityIcon />}
color="info"
trend="up"
trendValue="+234 this week"
subtitle="Knowledge engagement"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Avg Rating"
value={stats.avgRating}
icon={<ThumbUpIcon />}
color="success"
trend="up"
trendValue="+0.2 this month"
subtitle="Content quality"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Knowledge Gaps"
value={stats.knowledgeGaps}
icon={<WarningIcon />}
color="warning"
trend="down"
trendValue="-2 this week"
subtitle="Identified gaps"
/>
</Grid>
</Grid>
{/* Recent Articles */}
<Card sx={{ mb: 3 }}>
<CardContent>
<Typography variant="h6" gutterBottom>
Recent Articles
</Typography>
<List>
{articles.slice(0, 3).map((article) => (
<ListItem key={article.id}>
<ListItemIcon>
<ArticleIcon />
</ListItemIcon>
<ListItemText
primary={
<Box display="flex" alignItems="center" justifyContent="space-between">
<Typography variant="body1" fontWeight="bold">
{article.title}
</Typography>
<Chip
label={article.status}
color={getStatusColor(article.status) as any}
size="small"
/>
</Box>
}
secondary={
<Box>
<Typography variant="body2" color="textSecondary">
{article.description}
</Typography>
<Box display="flex" alignItems="center" mt={1}>
<Typography variant="caption" sx={{ mr: 2 }}>
By {article.author}
</Typography>
<Typography variant="caption" sx={{ mr: 2 }}>
{article.views} views
</Typography>
<Typography variant="caption" sx={{ mr: 2 }}>
{article.likes} likes
</Typography>
<Rating value={article.rating} size="small" readOnly />
</Box>
</Box>
}
/>
</ListItem>
))}
</List>
</CardContent>
</Card>
</>
)}
{/* Knowledge Base Tab */}
{activeTab === 1 && (
<>
<Typography variant="h5" gutterBottom>
Knowledge Base
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Manage and organize knowledge articles and documentation
</Typography>
<Card sx={{ mt: 3 }}>
<CardContent>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Title</TableCell>
<TableCell>Category</TableCell>
<TableCell>Status</TableCell>
<TableCell>Author</TableCell>
<TableCell>Views</TableCell>
<TableCell>Rating</TableCell>
<TableCell>Last Updated</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{articles.map((article) => (
<TableRow key={article.id}>
<TableCell>
<Box>
<Typography variant="body2" fontWeight="bold">
{article.title}
</Typography>
<Typography variant="caption" color="textSecondary">
{article.description}
</Typography>
<Box sx={{ mt: 0.5 }}>
{article.tags.map((tag, index) => (
<Chip
key={index}
label={tag}
size="small"
variant="outlined"
sx={{ mr: 0.5, mb: 0.5 }}
/>
))}
</Box>
</Box>
</TableCell>
<TableCell>
<Chip label={article.category} size="small" />
</TableCell>
<TableCell>
<Chip
label={article.status}
color={getStatusColor(article.status) as any}
size="small"
/>
</TableCell>
<TableCell>
<Typography variant="body2">
{article.author}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{article.views}
</Typography>
</TableCell>
<TableCell>
<Box display="flex" alignItems="center">
<Rating value={article.rating} size="small" readOnly />
<Typography variant="caption" sx={{ ml: 1 }}>
({article.likes})
</Typography>
</Box>
</TableCell>
<TableCell>
<Typography variant="body2">
{formatTime(article.lastUpdated)}
</Typography>
</TableCell>
<TableCell>
<Box>
<IconButton size="small">
<EditIcon />
</IconButton>
<IconButton size="small">
<ShareIcon />
</IconButton>
<IconButton size="small">
{article.isBookmarked ? <BookmarkIcon color="primary" /> : <BookmarkIcon />}
</IconButton>
</Box>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
{/* Learning Modules Tab */}
{activeTab === 2 && (
<>
<Typography variant="h5" gutterBottom>
Learning Modules
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Training modules and educational content for skill development
</Typography>
<Grid container spacing={3} sx={{ mt: 2 }}>
{learningModules.map((module) => (
<Grid size={{ xs: 12, md: 6, lg: 4 }} key={module.id}>
<Card>
<CardContent>
<Box display="flex" justifyContent="space-between" alignItems="start" mb={2}>
<Typography variant="h6">
{module.name}
</Typography>
<Chip
label={module.difficulty}
color={getDifficultyColor(module.difficulty) as any}
size="small"
/>
</Box>
<Typography variant="body2" color="textSecondary" paragraph>
{module.description}
</Typography>
<Box mb={2}>
<Typography variant="body2" fontWeight="bold">
Progress:
</Typography>
<LinearProgress
variant="determinate"
value={module.completionRate}
color={module.completionRate > 80 ? 'success' : module.completionRate > 60 ? 'warning' : 'error'}
sx={{ mt: 1 }}
/>
<Typography variant="caption">
{module.completionRate}% completion rate
</Typography>
</Box>
<Box display="flex" justifyContent="space-between" mb={2}>
<Typography variant="caption">
Duration: {module.duration} min
</Typography>
<Typography variant="caption">
Enrolled: {module.enrolledUsers}
</Typography>
</Box>
<Box mb={2}>
<Box display="flex" alignItems="center" mb={1}>
<Rating value={module.rating} size="small" readOnly />
<Typography variant="caption" sx={{ ml: 1 }}>
{module.rating}
</Typography>
</Box>
</Box>
<Box mb={2}>
<Typography variant="body2" fontWeight="bold">
Skills:
</Typography>
<Box sx={{ mt: 0.5 }}>
{module.skills.map((skill, index) => (
<Chip
key={index}
label={skill}
size="small"
sx={{ mr: 0.5, mb: 0.5 }}
/>
))}
</Box>
</Box>
<Button
variant="contained"
size="small"
fullWidth
>
Enroll
</Button>
</CardContent>
</Card>
</Grid>
))}
</Grid>
</>
)}
{/* Knowledge Gaps Tab */}
{activeTab === 3 && (
<>
<Typography variant="h5" gutterBottom>
Knowledge Gaps
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Identify and prioritize missing knowledge areas
</Typography>
<Card sx={{ mt: 3 }}>
<CardContent>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Title</TableCell>
<TableCell>Category</TableCell>
<TableCell>Priority</TableCell>
<TableCell>Requested By</TableCell>
<TableCell>Status</TableCell>
<TableCell>Related Incidents</TableCell>
<TableCell>Effort</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{knowledgeGaps.map((gap) => (
<TableRow key={gap.id}>
<TableCell>
<Typography variant="body2" fontWeight="bold">
{gap.title}
</Typography>
<Typography variant="caption" color="textSecondary">
{gap.description}
</Typography>
</TableCell>
<TableCell>
<Chip label={gap.category} size="small" />
</TableCell>
<TableCell>
<Chip
label={gap.priority}
color={getPriorityColor(gap.priority) as any}
size="small"
/>
</TableCell>
<TableCell>
<Typography variant="body2">
{gap.requestedBy}
</Typography>
</TableCell>
<TableCell>
<Chip
label={gap.status}
color={getStatusColor(gap.status) as any}
size="small"
/>
</TableCell>
<TableCell>
<Typography variant="body2">
{gap.relatedIncidents.length > 0 ? gap.relatedIncidents.join(', ') : 'None'}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{gap.estimatedEffort}
</Typography>
</TableCell>
<TableCell>
<Button size="small" variant="outlined">
Create Article
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
{/* Search Analytics Tab */}
{activeTab === 4 && (
<>
<Typography variant="h5" gutterBottom>
Search Analytics
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Analyze search patterns and knowledge base effectiveness
</Typography>
<Card sx={{ mt: 3 }}>
<CardContent>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Search Query</TableCell>
<TableCell>Category</TableCell>
<TableCell>Results Found</TableCell>
<TableCell>Success Rate</TableCell>
<TableCell>User</TableCell>
<TableCell>Timestamp</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{searchQueries.map((query) => (
<TableRow key={query.id}>
<TableCell>
<Typography variant="body2" fontWeight="bold">
{query.query}
</Typography>
</TableCell>
<TableCell>
<Chip label={query.category} size="small" />
</TableCell>
<TableCell>
<Typography variant="body2">
{query.resultsFound}
</Typography>
</TableCell>
<TableCell>
<Box display="flex" alignItems="center">
<LinearProgress
variant="determinate"
value={query.successRate}
color={query.successRate > 80 ? 'success' : query.successRate > 60 ? 'warning' : 'error'}
sx={{ width: 60, mr: 1 }}
/>
<Typography variant="caption">
{query.successRate}%
</Typography>
</Box>
</TableCell>
<TableCell>
<Typography variant="body2">
{query.user}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{formatTime(query.timestamp)}
</Typography>
</TableCell>
<TableCell>
<Button size="small" variant="outlined">
Analyze
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
</Box>
);
};
export default KnowledgeDashboard;

View File

@@ -0,0 +1,497 @@
import React from 'react';
import {
Card,
CardContent,
Typography,
Box,
Chip,
Avatar,
LinearProgress,
IconButton,
Tooltip,
Grid,
Paper,
Divider,
} from '@mui/material';
import {
BugReport as IncidentIcon,
Schedule as SLIcon,
Security as SecurityIcon,
Monitor as MonitorIcon,
Group as CollaborationIcon,
AutoFixHigh as AutomationIcon,
Assessment as AnalyticsIcon,
School as KnowledgeIcon,
Gavel as ComplianceIcon,
TrendingUp as TrendingUpIcon,
TrendingDown as TrendingDownIcon,
CheckCircle as CheckCircleIcon,
Warning as WarningIcon,
Error as ErrorIcon,
ArrowForward as ArrowForwardIcon,
Link as LinkIcon,
} from '@mui/icons-material';
interface ModuleStats {
id: string;
name: string;
status: 'healthy' | 'warning' | 'critical' | 'maintenance';
healthScore: number;
activeItems: number;
totalItems: number;
responseTime: number;
lastUpdated: string;
trends: {
incidents: 'up' | 'down' | 'stable';
performance: 'up' | 'down' | 'stable';
usage: 'up' | 'down' | 'stable';
};
}
interface ModuleRelationship {
source: string;
target: string;
type: 'data_flow' | 'dependency' | 'integration' | 'escalation';
strength: 'weak' | 'medium' | 'strong';
}
interface ModuleOverviewCardsProps {
onModuleClick: (moduleId: string) => void;
}
const ModuleOverviewCards: React.FC<ModuleOverviewCardsProps> = ({ onModuleClick }) => {
// Mock data - this would come from your backend API
const moduleStats: ModuleStats[] = [
{
id: 'incident_intelligence',
name: 'Incident Intelligence',
status: 'healthy',
healthScore: 95,
activeItems: 12,
totalItems: 156,
responseTime: 120,
lastUpdated: '2024-01-15T10:30:00Z',
trends: { incidents: 'down', performance: 'up', usage: 'stable' }
},
{
id: 'security',
name: 'Security & Access',
status: 'healthy',
healthScore: 98,
activeItems: 3,
totalItems: 45,
responseTime: 85,
lastUpdated: '2024-01-15T10:25:00Z',
trends: { incidents: 'stable', performance: 'up', usage: 'up' }
},
{
id: 'monitoring',
name: 'System Monitoring',
status: 'warning',
healthScore: 87,
activeItems: 8,
totalItems: 234,
responseTime: 200,
lastUpdated: '2024-01-15T10:20:00Z',
trends: { incidents: 'up', performance: 'down', usage: 'up' }
},
{
id: 'sla_oncall',
name: 'SLA & On-Call',
status: 'healthy',
healthScore: 92,
activeItems: 5,
totalItems: 78,
responseTime: 95,
lastUpdated: '2024-01-15T10:28:00Z',
trends: { incidents: 'stable', performance: 'stable', usage: 'stable' }
},
{
id: 'automation_orchestration',
name: 'Automation',
status: 'healthy',
healthScore: 89,
activeItems: 15,
totalItems: 67,
responseTime: 150,
lastUpdated: '2024-01-15T10:22:00Z',
trends: { incidents: 'down', performance: 'up', usage: 'up' }
},
{
id: 'collaboration_war_rooms',
name: 'War Rooms',
status: 'healthy',
healthScore: 94,
activeItems: 2,
totalItems: 23,
responseTime: 110,
lastUpdated: '2024-01-15T10:26:00Z',
trends: { incidents: 'stable', performance: 'stable', usage: 'down' }
},
{
id: 'analytics_predictive_insights',
name: 'Analytics & AI',
status: 'healthy',
healthScore: 96,
activeItems: 7,
totalItems: 89,
responseTime: 180,
lastUpdated: '2024-01-15T10:24:00Z',
trends: { incidents: 'down', performance: 'up', usage: 'up' }
},
{
id: 'knowledge_learning',
name: 'Knowledge Base',
status: 'healthy',
healthScore: 91,
activeItems: 4,
totalItems: 156,
responseTime: 75,
lastUpdated: '2024-01-15T10:27:00Z',
trends: { incidents: 'stable', performance: 'stable', usage: 'up' }
},
{
id: 'compliance_governance',
name: 'Compliance',
status: 'healthy',
healthScore: 97,
activeItems: 1,
totalItems: 34,
responseTime: 90,
lastUpdated: '2024-01-15T10:29:00Z',
trends: { incidents: 'stable', performance: 'stable', usage: 'stable' }
}
];
const moduleRelationships: ModuleRelationship[] = [
{ source: 'incident_intelligence', target: 'security', type: 'data_flow', strength: 'strong' },
{ source: 'incident_intelligence', target: 'monitoring', type: 'dependency', strength: 'strong' },
{ source: 'incident_intelligence', target: 'sla_oncall', type: 'escalation', strength: 'strong' },
{ source: 'incident_intelligence', target: 'automation_orchestration', type: 'integration', strength: 'medium' },
{ source: 'incident_intelligence', target: 'collaboration_war_rooms', type: 'data_flow', strength: 'strong' },
{ source: 'security', target: 'compliance_governance', type: 'data_flow', strength: 'strong' },
{ source: 'monitoring', target: 'analytics_predictive_insights', type: 'data_flow', strength: 'strong' },
{ source: 'automation_orchestration', target: 'knowledge_learning', type: 'data_flow', strength: 'medium' },
{ source: 'collaboration_war_rooms', target: 'knowledge_learning', type: 'data_flow', strength: 'medium' },
];
const getModuleIcon = (moduleId: string) => {
const iconMap: { [key: string]: React.ComponentType } = {
incident_intelligence: IncidentIcon,
security: SecurityIcon,
monitoring: MonitorIcon,
sla_oncall: SLIcon,
automation_orchestration: AutomationIcon,
collaboration_war_rooms: CollaborationIcon,
analytics_predictive_insights: AnalyticsIcon,
knowledge_learning: KnowledgeIcon,
compliance_governance: ComplianceIcon,
};
return iconMap[moduleId] || IncidentIcon;
};
const getStatusColor = (status: string) => {
switch (status) {
case 'healthy': return 'success';
case 'warning': return 'warning';
case 'critical': return 'error';
case 'maintenance': return 'info';
default: return 'default';
}
};
const getStatusIcon = (status: string) => {
switch (status) {
case 'healthy': return <CheckCircleIcon color="success" />;
case 'warning': return <WarningIcon color="warning" />;
case 'critical': return <ErrorIcon color="error" />;
case 'maintenance': return <WarningIcon color="info" />;
default: return <CheckCircleIcon />;
}
};
const getTrendIcon = (trend: string) => {
switch (trend) {
case 'up': return <TrendingUpIcon color="success" fontSize="small" />;
case 'down': return <TrendingDownIcon color="error" fontSize="small" />;
case 'stable': return <div style={{ width: 16, height: 16 }} />;
default: return <div style={{ width: 16, height: 16 }} />;
}
};
const getRelationshipStrength = (strength: string) => {
switch (strength) {
case 'strong': return { width: 3, opacity: 1 };
case 'medium': return { width: 2, opacity: 0.7 };
case 'weak': return { width: 1, opacity: 0.4 };
default: return { width: 1, opacity: 0.4 };
}
};
const getRelationshipColor = (type: string) => {
switch (type) {
case 'data_flow': return '#1976d2';
case 'dependency': return '#ed6c02';
case 'integration': return '#2e7d32';
case 'escalation': return '#d32f2f';
default: return '#666';
}
};
const formatTime = (timestamp: string) => {
const date = new Date(timestamp);
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffMins = Math.floor(diffMs / 60000);
if (diffMins < 1) return 'Just now';
if (diffMins < 60) return `${diffMins}m ago`;
const diffHours = Math.floor(diffMins / 60);
if (diffHours < 24) return `${diffHours}h ago`;
const diffDays = Math.floor(diffHours / 24);
return `${diffDays}d ago`;
};
return (
<Box>
{/* Module Overview Header */}
<Box mb={3}>
<Typography variant="h5" gutterBottom>
Enterprise Module Overview
</Typography>
<Typography variant="body2" color="textSecondary">
Real-time status and relationships between all system modules
</Typography>
</Box>
{/* Module Cards Grid */}
<Grid container spacing={3} mb={4}>
{moduleStats.map((module) => {
const IconComponent = getModuleIcon(module.id);
const moduleRelations = moduleRelationships.filter(
rel => rel.source === module.id || rel.target === module.id
);
return (
<Grid size={{ xs: 12, sm: 6, md: 4 }} key={module.id}>
<Card
sx={{
height: '100%',
cursor: 'pointer',
transition: 'all 0.2s ease-in-out',
'&:hover': {
transform: 'translateY(-2px)',
boxShadow: 4,
}
}}
onClick={() => onModuleClick(module.id)}
>
<CardContent>
{/* Module Header */}
<Box display="flex" alignItems="center" justifyContent="space-between" mb={2}>
<Box display="flex" alignItems="center">
<Avatar sx={{ bgcolor: `${getStatusColor(module.status)}.main`, mr: 2 }}>
<IconComponent />
</Avatar>
<Box>
<Typography variant="h6" component="div">
{module.name}
</Typography>
<Box display="flex" alignItems="center">
{getStatusIcon(module.status)}
<Chip
label={module.status.toUpperCase()}
color={getStatusColor(module.status) as any}
size="small"
sx={{ ml: 1 }}
/>
</Box>
</Box>
</Box>
<IconButton size="small" onClick={(e) => {
e.stopPropagation();
onModuleClick(module.id);
}}>
<ArrowForwardIcon />
</IconButton>
</Box>
{/* Health Score */}
<Box mb={2}>
<Box display="flex" justifyContent="space-between" mb={1}>
<Typography variant="body2">Health Score</Typography>
<Typography variant="body2" fontWeight="bold">
{module.healthScore}%
</Typography>
</Box>
<LinearProgress
variant="determinate"
value={module.healthScore}
color={getStatusColor(module.status) as any}
sx={{ height: 8, borderRadius: 4 }}
/>
</Box>
{/* Key Metrics */}
<Box mb={2}>
<Grid container spacing={1}>
<Grid size={{ xs: 6 }}>
<Paper variant="outlined" sx={{ p: 1, textAlign: 'center' }}>
<Typography variant="h6" color="primary">
{module.activeItems}
</Typography>
<Typography variant="caption" color="textSecondary">
Active
</Typography>
</Paper>
</Grid>
<Grid size={{ xs: 6 }}>
<Paper variant="outlined" sx={{ p: 1, textAlign: 'center' }}>
<Typography variant="h6" color="secondary">
{module.totalItems}
</Typography>
<Typography variant="caption" color="textSecondary">
Total
</Typography>
</Paper>
</Grid>
</Grid>
</Box>
{/* Performance Metrics */}
<Box mb={2}>
<Typography variant="body2" color="textSecondary" gutterBottom>
Response Time: {module.responseTime}ms
</Typography>
<Typography variant="body2" color="textSecondary">
Last Updated: {formatTime(module.lastUpdated)}
</Typography>
</Box>
{/* Trends */}
<Box>
<Typography variant="body2" color="textSecondary" gutterBottom>
Trends
</Typography>
<Box display="flex" justifyContent="space-between">
<Box display="flex" alignItems="center">
{getTrendIcon(module.trends.incidents)}
<Typography variant="caption" sx={{ ml: 0.5 }}>
Incidents
</Typography>
</Box>
<Box display="flex" alignItems="center">
{getTrendIcon(module.trends.performance)}
<Typography variant="caption" sx={{ ml: 0.5 }}>
Performance
</Typography>
</Box>
<Box display="flex" alignItems="center">
{getTrendIcon(module.trends.usage)}
<Typography variant="caption" sx={{ ml: 0.5 }}>
Usage
</Typography>
</Box>
</Box>
</Box>
{/* Relationships Indicator */}
{moduleRelations.length > 0 && (
<Box mt={2}>
<Divider sx={{ mb: 1 }} />
<Box display="flex" alignItems="center" justifyContent="space-between">
<Typography variant="caption" color="textSecondary">
{moduleRelations.length} Connected Modules
</Typography>
<Tooltip title="View Module Relationships">
<IconButton size="small">
<LinkIcon fontSize="small" />
</IconButton>
</Tooltip>
</Box>
</Box>
)}
</CardContent>
</Card>
</Grid>
);
})}
</Grid>
{/* Module Relationships Visualization */}
<Paper sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom>
Module Relationships & Data Flow
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Visual representation of how modules interact and share data
</Typography>
{/* Simplified relationship diagram */}
<Box sx={{ mt: 2, p: 2, bgcolor: 'grey.50', borderRadius: 1 }}>
<Grid container spacing={2}>
{moduleRelationships.map((rel, index) => {
const sourceModule = moduleStats.find(m => m.id === rel.source);
const targetModule = moduleStats.find(m => m.id === rel.target);
const strength = getRelationshipStrength(rel.strength);
const color = getRelationshipColor(rel.type);
return (
<Grid size={{ xs: 12, sm: 6, md: 4 }} key={index}>
<Box
display="flex"
alignItems="center"
sx={{
p: 1,
borderLeft: `${strength.width}px solid ${color}`,
borderLeftOpacity: strength.opacity,
bgcolor: 'white',
borderRadius: 1,
mb: 1
}}
>
<Typography variant="caption" sx={{ flex: 1 }}>
<strong>{sourceModule?.name}</strong> <strong>{targetModule?.name}</strong>
</Typography>
<Chip
label={rel.type.replace('_', ' ')}
size="small"
sx={{
ml: 1,
bgcolor: `${color}20`,
color: color,
fontSize: '0.7rem'
}}
/>
</Box>
</Grid>
);
})}
</Grid>
</Box>
{/* Legend */}
<Box sx={{ mt: 2, display: 'flex', flexWrap: 'wrap', gap: 2 }}>
<Box display="flex" alignItems="center">
<Box sx={{ width: 20, height: 3, bgcolor: '#1976d2', mr: 1 }} />
<Typography variant="caption">Data Flow</Typography>
</Box>
<Box display="flex" alignItems="center">
<Box sx={{ width: 20, height: 3, bgcolor: '#ed6c02', mr: 1 }} />
<Typography variant="caption">Dependency</Typography>
</Box>
<Box display="flex" alignItems="center">
<Box sx={{ width: 20, height: 3, bgcolor: '#2e7d32', mr: 1 }} />
<Typography variant="caption">Integration</Typography>
</Box>
<Box display="flex" alignItems="center">
<Box sx={{ width: 20, height: 3, bgcolor: '#d32f2f', mr: 1 }} />
<Typography variant="caption">Escalation</Typography>
</Box>
</Box>
</Paper>
</Box>
);
};
export default ModuleOverviewCards;

View File

@@ -0,0 +1,855 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Card,
CardContent,
Typography,
Grid,
Chip,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
IconButton,
Button,
LinearProgress,
Alert,
Avatar,
Tabs,
Tab,
} from '@mui/material';
import {
Monitor as MonitorIcon,
Speed as SpeedIcon,
Memory as MemoryIcon,
Cloud as CloudIcon,
Warning as WarningIcon,
CheckCircle as CheckCircleIcon,
TrendingUp as TrendingUpIcon,
TrendingDown as TrendingDownIcon,
Refresh as RefreshIcon,
Settings as SettingsIcon,
Computer as ComputerIcon,
Storage as DatabaseIcon,
Queue as QueueIcon,
} from '@mui/icons-material';
interface MonitoringDashboardProps {
onNavigateToModule: (moduleId: string) => void;
}
interface MonitoringStats {
totalTargets: number;
healthyTargets: number;
warningTargets: number;
criticalTargets: number;
systemHealth: number;
avgResponseTime: number;
activeAlerts: number;
resolvedAlerts: number;
dataRetention: number;
lastHealthCheck: string;
}
interface HealthCheck {
id: string;
target: string;
targetType: string;
status: 'HEALTHY' | 'WARNING' | 'CRITICAL' | 'UNKNOWN';
responseTime: number;
lastChecked: string;
uptime: number;
errorMessage?: string;
}
interface SystemMetric {
id: string;
name: string;
category: string;
value: number;
unit: string;
threshold: number;
status: 'NORMAL' | 'WARNING' | 'CRITICAL';
trend: 'up' | 'down' | 'stable';
lastUpdated: string;
}
interface AlertItem {
id: string;
title: string;
severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
status: 'TRIGGERED' | 'ACKNOWLEDGED' | 'RESOLVED';
target: string;
triggeredAt: string;
description: string;
value: number;
threshold: number;
}
const MonitoringDashboard: React.FC<MonitoringDashboardProps> = ({ onNavigateToModule }) => {
const [activeTab, setActiveTab] = useState(0);
const [stats, setStats] = useState<MonitoringStats>({
totalTargets: 0,
healthyTargets: 0,
warningTargets: 0,
criticalTargets: 0,
systemHealth: 0,
avgResponseTime: 0,
activeAlerts: 0,
resolvedAlerts: 0,
dataRetention: 0,
lastHealthCheck: '',
});
const [healthChecks, setHealthChecks] = useState<HealthCheck[]>([]);
const [systemMetrics, setSystemMetrics] = useState<SystemMetric[]>([]);
const [alertList, setAlertList] = useState<AlertItem[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
loadMonitoringData();
}, []);
const loadMonitoringData = async () => {
try {
setIsLoading(true);
setError(null);
// Mock data - replace with actual API calls
setStats({
totalTargets: 45,
healthyTargets: 38,
warningTargets: 5,
criticalTargets: 2,
systemHealth: 87,
avgResponseTime: 245,
activeAlerts: 8,
resolvedAlerts: 23,
dataRetention: 89,
lastHealthCheck: '2024-01-15T10:30:00Z',
});
setHealthChecks([
{
id: '1',
target: 'API Gateway',
targetType: 'APPLICATION',
status: 'HEALTHY',
responseTime: 120,
lastChecked: '2024-01-15T10:29:00Z',
uptime: 99.9,
},
{
id: '2',
target: 'Database Primary',
targetType: 'DATABASE',
status: 'WARNING',
responseTime: 450,
lastChecked: '2024-01-15T10:29:00Z',
uptime: 99.5,
},
{
id: '3',
target: 'Redis Cache',
targetType: 'CACHE',
status: 'HEALTHY',
responseTime: 15,
lastChecked: '2024-01-15T10:29:00Z',
uptime: 99.8,
},
{
id: '4',
target: 'Message Queue',
targetType: 'QUEUE',
status: 'CRITICAL',
responseTime: 0,
lastChecked: '2024-01-15T10:29:00Z',
uptime: 85.2,
errorMessage: 'Connection timeout',
},
{
id: '5',
target: 'External API',
targetType: 'EXTERNAL_API',
status: 'HEALTHY',
responseTime: 320,
lastChecked: '2024-01-15T10:29:00Z',
uptime: 99.7,
},
]);
setSystemMetrics([
{
id: '1',
name: 'CPU Usage',
category: 'SYSTEM_RESOURCES',
value: 68,
unit: '%',
threshold: 80,
status: 'NORMAL',
trend: 'up',
lastUpdated: '2024-01-15T10:30:00Z',
},
{
id: '2',
name: 'Memory Usage',
category: 'SYSTEM_RESOURCES',
value: 82,
unit: '%',
threshold: 85,
status: 'WARNING',
trend: 'up',
lastUpdated: '2024-01-15T10:30:00Z',
},
{
id: '3',
name: 'Disk Usage',
category: 'SYSTEM_RESOURCES',
value: 45,
unit: '%',
threshold: 90,
status: 'NORMAL',
trend: 'stable',
lastUpdated: '2024-01-15T10:30:00Z',
},
{
id: '4',
name: 'API Response Time',
category: 'API_RESPONSE_TIME',
value: 245,
unit: 'ms',
threshold: 500,
status: 'NORMAL',
trend: 'down',
lastUpdated: '2024-01-15T10:30:00Z',
},
{
id: '5',
name: 'Error Rate',
category: 'ERROR_RATE',
value: 0.2,
unit: '%',
threshold: 1,
status: 'NORMAL',
trend: 'down',
lastUpdated: '2024-01-15T10:30:00Z',
},
]);
setAlertList([
{
id: '1',
title: 'High Memory Usage',
severity: 'HIGH',
status: 'TRIGGERED',
target: 'Database Primary',
triggeredAt: '2024-01-15T10:25:00Z',
description: 'Memory usage has exceeded 80% threshold',
value: 82,
threshold: 80,
},
{
id: '2',
title: 'Queue Connection Failed',
severity: 'CRITICAL',
status: 'TRIGGERED',
target: 'Message Queue',
triggeredAt: '2024-01-15T10:20:00Z',
description: 'Unable to connect to message queue',
value: 0,
threshold: 1,
},
{
id: '3',
title: 'Slow API Response',
severity: 'MEDIUM',
status: 'ACKNOWLEDGED',
target: 'API Gateway',
triggeredAt: '2024-01-15T09:45:00Z',
description: 'API response time above normal threshold',
value: 650,
threshold: 500,
},
]);
} catch (error) {
console.error('Failed to load monitoring data:', error);
setError('Failed to load monitoring data');
} finally {
setIsLoading(false);
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'HEALTHY':
case 'NORMAL':
case 'RESOLVED':
return 'success';
case 'WARNING':
case 'ACKNOWLEDGED':
return 'warning';
case 'CRITICAL':
case 'TRIGGERED':
return 'error';
case 'UNKNOWN':
return 'default';
default:
return 'default';
}
};
const getSeverityColor = (severity: string) => {
switch (severity) {
case 'CRITICAL':
return 'error';
case 'HIGH':
return 'warning';
case 'MEDIUM':
return 'info';
case 'LOW':
return 'success';
default:
return 'default';
}
};
const getTargetIcon = (targetType: string) => {
switch (targetType) {
case 'APPLICATION':
return <ComputerIcon />;
case 'DATABASE':
return <DatabaseIcon />;
case 'CACHE':
return <MemoryIcon />;
case 'QUEUE':
return <QueueIcon />;
case 'EXTERNAL_API':
return <CloudIcon />;
case 'INFRASTRUCTURE':
return <MonitorIcon />;
default:
return <MonitorIcon />;
}
};
const formatTime = (timestamp: string) => {
return new Date(timestamp).toLocaleString();
};
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;
subtitle?: string;
}> = ({ title, value, icon, color, trend, trendValue, subtitle }) => (
<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>
{subtitle && (
<Typography variant="body2" color="textSecondary">
{subtitle}
</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 Monitoring Dashboard...
</Typography>
</Box>
);
}
if (error) {
return (
<Alert severity="error" action={
<Button color="inherit" size="small" onClick={loadMonitoringData}>
Retry
</Button>
}>
{error}
</Alert>
);
}
return (
<Box>
{/* Header */}
<Box display="flex" justifyContent="space-between" alignItems="center" mb={3}>
<Box>
<Typography variant="h4" gutterBottom>
System Monitoring
</Typography>
<Typography variant="subtitle1" color="textSecondary">
Real-time system health monitoring and alerting
</Typography>
</Box>
<Box>
<IconButton onClick={loadMonitoringData}>
<RefreshIcon />
</IconButton>
<IconButton>
<SettingsIcon />
</IconButton>
</Box>
</Box>
{/* System Health Alert */}
<Alert
severity={stats.systemHealth > 90 ? 'success' : stats.systemHealth > 70 ? 'warning' : 'error'}
sx={{ mb: 3 }}
icon={stats.systemHealth > 90 ? <CheckCircleIcon /> : <WarningIcon />}
>
<Typography variant="h6">
System Health: {stats.systemHealth}%
</Typography>
<Typography variant="body2">
Overall system health is {stats.systemHealth > 90 ? 'excellent' : stats.systemHealth > 70 ? 'good' : 'degraded'}.
{stats.criticalTargets > 0 && ` ${stats.criticalTargets} critical issues require immediate attention.`}
</Typography>
</Alert>
{/* Tabs */}
<Paper sx={{ mb: 3 }}>
<Tabs value={activeTab} onChange={handleTabChange} indicatorColor="primary" textColor="primary">
<Tab label="Overview" />
<Tab label="Health Checks" />
<Tab label="System Metrics" />
<Tab label="Alerts" />
</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 Targets"
value={stats.totalTargets}
icon={<MonitorIcon />}
color="primary"
trend="up"
trendValue="+2 this week"
subtitle={`${stats.healthyTargets} healthy`}
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="System Health"
value={`${stats.systemHealth}%`}
icon={<CheckCircleIcon />}
color="success"
trend="up"
trendValue="+3% this hour"
subtitle="Overall health score"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Avg Response Time"
value={`${stats.avgResponseTime}ms`}
icon={<SpeedIcon />}
color="info"
trend="down"
trendValue="-15ms from yesterday"
subtitle="Across all targets"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Active Alerts"
value={stats.activeAlerts}
icon={<WarningIcon />}
color="warning"
trend="down"
trendValue="-3 from yesterday"
subtitle={`${stats.resolvedAlerts} resolved today`}
/>
</Grid>
</Grid>
{/* Health Status Overview */}
<Grid container spacing={3} mb={3}>
<Grid size={{ xs: 12, md: 4 }}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Target Status Distribution
</Typography>
<Box mb={2}>
<Box display="flex" justifyContent="space-between" mb={1}>
<Typography variant="body2">Healthy</Typography>
<Typography variant="body2">{stats.healthyTargets}</Typography>
</Box>
<LinearProgress
variant="determinate"
value={(stats.healthyTargets / stats.totalTargets) * 100}
color="success"
sx={{ height: 8, borderRadius: 4 }}
/>
</Box>
<Box mb={2}>
<Box display="flex" justifyContent="space-between" mb={1}>
<Typography variant="body2">Warning</Typography>
<Typography variant="body2">{stats.warningTargets}</Typography>
</Box>
<LinearProgress
variant="determinate"
value={(stats.warningTargets / stats.totalTargets) * 100}
color="warning"
sx={{ height: 8, borderRadius: 4 }}
/>
</Box>
<Box mb={2}>
<Box display="flex" justifyContent="space-between" mb={1}>
<Typography variant="body2">Critical</Typography>
<Typography variant="body2">{stats.criticalTargets}</Typography>
</Box>
<LinearProgress
variant="determinate"
value={(stats.criticalTargets / stats.totalTargets) * 100}
color="error"
sx={{ height: 8, borderRadius: 4 }}
/>
</Box>
</CardContent>
</Card>
</Grid>
<Grid size={{ xs: 12, md: 8 }}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Recent Health Checks
</Typography>
<TableContainer>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>Target</TableCell>
<TableCell>Type</TableCell>
<TableCell>Status</TableCell>
<TableCell>Response Time</TableCell>
<TableCell>Uptime</TableCell>
<TableCell>Last Checked</TableCell>
</TableRow>
</TableHead>
<TableBody>
{healthChecks.slice(0, 5).map((check) => (
<TableRow key={check.id}>
<TableCell>
<Box display="flex" alignItems="center">
{getTargetIcon(check.targetType)}
<Typography variant="body2" sx={{ ml: 1 }}>
{check.target}
</Typography>
</Box>
</TableCell>
<TableCell>
<Chip label={check.targetType} size="small" />
</TableCell>
<TableCell>
<Chip
label={check.status}
color={getStatusColor(check.status) as any}
size="small"
/>
</TableCell>
<TableCell>
<Typography variant="body2">
{check.responseTime}ms
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{check.uptime}%
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{formatTime(check.lastChecked)}
</Typography>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</Grid>
</Grid>
</>
)}
{/* Health Checks Tab */}
{activeTab === 1 && (
<>
<Typography variant="h5" gutterBottom>
Health Checks & Target Monitoring
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Detailed monitoring status for all system targets
</Typography>
<Card sx={{ mt: 3 }}>
<CardContent>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Target</TableCell>
<TableCell>Type</TableCell>
<TableCell>Status</TableCell>
<TableCell>Response Time</TableCell>
<TableCell>Uptime</TableCell>
<TableCell>Last Checked</TableCell>
<TableCell>Error Message</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{healthChecks.map((check) => (
<TableRow key={check.id}>
<TableCell>
<Box display="flex" alignItems="center">
{getTargetIcon(check.targetType)}
<Typography variant="body2" sx={{ ml: 1 }}>
{check.target}
</Typography>
</Box>
</TableCell>
<TableCell>
<Chip label={check.targetType} size="small" />
</TableCell>
<TableCell>
<Chip
label={check.status}
color={getStatusColor(check.status) as any}
size="small"
/>
</TableCell>
<TableCell>
<Typography variant="body2">
{check.responseTime}ms
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{check.uptime}%
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{formatTime(check.lastChecked)}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2" color="error">
{check.errorMessage || '-'}
</Typography>
</TableCell>
<TableCell>
<Button size="small" variant="outlined">
Configure
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
{/* System Metrics Tab */}
{activeTab === 2 && (
<>
<Typography variant="h5" gutterBottom>
System Metrics & Performance
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Real-time system performance metrics and trends
</Typography>
<Grid container spacing={3} sx={{ mt: 2 }}>
{systemMetrics.map((metric) => (
<Grid size={{ xs: 12, sm: 6, md: 4 }} key={metric.id}>
<Card>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between" mb={2}>
<Typography variant="h6">
{metric.name}
</Typography>
<Chip
label={metric.status}
color={getStatusColor(metric.status) as any}
size="small"
/>
</Box>
<Typography variant="h4" component="div" color="primary">
{metric.value}{metric.unit}
</Typography>
<Box display="flex" alignItems="center" mt={1}>
{metric.trend === 'up' ? (
<TrendingUpIcon color="error" fontSize="small" />
) : metric.trend === 'down' ? (
<TrendingDownIcon color="success" fontSize="small" />
) : null}
<Typography variant="caption" color="textSecondary" sx={{ ml: 0.5 }}>
{metric.trend === 'up' ? 'Increasing' : metric.trend === 'down' ? 'Decreasing' : 'Stable'}
</Typography>
</Box>
<Box mt={2}>
<Box display="flex" justifyContent="space-between" mb={1}>
<Typography variant="caption">Threshold</Typography>
<Typography variant="caption">{metric.threshold}{metric.unit}</Typography>
</Box>
<LinearProgress
variant="determinate"
value={(metric.value / metric.threshold) * 100}
color={metric.status === 'CRITICAL' ? 'error' : metric.status === 'WARNING' ? 'warning' : 'success'}
sx={{ height: 6, borderRadius: 3 }}
/>
</Box>
</CardContent>
</Card>
</Grid>
))}
</Grid>
</>
)}
{/* Alerts Tab */}
{activeTab === 3 && (
<>
<Typography variant="h5" gutterBottom>
Monitoring Alerts
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Active alerts and alert management
</Typography>
<Card sx={{ mt: 3 }}>
<CardContent>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Alert</TableCell>
<TableCell>Severity</TableCell>
<TableCell>Target</TableCell>
<TableCell>Status</TableCell>
<TableCell>Value</TableCell>
<TableCell>Threshold</TableCell>
<TableCell>Triggered At</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{alertList.map((alert) => (
<TableRow key={alert.id}>
<TableCell>
<Typography variant="body2" fontWeight="bold">
{alert.title}
</Typography>
<Typography variant="caption" color="textSecondary">
{alert.description}
</Typography>
</TableCell>
<TableCell>
<Chip
label={alert.severity}
color={getSeverityColor(alert.severity) as any}
size="small"
/>
</TableCell>
<TableCell>
<Typography variant="body2">
{alert.target}
</Typography>
</TableCell>
<TableCell>
<Chip
label={alert.status}
color={getStatusColor(alert.status) as any}
size="small"
/>
</TableCell>
<TableCell>
<Typography variant="body2" fontWeight="bold">
{alert.value}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{alert.threshold}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{formatTime(alert.triggeredAt)}
</Typography>
</TableCell>
<TableCell>
<Button size="small" variant="outlined">
Acknowledge
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
</Box>
);
};
export default MonitoringDashboard;

View 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;

View File

@@ -0,0 +1,877 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Card,
CardContent,
Typography,
Grid,
Chip,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
IconButton,
Button,
LinearProgress,
Alert,
Avatar,
Tabs,
Tab,
} from '@mui/material';
import {
Schedule as ScheduleIcon,
Person as PersonIcon,
Warning as WarningIcon,
CheckCircle as CheckCircleIcon,
TrendingUp as TrendingUpIcon,
TrendingDown as TrendingDownIcon,
Refresh as RefreshIcon,
Settings as SettingsIcon,
AccessTime as AccessTimeIcon,
Timer as TimerIcon,
Assessment as AssessmentIcon,
Phone as PhoneIcon,
Email as EmailIcon,
} from '@mui/icons-material';
interface SLAOnCallDashboardProps {
onNavigateToModule: (moduleId: string) => void;
}
interface SLAStats {
totalSLAs: number;
metSLAs: number;
breachedSLAs: number;
avgResponseTime: number;
avgResolutionTime: number;
complianceRate: number;
escalationCount: number;
businessHoursCompliance: number;
}
interface OnCallStats {
totalRotations: number;
activeAssignments: number;
upcomingHandoffs: number;
avgResponseTime: number;
incidentsHandled: number;
availabilityRate: number;
}
interface SLAInstance {
id: string;
incidentId: string;
incidentTitle: string;
slaType: string;
targetTime: string;
status: 'ACTIVE' | 'MET' | 'BREACHED';
timeRemaining: number;
progress: number;
escalationLevel: number;
assignedTo: string;
}
interface OnCallAssignment {
id: string;
user: {
name: string;
email: string;
phone: string;
};
rotation: {
name: string;
teamName: string;
};
startTime: string;
endTime: string;
status: 'ACTIVE' | 'UPCOMING' | 'COMPLETED';
incidentsHandled: number;
responseTime: number;
}
interface EscalationEvent {
id: string;
incidentId: string;
incidentTitle: string;
escalationLevel: number;
triggeredAt: string;
status: 'PENDING' | 'TRIGGERED' | 'ACKNOWLEDGED' | 'RESOLVED';
reason: string;
notifiedUsers: string[];
}
const SLAOnCallDashboard: React.FC<SLAOnCallDashboardProps> = ({ onNavigateToModule }) => {
const [activeTab, setActiveTab] = useState(0);
const [slaStats, setSlaStats] = useState<SLAStats>({
totalSLAs: 0,
metSLAs: 0,
breachedSLAs: 0,
avgResponseTime: 0,
avgResolutionTime: 0,
complianceRate: 0,
escalationCount: 0,
businessHoursCompliance: 0,
});
const [onCallStats, setOnCallStats] = useState<OnCallStats>({
totalRotations: 0,
activeAssignments: 0,
upcomingHandoffs: 0,
avgResponseTime: 0,
incidentsHandled: 0,
availabilityRate: 0,
});
const [slaInstances, setSlaInstances] = useState<SLAInstance[]>([]);
const [onCallAssignments, setOnCallAssignments] = useState<OnCallAssignment[]>([]);
const [escalationEvents, setEscalationEvents] = useState<EscalationEvent[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
loadSLAOnCallData();
}, []);
const loadSLAOnCallData = async () => {
try {
setIsLoading(true);
setError(null);
// Mock data - replace with actual API calls
setSlaStats({
totalSLAs: 156,
metSLAs: 142,
breachedSLAs: 14,
avgResponseTime: 0.8,
avgResolutionTime: 2.5,
complianceRate: 91,
escalationCount: 8,
businessHoursCompliance: 94,
});
setOnCallStats({
totalRotations: 12,
activeAssignments: 8,
upcomingHandoffs: 3,
avgResponseTime: 0.5,
incidentsHandled: 45,
availabilityRate: 98,
});
setSlaInstances([
{
id: '1',
incidentId: 'INC-001',
incidentTitle: 'Database Connection Timeout',
slaType: 'Response Time',
targetTime: '2024-01-15T11:30:00Z',
status: 'ACTIVE',
timeRemaining: 45,
progress: 75,
escalationLevel: 0,
assignedTo: 'John Doe',
},
{
id: '2',
incidentId: 'INC-002',
incidentTitle: 'API Gateway Error',
slaType: 'Resolution Time',
targetTime: '2024-01-15T12:00:00Z',
status: 'BREACHED',
timeRemaining: -15,
progress: 100,
escalationLevel: 2,
assignedTo: 'Jane Smith',
},
{
id: '3',
incidentId: 'INC-003',
incidentTitle: 'Cache Service Down',
slaType: 'First Response',
targetTime: '2024-01-15T11:45:00Z',
status: 'MET',
timeRemaining: 0,
progress: 100,
escalationLevel: 0,
assignedTo: 'Mike Johnson',
},
]);
setOnCallAssignments([
{
id: '1',
user: {
name: 'John Doe',
email: 'john.doe@company.com',
phone: '+1-555-0123',
},
rotation: {
name: 'Primary On-Call',
teamName: 'Platform Engineering',
},
startTime: '2024-01-15T08:00:00Z',
endTime: '2024-01-16T08:00:00Z',
status: 'ACTIVE',
incidentsHandled: 3,
responseTime: 0.3,
},
{
id: '2',
user: {
name: 'Jane Smith',
email: 'jane.smith@company.com',
phone: '+1-555-0124',
},
rotation: {
name: 'Secondary On-Call',
teamName: 'Platform Engineering',
},
startTime: '2024-01-16T08:00:00Z',
endTime: '2024-01-17T08:00:00Z',
status: 'UPCOMING',
incidentsHandled: 0,
responseTime: 0,
},
{
id: '3',
user: {
name: 'Mike Johnson',
email: 'mike.johnson@company.com',
phone: '+1-555-0125',
},
rotation: {
name: 'Database Team',
teamName: 'Database Operations',
},
startTime: '2024-01-15T06:00:00Z',
endTime: '2024-01-15T18:00:00Z',
status: 'ACTIVE',
incidentsHandled: 2,
responseTime: 0.7,
},
]);
setEscalationEvents([
{
id: '1',
incidentId: 'INC-002',
incidentTitle: 'API Gateway Error',
escalationLevel: 2,
triggeredAt: '2024-01-15T10:45:00Z',
status: 'TRIGGERED',
reason: 'SLA breach threshold exceeded',
notifiedUsers: ['team-lead@company.com', 'manager@company.com'],
},
{
id: '2',
incidentId: 'INC-004',
incidentTitle: 'Critical System Failure',
escalationLevel: 3,
triggeredAt: '2024-01-15T09:30:00Z',
status: 'ACKNOWLEDGED',
reason: 'No response within 15 minutes',
notifiedUsers: ['director@company.com', 'cto@company.com'],
},
]);
} catch (error) {
console.error('Failed to load SLA & On-Call data:', error);
setError('Failed to load SLA & On-Call data');
} finally {
setIsLoading(false);
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'MET':
case 'COMPLETED':
case 'RESOLVED':
case 'ACTIVE':
return 'success';
case 'BREACHED':
case 'TRIGGERED':
return 'error';
case 'PENDING':
case 'UPCOMING':
return 'warning';
case 'ACKNOWLEDGED':
return 'info';
default:
return 'default';
}
};
const formatTime = (timestamp: string) => {
return new Date(timestamp).toLocaleString();
};
const formatDuration = (minutes: number) => {
if (minutes < 0) {
return `Overdue by ${Math.abs(minutes)}m`;
}
return `${minutes}m remaining`;
};
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;
subtitle?: string;
}> = ({ title, value, icon, color, trend, trendValue, subtitle }) => (
<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>
{subtitle && (
<Typography variant="body2" color="textSecondary">
{subtitle}
</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 SLA & On-Call Dashboard...
</Typography>
</Box>
);
}
if (error) {
return (
<Alert severity="error" action={
<Button color="inherit" size="small" onClick={loadSLAOnCallData}>
Retry
</Button>
}>
{error}
</Alert>
);
}
return (
<Box>
{/* Header */}
<Box display="flex" justifyContent="space-between" alignItems="center" mb={3}>
<Box>
<Typography variant="h4" gutterBottom>
SLA & On-Call Management
</Typography>
<Typography variant="subtitle1" color="textSecondary">
Service level agreement monitoring and on-call rotation management
</Typography>
</Box>
<Box>
<IconButton onClick={loadSLAOnCallData}>
<RefreshIcon />
</IconButton>
<IconButton>
<SettingsIcon />
</IconButton>
</Box>
</Box>
{/* SLA Compliance Alert */}
<Alert
severity={slaStats.complianceRate > 95 ? 'success' : slaStats.complianceRate > 85 ? 'warning' : 'error'}
sx={{ mb: 3 }}
icon={slaStats.complianceRate > 95 ? <CheckCircleIcon /> : <WarningIcon />}
>
<Typography variant="h6">
SLA Compliance: {slaStats.complianceRate}%
</Typography>
<Typography variant="body2">
{slaStats.complianceRate > 95 ? 'Excellent' : slaStats.complianceRate > 85 ? 'Good' : 'Needs improvement'} SLA compliance rate.
{slaStats.breachedSLAs > 0 && ` ${slaStats.breachedSLAs} SLAs currently breached.`}
</Typography>
</Alert>
{/* Tabs */}
<Paper sx={{ mb: 3 }}>
<Tabs value={activeTab} onChange={handleTabChange} indicatorColor="primary" textColor="primary">
<Tab label="Overview" />
<Tab label="SLA Monitoring" />
<Tab label="On-Call Schedule" />
<Tab label="Escalations" />
</Tabs>
</Paper>
{/* Tab Content */}
{activeTab === 0 && (
<>
{/* SLA Stats Cards */}
<Typography variant="h5" gutterBottom>
SLA Performance
</Typography>
<Grid container spacing={3} mb={3}>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Total SLAs"
value={slaStats.totalSLAs}
icon={<ScheduleIcon />}
color="primary"
trend="up"
trendValue="+12 this week"
subtitle={`${slaStats.metSLAs} met, ${slaStats.breachedSLAs} breached`}
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Compliance Rate"
value={`${slaStats.complianceRate}%`}
icon={<CheckCircleIcon />}
color="success"
trend="up"
trendValue="+2% this month"
subtitle="SLA compliance score"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Avg Response Time"
value={`${slaStats.avgResponseTime}h`}
icon={<AccessTimeIcon />}
color="info"
trend="down"
trendValue="-0.2h this week"
subtitle="Time to first response"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Escalations"
value={slaStats.escalationCount}
icon={<WarningIcon />}
color="warning"
trend="down"
trendValue="-3 this week"
subtitle="Active escalations"
/>
</Grid>
</Grid>
{/* On-Call Stats Cards */}
<Typography variant="h5" gutterBottom sx={{ mt: 4 }}>
On-Call Management
</Typography>
<Grid container spacing={3} mb={3}>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Active Assignments"
value={onCallStats.activeAssignments}
icon={<PersonIcon />}
color="primary"
trend="neutral"
trendValue="No change"
subtitle={`${onCallStats.upcomingHandoffs} upcoming handoffs`}
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Availability Rate"
value={`${onCallStats.availabilityRate}%`}
icon={<CheckCircleIcon />}
color="success"
trend="up"
trendValue="+1% this month"
subtitle="On-call availability"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Incidents Handled"
value={onCallStats.incidentsHandled}
icon={<AssessmentIcon />}
color="info"
trend="up"
trendValue="+8 this week"
subtitle="This week's total"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Avg Response Time"
value={`${onCallStats.avgResponseTime}h`}
icon={<TimerIcon />}
color="secondary"
trend="down"
trendValue="-0.1h this week"
subtitle="On-call response time"
/>
</Grid>
</Grid>
{/* Current On-Call Status */}
<Card sx={{ mb: 3 }}>
<CardContent>
<Typography variant="h6" gutterBottom>
Current On-Call Assignments
</Typography>
<Grid container spacing={2}>
{onCallAssignments.filter(a => a.status === 'ACTIVE').map((assignment) => (
<Grid size={{ xs: 12, md: 6 }} key={assignment.id}>
<Paper sx={{ p: 2, border: '1px solid', borderColor: 'primary.main' }}>
<Box display="flex" alignItems="center" mb={2}>
<Avatar sx={{ mr: 2 }}>
{assignment.user.name.split(' ').map(n => n[0]).join('')}
</Avatar>
<Box>
<Typography variant="h6">
{assignment.user.name}
</Typography>
<Typography variant="body2" color="textSecondary">
{assignment.rotation.teamName}
</Typography>
</Box>
</Box>
<Box display="flex" justifyContent="space-between" mb={1}>
<Typography variant="body2">
<PhoneIcon fontSize="small" sx={{ mr: 0.5, verticalAlign: 'middle' }} />
{assignment.user.phone}
</Typography>
<Typography variant="body2">
<EmailIcon fontSize="small" sx={{ mr: 0.5, verticalAlign: 'middle' }} />
{assignment.user.email}
</Typography>
</Box>
<Box display="flex" justifyContent="space-between">
<Typography variant="caption" color="textSecondary">
Incidents: {assignment.incidentsHandled}
</Typography>
<Typography variant="caption" color="textSecondary">
Avg Response: {assignment.responseTime}h
</Typography>
</Box>
</Paper>
</Grid>
))}
</Grid>
</CardContent>
</Card>
</>
)}
{/* SLA Monitoring Tab */}
{activeTab === 1 && (
<>
<Typography variant="h5" gutterBottom>
SLA Monitoring & Tracking
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Real-time SLA status and performance tracking
</Typography>
<Card sx={{ mt: 3 }}>
<CardContent>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Incident</TableCell>
<TableCell>SLA Type</TableCell>
<TableCell>Status</TableCell>
<TableCell>Progress</TableCell>
<TableCell>Time Remaining</TableCell>
<TableCell>Escalation</TableCell>
<TableCell>Assigned To</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{slaInstances.map((sla) => (
<TableRow key={sla.id}>
<TableCell>
<Typography variant="body2" fontWeight="bold">
{sla.incidentId}
</Typography>
<Typography variant="caption" color="textSecondary">
{sla.incidentTitle}
</Typography>
</TableCell>
<TableCell>
<Chip label={sla.slaType} size="small" />
</TableCell>
<TableCell>
<Chip
label={sla.status}
color={getStatusColor(sla.status) as any}
size="small"
/>
</TableCell>
<TableCell>
<Box display="flex" alignItems="center">
<LinearProgress
variant="determinate"
value={sla.progress}
color={sla.status === 'BREACHED' ? 'error' : sla.status === 'ACTIVE' ? 'primary' : 'success'}
sx={{ width: 60, mr: 1 }}
/>
<Typography variant="caption">
{sla.progress}%
</Typography>
</Box>
</TableCell>
<TableCell>
<Typography
variant="body2"
color={sla.timeRemaining < 0 ? 'error' : sla.timeRemaining < 30 ? 'warning' : 'textPrimary'}
>
{formatDuration(sla.timeRemaining)}
</Typography>
</TableCell>
<TableCell>
{sla.escalationLevel > 0 ? (
<Chip
label={`Level ${sla.escalationLevel}`}
color="error"
size="small"
/>
) : (
<Typography variant="body2" color="textSecondary">
None
</Typography>
)}
</TableCell>
<TableCell>
<Typography variant="body2">
{sla.assignedTo}
</Typography>
</TableCell>
<TableCell>
<Button size="small" variant="outlined">
View Details
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
{/* On-Call Schedule Tab */}
{activeTab === 2 && (
<>
<Typography variant="h5" gutterBottom>
On-Call Schedule & Rotations
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Manage on-call rotations and assignments
</Typography>
<Card sx={{ mt: 3 }}>
<CardContent>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>On-Call Engineer</TableCell>
<TableCell>Rotation</TableCell>
<TableCell>Team</TableCell>
<TableCell>Schedule</TableCell>
<TableCell>Status</TableCell>
<TableCell>Performance</TableCell>
<TableCell>Contact</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{onCallAssignments.map((assignment) => (
<TableRow key={assignment.id}>
<TableCell>
<Box display="flex" alignItems="center">
<Avatar sx={{ mr: 2 }}>
{assignment.user.name.split(' ').map(n => n[0]).join('')}
</Avatar>
<Box>
<Typography variant="body2" fontWeight="bold">
{assignment.user.name}
</Typography>
<Typography variant="caption" color="textSecondary">
{assignment.user.email}
</Typography>
</Box>
</Box>
</TableCell>
<TableCell>
<Typography variant="body2">
{assignment.rotation.name}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{assignment.rotation.teamName}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{formatTime(assignment.startTime)}
</Typography>
<Typography variant="caption" color="textSecondary">
to {formatTime(assignment.endTime)}
</Typography>
</TableCell>
<TableCell>
<Chip
label={assignment.status}
color={getStatusColor(assignment.status) as any}
size="small"
/>
</TableCell>
<TableCell>
<Box>
<Typography variant="caption" display="block">
Incidents: {assignment.incidentsHandled}
</Typography>
<Typography variant="caption" display="block">
Response: {assignment.responseTime}h
</Typography>
</Box>
</TableCell>
<TableCell>
<Box>
<Typography variant="caption" display="block">
<PhoneIcon fontSize="small" sx={{ mr: 0.5, verticalAlign: 'middle' }} />
{assignment.user.phone}
</Typography>
</Box>
</TableCell>
<TableCell>
<Button size="small" variant="outlined">
Manage
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
{/* Escalations Tab */}
{activeTab === 3 && (
<>
<Typography variant="h5" gutterBottom>
Escalation Management
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Track and manage incident escalations
</Typography>
<Card sx={{ mt: 3 }}>
<CardContent>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Incident</TableCell>
<TableCell>Escalation Level</TableCell>
<TableCell>Status</TableCell>
<TableCell>Reason</TableCell>
<TableCell>Notified Users</TableCell>
<TableCell>Triggered At</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{escalationEvents.map((event) => (
<TableRow key={event.id}>
<TableCell>
<Typography variant="body2" fontWeight="bold">
{event.incidentId}
</Typography>
<Typography variant="caption" color="textSecondary">
{event.incidentTitle}
</Typography>
</TableCell>
<TableCell>
<Chip
label={`Level ${event.escalationLevel}`}
color={event.escalationLevel >= 3 ? 'error' : event.escalationLevel >= 2 ? 'warning' : 'info'}
size="small"
/>
</TableCell>
<TableCell>
<Chip
label={event.status}
color={getStatusColor(event.status) as any}
size="small"
/>
</TableCell>
<TableCell>
<Typography variant="body2">
{event.reason}
</Typography>
</TableCell>
<TableCell>
<Typography variant="caption">
{event.notifiedUsers.length} users notified
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{formatTime(event.triggeredAt)}
</Typography>
</TableCell>
<TableCell>
<Button size="small" variant="outlined">
Acknowledge
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
</Box>
);
};
export default SLAOnCallDashboard;

View File

@@ -0,0 +1,842 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Card,
CardContent,
Typography,
Grid,
Chip,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
IconButton,
Button,
LinearProgress,
Alert,
Avatar,
List,
ListItem,
ListItemText,
ListItemIcon,
Tabs,
Tab,
Switch,
FormControlLabel,
} from '@mui/material';
import {
Shield as ShieldIcon,
VpnKey as VpnKeyIcon,
Person as PersonIcon,
Warning as WarningIcon,
CheckCircle as CheckCircleIcon,
Error as ErrorIcon,
TrendingUp as TrendingUpIcon,
TrendingDown as TrendingDownIcon,
Refresh as RefreshIcon,
Settings as SettingsIcon,
} from '@mui/icons-material';
interface SecurityDashboardProps {
onNavigateToModule: (moduleId: string) => void;
}
interface SecurityStats {
totalUsers: number;
activeUsers: number;
mfaEnabled: number;
failedLogins: number;
securityEvents: number;
complianceScore: number;
riskLevel: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
lastSecurityScan: string;
}
interface SecurityEvent {
id: string;
type: string;
severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
description: string;
user: string;
timestamp: string;
status: 'ACTIVE' | 'RESOLVED' | 'INVESTIGATING';
source: string;
}
interface UserSecurity {
id: string;
username: string;
email: string;
clearanceLevel: number;
lastLogin: string;
mfaEnabled: boolean;
riskScore: number;
status: 'ACTIVE' | 'LOCKED' | 'SUSPENDED';
failedAttempts: number;
}
const SecurityDashboard: React.FC<SecurityDashboardProps> = ({ onNavigateToModule }) => {
const [activeTab, setActiveTab] = useState(0);
const [stats, setStats] = useState<SecurityStats>({
totalUsers: 0,
activeUsers: 0,
mfaEnabled: 0,
failedLogins: 0,
securityEvents: 0,
complianceScore: 0,
riskLevel: 'LOW',
lastSecurityScan: '',
});
const [securityEvents, setSecurityEvents] = useState<SecurityEvent[]>([]);
const [users, setUsers] = useState<UserSecurity[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
loadSecurityData();
}, []);
const loadSecurityData = async () => {
try {
setIsLoading(true);
setError(null);
// Mock data - replace with actual API calls
setStats({
totalUsers: 156,
activeUsers: 142,
mfaEnabled: 134,
failedLogins: 23,
securityEvents: 8,
complianceScore: 94,
riskLevel: 'LOW',
lastSecurityScan: '2024-01-15T10:30:00Z',
});
setSecurityEvents([
{
id: '1',
type: 'Failed Login',
severity: 'MEDIUM',
description: 'Multiple failed login attempts detected',
user: 'john.doe@company.com',
timestamp: '2024-01-15T10:25:00Z',
status: 'INVESTIGATING',
source: '192.168.1.100',
},
{
id: '2',
type: 'Privilege Escalation',
severity: 'HIGH',
description: 'Unusual privilege escalation attempt',
user: 'admin@company.com',
timestamp: '2024-01-15T09:45:00Z',
status: 'ACTIVE',
source: '10.0.0.50',
},
{
id: '3',
type: 'Data Access',
severity: 'LOW',
description: 'Access to restricted data classification',
user: 'analyst@company.com',
timestamp: '2024-01-15T08:30:00Z',
status: 'RESOLVED',
source: '172.16.0.25',
},
]);
setUsers([
{
id: '1',
username: 'john.doe',
email: 'john.doe@company.com',
clearanceLevel: 2,
lastLogin: '2024-01-15T10:20:00Z',
mfaEnabled: true,
riskScore: 25,
status: 'ACTIVE',
failedAttempts: 0,
},
{
id: '2',
username: 'jane.smith',
email: 'jane.smith@company.com',
clearanceLevel: 3,
lastLogin: '2024-01-15T09:15:00Z',
mfaEnabled: true,
riskScore: 15,
status: 'ACTIVE',
failedAttempts: 0,
},
{
id: '3',
username: 'admin',
email: 'admin@company.com',
clearanceLevel: 5,
lastLogin: '2024-01-15T08:45:00Z',
mfaEnabled: true,
riskScore: 5,
status: 'ACTIVE',
failedAttempts: 0,
},
]);
} catch (error) {
console.error('Failed to load security data:', error);
setError('Failed to load security data');
} finally {
setIsLoading(false);
}
};
const getSeverityColor = (severity: string) => {
switch (severity) {
case 'CRITICAL':
return 'error';
case 'HIGH':
return 'warning';
case 'MEDIUM':
return 'info';
case 'LOW':
return 'success';
default:
return 'default';
}
};
const formatTime = (timestamp: string) => {
return new Date(timestamp).toLocaleString();
};
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;
subtitle?: string;
}> = ({ title, value, icon, color, trend, trendValue, subtitle }) => (
<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>
{subtitle && (
<Typography variant="body2" color="textSecondary">
{subtitle}
</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 Security Dashboard...
</Typography>
</Box>
);
}
if (error) {
return (
<Alert severity="error" action={
<Button color="inherit" size="small" onClick={loadSecurityData}>
Retry
</Button>
}>
{error}
</Alert>
);
}
return (
<Box>
{/* Header */}
<Box display="flex" justifyContent="space-between" alignItems="center" mb={3}>
<Box>
<Typography variant="h4" gutterBottom>
Security & Access Control
</Typography>
<Typography variant="subtitle1" color="textSecondary">
Zero Trust Architecture & Enterprise Security Management
</Typography>
</Box>
<Box>
<IconButton onClick={loadSecurityData}>
<RefreshIcon />
</IconButton>
<IconButton>
<SettingsIcon />
</IconButton>
</Box>
</Box>
{/* Security Status Alert */}
<Alert
severity={stats.riskLevel === 'LOW' ? 'success' : stats.riskLevel === 'MEDIUM' ? 'warning' : 'error'}
sx={{ mb: 3 }}
icon={stats.riskLevel === 'LOW' ? <CheckCircleIcon /> : <WarningIcon />}
>
<Typography variant="h6">
Security Status: {stats.riskLevel} RISK
</Typography>
<Typography variant="body2">
System security posture is {stats.riskLevel.toLowerCase()}.
{stats.securityEvents > 0 && ` ${stats.securityEvents} active security events require attention.`}
</Typography>
</Alert>
{/* Tabs */}
<Paper sx={{ mb: 3 }}>
<Tabs value={activeTab} onChange={handleTabChange} indicatorColor="primary" textColor="primary">
<Tab label="Overview" />
<Tab label="Security Events" />
<Tab label="User Management" />
<Tab label="Compliance" />
</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 Users"
value={stats.totalUsers}
icon={<PersonIcon />}
color="primary"
trend="up"
trendValue="+3 this week"
subtitle={`${stats.activeUsers} active`}
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="MFA Enabled"
value={stats.mfaEnabled}
icon={<VpnKeyIcon />}
color="success"
trend="up"
trendValue="+5 this week"
subtitle={`${Math.round((stats.mfaEnabled / stats.totalUsers) * 100)}% coverage`}
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Security Events"
value={stats.securityEvents}
icon={<WarningIcon />}
color="warning"
trend="down"
trendValue="-2 from yesterday"
subtitle="Active incidents"
/>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<StatCard
title="Compliance Score"
value={`${stats.complianceScore}%`}
icon={<ShieldIcon />}
color="info"
trend="up"
trendValue="+1% this month"
subtitle="SOX, HIPAA, GDPR"
/>
</Grid>
</Grid>
{/* Security Features */}
<Grid container spacing={3} mb={3}>
<Grid size={{ xs: 12, md: 6 }}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Zero Trust Status
</Typography>
<List>
<ListItem>
<ListItemIcon>
<CheckCircleIcon color="success" />
</ListItemIcon>
<ListItemText
primary="Device Posture Assessment"
secondary="All devices verified and compliant"
/>
</ListItem>
<ListItem>
<ListItemIcon>
<CheckCircleIcon color="success" />
</ListItemIcon>
<ListItemText
primary="Adaptive Authentication"
secondary="Risk-based MFA active"
/>
</ListItem>
<ListItem>
<ListItemIcon>
<CheckCircleIcon color="success" />
</ListItemIcon>
<ListItemText
primary="Behavioral Analysis"
secondary="User behavior monitoring active"
/>
</ListItem>
<ListItem>
<ListItemIcon>
<WarningIcon color="warning" />
</ListItemIcon>
<ListItemText
primary="Geolocation Rules"
secondary="2 unusual location accesses"
/>
</ListItem>
</List>
</CardContent>
</Card>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Security Controls
</Typography>
<Box mb={2}>
<FormControlLabel
control={<Switch checked={true} />}
label="Zero Trust Enforcement"
/>
</Box>
<Box mb={2}>
<FormControlLabel
control={<Switch checked={true} />}
label="Multi-Factor Authentication"
/>
</Box>
<Box mb={2}>
<FormControlLabel
control={<Switch checked={true} />}
label="Device Registration"
/>
</Box>
<Box mb={2}>
<FormControlLabel
control={<Switch checked={false} />}
label="Strict Mode (Beta)"
/>
</Box>
<Button
variant="outlined"
onClick={() => onNavigateToModule('security')}
sx={{ mt: 2 }}
>
Advanced Security Settings
</Button>
</CardContent>
</Card>
</Grid>
</Grid>
{/* Recent Security Events */}
<Card>
<CardContent>
<Box display="flex" justifyContent="space-between" alignItems="center" mb={2}>
<Typography variant="h6">
Recent Security Events
</Typography>
<Button
variant="outlined"
size="small"
onClick={() => setActiveTab(1)}
>
View All
</Button>
</Box>
<TableContainer>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>Event Type</TableCell>
<TableCell>Severity</TableCell>
<TableCell>User</TableCell>
<TableCell>Source IP</TableCell>
<TableCell>Status</TableCell>
<TableCell>Timestamp</TableCell>
</TableRow>
</TableHead>
<TableBody>
{securityEvents.slice(0, 5).map((event) => (
<TableRow key={event.id}>
<TableCell>
<Typography variant="body2">
{event.type}
</Typography>
</TableCell>
<TableCell>
<Chip
label={event.severity}
color={getSeverityColor(event.severity) as any}
size="small"
/>
</TableCell>
<TableCell>
<Typography variant="body2">
{event.user}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2" fontFamily="monospace">
{event.source}
</Typography>
</TableCell>
<TableCell>
<Chip
label={event.status}
color={event.status === 'RESOLVED' ? 'success' : event.status === 'ACTIVE' ? 'error' : 'warning'}
size="small"
/>
</TableCell>
<TableCell>
<Typography variant="body2">
{formatTime(event.timestamp)}
</Typography>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
{/* Security Events Tab */}
{activeTab === 1 && (
<>
<Typography variant="h5" gutterBottom>
Security Events & Incidents
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Real-time security monitoring and threat detection
</Typography>
<Card sx={{ mt: 3 }}>
<CardContent>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Event Type</TableCell>
<TableCell>Severity</TableCell>
<TableCell>Description</TableCell>
<TableCell>User</TableCell>
<TableCell>Source IP</TableCell>
<TableCell>Status</TableCell>
<TableCell>Timestamp</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{securityEvents.map((event) => (
<TableRow key={event.id}>
<TableCell>
<Typography variant="body2" fontWeight="bold">
{event.type}
</Typography>
</TableCell>
<TableCell>
<Chip
label={event.severity}
color={getSeverityColor(event.severity) as any}
size="small"
/>
</TableCell>
<TableCell>
<Typography variant="body2">
{event.description}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2">
{event.user}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2" fontFamily="monospace">
{event.source}
</Typography>
</TableCell>
<TableCell>
<Chip
label={event.status}
color={event.status === 'RESOLVED' ? 'success' : event.status === 'ACTIVE' ? 'error' : 'warning'}
size="small"
/>
</TableCell>
<TableCell>
<Typography variant="body2">
{formatTime(event.timestamp)}
</Typography>
</TableCell>
<TableCell>
<Button size="small" variant="outlined">
Investigate
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
{/* User Management Tab */}
{activeTab === 2 && (
<>
<Typography variant="h5" gutterBottom>
User Security Management
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
User access control and security posture management
</Typography>
<Card sx={{ mt: 3 }}>
<CardContent>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>User</TableCell>
<TableCell>Email</TableCell>
<TableCell>Clearance Level</TableCell>
<TableCell>MFA Status</TableCell>
<TableCell>Risk Score</TableCell>
<TableCell>Status</TableCell>
<TableCell>Last Login</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{users.map((user) => (
<TableRow key={user.id}>
<TableCell>
<Box display="flex" alignItems="center">
<Avatar sx={{ width: 32, height: 32, mr: 2 }}>
{user.username[0].toUpperCase()}
</Avatar>
<Typography variant="body2" fontWeight="bold">
{user.username}
</Typography>
</Box>
</TableCell>
<TableCell>
<Typography variant="body2">
{user.email}
</Typography>
</TableCell>
<TableCell>
<Chip
label={`Level ${user.clearanceLevel}`}
color="info"
size="small"
/>
</TableCell>
<TableCell>
<Box display="flex" alignItems="center">
{user.mfaEnabled ? (
<CheckCircleIcon color="success" fontSize="small" />
) : (
<ErrorIcon color="error" fontSize="small" />
)}
<Typography variant="body2" sx={{ ml: 1 }}>
{user.mfaEnabled ? 'Enabled' : 'Disabled'}
</Typography>
</Box>
</TableCell>
<TableCell>
<Box display="flex" alignItems="center">
<LinearProgress
variant="determinate"
value={user.riskScore}
color={user.riskScore > 50 ? 'error' : user.riskScore > 25 ? 'warning' : 'success'}
sx={{ width: 60, mr: 1 }}
/>
<Typography variant="body2">
{user.riskScore}%
</Typography>
</Box>
</TableCell>
<TableCell>
<Chip
label={user.status}
color={user.status === 'ACTIVE' ? 'success' : user.status === 'LOCKED' ? 'error' : 'warning'}
size="small"
/>
</TableCell>
<TableCell>
<Typography variant="body2">
{formatTime(user.lastLogin)}
</Typography>
</TableCell>
<TableCell>
<Button size="small" variant="outlined">
Manage
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
{/* Compliance Tab */}
{activeTab === 3 && (
<>
<Typography variant="h5" gutterBottom>
Compliance & Governance
</Typography>
<Typography variant="body2" color="textSecondary" gutterBottom>
Regulatory compliance and governance framework
</Typography>
<Grid container spacing={3} sx={{ mt: 2 }}>
<Grid size={{ xs: 12, md: 6 }}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Compliance Frameworks
</Typography>
<List>
<ListItem>
<ListItemIcon>
<CheckCircleIcon color="success" />
</ListItemIcon>
<ListItemText
primary="SOX Compliance"
secondary="94% compliant - 3 items pending"
/>
</ListItem>
<ListItem>
<ListItemIcon>
<CheckCircleIcon color="success" />
</ListItemIcon>
<ListItemText
primary="HIPAA Compliance"
secondary="96% compliant - 2 items pending"
/>
</ListItem>
<ListItem>
<ListItemIcon>
<CheckCircleIcon color="success" />
</ListItemIcon>
<ListItemText
primary="GDPR Compliance"
secondary="92% compliant - 5 items pending"
/>
</ListItem>
<ListItem>
<ListItemIcon>
<CheckCircleIcon color="success" />
</ListItemIcon>
<ListItemText
primary="PCI-DSS Compliance"
secondary="98% compliant - 1 item pending"
/>
</ListItem>
</List>
</CardContent>
</Card>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Audit Trail
</Typography>
<Typography variant="body2" color="textSecondary" paragraph>
Immutable audit trail captures all security events and access patterns.
Current retention period: 7 years (SOX compliant).
</Typography>
<List dense>
<ListItem>
<ListItemText
primary="Total Audit Records"
secondary="2,847,392 entries"
/>
</ListItem>
<ListItem>
<ListItemText
primary="Last Audit Report"
secondary="Generated 2 days ago"
/>
</ListItem>
<ListItem>
<ListItemText
primary="Compliance Score"
secondary={`${stats.complianceScore}% overall`}
/>
</ListItem>
</List>
<Button
variant="outlined"
onClick={() => onNavigateToModule('compliance_governance')}
sx={{ mt: 2 }}
>
View Compliance Dashboard
</Button>
</CardContent>
</Card>
</Grid>
</Grid>
</>
)}
</Box>
);
};
export default SecurityDashboard;

View File

@@ -0,0 +1,485 @@
import React, { useState, useEffect, useCallback } from 'react';
import {
Box,
Card,
CardContent,
Typography,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
Chip,
IconButton,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
FormControl,
InputLabel,
Select,
MenuItem,
OutlinedInput,
Checkbox,
ListItemText,
Alert,
CircularProgress,
Tabs,
Tab,
Avatar,
Tooltip,
Switch,
FormControlLabel,
Grid,
} from '@mui/material';
import {
Edit as EditIcon,
AdminPanelSettings as AdminIcon,
CheckCircle as CheckCircleIcon,
Cancel as CancelIcon,
Save as SaveIcon,
Refresh as RefreshIcon,
} from '@mui/icons-material';
import { useAuth } from '../../contexts/AuthContext';
import apiService from '../../services/api';
import { User } from '../../types';
interface UserManagementDashboardProps {
onNavigateToModule?: (moduleId: string) => void;
}
interface DashboardComponent {
name: string;
permissions: string[];
clearance_level?: number;
}
const UserManagementDashboard: React.FC<UserManagementDashboardProps> = ({ onNavigateToModule }) => {
const { user: currentUser } = useAuth();
const [users, setUsers] = useState<User[]>([]);
const [roles, setRoles] = useState<any[]>([]);
const [classifications, setClassifications] = useState<any[]>([]);
const [dashboardComponents, setDashboardComponents] = useState<Record<string, DashboardComponent>>({});
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [selectedUser, setSelectedUser] = useState<User | null>(null);
const [editDialogOpen, setEditDialogOpen] = useState(false);
const [selectedRoles, setSelectedRoles] = useState<string[]>([]);
const [selectedClearance, setSelectedClearance] = useState<string>('');
const [activeTab, setActiveTab] = useState(0);
const [userAccessAnalysis, setUserAccessAnalysis] = useState<Record<string, any>>({});
const loadData = useCallback(async () => {
try {
setLoading(true);
setError(null);
const [usersData, rolesData, classificationsData, componentsData] = await Promise.all([
apiService.getUsers(),
apiService.getRoles(),
apiService.getDataClassifications(),
apiService.getDashboardPermissions(),
]);
// Handle paginated response
const usersList = Array.isArray(usersData) ? usersData : (usersData as any).results || [];
setUsers(usersList);
setRoles(rolesData);
setClassifications(classificationsData);
setDashboardComponents(componentsData);
analyzeUserAccess(usersList, componentsData);
} catch (err: any) {
setError(err.message || 'Failed to load user management data');
console.error('Error loading data:', err);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
loadData();
}, [loadData]);
const analyzeUserAccess = (usersList: User[], components: Record<string, DashboardComponent>) => {
const analysis: Record<string, any> = {};
usersList.forEach(user => {
const accessibleComponents: string[] = [];
const deniedComponents: string[] = [];
Object.entries(components).forEach(([componentName, requirements]) => {
let canAccess = true;
const reasons: string[] = [];
// Superusers bypass all checks
if (user.is_superuser || user.is_staff) {
accessibleComponents.push(componentName);
return;
}
// Check clearance level
if (requirements.clearance_level && user.clearance_level.level < requirements.clearance_level) {
canAccess = false;
reasons.push(`Insufficient clearance (has ${user.clearance_level.level}, needs ${requirements.clearance_level})`);
}
// Check permissions
if (requirements.permissions.length > 0) {
const userPermissions = user.roles?.flatMap(role =>
role.permissions.map(perm => perm.codename)
) || [];
const hasRequiredPermission = requirements.permissions.some(perm =>
userPermissions.includes(perm)
);
if (!hasRequiredPermission) {
canAccess = false;
reasons.push(`Missing permissions: ${requirements.permissions.join(', ')}`);
}
}
if (canAccess) {
accessibleComponents.push(componentName);
} else {
deniedComponents.push(componentName);
}
});
analysis[user.id] = {
accessible: accessibleComponents,
denied: deniedComponents,
totalAccessible: accessibleComponents.length,
totalDenied: deniedComponents.length,
};
});
setUserAccessAnalysis(analysis);
};
const handleEditUser = (user: User) => {
setSelectedUser(user);
setSelectedRoles(user.roles?.map(role => role.id) || []);
setSelectedClearance((user.clearance_level as any)?.id || '');
setEditDialogOpen(true);
};
const handleSaveUser = async () => {
if (!selectedUser) return;
try {
setLoading(true);
// Update roles if changed
if (JSON.stringify(selectedRoles.sort()) !== JSON.stringify((selectedUser.roles?.map(role => role.id) || []).sort())) {
await apiService.updateUserRoles(selectedUser.id, selectedRoles);
}
// Update clearance level if changed
if (selectedClearance !== (selectedUser.clearance_level as any)?.id) {
await apiService.updateUserClearance(selectedUser.id, selectedClearance);
}
// Reload data to reflect changes
await loadData();
setEditDialogOpen(false);
setSelectedUser(null);
} catch (err: any) {
setError(err.message || 'Failed to update user');
console.error('Error updating user:', err);
} finally {
setLoading(false);
}
};
const getClearanceColor = (level: number) => {
switch (level) {
case 1: return 'default';
case 2: return 'primary';
case 3: return 'secondary';
case 4: return 'warning';
case 5: return 'error';
default: return 'default';
}
};
const getAccessColor = (accessible: number, total: number) => {
const percentage = (accessible / total) * 100;
if (percentage >= 80) return 'success';
if (percentage >= 50) return 'warning';
return 'error';
};
if (loading) {
return (
<Box display="flex" justifyContent="center" alignItems="center" minHeight="400px">
<CircularProgress />
</Box>
);
}
if (error) {
return (
<Alert severity="error" action={
<Button color="inherit" size="small" onClick={loadData}>
Retry
</Button>
}>
{error}
</Alert>
);
}
return (
<Box sx={{ p: 3 }}>
<Box display="flex" justifyContent="space-between" alignItems="center" mb={3}>
<Typography variant="h4" component="h1">
User Management
</Typography>
<Button
variant="outlined"
startIcon={<RefreshIcon />}
onClick={loadData}
disabled={loading}
>
Refresh
</Button>
</Box>
<Card>
<CardContent>
<Tabs value={activeTab} onChange={(e, newValue) => setActiveTab(newValue)} sx={{ mb: 3 }}>
<Tab label="User Overview" />
<Tab label="Access Analysis" />
</Tabs>
{activeTab === 0 && (
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
<TableCell>User</TableCell>
<TableCell>Clearance Level</TableCell>
<TableCell>Roles</TableCell>
<TableCell>Access Level</TableCell>
<TableCell>Status</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{users.map((user) => (
<TableRow key={user.id}>
<TableCell>
<Box display="flex" alignItems="center">
<Avatar sx={{ mr: 2, bgcolor: 'primary.main' }}>
{user.first_name?.[0]}{user.last_name?.[0]}
</Avatar>
<Box>
<Typography variant="subtitle2">
{user.first_name} {user.last_name}
</Typography>
<Typography variant="caption" color="text.secondary">
{user.username}
</Typography>
</Box>
</Box>
</TableCell>
<TableCell>
<Chip
label={`${user.clearance_level?.name || 'None'} (Level ${user.clearance_level?.level || 0})`}
color={getClearanceColor(user.clearance_level?.level || 0) as any}
size="small"
/>
</TableCell>
<TableCell>
<Box display="flex" flexWrap="wrap" gap={0.5}>
{user.roles?.map((role) => (
<Chip
key={role.id}
label={role.name}
size="small"
variant="outlined"
/>
)) || <Typography variant="caption">No roles</Typography>}
</Box>
</TableCell>
<TableCell>
<Box display="flex" alignItems="center">
<Chip
label={`${userAccessAnalysis[user.id]?.totalAccessible || 0}/${Object.keys(dashboardComponents).length} components`}
color={getAccessColor(
userAccessAnalysis[user.id]?.totalAccessible || 0,
Object.keys(dashboardComponents).length
) as any}
size="small"
/>
{user.is_superuser && (
<Tooltip title="Superuser - Full Access">
<AdminIcon sx={{ ml: 1, color: 'success.main' }} />
</Tooltip>
)}
</Box>
</TableCell>
<TableCell>
<Box display="flex" alignItems="center">
<FormControlLabel
control={
<Switch
checked={user.is_active}
disabled
size="small"
/>
}
label={user.is_active ? 'Active' : 'Inactive'}
/>
</Box>
</TableCell>
<TableCell>
<IconButton
onClick={() => handleEditUser(user)}
disabled={user.id === currentUser?.id}
size="small"
>
<EditIcon />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
{activeTab === 1 && (
<Grid container spacing={3}>
{users.map((user) => (
<Grid size={{ xs: 12, md: 6 }} key={user.id}>
<Card variant="outlined">
<CardContent>
<Box display="flex" alignItems="center" mb={2}>
<Avatar sx={{ mr: 2, bgcolor: 'primary.main' }}>
{user.first_name?.[0]}{user.last_name?.[0]}
</Avatar>
<Box>
<Typography variant="h6">
{user.first_name} {user.last_name}
</Typography>
<Typography variant="caption" color="text.secondary">
{user.username}
</Typography>
</Box>
</Box>
<Typography variant="subtitle2" gutterBottom>
Accessible Components ({userAccessAnalysis[user.id]?.totalAccessible || 0})
</Typography>
<Box display="flex" flexWrap="wrap" gap={0.5} mb={2}>
{userAccessAnalysis[user.id]?.accessible?.map((component: string) => (
<Chip
key={component}
label={component}
size="small"
color="success"
icon={<CheckCircleIcon />}
/>
))}
</Box>
{userAccessAnalysis[user.id]?.denied?.length > 0 && (
<>
<Typography variant="subtitle2" gutterBottom>
Denied Components ({userAccessAnalysis[user.id]?.totalDenied || 0})
</Typography>
<Box display="flex" flexWrap="wrap" gap={0.5}>
{userAccessAnalysis[user.id]?.denied?.map((component: string) => (
<Chip
key={component}
label={component}
size="small"
color="error"
icon={<CancelIcon />}
/>
))}
</Box>
</>
)}
</CardContent>
</Card>
</Grid>
))}
</Grid>
)}
</CardContent>
</Card>
{/* Edit User Dialog */}
<Dialog open={editDialogOpen} onClose={() => setEditDialogOpen(false)} maxWidth="md" fullWidth>
<DialogTitle>
Edit User: {selectedUser?.first_name} {selectedUser?.last_name}
</DialogTitle>
<DialogContent>
<Grid container spacing={3} sx={{ mt: 1 }}>
<Grid size={{ xs: 12, md: 6 }}>
<FormControl fullWidth>
<InputLabel>Clearance Level</InputLabel>
<Select
value={selectedClearance}
onChange={(e) => setSelectedClearance(e.target.value)}
label="Clearance Level"
>
{classifications.map((classification) => (
<MenuItem key={classification.id} value={classification.id}>
{classification.name} (Level {classification.level})
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<FormControl fullWidth>
<InputLabel>Roles</InputLabel>
<Select
multiple
value={selectedRoles}
onChange={(e) => setSelectedRoles(e.target.value as string[])}
input={<OutlinedInput label="Roles" />}
renderValue={(selected) => (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{selected.map((value) => {
const role = roles.find(r => r.id === value);
return (
<Chip key={value} label={role?.name || value} size="small" />
);
})}
</Box>
)}
>
{roles.map((role) => (
<MenuItem key={role.id} value={role.id}>
<Checkbox checked={selectedRoles.indexOf(role.id) > -1} />
<ListItemText primary={role.name} secondary={role.description} />
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
</Grid>
</DialogContent>
<DialogActions>
<Button onClick={() => setEditDialogOpen(false)}>Cancel</Button>
<Button
onClick={handleSaveUser}
variant="contained"
startIcon={<SaveIcon />}
disabled={loading}
>
Save Changes
</Button>
</DialogActions>
</Dialog>
</Box>
);
};
export default UserManagementDashboard;

View File

@@ -0,0 +1,157 @@
import React from 'react';
import { Box, Typography, Avatar } from '@mui/material';
import { Security } from '@mui/icons-material';
interface ETBLogoProps {
size?: 'small' | 'medium' | 'large';
showSubtitle?: boolean;
variant?: 'horizontal' | 'vertical';
}
const ETBLogo: React.FC<ETBLogoProps> = ({
size = 'medium',
showSubtitle = true,
variant = 'horizontal'
}) => {
const getSizeConfig = () => {
switch (size) {
case 'small':
return {
avatarSize: 60,
iconSize: 30,
titleSize: 'h6',
subtitleSize: 'caption',
spacing: 1.5,
};
case 'large':
return {
avatarSize: 120,
iconSize: 60,
titleSize: 'h3',
subtitleSize: 'subtitle1',
spacing: 2,
};
default: // medium
return {
avatarSize: 100,
iconSize: 50,
titleSize: 'h4',
subtitleSize: 'subtitle1',
spacing: 2,
};
}
};
const config = getSizeConfig();
const LogoContent = () => (
<>
<Avatar
sx={{
width: config.avatarSize,
height: config.avatarSize,
background: 'linear-gradient(135deg, #1e3c72 0%, #2a5298 100%)',
boxShadow: '0 8px 32px rgba(30, 60, 114, 0.3)',
position: 'relative',
'&::before': {
content: '""',
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: '60%',
height: '60%',
background: 'linear-gradient(135deg, #ffffff 0%, #f0f0f0 100%)',
borderRadius: '50%',
opacity: 0.1,
},
}}
>
<Security sx={{ fontSize: config.iconSize, color: 'white' }} />
</Avatar>
<Box sx={{ ml: config.spacing }}>
<Typography
variant={config.titleSize as any}
component="h1"
sx={{
fontWeight: 700,
color: '#ffffff',
letterSpacing: '0.5px',
lineHeight: 1.2,
textShadow: '0 2px 4px rgba(0, 0, 0, 0.3)',
}}
>
ETB Security
</Typography>
{showSubtitle && (
<Typography
variant={config.subtitleSize as any}
sx={{
fontWeight: 500,
mt: 0.5,
opacity: 0.9,
color: '#94a3b8', // slate-400 for dark theme
fontSize: size === 'small' ? '0.75rem' : size === 'large' ? '1rem' : '0.875rem',
}}
>
Enterprise Threat & Breach Management
</Typography>
)}
</Box>
</>
);
if (variant === 'vertical') {
return (
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', textAlign: 'center' }}>
<Avatar
sx={{
width: config.avatarSize,
height: config.avatarSize,
background: 'linear-gradient(135deg, #1e3c72 0%, #2a5298 100%)',
boxShadow: '0 8px 32px rgba(30, 60, 114, 0.3)',
mb: config.spacing,
}}
>
<Security sx={{ fontSize: config.iconSize, color: 'white' }} />
</Avatar>
<Typography
variant={config.titleSize as any}
component="h1"
sx={{
fontWeight: 700,
color: '#ffffff',
letterSpacing: '0.5px',
lineHeight: 1.2,
textShadow: '0 2px 4px rgba(0, 0, 0, 0.3)',
}}
>
ETB Security
</Typography>
{showSubtitle && (
<Typography
variant={config.subtitleSize as any}
sx={{
fontWeight: 500,
mt: 0.5,
opacity: 0.9,
color: '#94a3b8', // slate-400 for dark theme
fontSize: size === 'small' ? '0.75rem' : size === 'large' ? '1rem' : '0.875rem',
}}
>
Enterprise Threat & Breach Management
</Typography>
)}
</Box>
);
}
return (
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<LogoContent />
</Box>
);
};
export default ETBLogo;

View File

@@ -0,0 +1,293 @@
/* ETB Security Enterprise Login Page Styles */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
* {
box-sizing: border-box;
}
html, body {
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
.etb-login-container {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
height: 100vh;
width: 100vw;
position: fixed;
top: 0;
left: 0;
overflow: hidden;
}
.etb-gradient-bg {
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 50%, #667eea 100%);
position: relative;
overflow: hidden;
height: 100vh;
width: 100vw;
display: flex;
align-items: center;
justify-content: center;
}
.etb-gradient-bg::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background:
radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(120, 119, 198, 0.2) 0%, transparent 50%);
z-index: 0;
}
.etb-glass-card {
background: rgba(255, 255, 255, 0.98);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 20px;
box-shadow:
0 25px 50px rgba(0, 0, 0, 0.15),
0 0 0 1px rgba(255, 255, 255, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
max-height: 90vh;
overflow-y: auto;
width: 100%;
max-width: 480px;
}
.etb-logo-gradient {
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.etb-primary-button {
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
box-shadow: 0 4px 20px rgba(30, 60, 114, 0.3);
border-radius: 12px;
transition: all 0.3s ease;
}
.etb-primary-button:hover {
background: linear-gradient(135deg, #2a5298 0%, #1e3c72 100%);
box-shadow: 0 6px 25px rgba(30, 60, 114, 0.4);
transform: translateY(-2px);
}
.etb-success-button {
background: linear-gradient(135deg, #4caf50 0%, #2e7d32 100%);
box-shadow: 0 4px 20px rgba(76, 175, 80, 0.3);
border-radius: 12px;
transition: all 0.3s ease;
}
.etb-success-button:hover {
background: linear-gradient(135deg, #2e7d32 0%, #4caf50 100%);
box-shadow: 0 6px 25px rgba(76, 175, 80, 0.4);
transform: translateY(-2px);
}
.etb-input-field {
border-radius: 12px;
}
.etb-input-field .MuiOutlinedInput-root:hover .MuiOutlinedInput-notchedOutline {
border-color: #1e3c72;
}
.etb-input-field .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline {
border-color: #1e3c72;
border-width: 2px;
}
.etb-security-badge {
background: linear-gradient(135deg, #f8f9ff 0%, #ffffff 100%);
border: 1px solid #e3f2fd;
border-radius: 12px;
transition: all 0.3s ease;
}
.etb-security-badge:hover {
transform: translateY(-4px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.etb-chip-gradient {
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
color: white;
font-weight: 600;
}
.etb-avatar-gradient {
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
box-shadow: 0 8px 32px rgba(30, 60, 114, 0.3);
}
.etb-avatar-success {
background: linear-gradient(135deg, #4caf50 0%, #2e7d32 100%);
}
.etb-fade-in {
animation: fadeIn 1s ease-in-out;
}
.etb-slide-up {
animation: slideUp 0.8s ease-out;
}
.etb-pulse {
animation: pulse 2s infinite;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(30, 60, 114, 0.4);
}
70% {
box-shadow: 0 0 0 10px rgba(30, 60, 114, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(30, 60, 114, 0);
}
}
/* Responsive Design */
@media (max-width: 1024px) {
.etb-glass-card {
max-width: 420px;
margin: 20px;
}
}
@media (max-width: 768px) {
.etb-glass-card {
max-width: 380px;
margin: 16px;
border-radius: 16px;
max-height: 85vh;
}
.etb-logo-gradient {
font-size: 1.5rem;
}
.etb-gradient-bg {
padding: 16px;
}
}
@media (max-width: 480px) {
.etb-glass-card {
max-width: calc(100vw - 32px);
margin: 16px;
border-radius: 12px;
max-height: 80vh;
}
.etb-primary-button,
.etb-success-button {
padding: 12px 24px;
}
.etb-gradient-bg {
padding: 16px;
}
}
@media (max-width: 360px) {
.etb-glass-card {
max-width: calc(100vw - 24px);
margin: 12px;
max-height: 75vh;
}
.etb-gradient-bg {
padding: 12px;
}
}
/* Landscape orientation for mobile */
@media (max-height: 600px) and (orientation: landscape) {
.etb-glass-card {
max-height: 90vh;
max-width: 400px;
}
.etb-gradient-bg {
padding: 8px;
}
}
/* Dark mode support */
@media (prefers-color-scheme: dark) {
.etb-glass-card {
background: rgba(30, 30, 30, 0.95);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.etb-security-badge {
background: linear-gradient(135deg, #2a2a2a 0%, #1a1a1a 100%);
border: 1px solid #333;
}
}
/* Accessibility improvements */
.etb-primary-button:focus,
.etb-success-button:focus {
outline: 2px solid #1e3c72;
outline-offset: 2px;
}
.etb-input-field .MuiOutlinedInput-root:focus-within {
outline: 2px solid #1e3c72;
outline-offset: 2px;
}
/* High contrast mode */
@media (prefers-contrast: high) {
.etb-logo-gradient {
background: #000000;
-webkit-text-fill-color: #000000;
}
.etb-primary-button {
background: #000000;
color: #ffffff;
}
.etb-success-button {
background: #006600;
color: #ffffff;
}
}

View File

@@ -0,0 +1,666 @@
import React, { useState, useEffect, useCallback, useRef } from 'react';
import { useAuth } from '../../contexts/AuthContext';
import { LoginFormData } from '../../types';
import { QRCodeSVG } from 'qrcode.react';
import { useNavigate } from 'react-router-dom';
import ETBLogo from './ETBLogo';
// Use Material-UI icons for compatibility
import {
Visibility,
VisibilityOff,
Person,
Lock,
Security,
VpnKey,
Analytics,
Timeline,
Warning,
CheckCircle,
Smartphone,
} from '@mui/icons-material';
const LoginPage: React.FC = () => {
const { login, verifyMFA, setupMFA, isLoading, error, mfaRequired, clearError } = useAuth();
const navigate = useNavigate();
// Form states
const [formData, setFormData] = useState<LoginFormData>({
username: '',
password: '',
mfa_token: '',
remember_me: false,
});
// UI states
const [showPassword, setShowPassword] = useState(false);
const [showMFA, setShowMFA] = useState(false);
const [mfaStep, setMfaStep] = useState(0);
const [mfaSetupData, setMfaSetupData] = useState<any>(null);
const [isAnimating, setIsAnimating] = useState(false);
// Security features
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [hasLoggedDeviceInfo, setHasLoggedDeviceInfo] = useState(false);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [hasLoggedLocationInfo, setHasLoggedLocationInfo] = useState(false);
const hasInitialized = useRef(false);
const securityFeatures = [
{
icon: Security,
title: 'Zero Trust Architecture',
description: 'Never trust, always verify - Enterprise-grade security',
status: 'active',
compliance: ['SOX', 'HIPAA', 'GDPR']
},
{
icon: VpnKey,
title: 'Adaptive Authentication',
description: 'Risk-based multi-factor authentication',
status: 'active',
compliance: ['PCI-DSS', 'ISO27001']
},
{
icon: Analytics,
title: 'AI Threat Detection',
description: 'Machine learning-powered security analysis',
status: 'active',
compliance: ['SOX', 'HIPAA']
},
{
icon: Timeline,
title: 'Immutable Audit Trail',
description: 'Tamper-proof compliance logging',
status: 'active',
compliance: ['SOX', 'HIPAA', 'GDPR', 'PCI-DSS']
},
];
// Memoize the device info gathering function
const gatherDeviceInfo = useCallback(async () => {
try {
const deviceInfo = {
userAgent: navigator.userAgent,
platform: navigator.platform,
language: navigator.language,
screenResolution: `${window.screen.width}x${window.screen.height}`,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
cookieEnabled: navigator.cookieEnabled,
onLine: navigator.onLine,
hardwareConcurrency: navigator.hardwareConcurrency,
deviceMemory: (navigator as any).deviceMemory,
};
if (process.env.NODE_ENV === 'development') {
console.log('Device Info:', deviceInfo);
}
sessionStorage.setItem('etb_device_info_logged', 'true');
setHasLoggedDeviceInfo(true);
} catch (error) {
console.error('Failed to gather device info:', error);
}
}, []);
// Memoize the location info gathering function
const gatherLocationInfo = useCallback(async () => {
try {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
const locationInfo = {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
};
if (process.env.NODE_ENV === 'development') {
console.log('Location Info:', locationInfo);
}
sessionStorage.setItem('etb_location_info_logged', 'true');
setHasLoggedLocationInfo(true);
},
(error) => {
if (process.env.NODE_ENV === 'development') {
console.log('Location access denied or failed:', error);
}
sessionStorage.setItem('etb_location_info_logged', 'true');
setHasLoggedLocationInfo(true);
}
);
}
} catch (error) {
console.error('Failed to gather location info:', error);
}
}, []);
useEffect(() => {
if (hasInitialized.current) return;
clearError();
const deviceInfoLogged = sessionStorage.getItem('etb_device_info_logged');
const locationInfoLogged = sessionStorage.getItem('etb_location_info_logged');
if (deviceInfoLogged) {
setHasLoggedDeviceInfo(true);
}
if (locationInfoLogged) {
setHasLoggedLocationInfo(true);
}
if (!deviceInfoLogged) {
gatherDeviceInfo();
}
if (!locationInfoLogged) {
gatherLocationInfo();
}
setIsAnimating(true);
hasInitialized.current = true;
}, [clearError, gatherDeviceInfo, gatherLocationInfo]);
const handleInputChange = (field: keyof LoginFormData) => (
event: React.ChangeEvent<HTMLInputElement>
) => {
setFormData(prev => ({
...prev,
[field]: event.target.value,
}));
};
const handleCheckboxChange = (field: keyof LoginFormData) => (
event: React.ChangeEvent<HTMLInputElement>
) => {
setFormData(prev => ({
...prev,
[field]: event.target.checked,
}));
};
const handleLogin = async (event: React.FormEvent) => {
event.preventDefault();
try {
await login({
username: formData.username,
password: formData.password,
mfa_token: formData.mfa_token,
});
if (mfaRequired) {
setShowMFA(true);
setMfaStep(1);
} else {
navigate('/dashboard');
}
} catch (error) {
console.error('Login failed:', error);
}
};
const handleMFAVerification = async (event: React.FormEvent) => {
event.preventDefault();
try {
await verifyMFA(formData.mfa_token || '', '');
navigate('/dashboard');
} catch (error) {
console.error('MFA verification failed:', error);
}
};
const handleMFASetup = async () => {
try {
const deviceName = `Device ${new Date().toLocaleDateString()}`;
const setupData = await setupMFA(deviceName);
setMfaSetupData(setupData);
setMfaStep(2);
} catch (error) {
console.error('MFA setup failed:', error);
}
};
const handleMFASetupComplete = async () => {
try {
await verifyMFA('', mfaSetupData?.device?.id);
navigate('/dashboard');
} catch (error) {
console.error('MFA setup completion failed:', error);
}
};
const renderETBLogo = () => (
<div className="flex justify-center mb-4">
<div className="transform scale-75 sm:scale-90 transition-transform duration-300">
<ETBLogo size="small" showSubtitle={true} variant="horizontal" />
</div>
</div>
);
const renderSecurityBadges = () => (
<div className="w-full">
<div className="text-center mb-3">
<h3 className="text-sm font-bold text-white mb-1">
Enterprise Security Platform
</h3>
<div className="flex justify-center items-center space-x-2 text-xs text-gray-300">
<div className="flex items-center">
<div className="w-1.5 h-1.5 bg-green-400 rounded-full mr-1"></div>
<span>All Systems Operational</span>
</div>
<span></span>
<span>Zero Trust Enabled</span>
</div>
</div>
<div className="space-y-3">
{securityFeatures.map((feature, index) => (
<div
key={index}
className={`etb-security-badge animate-fade-in relative overflow-hidden`}
style={{
background: 'linear-gradient(135deg, rgba(15, 23, 42, 0.8) 0%, rgba(30, 41, 59, 0.8) 100%)',
border: '1px solid rgba(59, 130, 246, 0.3)',
borderRadius: '12px',
padding: '16px 12px',
textAlign: 'left',
transition: 'all 0.3s ease',
boxShadow: '0 4px 16px rgba(0, 0, 0, 0.3)',
animationDelay: `${index * 100}ms`,
position: 'relative',
backdropFilter: 'blur(10px)',
display: 'flex',
alignItems: 'center',
gap: '12px'
}}
>
{/* Status indicator */}
<div className="absolute top-2 right-2">
<div className={`w-2 h-2 rounded-full ${feature.status === 'active' ? 'bg-green-400' : 'bg-red-400'}`}></div>
</div>
{/* Compliance badges */}
<div className="absolute top-2 left-2 flex space-x-1">
{feature.compliance.slice(0, 2).map((comp, idx) => (
<span
key={idx}
className="text-[9px] px-1.5 py-0.5 bg-blue-500/20 text-blue-300 rounded font-medium border border-blue-400/30"
>
{comp}
</span>
))}
</div>
{/* Icon */}
<div className="flex-shrink-0 mt-4">
<feature.icon className="w-6 h-6 text-blue-400" />
</div>
{/* Content */}
<div className="flex-1 min-w-0 mt-4">
<h4 className="text-sm font-bold text-white mb-1 leading-tight">
{feature.title}
</h4>
<p className="text-xs text-gray-300 leading-tight">
{feature.description}
</p>
</div>
</div>
))}
</div>
{/* Security status footer */}
<div className="mt-2 p-2 bg-gradient-to-r from-green-500/20 to-blue-500/20 border border-green-400/30 rounded-lg backdrop-blur-sm">
<div className="flex items-center justify-center space-x-3 text-[10px]">
<div className="flex items-center text-green-300">
<CheckCircle className="w-3 h-3 mr-1" />
<span className="font-medium">SECURE</span>
</div>
<div className="flex items-center text-blue-300">
<Security className="w-3 h-3 mr-1" />
<span className="font-medium">LOW RISK</span>
</div>
</div>
</div>
</div>
);
const renderLoginForm = () => (
<div className="flex flex-col lg:flex-row gap-6 h-full">
{/* Left side - Login Form */}
<div className="flex-1 flex flex-col justify-center">
<form onSubmit={handleLogin} className="w-full space-y-4">
{renderETBLogo()}
<div className="text-center">
<div className="mb-2">
<span className="inline-block px-3 py-1 bg-blue-500/20 text-blue-300 text-xs font-semibold rounded-full border border-blue-400/30 backdrop-blur-sm">
ENTERPRISE SECURITY
</span>
</div>
<h1 className="text-xl sm:text-2xl font-bold text-white mb-1">
Secure Access Portal
</h1>
<p className="text-xs sm:text-sm text-gray-300 leading-relaxed mb-2">
Enterprise-grade incident management and security platform
</p>
<div className="flex justify-center items-center space-x-3 text-xs text-gray-400">
<div className="flex items-center">
<div className="w-1.5 h-1.5 bg-green-400 rounded-full mr-1"></div>
<span>Zero Trust</span>
</div>
<div className="flex items-center">
<div className="w-1.5 h-1.5 bg-blue-400 rounded-full mr-1"></div>
<span>Compliance Ready</span>
</div>
</div>
</div>
{error && (
<div className="bg-red-500/20 border border-red-400/30 rounded-lg p-3 animate-slide-up backdrop-blur-sm">
<div className="flex items-center">
<Warning className="w-4 h-4 text-red-400 mr-2" />
<p className="text-xs font-medium text-red-300">{error}</p>
</div>
</div>
)}
<div className="space-y-3">
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Person className="h-4 w-4 text-blue-400" />
</div>
<input
type="text"
placeholder="Username"
value={formData.username}
onChange={handleInputChange('username')}
required
autoComplete="username"
className="etb-input-field pl-9 h-10 text-sm"
style={{
width: '100%',
padding: '8px 12px 8px 36px',
border: '1px solid rgba(59, 130, 246, 0.3)',
borderRadius: '10px',
fontSize: '14px',
height: '40px',
outline: 'none',
transition: 'border-color 0.2s ease',
background: 'rgba(15, 23, 42, 0.8)',
color: 'white',
backdropFilter: 'blur(10px)'
}}
/>
</div>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Lock className="h-4 w-4 text-blue-400" />
</div>
<input
type={showPassword ? 'text' : 'password'}
placeholder="Password"
value={formData.password}
onChange={handleInputChange('password')}
required
autoComplete="current-password"
className="etb-input-field pl-9 pr-9 h-10 text-sm"
style={{
width: '100%',
padding: '8px 36px 8px 36px',
border: '1px solid rgba(59, 130, 246, 0.3)',
borderRadius: '10px',
fontSize: '14px',
height: '40px',
outline: 'none',
transition: 'border-color 0.2s ease',
background: 'rgba(15, 23, 42, 0.8)',
color: 'white',
backdropFilter: 'blur(10px)'
}}
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute inset-y-0 right-0 pr-3 flex items-center"
>
{showPassword ? (
<VisibilityOff className="h-4 w-4 text-blue-400" />
) : (
<Visibility className="h-4 w-4 text-blue-400" />
)}
</button>
</div>
</div>
<div className="flex items-center">
<input
type="checkbox"
id="remember_me"
checked={formData.remember_me}
onChange={handleCheckboxChange('remember_me')}
className="h-3 w-3 text-blue-400 focus:ring-blue-400 border-gray-500 rounded bg-slate-800"
/>
<label htmlFor="remember_me" className="ml-2 text-xs text-gray-300">
Remember me for 30 days
</label>
</div>
<button
type="submit"
disabled={isLoading}
className="etb-primary-button w-full h-10 text-sm font-bold disabled:opacity-50 disabled:cursor-not-allowed relative overflow-hidden"
style={{
background: 'linear-gradient(135deg, #1e3c72 0%, #2a5298 50%, #667eea 100%)',
color: 'white',
fontWeight: '700',
padding: '8px 16px',
borderRadius: '12px',
border: '2px solid rgba(255, 255, 255, 0.2)',
width: '100%',
height: '40px',
fontSize: '14px',
cursor: 'pointer',
boxShadow: '0 4px 16px rgba(30, 60, 114, 0.4)',
transition: 'all 0.3s ease',
position: 'relative'
}}
>
{/* Enterprise gradient overlay */}
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white to-transparent opacity-0 hover:opacity-10 transition-opacity duration-300"></div>
{isLoading ? (
<div className="flex items-center justify-center relative z-10">
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
<span className="font-bold">Authenticating...</span>
</div>
) : (
<div className="flex items-center justify-center relative z-10">
<Security className="w-4 h-4 mr-2" />
<span className="font-bold">Secure Sign In</span>
</div>
)}
{/* Enterprise security indicator */}
<div className="absolute top-1 right-1">
<div className="w-1.5 h-1.5 bg-green-400 rounded-full animate-pulse"></div>
</div>
</button>
</form>
</div>
{/* Right side - Security Features */}
<div className="flex-1 flex flex-col justify-center">
{renderSecurityBadges()}
</div>
</div>
);
const renderMFAForm = () => (
<div className="w-full space-y-6">
{renderETBLogo()}
<div className="text-center">
<h2 className="text-2xl sm:text-3xl font-bold text-etb-blue-500 mb-2">
Multi-Factor Authentication
</h2>
<p className="text-sm sm:text-base text-gray-600">
Enter your authentication code to continue
</p>
</div>
<form onSubmit={handleMFAVerification} className="space-y-6">
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<VpnKey className="h-5 w-5 text-etb-blue-500" />
</div>
<input
type="text"
placeholder="Enter 6-digit code"
value={formData.mfa_token}
onChange={handleInputChange('mfa_token')}
required
className="etb-input-field pl-10 h-12 sm:h-14 text-center text-lg tracking-widest"
maxLength={6}
/>
</div>
<button
type="submit"
disabled={isLoading}
className="etb-primary-button w-full h-12 sm:h-14"
>
{isLoading ? (
<div className="flex items-center justify-center">
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-white mr-2"></div>
Verifying...
</div>
) : (
'Verify & Continue'
)}
</button>
</form>
</div>
);
const renderMFASetup = () => (
<div className="w-full space-y-6">
{renderETBLogo()}
<div className="text-center">
<h2 className="text-2xl sm:text-3xl font-bold text-etb-blue-500 mb-2">
Setup Multi-Factor Authentication
</h2>
<p className="text-sm sm:text-base text-gray-600">
Secure your account with two-factor authentication
</p>
</div>
{mfaStep === 1 && (
<div className="space-y-6">
<div className="bg-blue-50 border border-blue-200 rounded-xl p-6 text-center">
<Smartphone className="w-12 h-12 text-etb-blue-500 mx-auto mb-4" />
<h3 className="text-lg font-semibold text-etb-blue-500 mb-2">
Ready to Setup MFA?
</h3>
<p className="text-sm text-gray-600 mb-4">
We'll help you set up two-factor authentication for enhanced security.
</p>
<button
onClick={handleMFASetup}
disabled={isLoading}
className="etb-primary-button"
>
{isLoading ? 'Setting up...' : 'Start Setup'}
</button>
</div>
</div>
)}
{mfaStep === 2 && mfaSetupData && (
<div className="space-y-6">
<div className="bg-green-50 border border-green-200 rounded-xl p-6 text-center">
<CheckCircle className="w-12 h-12 text-green-500 mx-auto mb-4" />
<h3 className="text-lg font-semibold text-green-700 mb-2">
Scan QR Code
</h3>
<p className="text-sm text-gray-600 mb-4">
Use your authenticator app to scan this QR code
</p>
<div className="flex justify-center mb-4">
<QRCodeSVG value={mfaSetupData.qr_code_url} size={200} />
</div>
<button
onClick={handleMFASetupComplete}
disabled={isLoading}
className="etb-primary-button"
>
{isLoading ? 'Completing...' : 'Complete Setup'}
</button>
</div>
</div>
)}
</div>
);
return (
<div
className="fixed inset-0 h-screen w-screen overflow-hidden bg-gradient-to-br from-etb-blue-500 via-etb-blue-600 to-blue-700 flex items-center justify-center p-4 sm:p-6 lg:p-8"
style={{
background: 'linear-gradient(135deg, #1e3c72 0%, #2a5298 50%, #667eea 100%)',
minHeight: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '1rem'
}}
>
{/* Background Pattern */}
<div className="absolute inset-0 opacity-10">
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_20%_80%,rgba(120,119,198,0.3)_0%,transparent_50%)]"></div>
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_80%_20%,rgba(255,255,255,0.1)_0%,transparent_50%)]"></div>
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_40%_40%,rgba(120,119,198,0.2)_0%,transparent_50%)]"></div>
</div>
{/* Main Card */}
<div
className={`etb-glass-card w-full max-w-4xl h-[60vh] animate-fade-in relative ${
isAnimating ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-4'
} transition-all duration-1000 ease-out`}
style={{
background: 'linear-gradient(135deg, rgba(15, 23, 42, 0.95) 0%, rgba(30, 41, 59, 0.95) 50%, rgba(51, 65, 85, 0.95) 100%)',
backdropFilter: 'blur(20px)',
border: '2px solid rgba(59, 130, 246, 0.3)',
borderRadius: '24px',
boxShadow: '0 32px 64px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(59, 130, 246, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.1)',
width: '100%',
height: '60vh',
position: 'relative',
display: 'flex',
flexDirection: 'column'
}}
>
{/* Enterprise security border glow */}
<div className="absolute inset-0 rounded-3xl bg-gradient-to-r from-blue-500/20 via-cyan-500/10 to-blue-500/20 opacity-60 pointer-events-none"></div>
{/* Security status indicator */}
<div className="absolute top-4 right-4 z-10">
<div className="flex items-center space-x-2 bg-green-500/20 px-3 py-1.5 rounded-full border border-green-400/30 backdrop-blur-sm">
<div className="w-2 h-2 bg-green-400 rounded-full animate-pulse"></div>
<span className="text-xs font-medium text-green-300">SECURE</span>
</div>
</div>
<div className="flex-1 p-6 sm:p-8 lg:p-10 overflow-hidden">
{!showMFA && renderLoginForm()}
{showMFA && mfaStep === 0 && renderMFAForm()}
{showMFA && mfaStep > 0 && renderMFASetup()}
</div>
</div>
</div>
);
};
export default LoginPage;

View File

@@ -0,0 +1,475 @@
import React, { useState, useEffect, useCallback, useRef } from 'react';
import { useAuth } from '../../contexts/AuthContext';
import { LoginFormData } from '../../types';
import { QRCodeSVG } from 'qrcode.react';
import { useNavigate } from 'react-router-dom';
import ETBLogo from './ETBLogo';
import {
EyeIcon,
EyeSlashIcon,
UserIcon,
LockClosedIcon,
ShieldCheckIcon,
KeyIcon,
ChartBarIcon,
ClockIcon,
CloudIcon,
CheckBadgeIcon,
ExclamationTriangleIcon,
CheckCircleIcon,
DevicePhoneMobileIcon,
} from '@heroicons/react/24/outline';
const LoginPage: React.FC = () => {
const { login, verifyMFA, setupMFA, isLoading, error, mfaRequired, clearError } = useAuth();
const navigate = useNavigate();
// Form states
const [formData, setFormData] = useState<LoginFormData>({
username: '',
password: '',
mfa_token: '',
remember_me: false,
});
// UI states
const [showPassword, setShowPassword] = useState(false);
const [showMFA, setShowMFA] = useState(false);
const [mfaStep, setMfaStep] = useState(0);
const [mfaSetupData, setMfaSetupData] = useState<any>(null);
const [mfaDevices] = useState<any[]>([]);
const [selectedDevice, setSelectedDevice] = useState<string>('');
const [isAnimating, setIsAnimating] = useState(false);
// Security features
const [riskAssessment] = useState<any>(null);
const [hasLoggedDeviceInfo, setHasLoggedDeviceInfo] = useState(false);
const [hasLoggedLocationInfo, setHasLoggedLocationInfo] = useState(false);
const hasInitialized = useRef(false);
const securityFeatures = [
{ icon: ShieldCheckIcon, title: 'Zero Trust', description: 'Never trust, always verify' },
{ icon: KeyIcon, title: 'Multi-Factor Auth', description: 'Enhanced security layers' },
{ icon: ChartBarIcon, title: 'Risk Assessment', description: 'AI-powered threat analysis' },
{ icon: ClockIcon, title: 'Audit Logging', description: 'Complete activity tracking' },
];
// Memoize the device info gathering function
const gatherDeviceInfo = useCallback(async () => {
try {
const deviceInfo = {
userAgent: navigator.userAgent,
platform: navigator.platform,
language: navigator.language,
screenResolution: `${window.screen.width}x${window.screen.height}`,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
cookieEnabled: navigator.cookieEnabled,
onLine: navigator.onLine,
hardwareConcurrency: navigator.hardwareConcurrency,
deviceMemory: (navigator as any).deviceMemory,
};
if (process.env.NODE_ENV === 'development') {
console.log('Device Info:', deviceInfo);
}
sessionStorage.setItem('etb_device_info_logged', 'true');
setHasLoggedDeviceInfo(true);
} catch (error) {
console.error('Failed to gather device info:', error);
}
}, []);
// Memoize the location info gathering function
const gatherLocationInfo = useCallback(async () => {
try {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
const locationInfo = {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
};
if (process.env.NODE_ENV === 'development') {
console.log('Location Info:', locationInfo);
}
sessionStorage.setItem('etb_location_info_logged', 'true');
setHasLoggedLocationInfo(true);
},
(error) => {
if (process.env.NODE_ENV === 'development') {
console.log('Location access denied or failed:', error);
}
sessionStorage.setItem('etb_location_info_logged', 'true');
setHasLoggedLocationInfo(true);
}
);
}
} catch (error) {
console.error('Failed to gather location info:', error);
}
}, []);
useEffect(() => {
if (hasInitialized.current) return;
clearError();
const deviceInfoLogged = sessionStorage.getItem('etb_device_info_logged');
const locationInfoLogged = sessionStorage.getItem('etb_location_info_logged');
if (deviceInfoLogged) {
setHasLoggedDeviceInfo(true);
}
if (locationInfoLogged) {
setHasLoggedLocationInfo(true);
}
if (!deviceInfoLogged) {
gatherDeviceInfo();
}
if (!locationInfoLogged) {
gatherLocationInfo();
}
setIsAnimating(true);
hasInitialized.current = true;
}, [clearError, gatherDeviceInfo, gatherLocationInfo]);
const handleInputChange = (field: keyof LoginFormData) => (
event: React.ChangeEvent<HTMLInputElement>
) => {
setFormData(prev => ({
...prev,
[field]: event.target.value,
}));
};
const handleCheckboxChange = (field: keyof LoginFormData) => (
event: React.ChangeEvent<HTMLInputElement>
) => {
setFormData(prev => ({
...prev,
[field]: event.target.checked,
}));
};
const handleLogin = async (event: React.FormEvent) => {
event.preventDefault();
try {
await login({
username: formData.username,
password: formData.password,
mfa_token: formData.mfa_token,
});
if (mfaRequired) {
setShowMFA(true);
setMfaStep(1);
} else {
navigate('/dashboard');
}
} catch (error) {
console.error('Login failed:', error);
}
};
const handleMFAVerification = async (event: React.FormEvent) => {
event.preventDefault();
try {
await verifyMFA(formData.mfa_token || '', selectedDevice);
navigate('/dashboard');
} catch (error) {
console.error('MFA verification failed:', error);
}
};
const handleMFASetup = async () => {
try {
const deviceName = `Device ${new Date().toLocaleDateString()}`;
const setupData = await setupMFA(deviceName);
setMfaSetupData(setupData);
setMfaStep(2);
} catch (error) {
console.error('MFA setup failed:', error);
}
};
const handleMFASetupComplete = async () => {
try {
await verifyMFA('', mfaSetupData?.device?.id);
navigate('/dashboard');
} catch (error) {
console.error('MFA setup completion failed:', error);
}
};
const renderETBLogo = () => (
<div className="flex justify-center mb-6 sm:mb-8">
<div className="transform scale-90 sm:scale-100 transition-transform duration-300">
<ETBLogo size="medium" showSubtitle={true} variant="horizontal" />
</div>
</div>
);
const renderSecurityBadges = () => (
<div className="mb-6 sm:mb-8">
<h3 className="text-lg sm:text-xl font-semibold text-center mb-4 sm:mb-6 text-etb-blue-500">
Enterprise Security Features
</h3>
<div className="grid grid-cols-2 gap-3 sm:gap-4">
{securityFeatures.map((feature, index) => (
<div
key={index}
className={`etb-security-badge animate-fade-in`}
style={{ animationDelay: `${index * 200}ms` }}
>
<feature.icon className="w-6 h-6 sm:w-7 sm:h-7 text-etb-blue-500 mx-auto mb-2" />
<h4 className="text-xs sm:text-sm font-semibold text-gray-800 mb-1 leading-tight">
{feature.title}
</h4>
<p className="text-xs text-gray-600 leading-tight">
{feature.description}
</p>
</div>
))}
</div>
</div>
);
const renderLoginForm = () => (
<form onSubmit={handleLogin} className="w-full space-y-4 sm:space-y-6">
{renderETBLogo()}
<div className="text-center">
<h1 className="text-2xl sm:text-3xl lg:text-4xl font-bold text-etb-blue-500 mb-2">
Secure Access Portal
</h1>
<p className="text-sm sm:text-base text-gray-600 leading-relaxed">
Enterprise-grade incident management and security platform
</p>
</div>
{error && (
<div className="bg-red-50 border border-red-200 rounded-xl p-4 animate-slide-up">
<div className="flex items-center">
<ExclamationTriangleIcon className="w-5 h-5 text-red-500 mr-3" />
<p className="text-sm font-medium text-red-800">{error}</p>
</div>
</div>
)}
<div className="space-y-4">
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<UserIcon className="h-5 w-5 text-etb-blue-500" />
</div>
<input
type="text"
placeholder="Username"
value={formData.username}
onChange={handleInputChange('username')}
required
autoComplete="username"
className="etb-input-field pl-10 h-12 sm:h-14 text-sm sm:text-base"
/>
</div>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<LockClosedIcon className="h-5 w-5 text-etb-blue-500" />
</div>
<input
type={showPassword ? 'text' : 'password'}
placeholder="Password"
value={formData.password}
onChange={handleInputChange('password')}
required
autoComplete="current-password"
className="etb-input-field pl-10 pr-10 h-12 sm:h-14 text-sm sm:text-base"
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute inset-y-0 right-0 pr-3 flex items-center"
>
{showPassword ? (
<EyeSlashIcon className="h-5 w-5 text-etb-blue-500" />
) : (
<EyeIcon className="h-5 w-5 text-etb-blue-500" />
)}
</button>
</div>
</div>
<div className="flex items-center">
<input
type="checkbox"
id="remember_me"
checked={formData.remember_me}
onChange={handleCheckboxChange('remember_me')}
className="h-4 w-4 text-etb-blue-500 focus:ring-etb-blue-500 border-gray-300 rounded"
/>
<label htmlFor="remember_me" className="ml-2 text-sm text-gray-700">
Remember me for 30 days
</label>
</div>
<button
type="submit"
disabled={isLoading}
className="etb-primary-button w-full h-12 sm:h-14 text-sm sm:text-base font-semibold disabled:opacity-50 disabled:cursor-not-allowed"
>
{isLoading ? (
<div className="flex items-center justify-center">
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-white mr-2"></div>
Signing In...
</div>
) : (
<div className="flex items-center justify-center">
<ShieldCheckIcon className="w-5 h-5 mr-2" />
Sign In
</div>
)}
</button>
{renderSecurityBadges()}
</form>
);
const renderMFAForm = () => (
<div className="w-full space-y-6">
{renderETBLogo()}
<div className="text-center">
<h2 className="text-2xl sm:text-3xl font-bold text-etb-blue-500 mb-2">
Multi-Factor Authentication
</h2>
<p className="text-sm sm:text-base text-gray-600">
Enter your authentication code to continue
</p>
</div>
<form onSubmit={handleMFAVerification} className="space-y-6">
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<KeyIcon className="h-5 w-5 text-etb-blue-500" />
</div>
<input
type="text"
placeholder="Enter 6-digit code"
value={formData.mfa_token}
onChange={handleInputChange('mfa_token')}
required
className="etb-input-field pl-10 h-12 sm:h-14 text-center text-lg tracking-widest"
maxLength={6}
/>
</div>
<button
type="submit"
disabled={isLoading}
className="etb-primary-button w-full h-12 sm:h-14"
>
{isLoading ? (
<div className="flex items-center justify-center">
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-white mr-2"></div>
Verifying...
</div>
) : (
'Verify & Continue'
)}
</button>
</form>
</div>
);
const renderMFASetup = () => (
<div className="w-full space-y-6">
{renderETBLogo()}
<div className="text-center">
<h2 className="text-2xl sm:text-3xl font-bold text-etb-blue-500 mb-2">
Setup Multi-Factor Authentication
</h2>
<p className="text-sm sm:text-base text-gray-600">
Secure your account with two-factor authentication
</p>
</div>
{mfaStep === 1 && (
<div className="space-y-6">
<div className="bg-blue-50 border border-blue-200 rounded-xl p-6 text-center">
<DevicePhoneMobileIcon className="w-12 h-12 text-etb-blue-500 mx-auto mb-4" />
<h3 className="text-lg font-semibold text-etb-blue-500 mb-2">
Ready to Setup MFA?
</h3>
<p className="text-sm text-gray-600 mb-4">
We'll help you set up two-factor authentication for enhanced security.
</p>
<button
onClick={handleMFASetup}
disabled={isLoading}
className="etb-primary-button"
>
{isLoading ? 'Setting up...' : 'Start Setup'}
</button>
</div>
</div>
)}
{mfaStep === 2 && mfaSetupData && (
<div className="space-y-6">
<div className="bg-green-50 border border-green-200 rounded-xl p-6 text-center">
<CheckCircleIcon className="w-12 h-12 text-green-500 mx-auto mb-4" />
<h3 className="text-lg font-semibold text-green-700 mb-2">
Scan QR Code
</h3>
<p className="text-sm text-gray-600 mb-4">
Use your authenticator app to scan this QR code
</p>
<div className="flex justify-center mb-4">
<QRCodeSVG value={mfaSetupData.qr_code_url} size={200} />
</div>
<button
onClick={handleMFASetupComplete}
disabled={isLoading}
className="etb-primary-button"
>
{isLoading ? 'Completing...' : 'Complete Setup'}
</button>
</div>
</div>
)}
</div>
);
return (
<div className="fixed inset-0 h-screen w-screen overflow-hidden bg-gradient-to-br from-etb-blue-500 via-etb-blue-600 to-blue-700 flex items-center justify-center p-4 sm:p-6 lg:p-8">
{/* Background Pattern */}
<div className="absolute inset-0 opacity-10">
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_20%_80%,rgba(120,119,198,0.3)_0%,transparent_50%)]"></div>
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_80%_20%,rgba(255,255,255,0.1)_0%,transparent_50%)]"></div>
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_40%_40%,rgba(120,119,198,0.2)_0%,transparent_50%)]"></div>
</div>
{/* Main Card */}
<div className={`etb-glass-card w-full max-w-md sm:max-w-lg lg:max-w-xl max-h-[90vh] sm:max-h-[85vh] lg:max-h-[80vh] overflow-y-auto animate-fade-in ${
isAnimating ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-4'
} transition-all duration-1000 ease-out`}>
<div className="p-6 sm:p-8 lg:p-10">
{!showMFA && renderLoginForm()}
{showMFA && mfaStep === 0 && renderMFAForm()}
{showMFA && mfaStep > 0 && renderMFASetup()}
</div>
</div>
</div>
);
};
export default LoginPage;

View File

@@ -0,0 +1,250 @@
import React, { createContext, useContext, useReducer, useEffect, ReactNode } from 'react';
import { User, LoginRequest, LoginResponse, ApiError } from '../types';
import apiService from '../services/api';
interface AuthState {
user: User | null;
isAuthenticated: boolean;
isLoading: boolean;
error: string | null;
mfaRequired: boolean;
mfaDevices: any[];
}
interface AuthContextType extends AuthState {
login: (credentials: LoginRequest) => Promise<void>;
logout: () => Promise<void>;
verifyMFA: (token: string, deviceId?: string) => Promise<void>;
setupMFA: (deviceName: string) => Promise<any>;
clearError: () => void;
refreshUser: () => Promise<void>;
}
type AuthAction =
| { type: 'LOGIN_START' }
| { type: 'LOGIN_SUCCESS'; payload: { user: User; mfaRequired: boolean } }
| { type: 'LOGIN_FAILURE'; payload: string }
| { type: 'LOGOUT' }
| { type: 'MFA_VERIFIED'; payload: User }
| { type: 'MFA_SETUP'; payload: any[] }
| { type: 'CLEAR_ERROR' }
| { type: 'SET_LOADING'; payload: boolean }
| { type: 'SET_USER'; payload: User };
const initialState: AuthState = {
user: null,
isAuthenticated: false,
isLoading: false,
error: null,
mfaRequired: false,
mfaDevices: [],
};
const authReducer = (state: AuthState, action: AuthAction): AuthState => {
switch (action.type) {
case 'LOGIN_START':
return {
...state,
isLoading: true,
error: null,
};
case 'LOGIN_SUCCESS':
return {
...state,
isLoading: false,
isAuthenticated: !action.payload.mfaRequired,
user: action.payload.mfaRequired ? null : action.payload.user,
mfaRequired: action.payload.mfaRequired,
error: null,
};
case 'LOGIN_FAILURE':
return {
...state,
isLoading: false,
isAuthenticated: false,
user: null,
mfaRequired: false,
error: action.payload,
};
case 'LOGOUT':
return {
...initialState,
};
case 'MFA_VERIFIED':
return {
...state,
isLoading: false,
isAuthenticated: true,
user: action.payload,
mfaRequired: false,
error: null,
};
case 'MFA_SETUP':
return {
...state,
mfaDevices: action.payload,
};
case 'CLEAR_ERROR':
return {
...state,
error: null,
};
case 'SET_LOADING':
return {
...state,
isLoading: action.payload,
};
case 'SET_USER':
return {
...state,
user: action.payload,
isAuthenticated: true,
};
default:
return state;
}
};
const AuthContext = createContext<AuthContextType | undefined>(undefined);
interface AuthProviderProps {
children: ReactNode;
}
export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
const [state, dispatch] = useReducer(authReducer, initialState);
// Check for existing authentication on mount
useEffect(() => {
const checkAuth = async () => {
const token = apiService.getToken();
if (token) {
try {
dispatch({ type: 'SET_LOADING', payload: true });
const user = await apiService.getCurrentUser();
dispatch({ type: 'SET_USER', payload: user });
} catch (error) {
console.error('Failed to verify existing authentication:', error);
apiService.clearToken();
} finally {
dispatch({ type: 'SET_LOADING', payload: false });
}
}
};
checkAuth();
}, []);
const login = async (credentials: LoginRequest): Promise<void> => {
try {
dispatch({ type: 'LOGIN_START' });
const response: LoginResponse = await apiService.login(credentials);
if (response.user.mfa_enabled && !credentials.mfa_token) {
// MFA is enabled but no token provided
dispatch({
type: 'LOGIN_SUCCESS',
payload: { user: response.user, mfaRequired: true }
});
} else {
// Login successful
dispatch({
type: 'LOGIN_SUCCESS',
payload: { user: response.user, mfaRequired: false }
});
}
} catch (error) {
const apiError = error as ApiError;
dispatch({
type: 'LOGIN_FAILURE',
payload: apiError.message || 'Login failed'
});
throw error;
}
};
const logout = async (): Promise<void> => {
try {
await apiService.logout();
} catch (error) {
console.error('Logout error:', error);
} finally {
dispatch({ type: 'LOGOUT' });
}
};
const verifyMFA = async (token: string, deviceId?: string): Promise<void> => {
try {
dispatch({ type: 'SET_LOADING', payload: true });
const verified = await apiService.verifyMFA(token, deviceId);
if (verified) {
const user = await apiService.getCurrentUser();
dispatch({ type: 'MFA_VERIFIED', payload: user });
} else {
throw new Error('Invalid MFA token');
}
} catch (error) {
const apiError = error as ApiError;
dispatch({
type: 'LOGIN_FAILURE',
payload: apiError.message || 'MFA verification failed'
});
throw error;
}
};
const setupMFA = async (deviceName: string): Promise<any> => {
try {
const setupData = await apiService.setupMFA(deviceName);
return setupData;
} catch (error) {
const apiError = error as ApiError;
dispatch({
type: 'LOGIN_FAILURE',
payload: apiError.message || 'MFA setup failed'
});
throw error;
}
};
const clearError = (): void => {
dispatch({ type: 'CLEAR_ERROR' });
};
const refreshUser = async (): Promise<void> => {
try {
const user = await apiService.getCurrentUser();
dispatch({ type: 'SET_USER', payload: user });
} catch (error) {
console.error('Failed to refresh user:', error);
dispatch({ type: 'LOGOUT' });
}
};
const contextValue: AuthContextType = {
...state,
login,
logout,
verifyMFA,
setupMFA,
clearError,
refreshUser,
};
return (
<AuthContext.Provider value={contextValue}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = (): AuthContextType => {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};

View File

@@ -0,0 +1,49 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
* {
box-sizing: border-box;
}
html, body {
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
}
}
@layer components {
.etb-glass-card {
@apply bg-white backdrop-blur-xl border border-white/30 rounded-2xl shadow-2xl;
box-shadow:
0 25px 50px rgba(0, 0, 0, 0.15),
0 0 0 1px rgba(255, 255, 255, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
}
.etb-primary-button {
@apply bg-gradient-to-r from-etb-blue-500 to-etb-blue-600 text-white font-semibold py-3 px-6 rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 hover:-translate-y-0.5;
}
.etb-input-field {
@apply w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-etb-blue-500 focus:border-etb-blue-500 transition-colors duration-200;
}
.etb-security-badge {
@apply bg-gradient-to-br from-gray-50 to-white border border-gray-200 rounded-xl p-4 text-center transition-all duration-300 hover:-translate-y-1 hover:shadow-lg;
}
}

View File

@@ -0,0 +1,14 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

1
etb-dashboard/src/react-app-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="react-scripts" />

View File

@@ -0,0 +1,15 @@
import { ReportHandler } from 'web-vitals';
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

View File

@@ -0,0 +1,434 @@
import axios from 'axios';
import {
LoginRequest,
LoginResponse,
User,
Incident,
SLADefinition,
SLAInstance,
OnCallRotation,
OnCallAssignment,
MonitoringTarget,
HealthCheck,
SystemMetric,
MetricMeasurement,
Alert,
AuditLog,
RiskAssessment,
DevicePosture,
ComplianceFramework,
KnowledgeArticle,
Postmortem,
Runbook,
WarRoom,
ChatMessage,
Dashboard,
MFADevice,
MFASetupResponse,
ChangePasswordFormData,
CreateIncidentFormData,
FilterOptions,
PaginatedResponse,
ApiError
} from '../types';
// API Configuration
const API_BASE_URL = process.env.REACT_APP_API_URL || process.env.REACT_APP_API_BASE_URL || 'http://localhost:8000';
class ApiService {
private api: any;
private token: string | null = null;
constructor() {
this.api = axios.create({
baseURL: `${API_BASE_URL}/api/v1`,
timeout: 30000,
headers: {
'Content-Type': 'application/json',
},
});
// Request interceptor to add auth token
this.api.interceptors.request.use(
(config: any) => {
if (this.token) {
config.headers.Authorization = `Token ${this.token}`;
}
return config;
},
(error: any) => {
return Promise.reject(error);
}
);
// Response interceptor for error handling
this.api.interceptors.response.use(
(response: any) => {
return response;
},
(error: any) => {
if (error.response?.status === 401) {
this.clearToken();
window.location.href = '/login';
}
return Promise.reject(this.handleError(error));
}
);
// Load token from localStorage on initialization
this.loadToken();
}
private loadToken(): void {
const token = localStorage.getItem('auth_token');
if (token) {
this.setToken(token);
}
}
private handleError(error: any): ApiError {
const apiError: ApiError = {
message: 'An unexpected error occurred',
status: error.response?.status,
};
if (error.response?.data) {
const data = error.response.data as any;
apiError.message = data.message || data.detail || apiError.message;
apiError.code = data.code;
apiError.details = data.errors || data.details;
} else if (error.request) {
apiError.message = 'Network error - please check your connection';
}
return apiError;
}
public setToken(token: string): void {
this.token = token;
localStorage.setItem('auth_token', token);
}
public clearToken(): void {
this.token = null;
localStorage.removeItem('auth_token');
}
public getToken(): string | null {
return this.token;
}
// Authentication endpoints
async login(credentials: LoginRequest): Promise<LoginResponse> {
const response = await this.api.post('/security/api/auth/login/', credentials);
const data = response.data;
if (data.token) {
this.setToken(data.token);
}
return data;
}
async logout(): Promise<void> {
try {
await this.api.post('/security/api/auth/logout/');
} finally {
this.clearToken();
}
}
async getCurrentUser(): Promise<User> {
const response = await this.api.get('/security/api/auth/profile/');
return response.data;
}
async changePassword(data: ChangePasswordFormData): Promise<void> {
await this.api.post('/security/api/auth/change-password/', data);
}
async getMFAStatus(): Promise<{ mfa_enabled: boolean; devices: MFADevice[] }> {
const response = await this.api.get('/security/api/auth/mfa-status/');
return response.data;
}
async setupMFA(deviceName: string): Promise<MFASetupResponse> {
const response = await this.api.post('/security/api/mfa-devices/', {
name: deviceName,
device_type: 'TOTP'
});
return response.data;
}
async verifyMFA(token: string, deviceId?: string): Promise<boolean> {
const response = await this.api.post('/security/api/mfa-devices/verify/', {
token,
device_id: deviceId
});
return response.data.verified;
}
// Incident Management endpoints
async getIncidents(filters?: FilterOptions): Promise<PaginatedResponse<Incident>> {
const response = await this.api.get('/incidents/incidents/', { params: filters });
return response.data;
}
async getIncident(id: string): Promise<Incident> {
const response = await this.api.get(`/incidents/incidents/${id}/`);
return response.data;
}
async createIncident(data: CreateIncidentFormData): Promise<Incident> {
const response = await this.api.post('/incidents/incidents/', data);
return response.data;
}
async updateIncident(id: string, data: Partial<CreateIncidentFormData>): Promise<Incident> {
const response = await this.api.patch(`/incidents/incidents/${id}/`, data);
return response.data;
}
async deleteIncident(id: string): Promise<void> {
await this.api.delete(`/incidents/incidents/${id}/`);
}
// SLA Management endpoints
async getSLADefinitions(): Promise<SLADefinition[]> {
const response = await this.api.get('/sla/sla-definitions/');
return response.data.results || response.data;
}
async getSLAInstances(filters?: FilterOptions): Promise<PaginatedResponse<SLAInstance>> {
const response = await this.api.get('/sla/sla-instances/', { params: filters });
return response.data;
}
// On-Call Management endpoints
async getOnCallRotations(): Promise<OnCallRotation[]> {
const response = await this.api.get('/sla/on-call-rotations/');
return response.data.results || response.data;
}
async getOnCallAssignments(filters?: FilterOptions): Promise<PaginatedResponse<OnCallAssignment>> {
const response = await this.api.get('/sla/on-call-assignments/', { params: filters });
return response.data;
}
async getCurrentOnCall(): Promise<OnCallAssignment | null> {
const response = await this.api.get('/sla/on-call-assignments/current/');
return response.data;
}
// Monitoring endpoints
async getMonitoringTargets(): Promise<MonitoringTarget[]> {
const response = await this.api.get('/monitoring/monitoring-targets/');
return response.data.results || response.data;
}
async getHealthChecks(filters?: FilterOptions): Promise<PaginatedResponse<HealthCheck>> {
const response = await this.api.get('/monitoring/health-checks/', { params: filters });
return response.data;
}
async getSystemMetrics(): Promise<SystemMetric[]> {
const response = await this.api.get('/monitoring/system-metrics/');
return response.data.results || response.data;
}
async getMetricMeasurements(metricId: string, filters?: FilterOptions): Promise<PaginatedResponse<MetricMeasurement>> {
const response = await this.api.get(`/monitoring/metric-measurements/?metric=${metricId}`, { params: filters });
return response.data;
}
async getAlerts(filters?: FilterOptions): Promise<PaginatedResponse<Alert>> {
const response = await this.api.get('/monitoring/alerts/', { params: filters });
return response.data;
}
async acknowledgeAlert(alertId: string): Promise<Alert> {
const response = await this.api.patch(`/monitoring/alerts/${alertId}/`, { status: 'ACKNOWLEDGED' });
return response.data;
}
async resolveAlert(alertId: string): Promise<Alert> {
const response = await this.api.patch(`/monitoring/alerts/${alertId}/`, { status: 'RESOLVED' });
return response.data;
}
// Security endpoints
async getAuditLogs(filters?: FilterOptions): Promise<PaginatedResponse<AuditLog>> {
const response = await this.api.get('/security/api/audit-logs/', { params: filters });
return response.data;
}
async getRiskAssessments(filters?: FilterOptions): Promise<PaginatedResponse<RiskAssessment>> {
const response = await this.api.get('/security/api/risk-assessments/', { params: filters });
return response.data;
}
async performRiskAssessment(): Promise<RiskAssessment> {
const response = await this.api.post('/security/api/zero-trust/assess/');
return response.data;
}
async getDevicePostures(): Promise<DevicePosture[]> {
const response = await this.api.get('/security/api/device-postures/');
return response.data.results || response.data;
}
async getZeroTrustStatus(): Promise<any> {
const response = await this.api.get('/security/api/zero-trust/status/');
return response.data;
}
// Compliance endpoints
async getComplianceFrameworks(): Promise<ComplianceFramework[]> {
const response = await this.api.get('/compliance/frameworks/');
return response.data.results || response.data;
}
// Knowledge Management endpoints
async getKnowledgeArticles(filters?: FilterOptions): Promise<PaginatedResponse<KnowledgeArticle>> {
const response = await this.api.get('/knowledge/knowledge-articles/', { params: filters });
return response.data;
}
async getPostmortems(filters?: FilterOptions): Promise<PaginatedResponse<Postmortem>> {
const response = await this.api.get('/knowledge/postmortems/', { params: filters });
return response.data;
}
// Automation endpoints
async getRunbooks(): Promise<Runbook[]> {
const response = await this.api.get('/automation/runbooks/');
return response.data.results || response.data;
}
async executeRunbook(runbookId: string, incidentId: string): Promise<any> {
const response = await this.api.post(`/automation/runbooks/${runbookId}/execute/`, {
incident_id: incidentId
});
return response.data;
}
// Collaboration endpoints
async getWarRooms(filters?: FilterOptions): Promise<PaginatedResponse<WarRoom>> {
const response = await this.api.get('/collaboration/war-rooms/', { params: filters });
return response.data;
}
async getWarRoomMessages(warRoomId: string, filters?: FilterOptions): Promise<PaginatedResponse<ChatMessage>> {
const response = await this.api.get(`/collaboration/war-rooms/${warRoomId}/messages/`, { params: filters });
return response.data;
}
async sendMessage(warRoomId: string, content: string, messageType: string = 'TEXT'): Promise<ChatMessage> {
const response = await this.api.post(`/collaboration/war-rooms/${warRoomId}/messages/`, {
content,
message_type: messageType
});
return response.data;
}
// Analytics endpoints
async getDashboardData(dashboardId: string): Promise<any> {
const response = await this.api.get(`/analytics/dashboard/${dashboardId}/data/`);
return response.data;
}
async getKPISummary(): Promise<any> {
const response = await this.api.get('/analytics/kpi-metrics/summary/');
return response.data;
}
async getAnomalySummary(): Promise<any> {
const response = await this.api.get('/analytics/anomaly-detections/summary/');
return response.data;
}
async getCostSummary(): Promise<any> {
const response = await this.api.get('/analytics/cost-analyses/summary/');
return response.data;
}
// User Management endpoints
async getUsersWithFilters(filters?: FilterOptions): Promise<PaginatedResponse<User>> {
const response = await this.api.get('/security/api/users/', { params: filters });
return response.data;
}
async getUserById(id: string): Promise<User> {
const response = await this.api.get(`/security/api/users/${id}/`);
return response.data;
}
async updateUser(id: string, data: Partial<User>): Promise<User> {
const response = await this.api.patch(`/security/users/${id}/`, data);
return response.data;
}
// Dashboard endpoints
async getDashboards(): Promise<Dashboard[]> {
const response = await this.api.get('/monitoring/monitoring-dashboards/');
return response.data.results || response.data;
}
async getDashboard(id: string): Promise<Dashboard> {
const response = await this.api.get(`/monitoring/monitoring-dashboards/${id}/`);
return response.data;
}
// Health check endpoints
async getHealthStatus(): Promise<any> {
const response = await this.api.get('/health/');
return response.data;
}
async getSystemStatus(): Promise<any> {
const response = await this.api.get('/monitoring/system-status/');
return response.data;
}
// User Management endpoints (Admin only)
async getUsers(): Promise<User[]> {
const response = await this.api.get('/security/api/users/');
return response.data.results || response.data;
}
async getUser(id: string): Promise<User> {
const response = await this.api.get(`/security/api/users/${id}/`);
return response.data;
}
async updateUserRoles(userId: string, roleIds: string[]): Promise<void> {
await this.api.patch(`/security/api/users/${userId}/update_roles/`, {
role_ids: roleIds
});
}
async updateUserClearance(userId: string, clearanceLevelId: string): Promise<void> {
await this.api.patch(`/security/api/users/${userId}/update_clearance/`, {
clearance_level_id: clearanceLevelId
});
}
async getDashboardPermissions(): Promise<any> {
const response = await this.api.get('/security/api/users/dashboard_permissions/');
return response.data;
}
async getRoles(): Promise<any[]> {
const response = await this.api.get('/security/api/roles/');
return response.data.results || response.data;
}
async getDataClassifications(): Promise<any[]> {
const response = await this.api.get('/security/api/classifications/');
return response.data.results || response.data;
}
}
// Create and export a singleton instance
const apiService = new ApiService();
export default apiService;

View File

@@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

View File

@@ -0,0 +1,496 @@
// Core types for ETB Dashboard
export interface User {
id: string;
username: string;
email: string;
first_name: string;
last_name: string;
mfa_enabled: boolean;
is_superuser: boolean;
is_staff: boolean;
is_active: boolean;
clearance_level: {
name: string;
level: number;
};
roles: Role[];
department?: string;
employee_id?: string;
phone_number?: string;
emergency_contact?: string;
oncall_preferences?: Record<string, any>;
created_at: string;
updated_at: string;
}
export interface Role {
id: string;
name: string;
description: string;
permissions: Permission[];
data_classification_access: DataClassification[];
is_active: boolean;
}
export interface Permission {
id: string;
name: string;
codename: string;
content_type: string;
}
export interface DataClassification {
id: string;
name: string;
level: number;
description: string;
color_code: string;
requires_clearance: boolean;
}
export interface LoginRequest {
username: string;
password: string;
mfa_token?: string;
}
export interface LoginResponse {
token: string;
user: User;
message: string;
}
export interface MFADevice {
id: string;
name: string;
device_type: 'TOTP' | 'HOTP' | 'SMS' | 'EMAIL' | 'HARDWARE';
is_active: boolean;
is_primary: boolean;
last_used?: string;
created_at: string;
}
export interface MFASetupResponse {
device: MFADevice;
qr_code_data: string;
secret_key: string;
}
export interface Incident {
id: string;
title: string;
description: string;
free_text: string;
category?: string;
subcategory?: string;
classification_confidence?: number;
severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL' | 'EMERGENCY';
suggested_severity?: string;
severity_confidence?: number;
priority: 'P1' | 'P2' | 'P3' | 'P4';
status: 'OPEN' | 'IN_PROGRESS' | 'RESOLVED' | 'CLOSED' | 'CANCELLED';
assigned_to?: User;
reporter?: User;
created_at: string;
updated_at: string;
resolved_at?: string;
affected_users: number;
business_impact?: string;
estimated_downtime?: string;
ai_processed: boolean;
automation_enabled: boolean;
is_duplicate: boolean;
data_classification?: DataClassification;
security_clearance_required: boolean;
is_sensitive: boolean;
}
export interface SLADefinition {
id: string;
name: string;
description: string;
sla_type: 'RESPONSE_TIME' | 'RESOLUTION_TIME' | 'ACKNOWLEDGMENT_TIME' | 'FIRST_RESPONSE';
incident_categories: string[];
incident_severities: string[];
incident_priorities: string[];
target_duration_minutes: number;
business_hours_only: boolean;
escalation_enabled: boolean;
is_active: boolean;
}
export interface SLAInstance {
id: string;
sla_definition: SLADefinition;
incident: Incident;
status: 'ACTIVE' | 'MET' | 'BREACHED' | 'CANCELLED';
target_time: string;
started_at: string;
met_at?: string;
breached_at?: string;
escalation_triggered: boolean;
escalation_level: number;
response_time?: string;
resolution_time?: string;
}
export interface OnCallRotation {
id: string;
name: string;
description: string;
rotation_type: 'WEEKLY' | 'DAILY' | 'MONTHLY' | 'CUSTOM';
status: 'ACTIVE' | 'PAUSED' | 'INACTIVE';
team_name: string;
timezone: string;
external_system: 'PAGERDUTY' | 'OPSGENIE' | 'INTERNAL' | 'CUSTOM';
created_at: string;
updated_at: string;
}
export interface OnCallAssignment {
id: string;
rotation: OnCallRotation;
user: User;
start_time: string;
end_time: string;
status: 'SCHEDULED' | 'ACTIVE' | 'COMPLETED' | 'CANCELLED';
handoff_notes?: string;
incidents_handled: number;
response_time_avg?: string;
}
export interface MonitoringTarget {
id: string;
name: string;
description: string;
target_type: 'APPLICATION' | 'DATABASE' | 'CACHE' | 'QUEUE' | 'EXTERNAL_API' | 'SERVICE' | 'INFRASTRUCTURE' | 'MODULE';
endpoint_url?: string;
status: 'ACTIVE' | 'INACTIVE' | 'MAINTENANCE' | 'ERROR';
last_checked?: string;
last_status: 'HEALTHY' | 'WARNING' | 'CRITICAL' | 'UNKNOWN';
related_module?: string;
}
export interface HealthCheck {
id: string;
target: MonitoringTarget;
check_type: 'HTTP' | 'DATABASE' | 'CACHE' | 'QUEUE' | 'CUSTOM' | 'PING' | 'SSL';
status: 'HEALTHY' | 'WARNING' | 'CRITICAL' | 'UNKNOWN';
response_time_ms?: number;
status_code?: number;
error_message?: string;
checked_at: string;
}
export interface SystemMetric {
id: string;
name: string;
description: string;
metric_type: 'PERFORMANCE' | 'BUSINESS' | 'SECURITY' | 'INFRASTRUCTURE' | 'CUSTOM';
category: string;
unit: string;
aggregation_method: 'AVERAGE' | 'SUM' | 'COUNT' | 'MIN' | 'MAX' | 'PERCENTILE_95' | 'PERCENTILE_99';
warning_threshold?: number;
critical_threshold?: number;
is_active: boolean;
related_module?: string;
}
export interface MetricMeasurement {
id: string;
metric: SystemMetric;
value: number;
timestamp: string;
tags: Record<string, any>;
metadata: Record<string, any>;
}
export interface Alert {
id: string;
title: string;
description: string;
severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
status: 'TRIGGERED' | 'ACKNOWLEDGED' | 'RESOLVED' | 'SUPPRESSED';
triggered_value?: number;
threshold_value?: number;
triggered_at: string;
acknowledged_at?: string;
resolved_at?: string;
acknowledged_by?: User;
resolved_by?: User;
}
export interface AuditLog {
id: string;
timestamp: string;
user?: User;
action_type: string;
resource_type?: string;
resource_id?: string;
ip_address?: string;
user_agent?: string;
details: Record<string, any>;
severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
hash_value: string;
}
export interface RiskAssessment {
id: string;
user: User;
assessment_type: string;
device_risk_score: number;
location_risk_score: number;
behavior_risk_score: number;
network_risk_score: number;
time_risk_score: number;
user_risk_score: number;
overall_risk_score: number;
risk_level: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
access_decision: 'ALLOW' | 'DENY' | 'STEP_UP' | 'REVIEW';
decision_reason: string;
assessed_at: string;
}
export interface DevicePosture {
id: string;
user: User;
device_id: string;
device_name?: string;
device_type: 'DESKTOP' | 'LAPTOP' | 'MOBILE' | 'TABLET' | 'SERVER' | 'IOT' | 'UNKNOWN';
os_type: 'WINDOWS' | 'MACOS' | 'LINUX' | 'ANDROID' | 'IOS' | 'UNKNOWN';
is_managed: boolean;
has_antivirus: boolean;
firewall_enabled: boolean;
encryption_enabled: boolean;
risk_score: number;
is_compliant: boolean;
is_trusted: boolean;
trust_level: 'HIGH' | 'MEDIUM' | 'LOW' | 'UNTRUSTED';
last_seen: string;
}
export interface ComplianceFramework {
id: string;
name: string;
framework_type: 'GDPR' | 'HIPAA' | 'SOX' | 'ISO27001' | 'PCI_DSS' | 'NIST' | 'CUSTOM';
version: string;
description: string;
applicable_regions: string[];
industry_sectors: string[];
compliance_requirements: string[];
is_active: boolean;
effective_date: string;
review_date?: string;
}
export interface KnowledgeArticle {
id: string;
title: string;
content: string;
category: string;
tags: string[];
author: User;
is_published: boolean;
view_count: number;
rating: number;
created_at: string;
updated_at: string;
}
export interface Postmortem {
id: string;
incident: Incident;
title: string;
summary: string;
timeline: string;
root_cause: string;
impact: string;
lessons_learned: string;
action_items: string[];
author: User;
participants: User[];
is_published: boolean;
completion_percentage: number;
created_at: string;
updated_at: string;
}
export interface Runbook {
id: string;
name: string;
description: string;
category: string;
severity_levels: string[];
steps: RunbookStep[];
is_active: boolean;
execution_count: number;
success_rate: number;
created_by: User;
created_at: string;
updated_at: string;
}
export interface RunbookStep {
id: string;
order: number;
title: string;
description: string;
action_type: 'MANUAL' | 'AUTOMATED' | 'CONDITIONAL';
command?: string;
expected_result?: string;
timeout_seconds?: number;
retry_count?: number;
is_required: boolean;
}
export interface WarRoom {
id: string;
name: string;
description: string;
incident: Incident;
status: 'ACTIVE' | 'RESOLVED' | 'CLOSED';
participants: User[];
created_by: User;
created_at: string;
updated_at: string;
}
export interface ChatMessage {
id: string;
war_room: WarRoom;
user: User;
content: string;
message_type: 'TEXT' | 'SYSTEM' | 'COMMAND' | 'FILE';
is_edited: boolean;
edited_at?: string;
reactions: MessageReaction[];
created_at: string;
}
export interface MessageReaction {
id: string;
message: ChatMessage;
user: User;
emoji: string;
created_at: string;
}
export interface DashboardWidget {
id: string;
type: 'CHART' | 'TABLE' | 'METRIC' | 'ALERT' | 'CUSTOM';
title: string;
config: Record<string, any>;
position: {
x: number;
y: number;
width: number;
height: number;
};
}
export interface Dashboard {
id: string;
name: string;
description: string;
dashboard_type: 'SYSTEM_OVERVIEW' | 'PERFORMANCE' | 'BUSINESS_METRICS' | 'SECURITY' | 'INFRASTRUCTURE' | 'CUSTOM';
widgets: DashboardWidget[];
is_public: boolean;
allowed_users: User[];
allowed_roles: string[];
auto_refresh_enabled: boolean;
refresh_interval_seconds: number;
is_active: boolean;
created_by: User;
created_at: string;
updated_at: string;
}
// API Response types
export interface ApiResponse<T> {
data: T;
message?: string;
status: 'success' | 'error';
errors?: string[];
}
export interface PaginatedResponse<T> {
results: T[];
count: number;
next?: string;
previous?: string;
}
// Form types
export interface LoginFormData {
username: string;
password: string;
mfa_token?: string;
remember_me?: boolean;
}
export interface ChangePasswordFormData {
current_password: string;
new_password: string;
confirm_password: string;
}
export interface CreateIncidentFormData {
title: string;
description: string;
category?: string;
severity: string;
priority: string;
assigned_to?: string;
business_impact?: string;
affected_users: number;
}
// Navigation types
export interface NavigationItem {
id: string;
label: string;
icon: string;
path: string;
children?: NavigationItem[];
permissions?: string[];
clearance_level?: number;
}
// Theme types
export interface ThemeConfig {
mode: 'light' | 'dark';
primaryColor: string;
secondaryColor: string;
fontFamily: string;
}
// Error types
export interface ApiError {
message: string;
code?: string;
details?: Record<string, any>;
status?: number;
}
// Loading states
export interface LoadingState {
isLoading: boolean;
error?: string;
data?: any;
}
// Filter types
export interface FilterOptions {
search?: string;
category?: string;
severity?: string;
status?: string;
assigned_to?: string;
date_from?: string;
date_to?: string;
sort_by?: string;
sort_order?: 'asc' | 'desc';
page?: number;
page_size?: number;
}

View File

@@ -0,0 +1,63 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./src/**/*.{js,jsx,ts,tsx}",
],
theme: {
extend: {
colors: {
'etb-blue': {
50: '#f0f4ff',
100: '#e0e9ff',
200: '#c7d7ff',
300: '#a5b8ff',
400: '#8190ff',
500: '#1e3c72',
600: '#2a5298',
700: '#1a2f5c',
800: '#142547',
900: '#0f1b33',
},
'etb-gray': {
50: '#f8fafc',
100: '#f1f5f9',
200: '#e2e8f0',
300: '#cbd5e1',
400: '#94a3b8',
500: '#64748b',
600: '#475569',
700: '#334155',
800: '#1e293b',
900: '#0f172a',
}
},
fontFamily: {
'inter': ['Inter', 'system-ui', 'sans-serif'],
},
animation: {
'fade-in': 'fadeIn 1s ease-in-out',
'slide-up': 'slideUp 0.8s ease-out',
'pulse-glow': 'pulseGlow 2s infinite',
},
keyframes: {
fadeIn: {
'0%': { opacity: '0', transform: 'translateY(20px)' },
'100%': { opacity: '1', transform: 'translateY(0)' },
},
slideUp: {
'0%': { opacity: '0', transform: 'translateY(30px)' },
'100%': { opacity: '1', transform: 'translateY(0)' },
},
pulseGlow: {
'0%': { boxShadow: '0 0 0 0 rgba(30, 60, 114, 0.4)' },
'70%': { boxShadow: '0 0 0 10px rgba(30, 60, 114, 0)' },
'100%': { boxShadow: '0 0 0 0 rgba(30, 60, 114, 0)' },
},
},
backdropBlur: {
'xs': '2px',
},
},
},
plugins: [],
}

View File

@@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}