SIMON Master Console Rebuild
True command center for SIMON + WEB360
This rebuild gives you a cleaner production spine: interactive tree, live viewer, guarded editor, dynamic graphics, architecture map, and note storage. It is designed to sit beside your existing
console.php
and become the stronger replacement path.
Dashboard
Tree
Viewer
System Map
File Registry
Notes
Files
15,025
Folders
377
Scanned Size
3.79 GB
PHP Files
791
Editable Text Files
12,309
File viewer
guarded to /htdocs
/simon/old/analytics.php
<?php declare(strict_types=1); require_once __DIR__ . '/_bootstrap.php'; date_default_timezone_set('America/Chicago'); require_once __DIR__ . '/../_guards/role_guard.php'; require_once __DIR__ . '/../_guardian/tracker.php'; guardian_require_role(['admin']); guardian_track_event('admin_page_view', [ 'page' => '/simon/analytics.php', 'title' => 'SIMON Traffic Intelligence', 'area' => 'admin', 'classification' => 'protected_admin', ]); /** * SIMON Traffic Intelligence Dashboard * Suggested path: /simon/analytics.php * * Data sources in priority order: * 1) /simon/data/traffic_events.json * 2) /simon/data/analytics_events.json * 3) /simon/data/traffic_events.ndjson * 4) /sitelogs/traffic.log (Apache-style access log mirror) * 5) Demo dataset fallback */ const APP_NAME = 'SIMON Traffic Intelligence'; const APP_SUBTITLE = 'Traffic · Top Pages · Direct · iOS · Filters · Analytics'; function h(string $value): string { return htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); } function pct(float $value, int $decimals = 1): string { return number_format($value, $decimals) . '%'; } function num(float|int $value): string { return number_format((float) $value, 0); } function compact_num(float $value): string { $abs = abs($value); if ($abs >= 1000000) { return number_format($value / 1000000, 2) . 'M'; } if ($abs >= 1000) { return number_format($value / 1000, 1) . 'K'; } return number_format($value, 0); } function fmt_bytes(int $bytes): string { if ($bytes >= 1073741824) { return number_format($bytes / 1073741824, 2) . ' GB'; } if ($bytes >= 1048576) { return number_format($bytes / 1048576, 2) . ' MB'; } if ($bytes >= 1024) { return number_format($bytes / 1024, 2) . ' KB'; } return $bytes . ' B'; } function short_text(string $text, int $max = 80): string { if ($max < 1) { return ''; } if (function_exists('mb_strlen') && function_exists('mb_substr')) { return mb_strlen($text) <= $max ? $text : mb_substr($text, 0, $max - 1) . '…'; } return strlen($text) <= $max ? $text : substr($text, 0, $max - 1) . '…'; } function read_json_file(string $path, array $fallback = []): array { if (!is_file($path) || !is_readable($path)) { return $fallback; } $raw = @file_get_contents($path); if ($raw === false || trim($raw) === '') { return $fallback; } $decoded = json_decode($raw, true); return is_array($decoded) ? $decoded : $fallback; } function read_ndjson_file(string $path): array { if (!is_file($path) || !is_readable($path)) { return []; } $rows = []; $fh = @fopen($path, 'rb'); if (!$fh) { return []; } while (!feof($fh)) { $line = fgets($fh); if ($line === false) { break; } $line = trim($line); if ($line === '') { continue; } $decoded = json_decode($line, true); if (is_array($decoded)) { $rows[] = $decoded; } } fclose($fh); return $rows; } function random_pick(array $values): mixed { return $values[array_rand($values)]; } function parse_apache_datetime(string $raw): ?int { $dt = DateTime::createFromFormat('d/M/Y:H:i:s O', $raw); return $dt ? $dt->getTimestamp() : null; } function classify_source_from_referrer(string $referrer): string { $r = strtolower(trim($referrer)); if ($r === '' || $r === '-') return 'direct'; if (str_contains($r, 'google.')) return 'organic'; if (str_contains($r, 'bing.')) return 'organic'; if (str_contains($r, 'duckduckgo.')) return 'organic'; if (str_contains($r, 'facebook.')) return 'social'; if (str_contains($r, 'instagram.')) return 'social'; if (str_contains($r, 'x.com') || str_contains($r, 'twitter.')) return 'social'; if (str_contains($r, 'tiktok.')) return 'social'; if (str_contains($r, 'linkedin.')) return 'social'; if (str_contains($r, 'mail')) return 'email'; return 'referral'; } function detect_device_type(string $ua): string { $u = strtolower($ua); if ($u === '' || $u === '-') return 'desktop'; if (str_contains($u, 'ipad') || str_contains($u, 'tablet')) return 'tablet'; if (str_contains($u, 'mobile') || str_contains($u, 'iphone') || str_contains($u, 'android')) return 'mobile'; return 'desktop'; } function detect_os(string $ua): string { $u = strtolower($ua); if (str_contains($u, 'iphone') || str_contains($u, 'ios')) return 'iOS'; if (str_contains($u, 'ipad')) return 'iPadOS'; if (str_contains($u, 'android')) return 'Android'; if (str_contains($u, 'mac os') || str_contains($u, 'macintosh')) return 'macOS'; if (str_contains($u, 'windows')) return 'Windows'; if (str_contains($u, 'linux')) return 'Linux'; return 'Unknown'; } function detect_browser(string $ua): string { $u = strtolower($ua); if (str_contains($u, 'edg')) return 'Edge'; if (str_contains($u, 'firefox')) return 'Firefox'; if (str_contains($u, 'chrome') && !str_contains($u, 'edg')) return 'Chrome'; if (str_contains($u, 'safari') && !str_contains($u, 'chrome')) return 'Safari'; if (str_contains($u, 'opr') || str_contains($u, 'opera')) return 'Opera'; if (str_contains($u, 'curl')) return 'cURL'; if (str_contains($u, 'bot')) return 'Bot'; return 'Unknown'; } function load_access_log_events(string $path, int $maxLines = 50000): array { if ($path === '' || !is_file($path) || !is_readable($path)) { return []; } $pattern = '/^(?P<ip>\S+)\s+\S+\s+\S+\s+\[(?P<dt>[^\]]+)\]\s+"(?P<method>[A-Z]+)\s+(?P<path>[^"]*?)\s+[^"]*"\s+(?P<status>\d{3})\s+(?P<size>-|\d+)\s+"(?P<ref>[^"]*)"\s+"(?P<ua>[^"]*)"/'; $lines = @file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: []; if (count($lines) > $maxLines) { $lines = array_slice($lines, -$maxLines); } $events = []; foreach ($lines as $line) { if (!preg_match($pattern, $line, $m)) { continue; } $ts = parse_apache_datetime((string) $m['dt']); if ($ts === null) { continue; } $pathValue = trim((string) $m['path']); if ($pathValue === '') { $pathValue = '/'; } $referrer = trim((string) $m['ref']); $ua = trim((string) $m['ua']); $status = (int) $m['status']; $size = ($m['size'] === '-') ? 0 : (int) $m['size']; $source = classify_source_from_referrer($referrer); $events[] = [ 'timestamp' => date('Y-m-d H:i:s', $ts), 'page' => explode('?', $pathValue, 2)[0], 'path' => $pathValue, 'source' => $source, 'medium' => $source === 'organic' ? 'search' : ($source === 'social' ? 'social' : ''), 'campaign' => '', 'referrer' => $referrer === '-' ? '' : $referrer, 'device_type' => detect_device_type($ua), 'os' => detect_os($ua), 'browser' => detect_browser($ua), 'country' => 'Unknown', 'city' => '', 'visitor_id' => trim((string) $m['ip']), 'session_id' => trim((string) $m['ip']) . '-' . date('YmdH', $ts), 'ip' => trim((string) $m['ip']), 'event' => 'pageview', 'duration_seconds' => 0, 'is_conversion' => false, 'status' => $status, 'bytes' => $size, 'user_agent' => $ua, ]; } return $events; } function normalize_events(array $raw): array { $events = []; foreach ($raw as $row) { if (!is_array($row)) { continue; } $timestamp = (string) ($row['timestamp'] ?? $row['time'] ?? $row['datetime'] ?? ''); if ($timestamp === '') { continue; } $ts = strtotime($timestamp); if ($ts === false) { continue; } $page = (string) ($row['page'] ?? $row['path'] ?? '/'); $source = strtolower(trim((string) ($row['source'] ?? $row['channel'] ?? 'direct'))); $device = strtolower(trim((string) ($row['device_type'] ?? $row['device'] ?? 'desktop'))); $os = trim((string) ($row['os'] ?? 'Unknown')); $browser = trim((string) ($row['browser'] ?? 'Unknown')); $country = trim((string) ($row['country'] ?? 'Unknown')); $city = trim((string) ($row['city'] ?? '')); $visitor = trim((string) ($row['visitor_id'] ?? $row['visitor'] ?? $row['ip'] ?? md5($page . $timestamp))); $session = trim((string) ($row['session_id'] ?? $row['session'] ?? $visitor . '-' . date('YmdH', $ts))); $referrer = trim((string) ($row['referrer'] ?? '')); $event = strtolower(trim((string) ($row['event'] ?? 'pageview'))); $medium = trim((string) ($row['medium'] ?? '')); $campaign = trim((string) ($row['campaign'] ?? '')); $duration = (int) ($row['duration_seconds'] ?? 0); $isConversion = !empty($row['is_conversion']); $bytes = (int) ($row['bytes'] ?? 0); $status = (int) ($row['status'] ?? 200); $events[] = [ 'timestamp' => date('Y-m-d H:i:s', $ts), 'date' => date('Y-m-d', $ts), 'hour' => date('H:00', $ts), 'weekday' => date('D', $ts), 'page' => $page, 'source' => $source !== '' ? $source : 'direct', 'medium' => $medium, 'campaign' => $campaign, 'referrer' => $referrer, 'device_type' => $device !== '' ? $device : 'desktop', 'os' => $os !== '' ? $os : 'Unknown', 'browser' => $browser !== '' ? $browser : 'Unknown', 'country' => $country !== '' ? $country : 'Unknown', 'city' => $city, 'visitor_id' => $visitor, 'session_id' => $session, 'ip' => (string) ($row['ip'] ?? ''), 'event' => $event, 'duration_seconds' => $duration, 'is_conversion' => $isConversion, 'status' => $status, 'bytes' => $bytes, 'user_agent' => (string) ($row['user_agent'] ?? ''), ]; } usort($events, fn(array $a, array $b) => strcmp($a['timestamp'], $b['timestamp'])); return $events; } function generate_demo_events(int $days = 60, int $count = 2200): array { $pages = ['/', '/web360/', '/simon/', '/books/', '/art/', '/infinity/', '/contact/', '/pricing/', '/louie/', '/arcade/']; $sources = ['direct', 'organic', 'social', 'referral', 'email', 'paid']; $devices = ['desktop', 'mobile', 'tablet']; $oses = ['Windows', 'macOS', 'iOS', 'Android', 'Linux', 'iPadOS']; $browsers = ['Chrome', 'Safari', 'Edge', 'Firefox']; $countries = ['United States', 'Canada', 'United Kingdom', 'Australia']; $cities = ['Dallas', 'Farmers Branch', 'Austin', 'Toronto', 'London', 'Sydney']; $events = []; for ($i = 0; $i < $count; $i++) { $daysAgo = rand(0, max(1, $days - 1)); $hour = rand(0, 23); $minute = rand(0, 59); $second = rand(0, 59); $ts = strtotime("-$daysAgo days") ?: time(); $ts = mktime( $hour, $minute, $second, (int) date('m', $ts), (int) date('d', $ts), (int) date('Y', $ts) ); $source = random_pick($sources); $device = random_pick($devices); $os = random_pick($oses); if ($device === 'mobile' && rand(1, 100) <= 48) { $os = 'iOS'; } if ($device === 'tablet' && rand(1, 100) <= 55) { $os = 'iPadOS'; } if ($device === 'desktop' && in_array($os, ['iOS', 'Android', 'iPadOS'], true)) { $os = random_pick(['Windows', 'macOS', 'Linux']); } $visitor = 'v_' . rand(1, 520); $session = 's_' . rand(1, 980); $page = random_pick($pages); $ref = $source === 'direct' ? '' : ( $source === 'organic' ? 'google.com' : ( $source === 'social' ? random_pick(['facebook.com', 'instagram.com', 'x.com', 'tiktok.com']) : ( $source === 'referral' ? 'partner-site.com' : 'newsletter' ) ) ); $events[] = [ 'timestamp' => date('Y-m-d H:i:s', $ts), 'page' => $page, 'source' => $source, 'medium' => $source === 'paid' ? 'cpc' : ($source === 'email' ? 'email' : ''), 'campaign' => $source === 'paid' ? 'spring-promo' : '', 'referrer' => $ref, 'device_type' => $device, 'os' => $os, 'browser' => random_pick($browsers), 'country' => random_pick($countries), 'city' => random_pick($cities), 'visitor_id' => $visitor, 'session_id' => $session, 'ip' => '192.168.1.' . rand(2, 240), 'event' => 'pageview', 'duration_seconds' => rand(8, 280), 'is_conversion' => rand(1, 100) <= 4, 'status' => 200, 'bytes' => rand(800, 125000), 'user_agent' => random_pick([ 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile Safari/605.1.15', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/124.0 Safari/537.36', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/123.0 Safari/537.36', ]), ]; } return normalize_events($events); } function options_from_events(array $events, string $key): array { $values = []; foreach ($events as $event) { $v = trim((string) ($event[$key] ?? '')); if ($v !== '') { $values[$v] = true; } } $out = array_keys($values); natcasesort($out); return array_values($out); } function filter_events(array $events, array $filters): array { return array_values(array_filter($events, function (array $e) use ($filters): bool { if ($filters['date_from'] !== '' && $e['date'] < $filters['date_from']) return false; if ($filters['date_to'] !== '' && $e['date'] > $filters['date_to']) return false; if ($filters['source'] !== '' && strtolower($e['source']) !== strtolower($filters['source'])) return false; if ($filters['device_type'] !== '' && strtolower($e['device_type']) !== strtolower($filters['device_type'])) return false; if ($filters['os'] !== '' && strtolower($e['os']) !== strtolower($filters['os'])) return false; if ($filters['browser'] !== '' && strtolower($e['browser']) !== strtolower($filters['browser'])) return false; if ($filters['country'] !== '' && strtolower($e['country']) !== strtolower($filters['country'])) return false; if ($filters['event'] !== '' && strtolower($e['event']) !== strtolower($filters['event'])) return false; if ($filters['page'] !== '' && stripos($e['page'], $filters['page']) === false) return false; if ($filters['search'] !== '') { $haystack = strtolower(implode(' ', [ $e['page'], $e['source'], $e['os'], $e['browser'], $e['country'], $e['city'], $e['referrer'], $e['medium'], $e['campaign'], $e['user_agent'], ])); if (stripos($haystack, strtolower($filters['search'])) === false) { return false; } } return true; })); } function aggregate_count(array $events, string $key): array { $counts = []; foreach ($events as $event) { $label = trim((string) ($event[$key] ?? 'Unknown')); if ($label === '') { $label = 'Unknown'; } $counts[$label] = ($counts[$label] ?? 0) + 1; } arsort($counts); return $counts; } function aggregate_unique_visitors_by(array $events, string $key): array { $sets = []; foreach ($events as $event) { $label = trim((string) ($event[$key] ?? 'Unknown')); if ($label === '') { $label = 'Unknown'; } $sets[$label][$event['visitor_id']] = true; } $out = []; foreach ($sets as $label => $ids) { $out[$label] = count($ids); } arsort($out); return $out; } function build_series(array $events, string $key): array { $series = []; foreach ($events as $event) { const APP_NAME = 'SIMON Traffic Intelligence'; const APP_SUBTITLE = 'Traffic · Top Pages · Direct · iOS · Filters · Analytics'; function h(string $value): string { return htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); } function pct(float $value, int $decimals = 1): string { return number_format($value, $decimals) . '%'; } function num(float|int $value): string { return number_format((float)$value, 0); } function compact_num(float $value): string { $abs = abs($value); if ($abs >= 1000000) { return number_format($value / 1000000, 2) . 'M'; } if ($abs >= 1000) { return number_format($value / 1000, 1) . 'K'; } return number_format($value, 0); } function fmt_bytes(int $bytes): string { if ($bytes >= 1073741824) { return number_format($bytes / 1073741824, 2) . ' GB'; } if ($bytes >= 1048576) { return number_format($bytes / 1048576, 2) . ' MB'; } if ($bytes >= 1024) { return number_format($bytes / 1024, 2) . ' KB'; } return $bytes . ' B'; } function short_text(string $text, int $max = 80): string { return mb_strlen($text) <= $max ? $text : mb_substr($text, 0, $max - 1) . '…'; } function read_json_file(string $path, array $fallback = []): array { if (!is_file($path) || !is_readable($path)) { return $fallback; } $raw = @file_get_contents($path); if ($raw === false || trim($raw) === '') { return $fallback; } $decoded = json_decode($raw, true); return is_array($decoded) ? $decoded : $fallback; } function read_ndjson_file(string $path): array { if (!is_file($path) || !is_readable($path)) { return []; } $rows = []; $fh = @fopen($path, 'rb'); if (!$fh) { return []; } while (!feof($fh)) { $line = fgets($fh); if ($line === false) { break; } $line = trim($line); if ($line === '') { continue; } $decoded = json_decode($line, true); if (is_array($decoded)) { $rows[] = $decoded; } } fclose($fh); return $rows; } function random_pick(array $values): mixed { return $values[array_rand($values)]; } function parse_apache_datetime(string $raw): ?int { $dt = DateTime::createFromFormat('d/M/Y:H:i:s O', $raw); return $dt ? $dt->getTimestamp() : null; } function classify_source_from_referrer(string $referrer): string { $r = strtolower(trim($referrer)); if ($r === '' || $r === '-') return 'direct'; if (str_contains($r, 'google.')) return 'organic'; if (str_contains($r, 'bing.')) return 'organic'; if (str_contains($r, 'duckduckgo.')) return 'organic'; if (str_contains($r, 'facebook.')) return 'social'; if (str_contains($r, 'instagram.')) return 'social'; if (str_contains($r, 'x.com') || str_contains($r, 'twitter.')) return 'social'; if (str_contains($r, 'tiktok.')) return 'social'; if (str_contains($r, 'linkedin.')) return 'social'; if (str_contains($r, 'mail')) return 'email'; return 'referral'; } function detect_device_type(string $ua): string { $u = strtolower($ua); if ($u === '' || $u === '-') return 'desktop'; if (str_contains($u, 'ipad') || str_contains($u, 'tablet')) return 'tablet'; if (str_contains($u, 'mobile') || str_contains($u, 'iphone') || str_contains($u, 'android')) return 'mobile'; return 'desktop'; } function detect_os(string $ua): string { $u = strtolower($ua); if (str_contains($u, 'iphone') || str_contains($u, 'ios')) return 'iOS'; if (str_contains($u, 'ipad')) return 'iPadOS'; if (str_contains($u, 'android')) return 'Android'; if (str_contains($u, 'mac os') || str_contains($u, 'macintosh')) return 'macOS'; if (str_contains($u, 'windows')) return 'Windows'; if (str_contains($u, 'linux')) return 'Linux'; return 'Unknown'; } function detect_browser(string $ua): string { $u = strtolower($ua); if (str_contains($u, 'edg')) return 'Edge'; if (str_contains($u, 'firefox')) return 'Firefox'; if (str_contains($u, 'chrome') && !str_contains($u, 'edg')) return 'Chrome'; if (str_contains($u, 'safari') && !str_contains($u, 'chrome')) return 'Safari'; if (str_contains($u, 'opr') || str_contains($u, 'opera')) return 'Opera'; if (str_contains($u, 'curl')) return 'cURL'; if (str_contains($u, 'bot')) return 'Bot'; return 'Unknown'; } function load_access_log_events(string $path, int $maxLines = 50000): array { if (!is_file($path) || !is_readable($path)) { return []; } $pattern = '/^(?P<ip>\S+)\s+\S+\s+\S+\s+\[(?P<dt>[^\]]+)\]\s+"(?P<method>[A-Z]+)\s+(?P<path>[^"]*?)\s+[^"]*"\s+(?P<status>\d{3})\s+(?P<size>-|\d+)\s+"(?P<ref>[^"]*)"\s+"(?P<ua>[^"]*)"/'; $lines = @file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: []; if (count($lines) > $maxLines) { $lines = array_slice($lines, -$maxLines); } $events = []; foreach ($lines as $line) { if (!preg_match($pattern, $line, $m)) { continue; } $ts = parse_apache_datetime((string)$m['dt']); if ($ts === null) { continue; } $pathValue = trim((string)$m['path']); if ($pathValue === '') { $pathValue = '/'; } $referrer = trim((string)$m['ref']); $ua = trim((string)$m['ua']); $status = (int)$m['status']; $size = ($m['size'] === '-') ? 0 : (int)$m['size']; $source = classify_source_from_referrer($referrer); $events[] = [ 'timestamp' => date('Y-m-d H:i:s', $ts), 'page' => explode('?', $pathValue, 2)[0], 'path' => $pathValue, 'source' => $source, 'medium' => $source === 'organic' ? 'search' : ($source === 'social' ? 'social' : ''), 'campaign' => '', 'referrer' => $referrer === '-' ? '' : $referrer, 'device_type' => detect_device_type($ua), 'os' => detect_os($ua), 'browser' => detect_browser($ua), 'country' => 'Unknown', 'city' => '', 'visitor_id' => trim((string)$m['ip']), 'session_id' => trim((string)$m['ip']) . '-' . date('YmdH', $ts), 'ip' => trim((string)$m['ip']), 'event' => 'pageview', 'duration_seconds' => 0, 'is_conversion' => false, 'status' => $status, 'bytes' => $size, 'user_agent' => $ua, ]; } return $events; } function normalize_events(array $raw): array { $events = []; foreach ($raw as $row) { if (!is_array($row)) { continue; } $timestamp = (string)($row['timestamp'] ?? $row['time'] ?? $row['datetime'] ?? ''); if ($timestamp === '') { continue; } $ts = strtotime($timestamp); if ($ts === false) { continue; } $page = (string)($row['page'] ?? $row['path'] ?? '/'); $source = strtolower(trim((string)($row['source'] ?? $row['channel'] ?? 'direct'))); $device = strtolower(trim((string)($row['device_type'] ?? $row['device'] ?? 'desktop'))); $os = trim((string)($row['os'] ?? 'Unknown')); $browser = trim((string)($row['browser'] ?? 'Unknown')); $country = trim((string)($row['country'] ?? 'Unknown')); $city = trim((string)($row['city'] ?? '')); $visitor = trim((string)($row['visitor_id'] ?? $row['visitor'] ?? $row['ip'] ?? md5($page . $timestamp))); $session = trim((string)($row['session_id'] ?? $row['session'] ?? $visitor . '-' . date('YmdH', $ts))); $referrer = trim((string)($row['referrer'] ?? '')); $event = strtolower(trim((string)($row['event'] ?? 'pageview'))); $medium = trim((string)($row['medium'] ?? '')); $campaign = trim((string)($row['campaign'] ?? '')); $duration = (int)($row['duration_seconds'] ?? 0); $isConversion = !empty($row['is_conversion']); $bytes = (int)($row['bytes'] ?? 0); $status = (int)($row['status'] ?? 200); $events[] = [ 'timestamp' => date('Y-m-d H:i:s', $ts), 'date' => date('Y-m-d', $ts), 'hour' => date('H:00', $ts), 'weekday' => date('D', $ts), 'page' => $page, 'source' => $source !== '' ? $source : 'direct', 'medium' => $medium, 'campaign' => $campaign, 'referrer' => $referrer, 'device_type' => $device !== '' ? $device : 'desktop', 'os' => $os !== '' ? $os : 'Unknown', 'browser' => $browser !== '' ? $browser : 'Unknown', 'country' => $country !== '' ? $country : 'Unknown', 'city' => $city, 'visitor_id' => $visitor, 'session_id' => $session, 'ip' => (string)($row['ip'] ?? ''), 'event' => $event, 'duration_seconds' => $duration, 'is_conversion' => $isConversion, 'status' => $status, 'bytes' => $bytes, 'user_agent' => (string)($row['user_agent'] ?? ''), ]; } usort($events, fn(array $a, array $b) => strcmp($a['timestamp'], $b['timestamp'])); return $events; } function generate_demo_events(int $days = 60, int $count = 2200): array { $pages = ['/', '/web360/', '/simon/', '/books/', '/art/', '/infinity/', '/contact/', '/pricing/', '/louie/', '/arcade/']; $sources = ['direct', 'organic', 'social', 'referral', 'email', 'paid']; $devices = ['desktop', 'mobile', 'tablet']; $oses = ['Windows', 'macOS', 'iOS', 'Android', 'Linux', 'iPadOS']; $browsers = ['Chrome', 'Safari', 'Edge', 'Firefox']; $countries = ['United States', 'Canada', 'United Kingdom', 'Australia']; $cities = ['Dallas', 'Farmers Branch', 'Austin', 'Toronto', 'London', 'Sydney']; $events = []; for ($i = 0; $i < $count; $i++) { $daysAgo = rand(0, max(1, $days - 1)); $hour = rand(0, 23); $minute = rand(0, 59); $second = rand(0, 59); $ts = strtotime("-$daysAgo days") ?: time(); $ts = mktime( $hour, $minute, $second, (int)date('m', $ts), (int)date('d', $ts), (int)date('Y', $ts) ); $source = random_pick($sources); $device = random_pick($devices); $os = random_pick($oses); if ($device === 'mobile' && rand(1, 100) <= 48) { $os = 'iOS'; } if ($device === 'tablet' && rand(1, 100) <= 55) { $os = 'iPadOS'; } if ($device === 'desktop' && in_array($os, ['iOS', 'Android', 'iPadOS'], true)) { $os = random_pick(['Windows', 'macOS', 'Linux']); } $visitor = 'v_' . rand(1, 520); $session = 's_' . rand(1, 980); $page = random_pick($pages); $ref = $source === 'direct' ? '' : ( $source === 'organic' ? 'google.com' : ( $source === 'social' ? random_pick(['facebook.com', 'instagram.com', 'x.com', 'tiktok.com']) : ( $source === 'referral' ? 'partner-site.com' : 'newsletter' ) ) ); $events[] = [ 'timestamp' => date('Y-m-d H:i:s', $ts), 'page' => $page, 'source' => $source, 'medium' => $source === 'paid' ? 'cpc' : ($source === 'email' ? 'email' : ''), 'campaign' => $source === 'paid' ? 'spring-promo' : '', 'referrer' => $ref, 'device_type' => $device, 'os' => $os, 'browser' => random_pick($browsers), 'country' => random_pick($countries), 'city' => random_pick($cities), 'visitor_id' => $visitor, 'session_id' => $session, 'ip' => '192.168.1.' . rand(2, 240), 'event' => 'pageview', 'duration_seconds' => rand(8, 280), 'is_conversion' => rand(1, 100) <= 4, 'status' => 200, 'bytes' => rand(800, 125000), 'user_agent' => random_pick([ 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile Safari/605.1.15', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/124.0 Safari/537.36', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/123.0 Safari/537.36', ]), ]; } return normalize_events($events); } function options_from_events(array $events, string $key): array { $values = []; foreach ($events as $event) { $v = trim((string)($event[$key] ?? '')); if ($v !== '') { $values[$v] = true; } } $out = array_keys($values); natcasesort($out); return array_values($out); } function filter_events(array $events, array $filters): array { return array_values(array_filter($events, function (array $e) use ($filters): bool { if ($filters['date_from'] !== '' && $e['date'] < $filters['date_from']) return false; if ($filters['date_to'] !== '' && $e['date'] > $filters['date_to']) return false; if ($filters['source'] !== '' && strtolower($e['source']) !== strtolower($filters['source'])) return false; if ($filters['device_type'] !== '' && strtolower($e['device_type']) !== strtolower($filters['device_type'])) return false; if ($filters['os'] !== '' && strtolower($e['os']) !== strtolower($filters['os'])) return false; if ($filters['browser'] !== '' && strtolower($e['browser']) !== strtolower($filters['browser'])) return false; if ($filters['country'] !== '' && strtolower($e['country']) !== strtolower($filters['country'])) return false; if ($filters['event'] !== '' && strtolower($e['event']) !== strtolower($filters['event'])) return false; if ($filters['page'] !== '' && stripos($e['page'], $filters['page']) === false) return false; if ($filters['search'] !== '') { $haystack = strtolower(implode(' ', [ $e['page'], $e['source'], $e['os'], $e['browser'], $e['country'], $e['city'], $e['referrer'], $e['medium'], $e['campaign'], $e['user_agent'], ])); if (stripos($haystack, strtolower($filters['search'])) === false) { return false; } } return true; })); } function aggregate_count(array $events, string $key): array { $counts = []; foreach ($events as $event) { $label = trim((string)($event[$key] ?? 'Unknown')); if ($label === '') { $label = 'Unknown'; } $counts[$label] = ($counts[$label] ?? 0) + 1; } arsort($counts); return $counts; } function aggregate_unique_visitors_by(array $events, string $key): array { $sets = []; foreach ($events as $event) { $label = trim((string)($event[$key] ?? 'Unknown')); if ($label === '') { $label = 'Unknown'; } $sets[$label][$event['visitor_id']] = true; } $out = []; foreach ($sets as $label => $ids) { $out[$label] = count($ids); } arsort($out); return $out; } function build_series(array $events, string $key): array { $series = []; foreach ($events as $event) { $label = (string)$event[$key]; $series[$label] = ($series[$label] ?? 0) + 1; } ksort($series); return $series; } $baseDir = __DIR__; $dataDir = $baseDir . '/data'; $trafficFileA = $dataDir . '/traffic_events.json'; $trafficFileB = $dataDir . '/analytics_events.json'; $trafficFileC = $dataDir . '/traffic_events.ndjson'; $mirrorTrafficLog = dirname(__DIR__) . '/sitelogs/traffic.log'; $rawEvents = read_json_file($trafficFileA); $dataSource = ''; if ($rawEvents !== []) { $dataSource = 'traffic_events.json'; } else { $rawEvents = read_json_file($trafficFileB); if ($rawEvents !== []) { $dataSource = 'analytics_events.json'; } else { $rawEvents = read_ndjson_file($trafficFileC); if ($rawEvents !== []) { $dataSource = 'traffic_events.ndjson'; } else { $rawEvents = load_access_log_events($mirrorTrafficLog); if ($rawEvents !== []) { $dataSource = 'sitelogs/traffic.log'; } } } } $allEvents = $rawEvents !== [] ? normalize_events($rawEvents) : generate_demo_events(); if ($dataSource === '') { $dataSource = 'demo dataset'; } $allDates = array_column($allEvents, 'date'); sort($allDates); $defaultFrom = $allDates ? max($allDates[0], date('Y-m-d', strtotime('-30 days'))) : date('Y-m-d', strtotime('-30 days')); $defaultTo = $allDates ? $allDates[count($allDates) - 1] : date('Y-m-d'); $filters = [ 'date_from' => trim((string)($_GET['date_from'] ?? $defaultFrom)), 'date_to' => trim((string)($_GET['date_to'] ?? $defaultTo)), 'source' => trim((string)($_GET['source'] ?? '')), 'device_type' => trim((string)($_GET['device_type'] ?? '')), 'os' => trim((string)($_GET['os'] ?? '')), 'browser' => trim((string)($_GET['browser'] ?? '')), 'country' => trim((string)($_GET['country'] ?? '')), 'event' => trim((string)($_GET['event'] ?? '')), 'page' => trim((string)($_GET['page'] ?? '')), 'search' => trim((string)($_GET['search'] ?? '')), ]; $filtered = filter_events($allEvents, $filters); $totalEvents = count($filtered); $uniqueVisitors = count(array_unique(array_column($filtered, 'visitor_id'))); $uniqueSessions = count(array_unique(array_column($filtered, 'session_id'))); $totalPageviews = count(array_filter($filtered, fn(array $e): bool => $e['event'] === 'pageview')); $totalConversions = count(array_filter($filtered, fn(array $e): bool => !empty($e['is_conversion']))); $avgDuration = $totalEvents > 0 ? array_sum(array_column($filtered, 'duration_seconds')) / $totalEvents : 0; $conversionRate = $uniqueSessions > 0 ? ($totalConversions / $uniqueSessions) * 100 : 0; $totalBytes = array_sum(array_column($filtered, 'bytes')); $totalErrorEvents = count(array_filter($filtered, fn(array $e): bool => (int)$e['status'] >= 400)); $errorRate = $totalEvents > 0 ? ($totalErrorEvents / $totalEvents) * 100 : 0; $sourceCounts = aggregate_count($filtered, 'source'); $deviceCounts = aggregate_count($filtered, 'device_type'); $osCounts = aggregate_count($filtered, 'os'); $browserCounts = aggregate_count($filtered, 'browser'); $pageCounts = aggregate_count($filtered, 'page'); $countryCounts = aggregate_count($filtered, 'country'); $statusCounts = aggregate_count($filtered, 'status'); $referrerCounts = aggregate_count(array_filter($filtered, fn(array $e): bool => trim((string)$e['referrer']) !== ''), 'referrer'); $dailySeries = build_series($filtered, 'date'); $hourlySeries = build_series($filtered, 'hour'); $hourlySeries += array_fill_keys([ '00:00','01:00','02:00','03:00','04:00','05:00', '06:00','07:00','08:00','09:00','10:00','11:00', '12:00','13:00','14:00','15:00','16:00','17:00', '18:00','19:00','20:00','21:00','22:00','23:00' ], 0); ksort($hourlySeries); $directTraffic = $sourceCounts['direct'] ?? 0; $iosTraffic = $osCounts['iOS'] ?? 0; $mobileTraffic = $deviceCounts['mobile'] ?? 0; $desktopTraffic = $deviceCounts['desktop'] ?? 0; $tabletTraffic = $deviceCounts['tablet'] ?? 0; $topPages = array_slice($pageCounts, 0, 12, true); $topSources = array_slice($sourceCounts, 0, 8, true); $topReferrers = array_slice($referrerCounts, 0, 8, true); $topCountries = array_slice($countryCounts, 0, 8, true); $topStatuses = array_slice($statusCounts, 0, 8, true); $recentEvents = array_slice(array_reverse($filtered), 0, 50); $pageUniqueVisitors = aggregate_unique_visitors_by($filtered, 'page'); $pageUniqueVisitors = array_slice($pageUniqueVisitors, 0, 12, true); $maxDaily = $dailySeries ? max($dailySeries) : 1; $maxHourly = $hourlySeries ? max($hourlySeries) : 1; $maxTopPage = $topPages ? max($topPages) : 1; $maxTopSource = $topSources ? max($topSources) : 1; $sourceOptions = options_from_events($allEvents, 'source'); $deviceOptions = options_from_events($allEvents, 'device_type'); $osOptions = options_from_events($allEvents, 'os'); $browserOptions = options_from_events($allEvents, 'browser'); $countryOptions = options_from_events($allEvents, 'country'); $eventOptions = options_from_events($allEvents, 'event'); ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover"> <title><?= h(APP_NAME) ?></title> <meta name="theme-color" content="#07111f"> <style> :root{ --bg:#06101b; --bg2:#09182b; --panel:rgba(10,20,38,.84); --panel2:rgba(16,28,50,.94); --line:rgba(120,185,255,.16); --text:#edf6ff; --muted:#96abc7; --cyan:#6de9ff; --violet:#a88dff; --blue:#72abff; --green:#67f0b0; --gold:#ffd46f; --red:#ff7f98; --shadow:0 18px 60px rgba(0,0,0,.38); --radius:20px; } *{box-sizing:border-box} html,body{margin:0;padding:0} body{ min-height:100vh; color:var(--text); font:14px/1.5 Inter,system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif; background: radial-gradient(circle at top, rgba(109,233,255,.10), transparent 28%), radial-gradient(circle at 85% 18%, rgba(168,141,255,.12), transparent 26%), linear-gradient(180deg,var(--bg),var(--bg2)); } .wrap{max-width:1580px;margin:0 auto;padding:20px} .hero,.card{ background:linear-gradient(180deg,var(--panel),var(--panel2)); border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow); } .hero{padding:24px;position:relative;overflow:hidden} .hero:before{ content:""; position:absolute; right:-90px;top:-90px; width:320px;height:320px;border-radius:50%; background:radial-gradient(circle, rgba(109,233,255,.18), transparent 68%); filter:blur(14px); } .hero-grid{ display:grid; grid-template-columns:1.15fr .85fr; gap:20px; align-items:center; } .orb{ width:180px;height:180px;border-radius:50%; margin-left:auto; background: radial-gradient(circle at 35% 30%, rgba(255,255,255,.96), rgba(109,233,255,.56) 15%, rgba(114,171,255,.26) 34%, rgba(168,141,255,.14) 52%, transparent 70%); box-shadow:0 0 42px rgba(109,233,255,.28), inset 0 0 30px rgba(255,255,255,.12); position:relative; } .orb:before,.orb:after{ content:""; position:absolute;inset:-16px;border-radius:50%; border:1px solid rgba(109,233,255,.18); animation:spin 12s linear infinite; } .orb:after{ inset:-30px; border-color:rgba(168,141,255,.18); animation-direction:reverse; animation-duration:18s; } @keyframes spin{to{transform:rotate(360deg)}} .kicker{ display:inline-flex;padding:7px 11px;border:1px solid var(--line); border-radius:999px;text-transform:uppercase;letter-spacing:.12em; color:var(--muted);font-size:12px; } h1{margin:12px 0 8px;font-size:clamp(28px,4vw,48px)} .sub{color:#d8e7fb;max-width:920px} .badges{display:flex;gap:10px;flex-wrap:wrap;margin-top:14px} .badge{ display:inline-flex;padding:8px 11px;border-radius:999px; border:1px solid var(--line);background:rgba(255,255,255,.04); font-size:12px;color:#dff6ff; } .grid{display:grid;gap:18px;margin-top:18px} .stats{grid-template-columns:repeat(7,1fr)} .two{grid-template-columns:1.1fr .9fr} .three{grid-template-columns:repeat(3,1fr)} .metric{ padding:14px;border:1px solid rgba(255,255,255,.06); border-radius:16px;background:rgba(255,255,255,.03); } .metric .label{ color:var(--muted);font-size:12px; text-transform:uppercase;letter-spacing:.08em; } .metric .value{font-size:30px;font-weight:800;margin-top:6px} .metric .hint{margin-top:8px;color:var(--muted);font-size:13px} .filters form{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:12px} .filters label{ display:block;color:var(--muted);font-size:11px; text-transform:uppercase;letter-spacing:.08em;margin-bottom:6px; } .filters input,.filters select{ width:100%;padding:12px 13px;background:#081221; border:1px solid rgba(255,255,255,.12);color:var(--text); border-radius:14px;font:inherit; } .filters .actions{display:flex;gap:10px;align-items:end} .btn{ display:inline-flex;align-items:center;justify-content:center; padding:12px 14px;border-radius:14px;border:1px solid var(--line); background:linear-gradient(135deg, rgba(109,233,255,.18), rgba(168,141,255,.16)); color:#fff;text-decoration:none;font-weight:700;cursor:pointer; } .btn.alt{background:rgba(255,255,255,.04)} .table{width:100%;border-collapse:collapse} .table th,.table td{ padding:11px 10px;border-bottom:1px solid rgba(255,255,255,.08); text-align:left;vertical-align:top; } .table th{ font-size:12px;color:var(--muted); text-transform:uppercase;letter-spacing:.08em; } .muted{color:var(--muted)} .bar-list{display:grid;gap:12px} .bar-row{ display:grid;grid-template-columns:220px 1fr 80px; gap:12px;align-items:center; } .bar-track{ height:12px;background:rgba(255,255,255,.08); border-radius:999px;overflow:hidden; } .bar-fill{ height:100%; background:linear-gradient(90deg,var(--cyan),var(--violet),var(--blue)); border-radius:999px; } .spark{ display:flex;align-items:flex-end;gap:6px;height:240px; padding:10px;border-radius:16px;background:rgba(255,255,255,.03); border:1px solid rgba(255,255,255,.06);overflow-x:auto; } .spark .col{ min-width:18px;display:flex;flex-direction:column; align-items:center;justify-content:flex-end;gap:8px; } .spark .bar{ width:100%;border-radius:10px 10px 4px 4px; background:linear-gradient(180deg,var(--cyan),var(--violet)); min-height:2px; } .spark .lab{ font-size:10px;color:var(--muted); writing-mode:vertical-rl;transform:rotate(180deg); max-height:72px;overflow:hidden; } .spark .num{font-size:10px;color:#dff6ff} .pills{display:flex;gap:10px;flex-wrap:wrap} .pill{ padding:10px 12px;border-radius:999px;background:rgba(255,255,255,.04); border:1px solid var(--line);font-size:12px;color:#dff3ff; text-transform:uppercase;letter-spacing:.06em; } .note{ padding:14px;border-radius:16px;background:rgba(255,255,255,.03); border:1px solid rgba(255,255,255,.06);margin-bottom:12px; } .split{display:grid;grid-template-columns:1fr 1fr;gap:18px} @media (max-width:1300px){ .stats,.filters form,.three,.two,.split{grid-template-columns:repeat(2,1fr)} .hero-grid{grid-template-columns:1fr} .orb{margin:10px auto 0} } @media (max-width:780px){ .stats,.filters form,.three,.two,.split{grid-template-columns:1fr} .bar-row{grid-template-columns:1fr} } </style> </head> <body> <div class="wrap"> <section class="hero"> <div class="kicker">SIMON Analytics Console</div> <div class="hero-grid"> <div> <h1><?= h(APP_NAME) ?></h1> <div class="sub"> <?= h(APP_SUBTITLE) ?> — single-file PHP dashboard for traffic, top pages, direct traffic, iOS traffic, device and OS breakdowns, referrers, geographies, date filters, JSON event streams, and mirrored Apache access logs. </div> <div class="badges"> <span class="badge">Events: <?= h(num($totalEvents)) ?></span> <span class="badge">Visitors: <?= h(num($uniqueVisitors)) ?></span> <span class="badge">Sessions: <?= h(num($uniqueSessions)) ?></span> <span class="badge">Source: <?= h($dataSource) ?></span> </div> </div> <div class="orb" aria-hidden="true"></div> </div> </section> <section class="card filters" style="margin-top:18px;"> <div class="head"> <h2>Filters</h2> <span class="mini">date · source · device · os · page · search</span> </div> <form method="get"> <div> <label>Date From</label> <input type="date" name="date_from" value="<?= h($filters['date_from']) ?>"> </div> <div> <label>Date To</label> <input type="date" name="date_to" value="<?= h($filters['date_to']) ?>"> </div> <div> <label>Source</label> <select name="source"> <option value="">All</option> <?php foreach ($sourceOptions as $o): ?> <option value="<?= h($o) ?>" <?= $filters['source'] === $o ? 'selected' : '' ?>><?= h(ucfirst($o)) ?></option> <?php endforeach; ?> </select> </div> <div> <label>Device</label> <select name="device_type"> <option value="">All</option> <?php foreach ($deviceOptions as $o): ?> <option value="<?= h($o) ?>" <?= $filters['device_type'] === $o ? 'selected' : '' ?>><?= h(ucfirst($o)) ?></option> <?php endforeach; ?> </select> </div> <div> <label>OS</label> <select name="os"> <option value="">All</option> <?php foreach ($osOptions as $o): ?> <option value="<?= h($o) ?>" <?= $filters['os'] === $o ? 'selected' : '' ?>><?= h($o) ?></option> <?php endforeach; ?> </select> </div> <div> <label>Browser</label> <select name="browser"> <option value="">All</option> <?php foreach ($browserOptions as $o): ?> <option value="<?= h($o) ?>" <?= $filters['browser'] === $o ? 'selected' : '' ?>><?= h($o) ?></option> <?php endforeach; ?> </select> </div> <div> <label>Country</label> <select name="country"> <option value="">All</option> <?php foreach ($countryOptions as $o): ?> <option value="<?= h($o) ?>" <?= $filters['country'] === $o ? 'selected' : '' ?>><?= h($o) ?></option> <?php endforeach; ?> </select> </div> <div> <label>Event</label> <select name="event"> <option value="">All</option> <?php foreach ($eventOptions as $o): ?> <option value="<?= h($o) ?>" <?= $filters['event'] === $o ? 'selected' : '' ?>><?= h($o) ?></option> <?php endforeach; ?> </select> </div> <div> <label>Page Contains</label> <input type="text" name="page" value="<?= h($filters['page']) ?>" placeholder="/web360/"> </div> <div> <label>Search</label> <input type="text" name="search" value="<?= h($filters['search']) ?>" placeholder="Dallas, Safari, google.com"> </div> <div class="actions"> <button class="btn" type="submit">Apply Filters</button> <a class="btn alt" href="?">Reset</a> </div> </form> </section> <section class="grid stats"> <div class="metric"> <div class="label">Traffic Events</div> <div class="value"><?= h(compact_num($totalEvents)) ?></div> <div class="hint">All filtered events</div> </div> <div class="metric"> <div class="label">Pageviews</div> <div class="value"><?= h(compact_num($totalPageviews)) ?></div> <div class="hint">Pageview events</div> </div> <div class="metric"> <div class="label">Unique Visitors</div> <div class="value"><?= h(compact_num($uniqueVisitors)) ?></div> <div class="hint">Distinct visitor IDs</div> </div> <div class="metric"> <div class="label">Direct Traffic</div> <div class="value"><?= h(compact_num($directTraffic)) ?></div> <div class="hint"><?= h($totalEvents > 0 ? pct(($directTraffic / $totalEvents) * 100) : '0%') ?> of events</div> </div> <div class="metric"> <div class="label">iOS Traffic</div> <div class="value"><?= h(compact_num($iosTraffic)) ?></div> <div class="hint"><?= h($totalEvents > 0 ? pct(($iosTraffic / $totalEvents) * 100) : '0%') ?> of events</div> </div> <div class="metric"> <div class="label">Conversions</div> <div class="value"><?= h(compact_num($totalConversions)) ?></div> <div class="hint"><?= h(pct($conversionRate)) ?> of sessions</div> </div> <div class="metric"> <div class="label">Transferred</div> <div class="value"><?= h(fmt_bytes($totalBytes)) ?></div> <div class="hint"><?= h(pct($errorRate)) ?> error-rate</div> </div> </section> <section class="grid two"> <div class="card"> <div class="head"> <h2>Daily Traffic Trend</h2> <span class="mini">events by day</span> </div> <div class="spark"> <?php foreach ($dailySeries as $label => $value): ?> <?php $height = max(4, (int)round(($value / max(1, $maxDaily)) * 180)); ?> <div class="col"> <div class="num"><?= h((string)$value) ?></div> <div class="bar" style="height:<?= $height ?>px"></div> <div class="lab"><?= h(substr($label, 5)) ?></div> </div> <?php endforeach; ?> </div> </div> <div class="card"> <div class="head"> <h2>Hourly Distribution</h2> <span class="mini">events by hour</span> </div> <div class="spark"> <?php foreach ($hourlySeries as $label => $value): ?> <?php $height = max(4, (int)round(($value / max(1, $maxHourly)) * 180)); ?> <div class="col"> <div class="num"><?= h((string)$value) ?></div> <div class="bar" style="height:<?= $height ?>px"></div> <div class="lab"><?= h(substr($label, 0, 2)) ?></div> </div> <?php endforeach; ?> </div> </div> </section> <section class="grid split"> <div class="card"> <div class="head"> <h2>Traffic Mix</h2> <span class="mini">source snapshot</span> </div> <div class="pills"> <span class="pill">Mobile <?= h(num($mobileTraffic)) ?></span> <span class="pill">Desktop <?= h(num($desktopTraffic)) ?></span> <span class="pill">Tablet <?= h(num($tabletTraffic)) ?></span> <span class="pill">Avg Duration <?= h(num(round($avgDuration))) ?>s</span> </div> <div class="bar-list" style="margin-top:14px;"> <?php foreach ($topSources as $label => $value): ?> <div class="bar-row"> <div><?= h(ucfirst($label)) ?></div> <div class="bar-track"> <div class="bar-fill" style="width:<?= max(2, round(($value / max(1, $maxTopSource)) * 100)) ?>%"></div> </div> <div class="value"><?= h(num($value)) ?></div> </div> <?php endforeach; ?> </div> </div> <div class="card"> <div class="head"> <h2>Device / OS Snapshot</h2> <span class="mini">platform mix</span> </div> <table class="table"> <thead> <tr> <th>OS</th> <th>Events</th> <th>Share</th> </tr> </thead> <tbody> <?php foreach (array_slice($osCounts, 0, 10, true) as $label => $value): ?> <tr> <td><?= h($label) ?></td> <td><?= h(num($value)) ?></td> <td><?= h($totalEvents > 0 ? pct(($value / $totalEvents) * 100) : '0%') ?></td> </tr> <?php endforeach; ?> </tbody> </table> </div> </section> <section class="grid two"> <div class="card"> <div class="head"> <h2>Top Pages</h2> <span class="mini">pageviews by path</span> </div> <div class="bar-list"> <?php foreach ($topPages as $label => $value): ?> <div class="bar-row"> <div><?= h(short_text($label, 44)) ?></div> <div class="bar-track"> <div class="bar-fill" style="width:<?= max(2, round(($value / max(1, $maxTopPage)) * 100)) ?>%"></div> </div> <div class="value"><?= h(num($value)) ?></div> </div> <?php endforeach; ?> </div> </div> <div class="card"> <div class="head"> <h2>Unique Visitors by Top Page</h2> <span class="mini">audience depth</span> </div> <table class="table"> <thead> <tr> <th>Page</th> <th>Visitors</th> </tr> </thead> <tbody> <?php foreach ($pageUniqueVisitors as $label => $value): ?> <tr> <td><?= h(short_text($label, 48)) ?></td> <td><?= h(num($value)) ?></td> </tr> <?php endforeach; ?> </tbody> </table> </div> </section> <section class="grid three"> <div class="card"> <div class="head"> <h2>Top Referrers</h2> <span class="mini">external sources</span> </div> <table class="table"> <thead> <tr><th>Referrer</th><th>Events</th></tr> </thead> <tbody> <?php if (!$topReferrers): ?> <tr><td colspan="2" class="muted">No referrers in current filter.</td></tr> <?php endif; ?> <?php foreach ($topReferrers as $label => $value): ?> <tr> <td><?= h(short_text($label, 42)) ?></td> <td><?= h(num($value)) ?></td> </tr> <?php endforeach; ?> </tbody> </table> </div> <div class="card"> <div class="head"> <h2>Browsers</h2> <span class="mini">browser breakdown</span> </div> <table class="table"> <thead> <tr><th>Browser</th><th>Events</th></tr> </thead> <tbody> <?php foreach (array_slice($browserCounts, 0, 10, true) as $label => $value): ?> <tr> <td><?= h($label) ?></td> <td><?= h(num($value)) ?></td> </tr> <?php endforeach; ?> </tbody> </table> </div> <div class="card"> <div class="head"> <h2>Countries</h2> <span class="mini">geo view</span> </div> <table class="table"> <thead> <tr><th>Country</th><th>Events</th></tr> </thead> <tbody> <?php foreach ($topCountries as $label => $value): ?> <tr> <td><?= h($label) ?></td> <td><?= h(num($value)) ?></td> </tr> <?php endforeach; ?> </tbody> </table> </div> </section> <section class="grid two"> <div class="card"> <div class="head"> <h2>Status Codes</h2> <span class="mini">http status mix</span> </div> <table class="table"> <thead> <tr><th>Status</th><th>Events</th></tr> </thead> <tbody> <?php foreach ($topStatuses as $label => $value): ?> <tr> <td><?= h((string)$label) ?></td> <td><?= h(num($value)) ?></td> </tr> <?php endforeach; ?> </tbody> </table> </div> <div class="card"> <div class="head"> <h2>Source Diagnostics</h2> <span class="mini">ingestion visibility</span> </div> <div class="note"> <strong>Data source:</strong> <?= h($dataSource) ?><br> <strong>JSON A:</strong> <?= h(is_file($trafficFileA) ? 'present' : 'missing') ?><br> <strong>JSON B:</strong> <?= h(is_file($trafficFileB) ? 'present' : 'missing') ?><br> <strong>NDJSON:</strong> <?= h(is_file($trafficFileC) ? 'present' : 'missing') ?><br> <strong>Mirrored Apache log:</strong> <?= h(is_file($mirrorTrafficLog) ? 'present' : 'missing') ?><br> <strong>Readable mirrored log:</strong> <?= h(is_readable($mirrorTrafficLog) ? 'yes' : 'no') ?> </div> <div class="note"> <strong>Why this matters:</strong> this dashboard now prefers structured event files, but can still fall back to your live mirrored <code>/sitelogs/traffic.log</code> access log and normalize it into analytics events. </div> </div> </section> <section class="card"> <div class="head"> <h2>Recent Event Feed</h2> <span class="mini">latest 50 rows</span> </div> <table class="table"> <thead> <tr> <th>Time</th> <th>Page</th> <th>Source</th> <th>Device</th> <th>OS</th> <th>Browser</th> <th>Country</th> <th>Status</th> <th>Referrer</th> <th>Duration</th> </tr> </thead> <tbody> <?php if (!$recentEvents): ?> <tr> <td colspan="10" class="muted">No events match the current filters.</td> </tr> <?php else: ?> <?php foreach ($recentEvents as $event): ?> <tr> <td><?= h($event['timestamp']) ?></td> <td><?= h(short_text($event['page'], 38)) ?></td> <td><?= h(ucfirst($event['source'])) ?></td> <td><?= h(ucfirst($event['device_type'])) ?></td> <td><?= h($event['os']) ?></td> <td><?= h($event['browser']) ?></td> <td><?= h($event['country']) ?></td> <td><?= h((string)$event['status']) ?></td> <td><?= h($event['referrer'] !== '' ? short_text($event['referrer'], 34) : '—') ?></td> <td><?= h((string)$event['duration_seconds']) ?>s</td> </tr> <?php endforeach; ?> <?php endif; ?> </tbody> </table> </section> <section class="card"> <div class="head"> <h2>Install Notes</h2> <span class="mini">drop-in setup</span> </div> <div class="note"> Put this file at <code>/simon/analytics.php</code>. For live structured data, create <code>/simon/data/traffic_events.json</code>, <code>/simon/data/analytics_events.json</code>, or <code>/simon/data/traffic_events.ndjson</code>. </div> <div class="note"> If those files do not exist yet, this dashboard will automatically read and normalize <code>/sitelogs/traffic.log</code> so your SIMON analytics screen still works from mirrored Apache access data. </div> </section> </div> </body> </html>
Save file
Quick jump
open a path
Open