diff options
Diffstat (limited to 'api/debug/dashboard.php')
| -rw-r--r-- | api/debug/dashboard.php | 200 |
1 files changed, 200 insertions, 0 deletions
diff --git a/api/debug/dashboard.php b/api/debug/dashboard.php new file mode 100644 index 0000000..7a46661 --- /dev/null +++ b/api/debug/dashboard.php @@ -0,0 +1,200 @@ +<?php +require_once __DIR__ . '/config.php'; +header('Content-Type: text/html; charset=utf-8'); + +$errors = load_json(ERRORS_FILE); +$sessions = load_json(SESSIONS_FILE); + +$totalErrors = count($errors); +$totalSessions = count($sessions); +$byType = []; +$bySeverity = []; +$byCategory = []; +$byModule = []; +$byDay = []; +$topErrors = []; + +foreach ($errors as $e) { + $t = $e['type'] ?? 'unknown'; + $byType[$t] = ($byType[$t] ?? 0) + ($e['count'] ?? 1); + $s = $e['severity'] ?? 'error'; + $bySeverity[$s] = ($bySeverity[$s] ?? 0) + ($e['count'] ?? 1); + $m = $e['module'] ?? 'unknown'; + $byModule[$m] = ($byModule[$m] ?? 0) + ($e['count'] ?? 1); + $c = $e['category'] ?? 'runtime'; + $byCategory[$c] = ($byCategory[$c] ?? 0) + ($e['count'] ?? 1); + $day = substr($e['ingested_at'] ?? $e['last_seen'], 0, 10); + $byDay[$day] = ($byDay[$day] ?? 0) + ($e['count'] ?? 1); + + $hash = $e['hash'] ?? ''; + if ($hash) { + if (!isset($topErrors[$hash])) { + $topErrors[$hash] = ['hash' => $hash, 'message' => $e['message'] ?? '', 'count' => 0, 'type' => $t]; + } + $topErrors[$hash]['count'] += $e['count'] ?? 1; + } +} +usort($topErrors, fn($a, $b) => $b['count'] - $a['count']); +$topErrors = array_slice($topErrors, 0, 10); +ksort($byDay); + +$recentSessions = array_slice($sessions, -10, 10, true); +?> +<!DOCTYPE html> +<html lang="en"> +<head> +<meta charset="UTF-8"> +<meta name="viewport" content="width=device-width, initial-scale=1.0"> +<title>newOS4 Debug Dashboard</title> +<style> + :root { --bg: #0d0d12; --surface: #16161e; --border: #1e1e2a; --text: #e8e8f0; --muted: #888; --accent: #00e5c8; --error: #ff5555; --warn: #ffaa00; --info: #00aaff; } + * { box-sizing: border-box; margin: 0; padding: 0; } + body { background: var(--bg); color: var(--text); font-family: 'Courier New', monospace; padding: 20px; } + h1 { color: var(--accent); font-size: 1.4em; margin-bottom: 4px; } + .subtitle { color: var(--muted); font-size: 0.85em; margin-bottom: 20px; } + .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 12px; margin-bottom: 20px; } + .card { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 14px; } + .card h3 { font-size: 0.8em; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px; } + .stat-value { font-size: 2em; font-weight: bold; } + .stat-value.error { color: var(--error); } + .stat-value.warn { color: var(--warn); } + .stat-value.ok { color: var(--accent); } + table { width: 100%; border-collapse: collapse; font-size: 0.85em; } + th { text-align: left; color: var(--muted); padding: 6px 8px; border-bottom: 1px solid var(--border); font-size: 0.8em; text-transform: uppercase; } + td { padding: 6px 8px; border-bottom: 1px solid var(--border); } + .bar { display: inline-block; height: 6px; border-radius: 3px; background: var(--accent); margin-right: 6px; vertical-align: middle; } + .bar.error { background: var(--error); } + .bar.warn { background: var(--warn); } + .tag { display: inline-block; padding: 1px 6px; border-radius: 3px; font-size: 0.75em; background: var(--border); } + .tag.error { background: #3a1010; color: var(--error); } + .tag.warn { background: #3a2a00; color: var(--warn); } + .tag.ok { background: #002a20; color: var(--accent); } + .refresh { color: var(--muted); font-size: 0.8em; margin-top: 20px; text-align: center; } + a { color: var(--accent); text-decoration: none; } + a:hover { text-decoration: underline; } + .max-w-300 { max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +</style> +</head> +<body> +<h1>🔍 newOS4 Debug Dashboard</h1> +<div class="subtitle">Server-side error aggregation · <?= date('Y-m-d H:i:s') ?> · <a href="stats.php">JSON Stats</a> · <a href="clusters.php">Clusters</a> · <a href="errors.php">Raw Errors</a> · <a href="health.php">Health</a></div> + +<?php +// Find last sync time +$lastSync = null; +if (!empty($errors)) { + $lastSync = end($errors)['ingested_at'] ?? null; +} +if ($lastSync): ?> +<div style="color:var(--muted);font-size:0.8em;margin-bottom:16px;">Last sync: <?= htmlspecialchars(substr($lastSync, 0, 19)) ?> UTC</div> +<?php endif; ?> + +<div class="grid"> + <div class="card"> + <h3>Total Errors</h3> + <div class="stat-value <?= $totalErrors > 0 ? 'error' : 'ok' ?>"><?= $totalErrors ?></div> + </div> + <div class="card"> + <h3>Sessions</h3> + <div class="stat-value"><?= $totalSessions ?></div> + </div> + <div class="card"> + <h3>Error Types</h3> + <div class="stat-value"><?= count($byType) ?></div> + </div> + <div class="card"> + <h3>Modules Affected</h3> + <div class="stat-value"><?= count($byModule) ?></div> + </div> +</div> + +<div class="grid"> + <div class="card"> + <h3>By Severity</h3> + <table> + <?php foreach (['fatal' => 'error', 'error' => 'error', 'warning' => 'warn'] as $sev => $cls): ?> + <tr><td><span class="tag <?= $cls ?>"><?= $sev ?></span></td><td><?= $bySeverity[$sev] ?? 0 ?></td></tr> + <?php endforeach; ?> + </table> + </div> + <div class="card"> + <h3>By Category</h3> + <table> + <?php foreach (['network', 'module', 'runtime', 'vfs', 'security', 'tool'] as $cat): ?> + <?php if (($byCategory[$cat] ?? 0) > 0): ?> + <tr><td><?= $cat ?></td><td><?= $byCategory[$cat] ?? 0 ?></td></tr> + <?php endif; ?> + <?php endforeach; ?> + </table> + </div> + <div class="card"> + <h3>By Module</h3> + <table> + <?php + arsort($byModule); + $i = 0; + foreach (array_slice($byModule, 0, 8) as $mod => $count): + if ($i++ > 5) break; + ?> + <tr><td class="max-w-300"><?= htmlspecialchars($mod) ?></td><td><?= $count ?></td></tr> + <?php endforeach; ?> + </table> + </div> +</div> + +<div class="card" style="margin-bottom:20px;"> + <h3>Top Errors</h3> + <table> + <tr><th>#</th><th>Message</th><th>Type</th><th>Count</th></tr> + <?php $i = 1; foreach ($topErrors as $e): ?> + <tr> + <td><?= $i++ ?></td> + <td class="max-w-300"><a href="error.php?hash=<?= urlencode($e['hash'] ?? '') ?>" style="color:var(--text);text-decoration:none;" title="View details"><?= htmlspecialchars(substr($e['message'] ?? '', 0, 80)) ?></a></td> + <td><span class="tag"><?= htmlspecialchars($e['type'] ?? '?') ?></span></td> + <td><?= $e['count'] ?></td> + </tr> + <?php endforeach; ?> + <?php if (empty($topErrors)): ?> + <tr><td colspan="4" style="color:var(--muted);text-align:center;">No errors recorded</td></tr> + <?php endif; ?> + </table> +</div> + +<div class="card" style="margin-bottom:20px;"> + <h3>Daily Trend (last 14 days)</h3> + <table> + <tr><th>Day</th><th>Errors</th><th></th></tr> + <?php + $maxDay = max($byDay) ?: 1; + $days = array_slice($byDay, -14, 14, true); + foreach ($days as $day => $count): + $pct = round($count / $maxDay * 100, 0); + ?> + <tr> + <td><?= $day ?></td> + <td><?= $count ?></td> + <td><div class="bar" style="width:<?= $pct ?>%;"></div></td> + </tr> + <?php endforeach; ?> + </table> +</div> + +<div class="card"> + <h3>Recent Sessions</h3> + <table> + <tr><th>Session</th><th>Version</th><th>Errors</th><th>Last Seen</th></tr> + <?php foreach ($recentSessions as $sid => $s): ?> + <tr> + <td class="max-w-300"><a href="errors.php?session=<?= urlencode($sid) ?>" style="color:var(--text);text-decoration:none;" title="View this session's errors"><?= htmlspecialchars($sid) ?></a></td> + <td><?= htmlspecialchars($s['version'] ?? '?') ?></td> + <td><?= $s['error_count'] ?? 0 ?></td> + <td><?= htmlspecialchars(substr($s['last_seen'] ?? '', 0, 16)) ?></td> + </tr> + <?php endforeach; ?> + </table> +</div> + +<div class="refresh">Auto-refresh every 30s</div> +<script>setTimeout(() => location.reload(), 30000);</script> +</body> +</html> |