183 lines
7.9 KiB
HTML
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;">×</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 %}
|