176 lines
7.3 KiB
PHP
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>
|