shippingservice/shipping/templates/shipping/logs.html
2026-09-25 21:40:57 +05:30

183 lines
7.9 KiB
HTML

{% extends 'shipping/base.html' %}
{% block title %}Activity Audit Logs — SFX API Hub{% endblock %}
{% block content %}
<div class="page-header">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div>
<h1 class="page-title">📋 Activity Audit Logs</h1>
<p class="page-desc">Complete audit trail capturing all API calls, HTTP headers, request payloads, response codes, execution times, and errors across Forward and Reverse operations.</p>
</div>
<form action="{% url 'clear_logs_view' %}" method="POST" onsubmit="return confirm('Are you sure you want to clear all audit logs?');">
{% csrf_token %}
<button type="submit" class="btn btn-danger">Clear All Logs</button>
</form>
</div>
</div>
<!-- Filters Bar Card -->
<div class="card" style="margin-bottom: 1.5rem;">
<form method="GET" action="{% url 'logs_view' %}" style="display: grid; grid-template-columns: 2fr 1fr 1fr auto; gap: 1rem; align-items: end;">
<div class="form-group" style="margin: 0;">
<label class="form-label">Search Query (Order ID, AWB, Endpoint)</label>
<input type="text" name="q" class="form-control" placeholder="Search by AWB, Order ID, or keyword..." value="{{ query }}">
</div>
<div class="form-group" style="margin: 0;">
<label class="form-label">Service Filter</label>
<select name="service" class="form-select">
<option value="">All Services</option>
<option value="FORWARD" {% if service_filter == 'FORWARD' %}selected{% endif %}>Forward Logistics</option>
<option value="REVERSE" {% if service_filter == 'REVERSE' %}selected{% endif %}>Reverse Logistics</option>
<option value="WEBHOOK" {% if service_filter == 'WEBHOOK' %}selected{% endif %}>Webhook Events</option>
</select>
</div>
<div class="form-group" style="margin: 0;">
<label class="form-label">Status Filter</label>
<select name="status" class="form-select">
<option value="">All Statuses</option>
<option value="SUCCESS" {% if status_filter == 'SUCCESS' %}selected{% endif %}>Success (2xx)</option>
<option value="ERROR" {% if status_filter == 'ERROR' %}selected{% endif %}>Error (4xx/5xx)</option>
</select>
</div>
<div>
<button type="submit" class="btn btn-primary">Filter Logs</button>
</div>
</form>
</div>
<!-- Logs Data Table Card -->
<div class="card">
<div class="card-title">
<span>Audit Log History (Showing Latest {{ logs|length }} Entries)</span>
</div>
<table class="data-table">
<thead>
<tr>
<th>Timestamp</th>
<th>Service</th>
<th>Action Name</th>
<th>Method & Endpoint</th>
<th>Status Code</th>
<th>Latency</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for log in logs %}
<tr>
<td style="font-family: 'Fira Code', monospace; font-size: 0.8rem; color: var(--text-muted);">
{{ log.timestamp|date:"Y-m-d H:i:s" }}
</td>
<td>
{% if log.service_type == 'FORWARD' %}
<span class="badge badge-forward">Forward</span>
{% elif log.service_type == 'REVERSE' %}
<span class="badge badge-reverse">Reverse</span>
{% else %}
<span class="badge badge-webhook">Webhook</span>
{% endif %}
</td>
<td style="font-weight: 600;">{{ log.action_name }}</td>
<td>
<span style="font-family: 'Fira Code', monospace; font-size: 0.8rem; color: var(--accent-cyan); font-weight: 600;">{{ log.http_method }}</span>
<span style="font-family: 'Fira Code', monospace; font-size: 0.8rem; color: var(--text-muted);">{{ log.endpoint_url }}</span>
</td>
<td>
{% if log.response_status_code >= 200 and log.response_status_code < 300 %}
<span class="badge badge-success">{{ log.response_status_code }} OK</span>
{% else %}
<span class="badge badge-error">{{ log.response_status_code }} Error</span>
{% endif %}
</td>
<td style="font-family: 'Fira Code', monospace; font-size: 0.8rem;">
{{ log.execution_time_ms }} ms
</td>
<td>
<button class="btn btn-secondary" style="padding: 0.35rem 0.75rem; font-size: 0.8rem;" onclick="openInspectorModal({{ log.id }})">
Inspect Log
</button>
</td>
</tr>
{% empty %}
<tr>
<td colspan="7" style="text-align: center; color: var(--text-muted); padding: 2rem;">
No audit logs recorded matching criteria. Execute API calls from Forward or Reverse hubs to populate logs.
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Log Detail Inspector Modal -->
<div id="inspector_modal" class="modal">
<div class="modal-content">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem; border-bottom: 1px solid var(--border-color); padding-bottom: 0.75rem;">
<h3 style="font-size: 1.1rem; font-weight: 600;" id="modal_title">Inspect Log Entry #</h3>
<button onclick="closeInspectorModal()" style="background: none; border: none; color: var(--text-muted); font-size: 1.5rem; cursor: pointer;">&times;</button>
</div>
<div style="margin-bottom: 1rem;">
<div style="font-size: 0.85rem; color: var(--text-muted); margin-bottom: 0.25rem;">Endpoint URL & Method</div>
<div id="modal_url" style="font-family: 'Fira Code', monospace; font-size: 0.85rem; color: var(--accent-cyan); background: var(--bg-input); padding: 0.5rem; border-radius: 0.35rem;">-</div>
</div>
<div style="margin-bottom: 1rem;">
<div style="font-size: 0.85rem; color: var(--text-muted); margin-bottom: 0.25rem;">Request Headers</div>
<pre id="modal_req_headers" class="json-viewer" style="max-height: 150px;">-</pre>
</div>
<div style="margin-bottom: 1rem;">
<div style="font-size: 0.85rem; color: var(--text-muted); margin-bottom: 0.25rem;">Request Payload</div>
<pre id="modal_req_body" class="json-viewer" style="max-height: 200px;">-</pre>
</div>
<div style="margin-bottom: 1rem;">
<div style="font-size: 0.85rem; color: var(--text-muted); margin-bottom: 0.25rem;">Response Payload</div>
<pre id="modal_res_body" class="json-viewer" style="max-height: 250px;">-</pre>
</div>
<div style="text-align: right;">
<button class="btn btn-secondary" onclick="closeInspectorModal()">Close Inspector</button>
</div>
</div>
</div>
{% endblock %}
{% block extra_scripts %}
<script>
function openInspectorModal(logId) {
fetch(`/logs/${logId}/`)
.then(resp => resp.json())
.then(data => {
document.getElementById('modal_title').textContent = `Inspect Log #${data.id} — ${data.action_name} (${data.service_type})`;
document.getElementById('modal_url').textContent = `${data.http_method} ${data.endpoint_url}`;
try {
document.getElementById('modal_req_headers').textContent = JSON.stringify(JSON.parse(data.request_headers), null, 2);
} catch(e) {
document.getElementById('modal_req_headers').textContent = data.request_headers || 'No headers recorded';
}
try {
document.getElementById('modal_req_body').textContent = JSON.stringify(JSON.parse(data.request_body), null, 2);
} catch(e) {
document.getElementById('modal_req_body').textContent = data.request_body || 'No request body payload';
}
try {
document.getElementById('modal_res_body').textContent = JSON.stringify(JSON.parse(data.response_body), null, 2);
} catch(e) {
document.getElementById('modal_res_body').textContent = data.response_body || 'No response body payload';
}
document.getElementById('inspector_modal').classList.add('active');
});
}
function closeInspectorModal() {
document.getElementById('inspector_modal').classList.remove('active');
}
</script>
{% endblock %}