Files
whatsapp/webhook_logs.html
2026-01-20 22:13:02 -05:00

197 lines
6.8 KiB
HTML

<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Webhook Logs - Tiempo Real</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
pre.json { max-height: 60vh; overflow: auto; background: #f8f9fa; padding: 1rem; border-radius: 6px; }
.new-row { animation: highlight 1.5s ease; }
@keyframes highlight { from { background: #e6ffed } to { background: transparent } }
</style>
</head>
<body>
<div class="container py-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h3><i class="fab fa-whatsapp text-success"></i> Webhook Logs (Tiempo Real)</h3>
<div>
<input id="searchInput" class="form-control d-inline-block" style="width:260px" placeholder="Buscar ID o texto...">
</div>
</div>
<div class="mb-2">
<button class="btn btn-sm btn-outline-secondary" id="refreshBtn">Actualizar</button>
<label class="ms-3">Auto-refresh: <input id="autoRefresh" type="checkbox" checked></label>
</div>
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-body p-2">
<table class="table table-sm table-hover mb-0">
<thead class="table-light">
<tr>
<th>ID</th>
<th>Timestamp</th>
<th>Snippet</th>
<th>IP</th>
<th>Acción</th>
</tr>
</thead>
<tbody id="logsTable">
<tr><td colspan="5" class="text-center py-4 text-muted">Cargando...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h5>Payload (crudo)</h5>
<pre id="rawPayload" class="json">Selecciona un registro para ver el payload completo</pre>
<div class="mt-2 d-flex gap-2">
<button id="downloadBtn" class="btn btn-sm btn-outline-primary" disabled>Descargar JSON</button>
<button id="replayBtn" class="btn btn-sm btn-outline-success" disabled>Reproducir (Simular)</button>
</div>
</div>
</div>
</div>
</div>
<div class="mt-3 text-muted">Nota: Esta vista muestra los webhooks crudos como llegaron y se actualiza cada 3s (auto-refresh).</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/js/all.min.js"></script>
<script>
const API_LIST = 'api/get_webhook_logs.php?debug=true';
const API_GET = 'api/get_webhook_log.php?debug=true';
let auto = true;
let latestId = 0;
let pollingInterval = 3000;
let lastSelected = null;
async function fetchLogs(sinceId = 0, search = '') {
const params = new URLSearchParams();
if (sinceId) params.set('since_id', sinceId);
if (search) params.set('search', search);
params.set('limit', 30);
const res = await fetch(API_LIST + '&' + params.toString());
return await res.json();
}
async function refresh(full = true) {
const search = document.getElementById('searchInput').value.trim();
try {
const data = await fetchLogs(full ? 0 : latestId, search);
if (!data.success) { console.error(data); return; }
const rows = data.data || [];
if (!full && rows.length) {
// append new rows
prependRows(rows.reverse()); // since API returns ascending when since_id used
} else {
renderFullTable(rows);
}
if (rows.length) {
latestId = Math.max(...rows.map(r => r.id), latestId);
}
} catch (e) { console.error(e); }
}
function renderFullTable(rows) {
const tbody = document.getElementById('logsTable');
if (!rows || rows.length === 0) {
tbody.innerHTML = '<tr><td colspan="5" class="text-center text-muted">No hay registros</td></tr>';
return;
}
tbody.innerHTML = rows.map(r => rowHtml(r)).join('');
}
function prependRows(rows) {
const tbody = document.getElementById('logsTable');
rows.forEach(r => {
const tr = document.createElement('tr');
tr.innerHTML = rowHtml(r);
tr.classList.add('new-row');
tbody.insertBefore(tr, tbody.firstChild);
});
}
function rowHtml(r) {
const snippet = (r.snippet || '').replace(/</g, '&lt;').substring(0, 120);
return `
<tr data-id="${r.id}" onclick="selectLog(${r.id}, this)">
<td>${r.id}</td>
<td>${r.created_at}</td>
<td><small class="text-muted">${snippet}</small></td>
<td>${r.ip_address || ''}</td>
<td><button class="btn btn-sm btn-outline-primary" onclick="event.stopPropagation(); viewRaw(${r.id})">Ver</button></td>
</tr>
`;
}
async function viewRaw(id) {
const res = await fetch(API_GET + '&id=' + id);
const data = await res.json();
if (!data.success) { alert(data.error || 'Error'); return; }
const payload = data.data;
lastSelected = payload;
document.getElementById('rawPayload').textContent = payload.request_body_pretty || payload.request_body || '';
document.getElementById('downloadBtn').disabled = false;
document.getElementById('replayBtn').disabled = false;
}
function selectLog(id, el) {
// highlight
const rows = document.querySelectorAll('#logsTable tr');
rows.forEach(r => r.classList.remove('table-active'));
el.classList.add('table-active');
viewRaw(id);
}
function downloadSelected() {
if (!lastSelected) return;
const blob = new Blob([lastSelected.request_body || ''], {type: 'application/json'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `webhook_${lastSelected.id}.json`;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
}
async function replaySelected() {
if (!lastSelected) return alert('Selecciona un registro primero');
try {
const payload = lastSelected.request_body;
const resp = await fetch('api/webhook_replay.php', {method: 'POST', headers: {'Content-Type':'application/json'}, body: payload});
const data = await resp.json();
alert(data.success ? 'Reproducción ejecutada' : ('Error: ' + (data.error || '')));
} catch (e) {
alert('Error reproduciendo: ' + e.message);
}
}
// Event listeners
document.getElementById('refreshBtn').addEventListener('click', () => refresh(true));
document.getElementById('autoRefresh').addEventListener('change', (e) => { auto = e.target.checked; });
document.getElementById('downloadBtn').addEventListener('click', downloadSelected);
document.getElementById('replayBtn').addEventListener('click', replaySelected);
document.getElementById('searchInput').addEventListener('input', (e) => { setTimeout(()=>refresh(true), 350); });
// Polling loop
setInterval(() => { if (!auto) return; refresh(false); }, pollingInterval);
// Initial load
refresh(true);
</script>
</body>
</html>