Files
2026-03-10 18:42:51 -05:00

176 lines
7.3 KiB
PHP

<?php
/**
* Visor de logs del sistema - Accesible desde el navegador
* URL: /view_logs.php
*/
session_start();
require_once __DIR__ . '/config/config.php';
// Proteger: solo admins
if (!isset($_SESSION['admin_logged_in']) && !isset($_SESSION['user_id'])) {
http_response_code(403);
echo 'No autorizado';
exit;
}
$logFiles = [
'PHP Errors' => '/var/log/php-error.log',
'Nginx Errors' => '/var/log/nginx/whatsapp-error.log',
'Nginx Access' => '/var/log/nginx/whatsapp-access.log',
'App System' => __DIR__ . '/logs/system.log',
'App Errors' => __DIR__ . '/logs/app_errors.log',
'Worker (hoy)' => __DIR__ . '/logs/worker-' . date('Y-m-d') . '.log',
'Scheduled' => __DIR__ . '/logs/scheduled_messages.log',
'Supervisor PHP-FPM' => '/var/log/supervisor/php-fpm-error.log',
'Supervisor Worker 0' => '/var/log/supervisor/worker-00-error.log',
'Supervisor Worker 1' => '/var/log/supervisor/worker-01-error.log',
];
$selected = $_GET['log'] ?? 'App Errors';
$lines = intval($_GET['lines'] ?? 100);
$filter = trim($_GET['filter'] ?? '');
$content = '';
$selectedFile = $logFiles[$selected] ?? '';
if ($selectedFile && file_exists($selectedFile)) {
// Leer últimas N líneas sin exec()
$allLines = file($selectedFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if ($allLines === false) $allLines = [];
$allLines = array_slice($allLines, -$lines);
if ($filter) {
$allLines = array_filter($allLines, function($line) use ($filter) {
return stripos($line, $filter) !== false;
});
}
$content = implode("\n", $allLines);
} elseif ($selectedFile) {
$content = "(Archivo no existe: {$selectedFile})";
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>📋 Visor de Logs</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
<style>
body { background: #1a1a2e; color: #e0e0e0; font-family: monospace; }
.log-content {
background: #0d1117;
color: #c9d1d9;
padding: 16px;
border-radius: 8px;
font-size: 12px;
line-height: 1.6;
white-space: pre-wrap;
word-break: break-all;
max-height: 75vh;
overflow-y: auto;
border: 1px solid #30363d;
}
.log-content .error-line { color: #f85149; font-weight: bold; }
.log-content .warning-line { color: #d29922; }
.log-content .info-line { color: #58a6ff; }
.toolbar { background: #161b22; padding: 12px 16px; border-radius: 8px; margin-bottom: 12px; border: 1px solid #30363d; }
.btn-log { border-radius: 6px; font-size: 12px; }
.btn-log.active { background: #238636; border-color: #238636; }
h4 { color: #58a6ff; }
.badge-file { font-size: 10px; color: #8b949e; }
#auto-refresh-indicator { display: none; }
#auto-refresh-indicator.active { display: inline-block; animation: blink 1s infinite; }
@keyframes blink { 0%,100% { opacity: 1; } 50% { opacity: 0.3; } }
</style>
</head>
<body>
<div class="container-fluid p-3">
<div class="d-flex justify-content-between align-items-center mb-3">
<h4 class="mb-0">📋 Visor de Logs del Sistema</h4>
<div>
<a href="conversations.php" class="btn btn-sm btn-outline-light">← Volver</a>
<button onclick="location.reload()" class="btn btn-sm btn-outline-info ms-2">🔄 Refrescar</button>
<button id="auto-refresh-btn" onclick="toggleAutoRefresh()" class="btn btn-sm btn-outline-warning ms-2">⏱ Auto-refresh</button>
<span id="auto-refresh-indicator" class="ms-2 text-warning">●</span>
</div>
</div>
<div class="toolbar">
<form method="GET" class="d-flex gap-2 align-items-center flex-wrap">
<div>
<select name="log" class="form-select form-select-sm" style="background:#0d1117; color:#c9d1d9; border-color:#30363d;" onchange="this.form.submit()">
<?php foreach ($logFiles as $name => $path): ?>
<option value="<?= htmlspecialchars($name) ?>" <?= $name === $selected ? 'selected' : '' ?>>
<?= htmlspecialchars($name) ?> <?= file_exists($path) ? '' : '(no existe)' ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div>
<select name="lines" class="form-select form-select-sm" style="background:#0d1117; color:#c9d1d9; border-color:#30363d;">
<?php foreach ([50, 100, 200, 500, 1000] as $n): ?>
<option value="<?= $n ?>" <?= $lines === $n ? 'selected' : '' ?>><?= $n ?> líneas</option>
<?php endforeach; ?>
</select>
</div>
<div class="flex-grow-1">
<input type="text" name="filter" class="form-control form-control-sm" placeholder="Filtrar por texto..."
value="<?= htmlspecialchars($filter) ?>" style="background:#0d1117; color:#c9d1d9; border-color:#30363d;">
</div>
<button type="submit" class="btn btn-sm btn-primary btn-log">Aplicar</button>
<?php if ($filter): ?>
<a href="?log=<?= urlencode($selected) ?>&lines=<?= $lines ?>" class="btn btn-sm btn-outline-secondary btn-log">Limpiar filtro</a>
<?php endif; ?>
</form>
<?php if ($selectedFile): ?>
<div class="badge-file mt-1">📁 <?= htmlspecialchars($selectedFile) ?></div>
<?php endif; ?>
</div>
<div class="log-content" id="log-content"><?php
if (!$content) {
echo "(vacío)";
} else {
// Colorear líneas según nivel
$lines_arr = explode("\n", htmlspecialchars($content));
foreach ($lines_arr as $line) {
if (preg_match('/error|fatal|exception|fail/i', $line)) {
echo '<span class="error-line">' . $line . '</span>' . "\n";
} elseif (preg_match('/warning|warn/i', $line)) {
echo '<span class="warning-line">' . $line . '</span>' . "\n";
} elseif (preg_match('/info|notice/i', $line)) {
echo '<span class="info-line">' . $line . '</span>' . "\n";
} else {
echo $line . "\n";
}
}
}
?></div>
</div>
<script>
// Auto-scroll al final
const logEl = document.getElementById('log-content');
logEl.scrollTop = logEl.scrollHeight;
let autoRefreshInterval = null;
function toggleAutoRefresh() {
const btn = document.getElementById('auto-refresh-btn');
const indicator = document.getElementById('auto-refresh-indicator');
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
autoRefreshInterval = null;
btn.classList.remove('btn-warning');
btn.classList.add('btn-outline-warning');
indicator.classList.remove('active');
} else {
autoRefreshInterval = setInterval(() => location.reload(), 5000);
btn.classList.remove('btn-outline-warning');
btn.classList.add('btn-warning');
indicator.classList.add('active');
}
}
</script>
</body>
</html>