Files
PyMTA-server/email_frontend/templates/logs.html
2025-06-07 10:48:03 +01:00

323 lines
17 KiB
HTML

{% extends "base.html" %}
{% block title %}Logs - Email Server{% endblock %}
{% block extra_css %}
<style>
.log-entry {
border-left: 4px solid var(--bs-border-color);
padding: 0.75rem;
margin-bottom: 0.5rem;
background-color: var(--bs-body-bg);
border-radius: 0.375rem;
}
.log-email { border-left-color: #0d6efd; }
.log-auth { border-left-color: #198754; }
.log-error { border-left-color: #dc3545; }
.log-success { border-left-color: #198754; }
.log-failed { border-left-color: #dc3545; }
.log-content {
font-family: 'Courier New', monospace;
font-size: 0.875rem;
background-color: var(--bs-gray-100);
border-radius: 0.25rem;
padding: 0.5rem;
max-height: 150px;
overflow-y: auto;
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>
<i class="bi bi-journal-text me-2"></i>
Server Logs
</h2>
<div class="btn-group">
<a href="{{ url_for('email.logs', type='all') }}"
class="btn {{ 'btn-primary' if filter_type == 'all' else 'btn-outline-primary' }}">
<i class="bi bi-list-ul me-1"></i>
All Logs
</a>
<a href="{{ url_for('email.logs', type='emails') }}"
class="btn {{ 'btn-primary' if filter_type == 'emails' else 'btn-outline-primary' }}">
<i class="bi bi-envelope me-1"></i>
Email Logs
</a>
<a href="{{ url_for('email.logs', type='auth') }}"
class="btn {{ 'btn-primary' if filter_type == 'auth' else 'btn-outline-primary' }}">
<i class="bi bi-shield-lock me-1"></i>
Auth Logs
</a>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">
{% if filter_type == 'emails' %}
<i class="bi bi-envelope me-2"></i>
Email Activity
{% elif filter_type == 'auth' %}
<i class="bi bi-shield-lock me-2"></i>
Authentication Activity
{% else %}
<i class="bi bi-list-ul me-2"></i>
Recent Activity
{% endif %}
</h5>
<button class="btn btn-outline-secondary btn-sm" onclick="refreshLogs()">
<i class="bi bi-arrow-clockwise me-1"></i>
Refresh
</button>
</div>
<div class="card-body">
{% if logs %}
{% if filter_type == 'all' %}
<!-- Combined logs view -->
{% for log_entry in logs %}
{% if log_entry.type == 'email' %}
{% set log = log_entry.data %}
<div class="log-entry log-email log-{{ 'success' if log.status == 'relayed' else 'failed' }}">
<div class="d-flex justify-content-between align-items-start mb-2">
<div>
<span class="badge bg-primary me-2">EMAIL</span>
<strong>{{ log.mail_from }}</strong> → {{ log.rcpt_tos }}
{% if log.dkim_signed %}
<span class="badge bg-success ms-2">
<i class="bi bi-shield-check me-1"></i>
DKIM
</span>
{% endif %}
</div>
<small class="text-muted">{{ log.created_at.strftime('%Y-%m-%d %H:%M:%S') }}</small>
</div>
<div class="row">
<div class="col-md-6">
<strong>Status:</strong>
{% if log.status == 'relayed' %}
<span class="text-success">Sent Successfully</span>
{% else %}
<span class="text-danger">Failed</span>
{% endif %}
</div>
<div class="col-md-6">
<strong>Message ID:</strong> <code>{{ log.message_id }}</code>
</div>
</div>
{% if log.subject %}
<div class="mt-2">
<strong>Subject:</strong> {{ log.subject }}
</div>
{% endif %}
</div>
{% else %}
{% set log = log_entry.data %}
<div class="log-entry log-auth log-{{ 'success' if log.success else 'failed' }}">
<div class="d-flex justify-content-between align-items-start mb-2">
<div>
<span class="badge bg-success me-2">AUTH</span>
<strong>{{ log.identifier }}</strong>
<span class="badge {{ 'bg-success' if log.success else 'bg-danger' }} ms-2">
{{ 'Success' if log.success else 'Failed' }}
</span>
</div>
<small class="text-muted">{{ log.created_at.strftime('%Y-%m-%d %H:%M:%S') }}</small>
</div>
<div class="row">
<div class="col-md-6">
<strong>Type:</strong> {{ log.auth_type.upper() }}
</div>
<div class="col-md-6">
<strong>IP:</strong> <code>{{ log.ip_address or 'N/A' }}</code>
</div>
</div>
{% if log.message %}
<div class="mt-2">
<strong>Message:</strong> {{ log.message }}
</div>
{% endif %}
</div>
{% endif %}
{% endfor %}
{% elif filter_type == 'emails' %}
<!-- Email logs only -->
{% for log in logs %}
<div class="log-entry log-email log-{{ 'success' if log.status == 'relayed' else 'failed' }}">
<div class="d-flex justify-content-between align-items-start mb-2">
<div>
<strong>{{ log.mail_from }}</strong> → {{ log.rcpt_tos }}
{% if log.dkim_signed %}
<span class="badge bg-success ms-2">
<i class="bi bi-shield-check me-1"></i>
DKIM
</span>
{% endif %}
</div>
<small class="text-muted">{{ log.created_at.strftime('%Y-%m-%d %H:%M:%S') }}</small>
</div>
<div class="row">
<div class="col-md-3">
<strong>Status:</strong>
{% if log.status == 'relayed' %}
<span class="text-success">Sent</span>
{% else %}
<span class="text-danger">Failed</span>
{% endif %}
</div>
<div class="col-md-3">
<strong>Peer:</strong> <code>{{ log.peer }}</code>
</div>
<div class="col-md-6">
<strong>Message ID:</strong> <code>{{ log.message_id }}</code>
</div>
</div>
{% if log.subject %}
<div class="mt-2">
<strong>Subject:</strong> {{ log.subject }}
</div>
{% endif %}
{% if log.content and log.content|length > 50 %}
<div class="mt-2">
<button class="btn btn-outline-secondary btn-sm" type="button"
data-bs-toggle="collapse"
data-bs-target="#content-{{ log.id }}">
<i class="bi bi-eye me-1"></i>
View Content
</button>
<div class="collapse mt-2" id="content-{{ log.id }}">
<div class="log-content">{{ log.content }}</div>
</div>
</div>
{% endif %}
</div>
{% endfor %}
{% else %}
<!-- Auth logs only -->
{% for log in logs %}
<div class="log-entry log-auth log-{{ 'success' if log.success else 'failed' }}">
<div class="d-flex justify-content-between align-items-start mb-2">
<div>
<strong>{{ log.identifier }}</strong>
<span class="badge {{ 'bg-success' if log.success else 'bg-danger' }} ms-2">
{{ 'Success' if log.success else 'Failed' }}
</span>
</div>
<small class="text-muted">{{ log.created_at.strftime('%Y-%m-%d %H:%M:%S') }}</small>
</div>
<div class="row">
<div class="col-md-4">
<strong>Type:</strong> {{ log.auth_type.upper() }}
</div>
<div class="col-md-4">
<strong>IP:</strong> <code>{{ log.ip_address or 'N/A' }}</code>
</div>
<div class="col-md-4">
<strong>Result:</strong>
{% if log.success %}
<span class="text-success">Authenticated</span>
{% else %}
<span class="text-danger">Rejected</span>
{% endif %}
</div>
</div>
{% if log.message %}
<div class="mt-2">
<strong>Details:</strong> {{ log.message }}
</div>
{% endif %}
</div>
{% endfor %}
{% endif %}
<!-- Pagination -->
{% if has_prev or has_next %}
<nav aria-label="Log pagination" class="mt-4">
<ul class="pagination justify-content-center">
{% if has_prev %}
<li class="page-item">
<a class="page-link" href="{{ url_for('email.logs', type=filter_type, page=page-1) }}">
<i class="bi bi-chevron-left"></i>
Previous
</a>
</li>
{% endif %}
<li class="page-item active">
<span class="page-link">Page {{ page }}</span>
</li>
{% if has_next %}
<li class="page-item">
<a class="page-link" href="{{ url_for('email.logs', type=filter_type, page=page+1) }}">
Next
<i class="bi bi-chevron-right"></i>
</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% else %}
<div class="text-center py-5">
<i class="bi bi-journal-text text-muted" style="font-size: 4rem;"></i>
<h4 class="text-muted mt-3">No Logs Found</h4>
<p class="text-muted">
{% if filter_type == 'emails' %}
No email activity has been logged yet.
{% elif filter_type == 'auth' %}
No authentication attempts have been logged yet.
{% else %}
No activity has been logged yet.
{% endif %}
</p>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
function refreshLogs() {
window.location.reload();
}
// Auto-refresh every 30 seconds
setInterval(function() {
// Only auto-refresh if the user is viewing the page
if (document.visibilityState === 'visible') {
const button = document.querySelector('[onclick="refreshLogs()"]');
if (button) {
// Add visual indicator that refresh is happening
const originalText = button.innerHTML;
button.innerHTML = '<i class="bi bi-arrow-clockwise me-1 spin"></i>Refreshing...';
setTimeout(() => {
window.location.reload();
}, 1000);
}
}
}, 30000);
// Add CSS for spinning icon
const style = document.createElement('style');
style.textContent = `
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.spin {
animation: spin 1s linear infinite;
}
`;
document.head.appendChild(style);
</script>
{% endblock %}