Files
SneakyScan/app/web/templates/webhooks/logs.html

329 lines
12 KiB
HTML

{% extends "base.html" %}
{% block title %}Webhook Logs - {{ webhook.name }} - SneakyScanner{% endblock %}
{% block content %}
<div class="row mt-4">
<div class="col-12 mb-4">
<h1 style="color: #60a5fa;">Webhook Delivery Logs</h1>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ url_for('webhooks.list_webhooks') }}">Webhooks</a></li>
<li class="breadcrumb-item active">{{ webhook.name }}</li>
</ol>
</nav>
</div>
</div>
<!-- Webhook Info -->
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-md-6">
<h5 class="card-title">{{ webhook.name }}</h5>
<p class="text-muted mb-1"><strong>URL:</strong> <code>{{ webhook.url }}</code></p>
<p class="text-muted mb-0">
<strong>Status:</strong>
{% if webhook.enabled %}
<span class="badge bg-success">Enabled</span>
{% else %}
<span class="badge bg-secondary">Disabled</span>
{% endif %}
</p>
</div>
<div class="col-md-6 text-md-end">
<a href="{{ url_for('webhooks.edit_webhook', webhook_id=webhook.id) }}" class="btn btn-primary">
<i class="bi bi-pencil"></i> Edit Webhook
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Filters -->
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-body">
<div class="row g-3">
<div class="col-md-4">
<label for="status-filter" class="form-label">Status</label>
<select class="form-select" id="status-filter">
<option value="">All</option>
<option value="success">Success</option>
<option value="failed">Failed</option>
</select>
</div>
<div class="col-md-4 d-flex align-items-end">
<button type="button" class="btn btn-primary w-100" onclick="applyFilter()">
<i class="bi bi-funnel"></i> Apply Filter
</button>
</div>
<div class="col-md-4 d-flex align-items-end">
<button type="button" class="btn btn-outline-secondary w-100" onclick="refreshLogs()">
<i class="bi bi-arrow-clockwise"></i> Refresh
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Loading indicator -->
<div id="loading" class="text-center my-5">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
<!-- Logs table -->
<div id="logs-container" style="display: none;">
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Timestamp</th>
<th>Alert</th>
<th>Status</th>
<th>HTTP Code</th>
<th>Attempt</th>
<th>Details</th>
</tr>
</thead>
<tbody id="logs-tbody">
<!-- Populated via JavaScript -->
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Pagination -->
<nav aria-label="Logs pagination" id="pagination-container">
<ul class="pagination justify-content-center" id="pagination">
<!-- Populated via JavaScript -->
</ul>
</nav>
</div>
<!-- Empty state -->
<div id="empty-state" class="text-center my-5" style="display: none;">
<i class="bi bi-list-ul" style="font-size: 4rem; color: #94a3b8;"></i>
<p class="text-muted mt-3">No delivery logs yet.</p>
<p class="small text-muted">Logs will appear here after alerts trigger this webhook.</p>
</div>
<!-- Modal for log details -->
<div class="modal fade" id="logDetailModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Delivery Log Details</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="modal-content">
<!-- Populated via JavaScript -->
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
const webhookId = {{ webhook.id }};
let currentPage = 1;
let currentStatus = '';
const perPage = 20;
async function loadLogs(page = 1, status = '') {
try {
let url = `/api/webhooks/${webhookId}/logs?page=${page}&per_page=${perPage}`;
if (status) {
url += `&status=${status}`;
}
const response = await fetch(url);
const data = await response.json();
if (data.logs && data.logs.length > 0) {
renderLogs(data.logs);
renderPagination(data.page, data.pages, data.total);
document.getElementById('logs-container').style.display = 'block';
document.getElementById('empty-state').style.display = 'none';
} else {
document.getElementById('logs-container').style.display = 'none';
document.getElementById('empty-state').style.display = 'block';
}
} catch (error) {
console.error('Error loading logs:', error);
alert('Failed to load delivery logs');
} finally {
document.getElementById('loading').style.display = 'none';
}
}
function renderLogs(logs) {
const tbody = document.getElementById('logs-tbody');
tbody.innerHTML = '';
logs.forEach(log => {
const row = document.createElement('tr');
// Format timestamp
const timestamp = new Date(log.delivered_at).toLocaleString();
// Status badge
const statusBadge = log.status === 'success' ?
'<span class="badge bg-success">Success</span>' :
'<span class="badge bg-danger">Failed</span>';
// HTTP code badge
const httpBadge = log.response_code ?
`<span class="badge ${log.response_code < 400 ? 'bg-success' : 'bg-danger'}">${log.response_code}</span>` :
'<span class="text-muted">N/A</span>';
// Alert info
const alertInfo = log.alert_type ?
`<span class="badge bg-secondary">${log.alert_type}</span><br><small class="text-muted">${escapeHtml(log.alert_message || '')}</small>` :
`<small class="text-muted">Alert #${log.alert_id}</small>`;
row.innerHTML = `
<td><small>${timestamp}</small></td>
<td>${alertInfo}</td>
<td>${statusBadge}</td>
<td>${httpBadge}</td>
<td>${log.attempt_number || 1}</td>
<td>
<button class="btn btn-sm btn-outline-primary" onclick="showLogDetails(${JSON.stringify(log).replace(/"/g, '&quot;')})">
<i class="bi bi-eye"></i> View
</button>
</td>
`;
tbody.appendChild(row);
});
}
function renderPagination(currentPage, totalPages, totalItems) {
const pagination = document.getElementById('pagination');
pagination.innerHTML = '';
if (totalPages <= 1) {
document.getElementById('pagination-container').style.display = 'none';
return;
}
document.getElementById('pagination-container').style.display = 'block';
// Previous button
const prevLi = document.createElement('li');
prevLi.className = `page-item ${currentPage === 1 ? 'disabled' : ''}`;
prevLi.innerHTML = `<a class="page-link" href="#" onclick="changePage(${currentPage - 1}); return false;">Previous</a>`;
pagination.appendChild(prevLi);
// Page numbers
for (let i = 1; i <= totalPages; i++) {
if (i === 1 || i === totalPages || (i >= currentPage - 2 && i <= currentPage + 2)) {
const li = document.createElement('li');
li.className = `page-item ${i === currentPage ? 'active' : ''}`;
li.innerHTML = `<a class="page-link" href="#" onclick="changePage(${i}); return false;">${i}</a>`;
pagination.appendChild(li);
} else if (i === currentPage - 3 || i === currentPage + 3) {
const li = document.createElement('li');
li.className = 'page-item disabled';
li.innerHTML = '<a class="page-link" href="#">...</a>';
pagination.appendChild(li);
}
}
// Next button
const nextLi = document.createElement('li');
nextLi.className = `page-item ${currentPage === totalPages ? 'disabled' : ''}`;
nextLi.innerHTML = `<a class="page-link" href="#" onclick="changePage(${currentPage + 1}); return false;">Next</a>`;
pagination.appendChild(nextLi);
}
function changePage(page) {
currentPage = page;
loadLogs(page, currentStatus);
}
function applyFilter() {
currentStatus = document.getElementById('status-filter').value;
currentPage = 1;
loadLogs(1, currentStatus);
}
function refreshLogs() {
loadLogs(currentPage, currentStatus);
}
function showLogDetails(log) {
const modalContent = document.getElementById('modal-content');
let detailsHTML = `
<div class="mb-3">
<strong>Log ID:</strong> ${log.id}<br>
<strong>Alert ID:</strong> ${log.alert_id}<br>
<strong>Status:</strong> <span class="badge ${log.status === 'success' ? 'bg-success' : 'bg-danger'}">${log.status}</span><br>
<strong>HTTP Code:</strong> ${log.response_code || 'N/A'}<br>
<strong>Attempt:</strong> ${log.attempt_number || 1}<br>
<strong>Delivered At:</strong> ${new Date(log.delivered_at).toLocaleString()}
</div>
`;
if (log.response_body) {
detailsHTML += `
<div class="mb-3">
<strong>Response Body:</strong>
<pre class="bg-dark text-light p-2 rounded mt-2"><code>${escapeHtml(log.response_body)}</code></pre>
</div>
`;
}
if (log.error_message) {
detailsHTML += `
<div class="mb-3">
<strong>Error Message:</strong>
<div class="alert alert-danger mt-2">${escapeHtml(log.error_message)}</div>
</div>
`;
}
modalContent.innerHTML = detailsHTML;
const modal = new bootstrap.Modal(document.getElementById('logDetailModal'));
modal.show();
}
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Load logs on page load
document.addEventListener('DOMContentLoaded', () => {
loadLogs(1);
});
</script>
{% endblock %}