added webhooks, moved app name and verison to simple config file
This commit is contained in:
328
app/web/templates/webhooks/logs.html
Normal file
328
app/web/templates/webhooks/logs.html
Normal file
@@ -0,0 +1,328 @@
|
||||
{% 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 %}
|
||||
Reference in New Issue
Block a user