// Overview
Built to emulate enterprise environments, the ascendant.design SOC Homelab is a fully virtualized cybersecurity training ground for hands-on threat detection. Hosted on VirtualBox within a securely isolated network, the architecture consists of five virtual machines: a Windows Server 2022 Domain Controller, a Windows 11 Pro endpoint, a vulnerable Metasploitable 2 instance, and a Kali Linux attack node. Centralized monitoring is handled by a Wazuh SIEM, which actively detects simulated malicious behavior and correlates it directly with the MITRE ATT&CK framework.
// Overview
Built to emulate enterprise environments, the ascendant.design SOC Homelab is a fully virtualized cybersecurity training ground for hands-on threat detection. Hosted on VirtualBox within a securely isolated network, the architecture consists of five virtual machines: a Windows Server 2022 Domain Controller, a Windows 11 Pro endpoint, a vulnerable Metasploitable 2 instance, and a Kali Linux attack node. Centralized monitoring is handled by a Wazuh SIEM, which actively detects simulated malicious behavior and correlates it directly with the MITRE ATT&CK framework.
// SOC Live Dashboard
This live dashboard provides a real-time window into the automated telemetry captured across your virtualized cybersecurity infrastructure. It aggregates threat intelligence from active Wazuh SIEM endpoints—tracking total event volume, classifying alerts by risk severity level, and mapping incoming security events directly to the MITRE ATT&CK framework. Designed to showcase automated endpoint protection and continuous threat monitoring, the feed highlights active attack vectors and system telemetry in real time.
Interactive Sandbox
Simulate a Level 12 Command & Control alert to see how the front-end SOC dashboard updates telemetry in real-time.
Payload Injected! Refreshing telemetry…
Architecture Overview: Decoupled Telemetry Pipeline
This dashboard relies on a decoupled, push-based telemetry pipeline to securely bridge an isolated SIEM with a public web environment. An autonomous Python script on the Wazuh server queries the local OpenSearch API for security metrics—such as MITRE ATT&CK mappings and alert severities—and pushes them outbound as a lightweight JSON payload. Utilizing a pre-shared key (PSK) and custom headers, this outbound-only transmission bypasses external firewalls and maintains strict network segmentation, completely hiding the internal SIEM from inbound internet traffic.
Upon receipt, a custom WordPress REST API endpoint validates the payload and caches it in memory using Transients. This stateless caching prevents database thrashing and guarantees instantaneous page loads. Custom shortcodes then dynamically render the cached intelligence into responsive dashboards, delivering real-time SOC visibility with zero direct infrastructure exposure.
Event Volume (Last 24 Hours – UTC)
Live SOC Telemetry 8,621 Events
Severities
- Level 53,887
- Level 32,329
- Level 72,019
- Level 4225
- Level 972
Top MITRE Tactics
- Defense Evasion 2,533
- Impact 1,289
- Privilege Escalation 202
Active Agents
- HomelabWindowsP 1,985
- WIN-BACJJ5JI7U9 1,889
- WIN-QK6A1G7ET00 1,805
Top Security Events
- Software protection service scheduled successfully. 887
- Registry Value Entry Added to the System 822
- Registry Value Integrity Checksum Changed 774
- Name resolution for the name settings-win.data.microsoft.com timed out 567
Compliance Mapping
NIST Control Hits
- Control SI.7 2,416
- Control CM.1 2,266
- Control AU.6 789
SOC 2 Criteria Hits
- CC6.8 (Malicious Software) 2,416
- CC8.1 (Change Management) 2,266
- CC7.1 (System Monitoring) 789
Top Event Categories
- Windows 3,147
- Ossec 2,897
- Syscheck 2,416
- Syscheck_registry 2,308
Telemetry Sources
Event Channels
- EventChannel 3,151
- syscheck 2,416
- sca 2,267
- rootcheck 340
- journald 156
SOC Live Dashboard
This live dashboard provides a real-time window into the automated telemetry captured across your virtualized cybersecurity infrastructure. It aggregates threat intelligence from active Wazuh SIEM endpoints—tracking total event volume, classifying alerts by risk severity level, and mapping incoming security events directly to the MITRE ATT&CK framework. Designed to showcase automated endpoint protection and continuous threat monitoring, the feed highlights active attack vectors and system telemetry in real time.
Interactive Sandbox
Simulate a Level 12 Command & Control alert to see how the front-end SOC dashboard updates telemetry in real-time.
Payload Injected! Refreshing telemetry…
Architecture Overview: Decoupled Telemetry Pipeline
This dashboard relies on a decoupled, push-based telemetry pipeline to securely bridge an isolated SIEM with a public web environment. An autonomous Python script on the Wazuh server queries the local OpenSearch API for security metrics—such as MITRE ATT&CK mappings and alert severities—and pushes them outbound as a lightweight JSON payload. Utilizing a pre-shared key (PSK) and custom headers, this outbound-only transmission bypasses external firewalls and maintains strict network segmentation, completely hiding the internal SIEM from inbound internet traffic.
Upon receipt, a custom WordPress REST API endpoint validates the payload and caches it in memory using Transients. This stateless caching prevents database thrashing and guarantees instantaneous page loads. Custom shortcodes then dynamically render the cached intelligence into responsive dashboards, delivering real-time SOC visibility with zero direct infrastructure exposure.
Event Volume (Last 24 Hours – UTC)
Live SOC Telemetry 8,621 Events
Severities
- Level 53,887
- Level 32,329
- Level 72,019
- Level 4225
- Level 972
Top MITRE Tactics
- Defense Evasion 2,533
- Impact 1,289
- Privilege Escalation 202
Active Agents
- HomelabWindowsP 1,985
- WIN-BACJJ5JI7U9 1,889
- WIN-QK6A1G7ET00 1,805
Top Security Events
- Software protection service scheduled successfully. 887
- Registry Value Entry Added to the System 822
- Registry Value Integrity Checksum Changed 774
- Name resolution for the name settings-win.data.microsoft.com timed out 567
Compliance Mapping
NIST Control Hits
- Control SI.7 2,416
- Control CM.1 2,266
- Control AU.6 789
SOC 2 Criteria Hits
- CC6.8 (Malicious Software) 2,416
- CC8.1 (Change Management) 2,266
- CC7.1 (System Monitoring) 789
Top Event Categories
- Windows 3,147
- Ossec 2,897
- Syscheck 2,416
- Syscheck_registry 2,308
Telemetry Sources
Event Channels
- EventChannel 3,151
- syscheck 2,416
- sca 2,267
- rootcheck 340
- journald 156
// Tech Stack
Wazuh SIEM Manager
Kali Linux Virtual Machine
Windows 2022 Active Dir
Ubuntu Linux Instance
Windows 11 Pro Instance
// Network Overview
| VM | ROLE | OS | IP |
|---|---|---|---|
| DC1 | Domain Controller | Windows 2022 Server | 192.168.128.10 |
| Wazuh SIEM | SIEM | Ubuntu 22.04 | 192.168.128.8 |
| Client1 | Workstation | Windows 11 Pro | 192.168.128.7 |
| Client2 | Workstation | Ubuntu 26.04 | 192.168.128.11 |
| Kali Linux Attacker | Attacker | Kali Linux | 192.168.128.9 |
// VirtualBox and Wazuh SIEM Setup
// Wazuh SIEM PHP Construction Script
'POST',
'callback' => 'update_wazuh_telemetry',
'permission_callback' => '__return_true'
));
});
function update_wazuh_telemetry($request) {
$secret_token = 'INSERT_YOUR_SECRET_HERE';
if ($request->get_header('x-api-key') !== $secret_token) {
return new WP_Error('unauthorized', 'Invalid Token', array('status' => 401));
}
set_transient('wazuh_alert_stats_cache', $request->get_json_params(), 600);
return rest_ensure_response(array('status' => 'success'));
}
function get_wz_color($index) {
$colors = array('#ef4444', '#f97316', '#facc15', '#22c55e', '#009fe3');
return $colors[$index % count($colors)];
}
// --- 2. WIDGET 1: MASTER DASHBOARD ---
function display_wazuh_telemetry() {
$data = get_transient('wazuh_alert_stats_cache');
if (empty($data) || !isset($data['severities'])) return 'Awaiting Data...
';
$html = '';
$html .= 'Live SOC Telemetry ' . number_format($data['total']) . ' Events
';
$html .= '';
$html .= 'Severities
';
foreach ($data['severities'] as $sev) {
$lvl = intval($sev['level']);
$class = ($lvl >= 12) ? 'wz-critical' : (($lvl >= 8) ? 'wz-high' : (($lvl >= 4) ? 'wz-medium' : 'wz-low'));
$html .= '- Level ' . $lvl . '' . number_format($sev['count']) . '
';
}
$html .= '
';
$html .= '';
$html .= 'Top MITRE Tactics
';
$mitre_data = isset($data['mitre']) ? $data['mitre'] : [];
if (empty($mitre_data)) { $html .= '- No MITRE data 0
'; }
else { $i = 0; foreach ($mitre_data as $m) { $c = get_wz_color($i++); $html .= '- ' . esc_html(ucfirst($m['tactic'])) . ' ' . number_format($m['count']) . '
'; } }
$html .= '
';
$html .= 'Active Agents
';
$agent_data = isset($data['agents']) ? $data['agents'] : [];
if (empty($agent_data)) { $html .= '- No agent data 0
'; }
else { $i = 0; foreach ($agent_data as $a) { $c = get_wz_color($i++); $html .= '- ' . esc_html($a['name']) . ' ' . number_format($a['count']) . '
'; } }
$html .= '
';
$html .= 'Top Security Events
';
$event_data = isset($data['events']) ? $data['events'] : [];
if (empty($event_data)) { $html .= '- No events 0
'; }
else { $i = 0; foreach ($event_data as $e) { $c = get_wz_color($i++); $html .= '- ' . esc_html($e['desc']) . ' ' . number_format($e['count']) . '
'; } }
$html .= '
';
$html .= '';
return $html;
}
add_shortcode('wazuh_live_stats', 'display_wazuh_telemetry');
// --- 3. WIDGET 2: COMPLIANCE & AUTHENTICATION ---
function display_wazuh_compliance() {
$data = get_transient('wazuh_alert_stats_cache');
if (empty($data)) return 'Awaiting Data...
';
$html = 'Security Framework & Auth Groupings
';
$html .= '';
$html .= 'NIST Control Hits
';
$nist_data = isset($data['nist']) ? $data['nist'] : [];
if (empty($nist_data)) { $html .= '- No NIST data 0
'; }
else { $i = 0; foreach ($nist_data as $n) { $c = get_wz_color($i++); $html .= '- Control ' . esc_html($n['control']) . ' ' . number_format($n['count']) . '
'; } }
$html .= '
';
$html .= 'Top Event Categories
';
$group_data = isset($data['groups']) ? $data['groups'] : [];
if (empty($group_data)) { $html .= '- No category data 0
'; }
else { $i = 0; foreach ($group_data as $g) { $c = get_wz_color($i++); $html .= '- ' . esc_html(ucfirst($g['group'])) . ' ' . number_format($g['count']) . '
'; } }
$html .= '
';
return $html;
}
add_shortcode('wazuh_compliance_stats', 'display_wazuh_compliance');
// --- 4. WIDGET 3: TELEMETRY SOURCES ---
function display_wazuh_sources() {
$data = get_transient('wazuh_alert_stats_cache');
if (empty($data)) return 'Awaiting Data...
';
$html = 'Telemetry Sources
';
$html .= 'Event Channels
';
$loc_data = isset($data['locations']) ? $data['locations'] : [];
if (empty($loc_data)) { $html .= '- No source data 0
'; }
else { $i = 0; foreach ($loc_data as $l) { $c = get_wz_color($i++); $loc_name = (strlen($l['loc']) > 30) ? substr($l['loc'], 0, 30) . '...' : $l['loc']; $html .= '- ' . esc_html($loc_name) . ' ' . number_format($l['count']) . '
'; } }
$html .= '
';
return $html;
}
add_shortcode('wazuh_source_stats', 'display_wazuh_sources');
// --- 5. WIDGET 4: TIMELINE GRAPH ---
function display_wazuh_graph() {
$data = get_transient('wazuh_alert_stats_cache');
if (empty($data) || !isset($data['timeline'])) return 'Awaiting Graph Data...
';
$labels = [];
$counts = [];
foreach ((array)$data['timeline'] as $t) {
$labels[] = gmdate('g A', intval($t['time'] / 1000));
$counts[] = intval($t['count']);
}
$chart_id = 'wazuhChart_' . uniqid();
$html = '';
$html .= 'Event Volume (Last 24 Hours - UTC)
';
$html .= '';
$html .= '';
$html .= '';
static $chart_js_loaded = false;
if (!$chart_js_loaded) {
$html .= '';
$chart_js_loaded = true;
}
$html .= '';
return $html;
}
add_shortcode('wazuh_graph_stats', 'display_wazuh_graph');
// --- 6. WIDGET 5: INCIDENT RESPONSE DRILL-DOWN (LIVE DATA) ---
function display_wazuh_investigation() {
$time_param = isset($_GET['time']) ? sanitize_text_field($_GET['time']) : '';
$data = get_transient('wazuh_alert_stats_cache');
$raw_logs = isset($data['raw_logs']) ? $data['raw_logs'] : [];
$filtered_logs = [];
if ($time_param && !empty($raw_logs)) {
foreach ($raw_logs as $log) {
$log_hour = gmdate('g A', strtotime($log['time']));
if ($log_hour === $time_param) {
$filtered_logs[] = $log;
}
}
} else {
$filtered_logs = array_slice($raw_logs, 0, 50);
$time_param = 'Latest Activity';
}
$actual_count = count($filtered_logs);
$html = '';
$html .= 'SOC Investigation Drill-Down ' . esc_html($actual_count) . ' Events at ' . esc_html($time_param) . '
';
$html .= 'Live Environment: Displaying raw SIEM telemetry from the selected timeframe. Click any row to view the raw JSON payload.
';
$html .= '';
$html .= '';
$html .= '';
$html .= 'Timestamp Source / Agent Level Description MITRE Tactic ';
$html .= ' ';
if (empty($filtered_logs)) {
$html .= 'No raw events available for this timeframe. They may have fallen out of the 24-hour buffer. ';
} else {
$index = 0;
foreach ($filtered_logs as $log) {
$lvl = intval($log['level']);
$bg_color = ($lvl >= 12) ? '#ef4444' : (($lvl >= 8) ? '#f97316' : (($lvl >= 4) ? '#facc15' : '#009fe3'));
$text_color = ($lvl >= 12 || $lvl < 4 || $lvl >= 8) ? '#ffffff' : '#1a1a1a';
$display_time = gmdate('Y-m-d H:i:s', strtotime($log['time'])) . ' UTC';
$html .= '';
$html .= '' . esc_html($display_time) . ' ';
$html .= '' . esc_html($log['agent']) . ' ';
$html .= 'L' . $lvl . ' ';
$html .= '' . esc_html($log['desc']) . ' ';
$html .= '' . esc_html($log['mitre']) . ' ';
$html .= ' ';
$html .= '';
$html .= '';
$html .= "{\n \"@timestamp\": \"{$log['time']}\",\n \"agent\": {\n \"name\": \"{$log['agent']}\"\n },\n \"rule\": {\n \"level\": {$lvl},\n \"description\": \"" . htmlspecialchars($log['desc'], ENT_QUOTES) . "\",\n \"mitre\": {\n \"tactic\": \"{$log['mitre']}\"\n }\n }\n}";
$html .= ' ';
$index++;
}
}
$html .= '
';
$html .= '';
$html .= '← Back to Dashboard';
$html .= '';
return $html;
}
add_shortcode('wazuh_investigation_demo', 'display_wazuh_investigation');
// --- 7. WIDGET 6: INTERACTIVE SANDBOX BUTTON ---
add_action('rest_api_init', function () {
register_rest_route('soc/v1', '/simulate', array(
'methods' => 'POST',
'callback' => 'inject_simulated_alert',
'permission_callback' => '__return_true'
));
});
function inject_simulated_alert($request) {
$data = get_transient('wazuh_alert_stats_cache');
if (!$data) return new WP_Error('no_data', 'Awaiting live data sync.', array('status' => 404));
$fake_log = [
'time' => gmdate('Y-m-d\TH:i:s\Z'),
'agent' => 'WIN-QK6A1G7ET00',
'level' => 12,
'desc' => '[SIMULATED] High amount of POST requests in a small period of time',
'mitre' => 'Command and Control'
];
if (isset($data['raw_logs'])) {
array_unshift($data['raw_logs'], $fake_log);
$data['raw_logs'] = array_slice($data['raw_logs'], 0, 1000);
}
$data['total'] = isset($data['total']) ? $data['total'] + 1 : 1;
$updated_sev = false;
foreach ((array)$data['severities'] as &$sev) {
if (intval($sev['level']) === 12) {
$sev['count'] += 1;
$updated_sev = true;
break;
}
}
if (!$updated_sev) $data['severities'][] = ['level' => 12, 'count' => 1];
$current_hour = floor(time() / 3600) * 3600 * 1000;
$updated_time = false;
foreach ((array)$data['timeline'] as &$t) {
if ($t['time'] == $current_hour) {
$t['count'] += 1;
$updated_time = true;
break;
}
}
if (!$updated_time) $data['timeline'][] = ['time' => $current_hour, 'count' => 1];
set_transient('wazuh_alert_stats_cache', $data, 600);
return rest_ensure_response(array('status' => 'success'));
}
function display_sandbox_button() {
$btn_id = 'trigger_btn_' . uniqid();
$status_id = 'status_' . uniqid();
$html = '';
$html .= 'Interactive Sandbox
';
$html .= 'Simulate a Level 12 Command & Control alert to see how the front-end SOC dashboard updates telemetry in real-time.
';
$html .= '';
$html .= 'Payload Injected! Refreshing telemetry...
';
$html .= '';
$html .= '';
return $html;
}
add_shortcode('wazuh_sandbox_trigger', 'display_sandbox_button');
// --- 8. UNIVERSAL CSS ---
add_action('wp_footer', function() {
echo '';
});
?>
// Wazuh SIEM Python Push Script
import urllib.request
import json
import ssl
import base64
# --- 1. CONFIGURATION ---
WAZUH_URL = "https://INSERT_YOUR_WAZUH_IP:9200/wazuh-alerts*/_search"
WAZUH_USER = "INSERT_WAZUH_USERNAME"
WAZUH_PASS = "INSERT_WAZUH_PASSWORD"
WP_URL = "https://INSERT_YOUR_DOMAIN.com/wp-json/soc/v1/telemetry"
WP_SECRET = "INSERT_YOUR_SECRET_HERE"
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
# --- 2. EXPANDED TIME-SERIES QUERY ---
query_body = json.dumps({
"size": 0,
"aggs": {
"top_severities": { "terms": { "field": "rule.level", "size": 5 } },
"top_agents": { "terms": { "field": "agent.name", "size": 3 } },
"top_mitre": { "terms": { "field": "rule.mitre.tactic", "size": 3 } },
"top_events": { "terms": { "field": "rule.description", "size": 4 } },
"top_nist": { "terms": { "field": "rule.nist_800_53", "size": 3 } },
"top_groups": { "terms": { "field": "rule.groups", "size": 4 } },
"top_locations": { "terms": { "field": "location", "size": 5 } },
"timeline": {
"filter": { "range": { "@timestamp": { "gte": "now-24h" } } },
"aggs": {
"hourly": {
"date_histogram": { "field": "@timestamp", "fixed_interval": "1h", "min_doc_count": 0 }
}
}
}
}
}).encode('utf-8')
# --- 3. RAW LOGS QUERY ---
raw_query_body = json.dumps({
"size": 1000,
"sort": [{"@timestamp": {"order": "desc"}}],
"_source": ["@timestamp", "agent.name", "rule.level", "rule.description", "rule.mitre.tactic"],
"query": { "range": { "@timestamp": { "gte": "now-24h" } } }
}).encode('utf-8')
auth_string = f"{WAZUH_USER}:{WAZUH_PASS}"
base64_auth = base64.b64encode(auth_string.encode('utf-8')).decode('utf-8')
req_local = urllib.request.Request(WAZUH_URL, data=query_body, method="POST")
req_local.add_header("Authorization", f"Basic {base64_auth}")
req_local.add_header("Content-Type", "application/json")
try:
with urllib.request.urlopen(req_local, context=ctx) as response:
data = json.loads(response.read().decode())
except Exception as e:
print(f"Failed to query Wazuh: {e}")
exit(1)
req_raw = urllib.request.Request(WAZUH_URL, data=raw_query_body, method="POST")
req_raw.add_header("Authorization", f"Basic {base64_auth}")
req_raw.add_header("Content-Type", "application/json")
try:
with urllib.request.urlopen(req_raw, context=ctx) as response:
raw_data = json.loads(response.read().decode())
except Exception as e:
print(f"Failed to query raw logs: {e}")
raw_data = {}
# --- 4. FORMAT THE MULTI-PART PAYLOAD ---
aggs = data.get('aggregations', {})
timeline_buckets = aggs.get('timeline', {}).get('hourly', {}).get('buckets', [])
raw_hits = raw_data.get('hits', {}).get('hits', [])
parsed_raw_logs = []
for hit in raw_hits:
src = hit.get('_source', {})
rule = src.get('rule', {})
mitre = rule.get('mitre', {})
tactic = mitre.get('tactic', ['None'])
if isinstance(tactic, list):
tactic = tactic[0] if len(tactic) > 0 else 'None'
parsed_raw_logs.append({
"time": src.get('@timestamp', ''),
"agent": src.get('agent', {}).get('name', 'Unknown'),
"level": rule.get('level', 0),
"desc": rule.get('description', 'No description'),
"mitre": tactic
})
payload = {
"total": data.get('hits', {}).get('total', {}).get('value', 0),
"severities": [{"level": b['key'], "count": b['doc_count']} for b in aggs.get('top_severities', {}).get('buckets', [])],
"agents": [{"name": b['key'], "count": b['doc_count']} for b in aggs.get('top_agents', {}).get('buckets', [])],
"mitre": [{"tactic": b['key'], "count": b['doc_count']} for b in aggs.get('top_mitre', {}).get('buckets', [])],
"events": [{"desc": b['key'], "count": b['doc_count']} for b in aggs.get('top_events', {}).get('buckets', [])],
"nist": [{"control": b['key'], "count": b['doc_count']} for b in aggs.get('top_nist', {}).get('buckets', [])],
"groups": [{"group": b['key'], "count": b['doc_count']} for b in aggs.get('top_groups', {}).get('buckets', [])],
"locations": [{"loc": b['key'], "count": b['doc_count']} for b in aggs.get('top_locations', {}).get('buckets', [])],
"timeline": [{"time": b['key'], "count": b['doc_count']} for b in timeline_buckets[-24:]],
"raw_logs": parsed_raw_logs
}
# --- 5. PUSH TO WORDPRESS ---
req_remote = urllib.request.Request(WP_URL, data=json.dumps(payload).encode('utf-8'), method="POST")
req_remote.add_header("x-api-key", WP_SECRET)
req_remote.add_header("Content-Type", "application/json")
req_remote.add_header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
try:
with urllib.request.urlopen(req_remote) as response:
print(f"Successfully pushed expanded data. Status: {response.status}")
except Exception as e:
print(f"Failed to push to WordPress: {e}")
// Wazuh Code Formatting
/* Force strict boundary boxing on all widget elements */
.wazuh-stats-widget, .wazuh-stats-widget * {
box-sizing: border-box !important;
}
/* Master Widget Container */
.wazuh-stats-widget {
background: #ffffff;
border-radius: 16px;
padding: 24px;
width: 100%;
max-width: 100%;
box-shadow: 0 4px 24px rgba(0,0,0,0.04);
font-family: "Inter", sans-serif;
color: #1a1a1a;
margin-bottom: 20px;
}
/* Header Styling & Pulsing Blue Dot */
.wazuh-stats-widget h4 {
margin: 0 0 20px 0;
font-size: 1.1rem;
font-weight: 800;
letter-spacing: 0.02em;
text-transform: uppercase;
color: #1a1a1a !important;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 2px solid #f3f4f6;
padding-bottom: 12px;
}
.wazuh-stats-widget h4::before {
content: "";
display: inline-block;
width: 10px;
height: 10px;
background-color: #009fe3;
border-radius: 50%;
margin-right: 10px;
box-shadow: 0 0 8px rgba(0, 159, 227, 0.6);
animation: wazuh-pulse 2s infinite;
}
.wazuh-total-badge {
font-size: 0.75rem;
background: #e5e7eb;
padding: 4px 10px;
border-radius: 20px;
color: #4b5563;
font-weight: 700;
margin-left: auto;
}
@keyframes wazuh-pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
.wz-section-title {
margin: 0 0 10px 0;
font-size: 0.75rem;
color: #6b7280;
text-transform: uppercase;
font-weight: 700;
letter-spacing: 0.05em;
}
/* Master Flex Layouts */
.wz-master-layout {
display: flex;
gap: 20px;
align-items: flex-start;
max-width: 100%;
}
.wz-sidebar-severities {
width: 240px;
flex-shrink: 0;
background: #f4f5f7;
padding: 14px;
border-radius: 12px;
}
.wz-main-grid {
flex: 1;
display: flex;
flex-direction: column;
gap: 16px;
min-width: 0;
max-width: 100%;
}
.wz-sub-row {
display: flex;
gap: 16px;
width: 100%;
max-width: 100%;
}
.wz-card {
flex: 1;
background: #f4f5f7;
padding: 14px;
border-radius: 12px;
min-width: 0;
max-width: 100%;
}
.wz-full-card {
width: 100%;
max-width: 100%;
}
/* Severity Stack Styling */
.wazuh-stats-widget ul.wz-severities-stack {
display: flex !important;
flex-direction: column !important;
gap: 8px !important;
list-style: none !important;
padding: 0 !important;
margin: 0 !important;
}
.wz-severities-stack li {
display: flex !important;
justify-content: space-between !important;
align-items: center !important;
padding: 10px 12px !important;
background: #ffffff !important;
border-radius: 8px !important;
font-size: 0.8rem !important;
font-weight: 600 !important;
border-left: 4px solid #e5e7eb !important;
color: #1a1a1a !important;
}
.wz-severities-stack li.wz-critical { border-left-color: #ef4444 !important; }
.wz-severities-stack li.wz-high { border-left-color: #f97316 !important; }
.wz-severities-stack li.wz-medium { border-left-color: #facc15 !important; }
.wz-severities-stack li.wz-low { border-left-color: #009fe3 !important; }
.wz-severities-stack .wz-count {
background-color: #1a1a1a;
color: #ffffff;
padding: 2px 8px;
border-radius: 6px;
font-weight: 700;
font-size: 0.85rem;
width: 45px;
text-align: center;
}
/* Text List Styling & Wrapping */
.wazuh-stats-widget ul.wz-text-list {
display: flex !important;
flex-direction: column !important;
gap: 0 !important;
list-style: none !important;
padding: 0 !important;
margin: 0 !important;
width: 100%;
}
.wz-text-list li {
display: flex !important;
flex-direction: row !important;
justify-content: space-between !important;
align-items: center !important;
font-size: 0.8rem !important;
font-weight: 500 !important;
padding: 10px 0 !important;
border-bottom: 2px solid #e5e7eb !important;
background: transparent !important;
color: #1a1a1a !important;
width: 100%;
}
.wz-text-list li:last-child {
border-bottom: none !important;
padding-bottom: 0 !important;
}
.wz-text-list li > span:first-child {
flex: 1;
padding-right: 12px;
text-align: left;
word-break: break-word;
overflow-wrap: break-word;
white-space: normal;
}
.wz-text-list li > span:last-child {
font-weight: 700 !important;
background: #ffffff !important;
padding: 4px 0 !important;
border-radius: 10px !important;
font-size: 0.75rem !important;
flex-shrink: 0 !important;
width: 60px !important;
text-align: center !important;
display: inline-block !important;
margin-left: auto !important;
}
/* Mobile Responsiveness */
@media (max-width: 768px) {
.wazuh-stats-widget { padding: 16px; }
.wz-master-layout { flex-direction: column; }
.wz-sidebar-severities { width: 100%; margin-bottom: 10px; }
.wz-main-grid { width: 100%; }
.wz-sub-row { flex-direction: column; gap: 16px; width: 100%; }
.wazuh-stats-widget h4 { flex-direction: column; align-items: flex-start; gap: 12px; }
.wazuh-total-badge { margin-left: 0; }
}