Spaces:
Paused
Paused
| import { useEffect, useState } from 'react'; | |
| import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; | |
| import { Badge } from '@/components/ui/badge'; | |
| import { Button } from '@/components/ui/button'; | |
| import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; | |
| import { | |
| Shield, AlertTriangle, Activity, TrendingUp, TrendingDown, | |
| ExternalLink, RefreshCw, Clock, Globe, Server, Users, | |
| CheckCircle, XCircle, AlertCircle, Eye, Lock, Wifi, Zap, | |
| Info, Code, Database | |
| } from 'lucide-react'; | |
| import { useSecuritySimulation, SecurityEvent } from '@/hooks/useSecuritySimulation'; | |
| import { WidgetSchemaViewer } from './WidgetSchemaViewer'; | |
| import { widgetRegistry } from '@/lib/widgetRegistry'; | |
| interface WidgetDetailModalProps { | |
| isOpen: boolean; | |
| onClose: () => void; | |
| widget: { | |
| id: string; | |
| name: string; | |
| category: string; | |
| icon: React.ElementType; | |
| component: React.ReactNode; | |
| } | null; | |
| } | |
| // Widget descriptions | |
| const widgetDescriptions: Record<string, string> = { | |
| 'soc-dashboard': 'Security Operations Center dashboard viser real-time oversigt over alle sikkerhedshændelser, trusler og systemstatus på tværs af infrastrukturen.', | |
| 'threat-map': 'Geografisk visualisering af aktive cyberangreb og trusselsaktivitet i realtid. Viser angrebskilder, mål og angrebstyper.', | |
| 'malware-detection': 'Real-time malware detektion og klassificering. Overvåger alle endpoints og netværkstrafik for kendte og ukendte trusler.', | |
| 'intrusion-detection': 'Intrusion Detection System (IDS) overvåger netværkstrafik for mistænkelig aktivitet og kendte angrebsmønstre.', | |
| 'ddos-monitor': 'DDoS beskyttelse og monitoring. Overvåger båndbredde, pakkerater og anvender automatisk mitigation ved angreb.', | |
| 'phishing-detector': 'Email sikkerhed med avanceret phishing detektion, URL analyse og attachment sandboxing.', | |
| 'dark-web-monitor': 'Overvåger dark web forums, markedspladser og paste sites for lækkede credentials og virksomhedsdata.', | |
| 'zero-day-alert': 'Overvåger for zero-day sårbarheder og nye CVEs der påvirker jeres infrastruktur.', | |
| 'threat-level': 'Samlet trusselsniveau baseret på global threat intelligence, interne alerts og systemstatus.', | |
| 'firewall': 'Next-generation firewall status med deep packet inspection, application control og intrusion prevention.', | |
| 'siem-alerts': 'Security Information and Event Management - korrelerer logs og events på tværs af hele infrastrukturen.', | |
| 'waf-status': 'Web Application Firewall beskytter web apps mod OWASP Top 10 og avancerede angreb.', | |
| 'vpn-status': 'VPN gateway status og connected users. Overvåger tunnels, performance og sikkerhed.', | |
| 'security-alerts': 'Samlet oversigt over alle kritiske sikkerhedsadvarsler der kræver øjeblikkelig handling.', | |
| }; | |
| const widgetActions: Record<string, string[]> = { | |
| 'soc-dashboard': ['Eskalér til Tier 2', 'Bloker IP', 'Kør forensic scan'], | |
| 'threat-map': ['Aktivér GeoBlock', 'Rate Limiting', 'Kontakt ISP'], | |
| 'malware-detection': ['Isolér Endpoint', 'Kør Full Scan', 'Opdater Signatures'], | |
| 'intrusion-detection': ['Bloker Kilde IP', 'Opdater IDS Regler', 'Eksporter PCAP'], | |
| 'ddos-monitor': ['Aktivér Scrubbing', 'Whitelist IPs', 'Kontakt Upstream'], | |
| 'phishing-detector': ['Rapporter til CERT', 'Block Domain', 'Notify User'], | |
| 'dark-web-monitor': ['Force Password Reset', 'Notify Users', 'Enable MFA'], | |
| 'firewall': ['View Blocked IPs', 'Update Rules', 'Export Logs'], | |
| 'vpn-status': ['Disconnect User', 'View Sessions', 'Rotate Keys'], | |
| }; | |
| // Type for metrics | |
| type MetricStatus = 'good' | 'warning' | 'critical'; | |
| type MetricTrend = 'up' | 'down' | 'stable'; | |
| interface DynamicMetric { | |
| label: string; | |
| value: string; | |
| status?: MetricStatus; | |
| trend?: MetricTrend; | |
| } | |
| export const WidgetDetailModal = ({ isOpen, onClose, widget }: WidgetDetailModalProps) => { | |
| const simulation = useSecuritySimulation(1500); | |
| const [pulseMetric, setPulseMetric] = useState<number | null>(null); | |
| // Pulse effect when metrics update | |
| useEffect(() => { | |
| if (isOpen) { | |
| const randomMetric = Math.floor(Math.random() * 4); | |
| setPulseMetric(randomMetric); | |
| const timeout = setTimeout(() => setPulseMetric(null), 500); | |
| return () => clearTimeout(timeout); | |
| } | |
| }, [simulation.socMetrics.activeIncidents, simulation.threatData.blockedAttacks, isOpen]); | |
| if (!widget) return null; | |
| const Icon = widget.icon; | |
| const description = widgetDescriptions[widget.id] || 'Dette sikkerhedsmodul overvåger og beskytter din infrastruktur i realtid.'; | |
| const actions = widgetActions[widget.id] || ['View Details', 'Configure', 'Export Report']; | |
| // Helper to get status | |
| const getStatus = (condition: boolean, ifTrue: MetricStatus, ifFalse: MetricStatus): MetricStatus => | |
| condition ? ifTrue : ifFalse; | |
| // Generate dynamic metrics based on widget type | |
| const getDynamicMetrics = (): DynamicMetric[] => { | |
| switch (widget.id) { | |
| case 'soc-dashboard': | |
| return [ | |
| { label: 'Aktive Incidents', value: String(simulation.socMetrics.activeIncidents), status: getStatus(simulation.socMetrics.activeIncidents > 3, 'critical', 'warning') }, | |
| { label: 'Løste (24h)', value: String(simulation.socMetrics.resolvedToday), status: 'good' as MetricStatus }, | |
| { label: 'MTTD', value: `${simulation.socMetrics.mttd} min`, trend: 'down' as MetricTrend }, | |
| { label: 'MTTR', value: `${simulation.socMetrics.mttr} min`, trend: 'down' as MetricTrend }, | |
| ]; | |
| case 'threat-map': | |
| return [ | |
| { label: 'Live Angreb', value: String(simulation.liveAttacks), status: 'warning' as MetricStatus }, | |
| { label: 'Blokerede IPs', value: simulation.networkMetrics.blockedIPs.toLocaleString(), status: 'good' as MetricStatus }, | |
| { label: 'Top Kilde', value: 'CN (34%)', status: 'warning' as MetricStatus }, | |
| { label: 'Traffic', value: `${simulation.networkMetrics.inboundGbps} Gbps`, trend: 'stable' as MetricTrend }, | |
| ]; | |
| case 'malware-detection': | |
| return [ | |
| { label: 'Detekteret (24h)', value: String(simulation.malwareMetrics.detected24h), status: 'warning' as MetricStatus }, | |
| { label: 'Ransomware', value: String(simulation.malwareMetrics.ransomware), status: getStatus(simulation.malwareMetrics.ransomware > 2, 'critical', 'warning') }, | |
| { label: 'Trojans', value: String(simulation.malwareMetrics.trojans), status: 'warning' as MetricStatus }, | |
| { label: 'Quarantined', value: String(simulation.malwareMetrics.quarantined), status: 'good' as MetricStatus }, | |
| ]; | |
| case 'firewall': | |
| return [ | |
| { label: 'Status', value: simulation.firewallMetrics.status.toUpperCase(), status: 'good' as MetricStatus }, | |
| { label: 'Blocked (24h)', value: simulation.firewallMetrics.blocked24h.toLocaleString(), status: 'good' as MetricStatus }, | |
| { label: 'Allowed', value: simulation.firewallMetrics.allowed24h.toLocaleString(), trend: 'stable' as MetricTrend }, | |
| { label: 'Rules', value: simulation.firewallMetrics.rulesActive.toLocaleString(), status: 'good' as MetricStatus }, | |
| ]; | |
| case 'ddos-monitor': { | |
| const ddosStatus: MetricStatus = simulation.networkMetrics.ddosStatus === 'normal' ? 'good' : simulation.networkMetrics.ddosStatus === 'elevated' ? 'warning' : 'critical'; | |
| return [ | |
| { label: 'Status', value: simulation.networkMetrics.ddosStatus.toUpperCase(), status: ddosStatus }, | |
| { label: 'Inbound', value: `${simulation.networkMetrics.inboundGbps} Gbps`, trend: 'stable' as MetricTrend }, | |
| { label: 'Packets/sec', value: `${(simulation.networkMetrics.packetsPerSec / 1000000).toFixed(2)}M`, trend: 'stable' as MetricTrend }, | |
| { label: 'Connections', value: simulation.networkMetrics.activeConnections.toLocaleString(), trend: 'up' as MetricTrend }, | |
| ]; | |
| } | |
| case 'vpn-status': | |
| return [ | |
| { label: 'Status', value: 'CONNECTED', status: 'good' as MetricStatus }, | |
| { label: 'Users Online', value: String(simulation.vpnUsers), trend: 'stable' as MetricTrend }, | |
| { label: 'Bandwidth', value: `${simulation.networkMetrics.outboundGbps} Gbps`, trend: 'up' as MetricTrend }, | |
| { label: 'Tunnels', value: '12', status: 'good' as MetricStatus }, | |
| ]; | |
| case 'threat-level': { | |
| const threatStatus: MetricStatus = simulation.threatData.level === 'LOW' ? 'good' : simulation.threatData.level === 'MEDIUM' ? 'warning' : 'critical'; | |
| const threatTrend: MetricTrend = simulation.threatData.score > 50 ? 'up' : 'down'; | |
| return [ | |
| { label: 'Threat Level', value: simulation.threatData.level, status: threatStatus }, | |
| { label: 'Risk Score', value: `${simulation.threatData.score}/100`, trend: threatTrend }, | |
| { label: 'Active Threats', value: String(simulation.threatData.activeThreats), status: getStatus(simulation.threatData.activeThreats > 2, 'warning', 'good') }, | |
| { label: 'Blocked', value: simulation.threatData.blockedAttacks.toLocaleString(), status: 'good' as MetricStatus }, | |
| ]; | |
| } | |
| case 'security-alerts': | |
| return [ | |
| { label: 'Active Alerts', value: String(simulation.socMetrics.activeIncidents + simulation.socMetrics.criticalAlerts), status: 'critical' as MetricStatus }, | |
| { label: 'Critical', value: String(simulation.socMetrics.criticalAlerts), status: 'critical' as MetricStatus }, | |
| { label: 'Warning', value: String(simulation.socMetrics.warningAlerts), status: 'warning' as MetricStatus }, | |
| { label: 'Info', value: String(simulation.socMetrics.infoAlerts), status: 'good' as MetricStatus }, | |
| ]; | |
| default: | |
| return [ | |
| { label: 'Status', value: 'ACTIVE', status: 'good' as MetricStatus }, | |
| { label: 'Last Check', value: '< 1 min', trend: 'stable' as MetricTrend }, | |
| { label: 'Health', value: `${simulation.complianceScore}%`, status: 'good' as MetricStatus }, | |
| { label: 'Uptime', value: '99.9%', status: 'good' as MetricStatus }, | |
| ]; | |
| } | |
| }; | |
| const metrics = getDynamicMetrics(); | |
| const getStatusColor = (status?: MetricStatus) => { | |
| switch (status) { | |
| case 'good': return 'text-green-400'; | |
| case 'warning': return 'text-yellow-400'; | |
| case 'critical': return 'text-red-400'; | |
| default: return 'text-primary'; | |
| } | |
| }; | |
| const getSeverityBadge = (severity: 'info' | 'warning' | 'critical') => { | |
| switch (severity) { | |
| case 'critical': return <Badge variant="destructive" className="text-[8px] px-1 py-0 animate-pulse">CRITICAL</Badge>; | |
| case 'warning': return <Badge variant="secondary" className="text-[8px] px-1 py-0 bg-yellow-500/20 text-yellow-400">WARNING</Badge>; | |
| default: return <Badge variant="secondary" className="text-[8px] px-1 py-0 bg-blue-500/20 text-blue-400">INFO</Badge>; | |
| } | |
| }; | |
| const getTrendIcon = (trend?: 'up' | 'down' | 'stable') => { | |
| switch (trend) { | |
| case 'up': return <TrendingUp className="w-3 h-3 text-green-400" />; | |
| case 'down': return <TrendingDown className="w-3 h-3 text-red-400" />; | |
| default: return <Activity className="w-3 h-3 text-muted-foreground" />; | |
| } | |
| }; | |
| return ( | |
| <Dialog open={isOpen} onOpenChange={onClose}> | |
| <DialogContent className="max-w-2xl bg-background/95 backdrop-blur-xl border-primary/30"> | |
| <DialogHeader> | |
| <DialogTitle className="flex items-center gap-3 font-mono text-primary"> | |
| <div className="p-2 bg-primary/10 rounded-lg relative"> | |
| <Icon className="w-5 h-5" /> | |
| <div className="absolute -top-1 -right-1 w-2 h-2 bg-green-500 rounded-full animate-pulse" /> | |
| </div> | |
| <div className="flex-1"> | |
| <span className="text-lg">{widget.name}</span> | |
| <Badge variant="outline" className="ml-3 text-[10px]">{widget.category.toUpperCase()}</Badge> | |
| </div> | |
| <div className="flex items-center gap-2 text-xs text-green-400"> | |
| <Zap className="w-3 h-3" /> | |
| <span className="font-mono">LIVE</span> | |
| </div> | |
| </DialogTitle> | |
| </DialogHeader> | |
| {/* Description */} | |
| <p className="text-sm text-muted-foreground leading-relaxed pt-2"> | |
| {description} | |
| </p> | |
| <Tabs defaultValue="live" className="w-full"> | |
| <TabsList className="grid w-full grid-cols-2 bg-secondary/30"> | |
| <TabsTrigger value="live" className="font-mono text-xs gap-2"> | |
| <Zap className="w-3 h-3" /> | |
| LIVE DATA | |
| </TabsTrigger> | |
| <TabsTrigger value="schema" className="font-mono text-xs gap-2"> | |
| <Code className="w-3 h-3" /> | |
| SCHEMA | |
| </TabsTrigger> | |
| </TabsList> | |
| <TabsContent value="live" className="space-y-6 pt-4"> | |
| {/* Widget Preview */} | |
| <div className="bg-secondary/20 rounded-lg p-4 border border-border/50 relative overflow-hidden"> | |
| <div className="absolute top-2 right-2 flex items-center gap-1 text-[10px] text-green-400 font-mono"> | |
| <div className="w-1.5 h-1.5 bg-green-400 rounded-full animate-pulse" /> | |
| REAL-TIME | |
| </div> | |
| <div className="h-32"> | |
| {widget.component} | |
| </div> | |
| </div> | |
| {/* Metrics Grid - Live updating */} | |
| <div> | |
| <h4 className="text-xs font-mono text-muted-foreground mb-3 flex items-center gap-2"> | |
| <Activity className="w-3 h-3" /> | |
| KEY METRICS | |
| <span className="text-green-400 text-[10px] ml-auto">● Live</span> | |
| </h4> | |
| <div className="grid grid-cols-2 sm:grid-cols-4 gap-3"> | |
| {metrics.map((metric, i) => ( | |
| <div | |
| key={i} | |
| className={`bg-secondary/30 rounded-lg p-3 border transition-all duration-300 ${ | |
| pulseMetric === i ? 'border-primary/50 shadow-[0_0_10px_rgba(var(--primary),0.3)]' : 'border-border/30' | |
| }`} | |
| > | |
| <div className="text-[10px] text-muted-foreground mb-1">{metric.label}</div> | |
| <div className="flex items-center justify-between"> | |
| <span className={`text-lg font-mono transition-all duration-300 ${getStatusColor(metric.status)}`}> | |
| {metric.value} | |
| </span> | |
| {metric.trend && getTrendIcon(metric.trend)} | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| {/* Recent Events - Live feed */} | |
| <div> | |
| <h4 className="text-xs font-mono text-muted-foreground mb-3 flex items-center gap-2"> | |
| <Clock className="w-3 h-3" /> | |
| LIVE EVENT FEED | |
| <span className="text-green-400 text-[10px] ml-auto">● Live</span> | |
| </h4> | |
| <div className="space-y-2 max-h-40 overflow-y-auto"> | |
| {simulation.events.slice(0, 5).map((event, i) => ( | |
| <div | |
| key={event.id} | |
| className={`flex items-center gap-3 bg-secondary/20 rounded-lg p-2 border border-border/30 transition-all duration-500 ${ | |
| i === 0 ? 'animate-pulse border-primary/30' : '' | |
| }`} | |
| > | |
| <span className="text-[10px] font-mono text-primary/70 w-16">{event.time}</span> | |
| <span className="flex-1 text-xs text-muted-foreground truncate">{event.event}</span> | |
| {getSeverityBadge(event.severity)} | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| {/* Quick Actions */} | |
| <div> | |
| <h4 className="text-xs font-mono text-muted-foreground mb-3 flex items-center gap-2"> | |
| <Wifi className="w-3 h-3" /> | |
| QUICK ACTIONS | |
| </h4> | |
| <div className="flex flex-wrap gap-2"> | |
| {actions.map((action, i) => ( | |
| <Button key={i} variant="outline" size="sm" className="text-xs font-mono gap-2 hover:bg-primary/10 hover:border-primary/50 transition-colors"> | |
| {action} | |
| <ExternalLink className="w-3 h-3" /> | |
| </Button> | |
| ))} | |
| <Button variant="ghost" size="sm" className="text-xs font-mono gap-2 text-green-400 hover:text-green-300"> | |
| <RefreshCw className="w-3 h-3 animate-spin" style={{ animationDuration: '3s' }} /> | |
| Auto-Refresh ON | |
| </Button> | |
| </div> | |
| </div> | |
| </TabsContent> | |
| <TabsContent value="schema" className="pt-4"> | |
| {/* Widget Schema / Metadata */} | |
| <div className="space-y-4"> | |
| <div className="flex items-center gap-2 text-xs text-muted-foreground mb-2"> | |
| <Database className="w-3 h-3" /> | |
| <span>Widget metadata definerer hvad denne widget kan og kræver i økosystemet</span> | |
| </div> | |
| {widgetRegistry.get(widget.id) ? ( | |
| <WidgetSchemaViewer widgetId={widget.id} /> | |
| ) : ( | |
| <div className="p-6 border border-dashed border-border/50 rounded-lg text-center"> | |
| <Info className="w-8 h-8 text-muted-foreground mx-auto mb-3" /> | |
| <p className="text-sm text-muted-foreground"> | |
| Schema metadata er endnu ikke defineret for denne widget. | |
| </p> | |
| <p className="text-xs text-muted-foreground/70 mt-2"> | |
| Tilføj widget til registret for at aktivere økosystem-integration. | |
| </p> | |
| </div> | |
| )} | |
| {widgetRegistry.get(widget.id) && ( | |
| <div className="mt-4 p-3 bg-primary/5 rounded-lg border border-primary/20"> | |
| <h5 className="text-xs font-mono text-primary mb-2 flex items-center gap-2"> | |
| <Globe className="w-3 h-3" /> | |
| KOMPATIBLE WIDGETS | |
| </h5> | |
| <div className="flex flex-wrap gap-2"> | |
| {widgetRegistry.findCompatible(widget.id).slice(0, 5).map(compatible => ( | |
| <Badge key={compatible.id} variant="secondary" className="text-[10px]"> | |
| {compatible.name} | |
| </Badge> | |
| ))} | |
| {widgetRegistry.findCompatible(widget.id).length === 0 && ( | |
| <span className="text-xs text-muted-foreground">Ingen kompatible widgets fundet</span> | |
| )} | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| </TabsContent> | |
| </Tabs> | |
| </DialogContent> | |
| </Dialog> | |
| ); | |
| }; | |
| export default WidgetDetailModal; | |