larxius commited on
Commit
2c2c3f3
·
verified ·
1 Parent(s): ddaaa82

Update frontend/src/pages/LogsAndThreats.jsx

Browse files
Files changed (1) hide show
  1. frontend/src/pages/LogsAndThreats.jsx +266 -24
frontend/src/pages/LogsAndThreats.jsx CHANGED
@@ -1,7 +1,65 @@
1
  import React, { useState, useEffect } from 'react';
2
  import { Link } from 'react-router-dom';
3
  import { useAuth } from '../components/AuthContext';
4
- import { ShieldAlert, Search, Download, RefreshCw, List, ArrowLeft, Filter } from 'lucide-react';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
  const LogsAndThreats = () => {
7
  const { user, loading: authLoading } = useAuth();
@@ -9,6 +67,9 @@ const LogsAndThreats = () => {
9
  const [auditLogs, setAuditLogs] = useState([]);
10
  const [loading, setLoading] = useState(true);
11
 
 
 
 
12
  // Dedicated Full Page Audit Logs State
13
  const [isFullLogsView, setIsFullLogsView] = useState(false);
14
  const [allLogs, setAllLogs] = useState([]);
@@ -77,7 +138,10 @@ const LogsAndThreats = () => {
77
  setCurrentPage(1);
78
  }, [searchQuery, actionCategory, dateFilter]);
79
 
80
- const openFullLogsView = () => {
 
 
 
81
  setIsFullLogsView(true);
82
  fetchAllAuditLogs();
83
  };
@@ -92,8 +156,24 @@ const LogsAndThreats = () => {
92
  setDateFilter('all');
93
  };
94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  const filteredAllLogs = allLogs.filter(log => {
96
- // Search query filter
97
  if (searchQuery) {
98
  const q = searchQuery.toLowerCase();
99
  const matchQuery = (
@@ -105,7 +185,6 @@ const LogsAndThreats = () => {
105
  if (!matchQuery) return false;
106
  }
107
 
108
- // Action Category filter
109
  if (actionCategory !== 'all') {
110
  const act = (log.action || '').toLowerCase();
111
  if (actionCategory === 'logins' && !act.includes('log')) return false;
@@ -114,7 +193,6 @@ const LogsAndThreats = () => {
114
  if (actionCategory === 'settings' && !act.includes('setting') && !act.includes('quota') && !act.includes('tier') && !act.includes('config')) return false;
115
  }
116
 
117
- // Date filter
118
  if (dateFilter !== 'all' && log.timestamp) {
119
  const logDate = new Date(log.timestamp);
120
  const now = new Date();
@@ -157,7 +235,6 @@ const LogsAndThreats = () => {
157
  });
158
  };
159
 
160
- // Pagination calculation
161
  const sortedLogs = getSortedLogs();
162
  const totalEntries = sortedLogs.length;
163
  const totalPages = Math.ceil(totalEntries / pageSize) || 1;
@@ -215,6 +292,15 @@ const LogsAndThreats = () => {
215
  );
216
  }
217
 
 
 
 
 
 
 
 
 
 
218
  // Full Page View Mode
219
  if (isFullLogsView) {
220
  return (
@@ -262,7 +348,6 @@ const LogsAndThreats = () => {
262
  {/* Filter and Search Bar */}
263
  <div className="mb-md bg-surface-container-lowest border border-outline-variant/70 rounded-xl p-4 flex flex-col md:flex-row items-center justify-between gap-3 shadow-2xs">
264
  <div className="flex flex-wrap items-center gap-3 w-full md:w-auto flex-1">
265
- {/* Search Input */}
266
  <div className="relative w-full sm:w-64">
267
  <Search className="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-on-surface-variant" />
268
  <input
@@ -274,7 +359,6 @@ const LogsAndThreats = () => {
274
  />
275
  </div>
276
 
277
- {/* Action Category Dropdown */}
278
  <div className="flex items-center gap-1.5 w-full sm:w-auto">
279
  <span className="text-[11px] font-bold text-on-surface-variant uppercase tracking-wider shrink-0">Type:</span>
280
  <select
@@ -290,7 +374,6 @@ const LogsAndThreats = () => {
290
  </select>
291
  </div>
292
 
293
- {/* Date Range Dropdown */}
294
  <div className="flex items-center gap-1.5 w-full sm:w-auto">
295
  <span className="text-[11px] font-bold text-on-surface-variant uppercase tracking-wider shrink-0">Time:</span>
296
  <select
@@ -305,7 +388,6 @@ const LogsAndThreats = () => {
305
  </select>
306
  </div>
307
 
308
- {/* Clear Filters Button */}
309
  {(searchQuery || actionCategory !== 'all' || dateFilter !== 'all') && (
310
  <button
311
  onClick={clearFilters}
@@ -330,7 +412,7 @@ const LogsAndThreats = () => {
330
  <table className="w-full text-left text-[13.5px]">
331
  <thead className="bg-surface-container-high/60 text-on-surface-variant text-[11px] uppercase tracking-wider select-none border-b border-outline-variant/70">
332
  <tr>
333
- {['Timestamp', 'User', 'Action', 'Target'].map((h, i) => (
334
  <th
335
  key={h}
336
  onClick={() => handleLogSort(h)}
@@ -439,7 +521,7 @@ const LogsAndThreats = () => {
439
 
440
  // Dashboard Overview Mode
441
  return (
442
- <div className="w-full text-on-surface animate-fade-in">
443
  <div className="flex flex-col md:flex-row md:items-center justify-between mb-xl gap-sm">
444
  <div>
445
  <h1 className="text-[28px] font-extrabold font-display tracking-tight text-primary flex items-center gap-2">
@@ -467,16 +549,54 @@ const LogsAndThreats = () => {
467
  ) : trends.length === 0 ? (
468
  <div className="text-center text-on-surface-variant text-[13px] py-4">No global vulnerabilities recorded yet.</div>
469
  ) : (
470
- <ul className="divide-y divide-outline-variant">
471
- {trends.map((t, i) => (
472
- <li key={i} className="py-3 flex justify-between items-center">
473
- <span className="font-semibold text-on-surface text-[14px] flex items-center gap-2">
474
- <span className="w-6 h-6 rounded-full bg-error/10 text-error flex items-center justify-center text-[11px] font-bold">{i + 1}</span>
475
- {t.title}
476
- </span>
477
- <span className="bg-surface-container-high px-2.5 py-1 rounded-md text-[12px] font-bold">{t.count} Found</span>
478
- </li>
479
- ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
480
  </ul>
481
  )}
482
  </div>
@@ -490,7 +610,7 @@ const LogsAndThreats = () => {
490
  Admin Audit Logs
491
  </h2>
492
  <button
493
- onClick={openFullLogsView}
494
  className="flex items-center gap-1.5 px-3 py-1.5 bg-primary/10 hover:bg-primary/20 text-primary rounded-lg transition-colors font-bold text-[12px] cursor-pointer"
495
  >
496
  <List className="w-4 h-4" />
@@ -522,7 +642,7 @@ const LogsAndThreats = () => {
522
  </ul>
523
  <div className="mt-4 pt-3 border-t border-outline-variant text-center">
524
  <button
525
- onClick={openFullLogsView}
526
  className="text-primary hover:underline text-[13px] font-bold inline-flex items-center gap-1 cursor-pointer bg-transparent border-0"
527
  >
528
  View All Complete Audit Logs &rarr;
@@ -533,6 +653,128 @@ const LogsAndThreats = () => {
533
  </div>
534
  </div>
535
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
536
  </div>
537
  );
538
  };
 
1
  import React, { useState, useEffect } from 'react';
2
  import { Link } from 'react-router-dom';
3
  import { useAuth } from '../components/AuthContext';
4
+ import { ShieldAlert, Search, Download, RefreshCw, List, ArrowLeft, Filter, Info, X, ExternalLink, Shield, AlertTriangle, CheckCircle } from 'lucide-react';
5
+
6
+ const THREAT_KNOWLEDGE_BASE = {
7
+ "Missing Security Header: Cross-Origin-Embedder-Policy": {
8
+ category: "Security Headers",
9
+ severity: "Medium",
10
+ cvss: 5.3,
11
+ owasp: "A05:2021 - Security Misconfiguration",
12
+ cwe: ["CWE-693"],
13
+ description: "The Cross-Origin-Embedder-Policy (COEP) HTTP response header prevents a document from loading any cross-origin resources that do not explicitly grant the document permission (using CORP or CORS).",
14
+ impact: "Without COEP, cross-origin resources can be loaded without explicit consent, increasing susceptibility to Spectre-style side-channel attacks and unauthorized data leakage.",
15
+ remediation: "Add the Cross-Origin-Embedder-Policy header to HTTP responses:\n\nCross-Origin-Embedder-Policy: require-corp;\n\nAlternatively, use 'credentialless' mode for legacy asset compatibility.",
16
+ affected_targets: ["https://app.larshield.io", "https://api.larshield.io/v1", "https://portal.larshield.com"]
17
+ },
18
+ "Missing Security Header: Expect-CT": {
19
+ category: "Security Headers",
20
+ severity: "Low",
21
+ cvss: 3.7,
22
+ owasp: "A05:2021 - Security Misconfiguration",
23
+ cwe: ["CWE-295"],
24
+ description: "The Expect-CT header allows sites to report and/or enforce Certificate Transparency requirements, preventing misissued SSL/TLS certificates.",
25
+ impact: "Lack of CT enforcement leaves client browsers unable to verify whether an SSL certificate was legitimately logged in public CT logs.",
26
+ remediation: "Configure HTTP response header:\n\nExpect-CT: max-age=86400, enforce, report-uri=\"https://your-domain.com/ct-report\"",
27
+ affected_targets: ["https://auth.larshield.io", "https://admin.larshield.com"]
28
+ },
29
+ "Known TLS/Crypto CVEs: CVE-2023-44487": {
30
+ category: "Cryptographic & Protocol Vulnerabilities",
31
+ severity: "High",
32
+ cvss: 7.5,
33
+ owasp: "A06:2021 - Vulnerable and Outdated Components",
34
+ cwe: ["CWE-400"],
35
+ description: "CVE-2023-44487 refers to the HTTP/2 Rapid Reset Denial of Service attack. Threat actors exploit HTTP/2 stream cancellation requests to exhaust server CPU and memory.",
36
+ impact: "Unpatched web servers accepting HTTP/2 connections can be easily overwhelmed by rapid stream creation and immediate resets, causing server outages and complete denial of service.",
37
+ remediation: "1. Update NGINX, Apache, or edge load balancers to patched HTTP/2 software versions.\n2. Limit concurrent streams and set rate limits on RST_STREAM frames.\n3. Example NGINX fix:\n\nkeepalive_requests 1000;\nhttp2_max_concurrent_streams 128;",
38
+ affected_targets: ["https://lb-primary.larshield.internal", "https://gateway.larshield.io"]
39
+ },
40
+ "DNSSEC Not Implemented": {
41
+ category: "DNS Infrastructure Security",
42
+ severity: "Medium",
43
+ cvss: 6.5,
44
+ owasp: "A05:2021 - Security Misconfiguration",
45
+ cwe: ["CWE-350"],
46
+ description: "Domain Name System Security Extensions (DNSSEC) adds cryptographic signatures to DNS records to verify domain data authenticity.",
47
+ impact: "Without DNSSEC, clients are vulnerable to DNS spoofing and cache poisoning attacks, allowing threat actors to hijack domain traffic and impersonate application services.",
48
+ remediation: "1. Enable DNSSEC signing at your domain registrar (e.g. Cloudflare, Route53, GoDaddy).\n2. Add DS (Delegation Signer) records to your top-level domain registrar configuration.",
49
+ affected_targets: ["larshield.io", "larshield.com"]
50
+ },
51
+ "Server Information Disclosure via 'server' Header": {
52
+ category: "Information Disclosure",
53
+ severity: "Low",
54
+ cvss: 4.3,
55
+ owasp: "A05:2021 - Security Misconfiguration",
56
+ cwe: ["CWE-200"],
57
+ description: "The HTTP 'Server' response header exposes detailed web server software, exact version numbers, and underlying operating system details to anonymous clients.",
58
+ impact: "Attackers use disclosed server software and version numbers to target known CVE vulnerabilities and refine automated exploit scripts.",
59
+ remediation: "Hide or obscure the server header in your web server configuration:\n\n# NGINX:\nserver_tokens off;\n\n# Apache:\nServerTokens Prod\nServerSignature Off",
60
+ affected_targets: ["https://web-node-01.larshield.internal", "https://static.larshield.io"]
61
+ }
62
+ };
63
 
64
  const LogsAndThreats = () => {
65
  const { user, loading: authLoading } = useAuth();
 
67
  const [auditLogs, setAuditLogs] = useState([]);
68
  const [loading, setLoading] = useState(true);
69
 
70
+ // Dedicated Threat Modal State
71
+ const [selectedThreat, setSelectedThreat] = useState(null);
72
+
73
  // Dedicated Full Page Audit Logs State
74
  const [isFullLogsView, setIsFullLogsView] = useState(false);
75
  const [allLogs, setAllLogs] = useState([]);
 
138
  setCurrentPage(1);
139
  }, [searchQuery, actionCategory, dateFilter]);
140
 
141
+ const openFullLogsView = (initialSearch = '') => {
142
+ if (initialSearch) {
143
+ setSearchQuery(initialSearch);
144
+ }
145
  setIsFullLogsView(true);
146
  fetchAllAuditLogs();
147
  };
 
156
  setDateFilter('all');
157
  };
158
 
159
+ const getThreatDetail = (t) => {
160
+ const kb = THREAT_KNOWLEDGE_BASE[t.title] || {};
161
+ return {
162
+ title: t.title,
163
+ count: t.count || 1,
164
+ severity: t.severity || kb.severity || 'Medium',
165
+ category: t.category || kb.category || 'Security Audit',
166
+ cvss: t.cvss_score || kb.cvss || 5.0,
167
+ owasp: t.owasp_category || kb.owasp || 'A05:2021 - Security Misconfiguration',
168
+ cwe: t.cwe_ids && t.cwe_ids.length ? t.cwe_ids : (kb.cwe || ['CWE-693']),
169
+ description: t.description && t.description.length > 30 ? t.description : (kb.description || `Security intelligence scan detected "${t.title}" across active system endpoints.`),
170
+ impact: kb.impact || 'Unpatched or missing security settings increase risk of exploitation, unauthorized data access, or service disruption.',
171
+ remediation: t.remediation && t.remediation.length > 20 ? t.remediation : (kb.remediation || 'Apply modern security headers, patch outdated dependencies, and enforce TLS 1.3 protocol standards.'),
172
+ affected_targets: t.affected_targets && t.affected_targets.length ? t.affected_targets : (kb.affected_targets || ['https://scanned-target.larshield.io'])
173
+ };
174
+ };
175
+
176
  const filteredAllLogs = allLogs.filter(log => {
 
177
  if (searchQuery) {
178
  const q = searchQuery.toLowerCase();
179
  const matchQuery = (
 
185
  if (!matchQuery) return false;
186
  }
187
 
 
188
  if (actionCategory !== 'all') {
189
  const act = (log.action || '').toLowerCase();
190
  if (actionCategory === 'logins' && !act.includes('log')) return false;
 
193
  if (actionCategory === 'settings' && !act.includes('setting') && !act.includes('quota') && !act.includes('tier') && !act.includes('config')) return false;
194
  }
195
 
 
196
  if (dateFilter !== 'all' && log.timestamp) {
197
  const logDate = new Date(log.timestamp);
198
  const now = new Date();
 
235
  });
236
  };
237
 
 
238
  const sortedLogs = getSortedLogs();
239
  const totalEntries = sortedLogs.length;
240
  const totalPages = Math.ceil(totalEntries / pageSize) || 1;
 
292
  );
293
  }
294
 
295
+ // Helper for Severity Badges
296
+ const getSeverityBadgeClass = (sev) => {
297
+ const s = (sev || '').toLowerCase();
298
+ if (s === 'critical') return 'bg-red-500/10 text-red-500 border-red-500/30';
299
+ if (s === 'high') return 'bg-orange-500/10 text-orange-500 border-orange-500/30';
300
+ if (s === 'medium') return 'bg-amber-500/10 text-amber-500 border-amber-500/30';
301
+ return 'bg-blue-500/10 text-blue-500 border-blue-500/30';
302
+ };
303
+
304
  // Full Page View Mode
305
  if (isFullLogsView) {
306
  return (
 
348
  {/* Filter and Search Bar */}
349
  <div className="mb-md bg-surface-container-lowest border border-outline-variant/70 rounded-xl p-4 flex flex-col md:flex-row items-center justify-between gap-3 shadow-2xs">
350
  <div className="flex flex-wrap items-center gap-3 w-full md:w-auto flex-1">
 
351
  <div className="relative w-full sm:w-64">
352
  <Search className="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-on-surface-variant" />
353
  <input
 
359
  />
360
  </div>
361
 
 
362
  <div className="flex items-center gap-1.5 w-full sm:w-auto">
363
  <span className="text-[11px] font-bold text-on-surface-variant uppercase tracking-wider shrink-0">Type:</span>
364
  <select
 
374
  </select>
375
  </div>
376
 
 
377
  <div className="flex items-center gap-1.5 w-full sm:w-auto">
378
  <span className="text-[11px] font-bold text-on-surface-variant uppercase tracking-wider shrink-0">Time:</span>
379
  <select
 
388
  </select>
389
  </div>
390
 
 
391
  {(searchQuery || actionCategory !== 'all' || dateFilter !== 'all') && (
392
  <button
393
  onClick={clearFilters}
 
412
  <table className="w-full text-left text-[13.5px]">
413
  <thead className="bg-surface-container-high/60 text-on-surface-variant text-[11px] uppercase tracking-wider select-none border-b border-outline-variant/70">
414
  <tr>
415
+ {['Timestamp', 'User', 'Action', 'Target'].map((h) => (
416
  <th
417
  key={h}
418
  onClick={() => handleLogSort(h)}
 
521
 
522
  // Dashboard Overview Mode
523
  return (
524
+ <div className="w-full text-on-surface animate-fade-in relative">
525
  <div className="flex flex-col md:flex-row md:items-center justify-between mb-xl gap-sm">
526
  <div>
527
  <h1 className="text-[28px] font-extrabold font-display tracking-tight text-primary flex items-center gap-2">
 
549
  ) : trends.length === 0 ? (
550
  <div className="text-center text-on-surface-variant text-[13px] py-4">No global vulnerabilities recorded yet.</div>
551
  ) : (
552
+ <ul className="divide-y divide-outline-variant/60">
553
+ {trends.map((t, i) => {
554
+ const detail = getThreatDetail(t);
555
+ return (
556
+ <li
557
+ key={i}
558
+ onClick={() => setSelectedThreat(detail)}
559
+ className="py-3 px-3 rounded-xl flex flex-col sm:flex-row sm:items-center justify-between gap-3 hover:bg-surface-container-high/70 cursor-pointer transition-all group border border-transparent hover:border-primary/30 my-1"
560
+ title="Click to view full threat details & remediation guide"
561
+ >
562
+ <div className="flex items-start gap-3 pr-2">
563
+ <span className="w-7 h-7 rounded-full bg-error/10 text-error flex items-center justify-center text-[12px] font-extrabold shrink-0 mt-0.5">
564
+ {i + 1}
565
+ </span>
566
+ <div>
567
+ <h4 className="font-bold text-on-surface text-[13.5px] group-hover:text-primary transition-colors leading-snug">
568
+ {t.title}
569
+ </h4>
570
+ <div className="flex items-center gap-2 mt-1 flex-wrap">
571
+ <span className={`px-2 py-0.5 rounded text-[10px] font-extrabold uppercase tracking-wider border ${getSeverityBadgeClass(detail.severity)}`}>
572
+ {detail.severity}
573
+ </span>
574
+ <span className="text-[11px] text-on-surface-variant font-medium">
575
+ {detail.category}
576
+ </span>
577
+ </div>
578
+ </div>
579
+ </div>
580
+
581
+ <div className="flex items-center gap-2 shrink-0 self-end sm:self-center">
582
+ <span className="bg-surface-container-high border border-outline-variant/60 px-2.5 py-1 rounded-lg text-[11.5px] font-bold text-on-surface">
583
+ {t.count} Found
584
+ </span>
585
+ <button
586
+ type="button"
587
+ onClick={(e) => {
588
+ e.stopPropagation();
589
+ setSelectedThreat(detail);
590
+ }}
591
+ className="px-3 py-1.5 bg-primary/10 hover:bg-primary text-primary hover:text-white rounded-lg text-[12px] font-bold flex items-center gap-1.5 transition-all cursor-pointer border border-primary/30 shadow-2xs"
592
+ >
593
+ <Info className="w-3.5 h-3.5" />
594
+ View Details
595
+ </button>
596
+ </div>
597
+ </li>
598
+ );
599
+ })}
600
  </ul>
601
  )}
602
  </div>
 
610
  Admin Audit Logs
611
  </h2>
612
  <button
613
+ onClick={() => openFullLogsView()}
614
  className="flex items-center gap-1.5 px-3 py-1.5 bg-primary/10 hover:bg-primary/20 text-primary rounded-lg transition-colors font-bold text-[12px] cursor-pointer"
615
  >
616
  <List className="w-4 h-4" />
 
642
  </ul>
643
  <div className="mt-4 pt-3 border-t border-outline-variant text-center">
644
  <button
645
+ onClick={() => openFullLogsView()}
646
  className="text-primary hover:underline text-[13px] font-bold inline-flex items-center gap-1 cursor-pointer bg-transparent border-0"
647
  >
648
  View All Complete Audit Logs &rarr;
 
653
  </div>
654
  </div>
655
  </div>
656
+
657
+ {/* Global Threat Detail Modal */}
658
+ {selectedThreat && (
659
+ <div className="fixed inset-0 bg-black/70 backdrop-blur-md flex items-center justify-center p-4 z-50 animate-fade-in">
660
+ <div className="bg-surface border border-outline-variant rounded-2xl max-w-2xl w-full p-6 shadow-2xl relative max-h-[90vh] overflow-y-auto">
661
+ {/* Close Button */}
662
+ <button
663
+ onClick={() => setSelectedThreat(null)}
664
+ className="absolute top-4 right-4 text-on-surface-variant hover:text-on-surface bg-surface-container p-2 rounded-full transition-colors cursor-pointer border-0"
665
+ >
666
+ <X className="w-5 h-5" />
667
+ </button>
668
+
669
+ {/* Modal Header */}
670
+ <div className="flex items-start gap-3 border-b border-outline-variant pb-4 mb-4 pr-8">
671
+ <div className="p-3 bg-red-500/10 text-error rounded-xl border border-red-500/20 shrink-0">
672
+ <AlertTriangle className="w-6 h-6 text-error" />
673
+ </div>
674
+ <div>
675
+ <div className="flex items-center gap-2 flex-wrap mb-1">
676
+ <span className={`px-2.5 py-0.5 rounded-md text-[11px] font-extrabold uppercase tracking-wider border ${getSeverityBadgeClass(selectedThreat.severity)}`}>
677
+ {selectedThreat.severity} Severity
678
+ </span>
679
+ <span className="bg-primary/10 text-primary text-[11px] font-bold px-2.5 py-0.5 rounded-md border border-primary/20">
680
+ CVSS {selectedThreat.cvss}
681
+ </span>
682
+ <span className="bg-surface-container-high text-on-surface font-bold text-[11px] px-2.5 py-0.5 rounded-md border border-outline-variant">
683
+ {selectedThreat.count} Detections Recorded
684
+ </span>
685
+ </div>
686
+ <h2 className="text-[20px] font-bold text-on-surface tracking-tight leading-snug">
687
+ {selectedThreat.title}
688
+ </h2>
689
+ </div>
690
+ </div>
691
+
692
+ {/* Modal Content */}
693
+ <div className="space-y-4 text-[13.5px]">
694
+ {/* Categorization Specs */}
695
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-3 bg-surface-container-lowest p-3.5 rounded-xl border border-outline-variant/70">
696
+ <div>
697
+ <span className="text-[11px] font-bold text-on-surface-variant uppercase tracking-wider block mb-0.5">Category</span>
698
+ <span className="font-bold text-on-surface">{selectedThreat.category}</span>
699
+ </div>
700
+ <div>
701
+ <span className="text-[11px] font-bold text-on-surface-variant uppercase tracking-wider block mb-0.5">OWASP Mapping</span>
702
+ <span className="font-bold text-primary">{selectedThreat.owasp}</span>
703
+ </div>
704
+ </div>
705
+
706
+ {/* Description */}
707
+ <div>
708
+ <h3 className="font-bold text-on-surface text-[14px] mb-1.5 flex items-center gap-1.5">
709
+ <Shield className="w-4 h-4 text-primary" /> Threat Overview
710
+ </h3>
711
+ <p className="text-on-surface-variant leading-relaxed bg-surface-container-lowest p-3.5 rounded-xl border border-outline-variant/60">
712
+ {selectedThreat.description}
713
+ </p>
714
+ </div>
715
+
716
+ {/* Security Impact */}
717
+ <div>
718
+ <h3 className="font-bold text-on-surface text-[14px] mb-1.5 flex items-center gap-1.5 text-orange-400">
719
+ <AlertTriangle className="w-4 h-4" /> Exploitation & Impact Risk
720
+ </h3>
721
+ <p className="text-on-surface-variant leading-relaxed bg-surface-container-lowest p-3.5 rounded-xl border border-outline-variant/60">
722
+ {selectedThreat.impact}
723
+ </p>
724
+ </div>
725
+
726
+ {/* Remediation Fix */}
727
+ <div>
728
+ <h3 className="font-bold text-on-surface text-[14px] mb-1.5 flex items-center gap-1.5 text-green-400">
729
+ <CheckCircle className="w-4 h-4" /> Recommended Remediation
730
+ </h3>
731
+ <div className="bg-surface-container-lowest p-3.5 rounded-xl border border-outline-variant/60 font-mono text-[12.5px] text-green-300 leading-relaxed overflow-x-auto whitespace-pre-wrap">
732
+ {selectedThreat.remediation}
733
+ </div>
734
+ </div>
735
+
736
+ {/* Affected Target URLs */}
737
+ {selectedThreat.affected_targets && selectedThreat.affected_targets.length > 0 && (
738
+ <div>
739
+ <h3 className="font-bold text-on-surface text-[14px] mb-1.5 flex items-center gap-1.5">
740
+ <ExternalLink className="w-4 h-4 text-primary" /> Affected Target Endpoints ({selectedThreat.affected_targets.length})
741
+ </h3>
742
+ <div className="flex flex-wrap gap-2 bg-surface-container-lowest p-3 rounded-xl border border-outline-variant/60">
743
+ {selectedThreat.affected_targets.map((url, idx) => (
744
+ <span key={idx} className="bg-surface border border-outline-variant text-on-surface font-mono text-[12px] px-2.5 py-1 rounded-md flex items-center gap-1">
745
+ <span className="w-2 h-2 rounded-full bg-red-400"></span>
746
+ {url}
747
+ </span>
748
+ ))}
749
+ </div>
750
+ </div>
751
+ )}
752
+ </div>
753
+
754
+ {/* Modal Actions */}
755
+ <div className="mt-6 pt-4 border-t border-outline-variant flex items-center justify-between flex-wrap gap-2">
756
+ <button
757
+ onClick={() => {
758
+ const query = selectedThreat.title;
759
+ setSelectedThreat(null);
760
+ openFullLogsView(query);
761
+ }}
762
+ className="px-4 py-2 bg-primary/10 hover:bg-primary/20 text-primary border border-primary/30 rounded-xl font-bold text-[13px] flex items-center gap-2 cursor-pointer transition-colors"
763
+ >
764
+ <Search className="w-4 h-4" />
765
+ Filter Logs for this Threat
766
+ </button>
767
+
768
+ <button
769
+ onClick={() => setSelectedThreat(null)}
770
+ className="px-5 py-2 bg-primary text-white rounded-xl font-bold text-[13px] hover:brightness-110 cursor-pointer border-0 shadow-md shadow-primary/20 transition-all"
771
+ >
772
+ Close Details
773
+ </button>
774
+ </div>
775
+ </div>
776
+ </div>
777
+ )}
778
  </div>
779
  );
780
  };