329 lines
12 KiB
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, '"')})">
|
|
<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 %}
|