Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
Update frontend/src/pages/LogsAndThreats.jsx
Browse files
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
|
| 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 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
{
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 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 →
|
|
@@ -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 →
|
|
|
|
| 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 |
};
|