Files
PyMTA-server/email_server/server_web_ui/templates/logs.html

407 lines
24 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-partial { border-left-color: #fd7e14; } /* Orange for partial fail */
/* Message display styles are now in view_message_content.html */
</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>
Emails Log
</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 %}
{% set recipients = log_entry.recipients %}
{% set delivered = recipients|selectattr('status', 'equalto', 'success')|list %}
{% set failed = recipients|selectattr('status', 'ne', 'success')|list %}
{% if delivered and failed %}
{% set overall_status = 'partial' %}
{% elif delivered %}
{% set overall_status = 'relayed' %}
{% else %}
{% set overall_status = 'failed' %}
{% endif %}
<div class="log-entry log-email log-{% if overall_status == 'relayed' %}success{% elif overall_status == 'partial' %}partial{% else %}failed{% endif %}">
<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>
{% if log.to_address %}
<span class="text-primary">To:</span> {{ log.to_address }}
{% endif %}
{% if log.cc_addresses %}
<br><span class="ms-4 text-info">CC:</span> {{ log.cc_addresses }}
{% endif %}
{% if log.bcc_addresses %}
<br><span class="ms-4 text-warning">BCC:</span> {{ log.bcc_addresses }}
{% endif %}
{% 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.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}</small>
</div>
<div class="row">
<div class="col-md-6">
<strong>Status:</strong>
{% if overall_status == 'relayed' %}
<span class="text-success">Sent Successfully</span>
{% elif overall_status == 'partial' %}
<span class="text-warning">Partial Fail</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 class="mt-2">
<a href="{{ url_for('email.view_message_content', log_id=log.id) }}" class="btn btn-sm btn-primary">
<i class="fas fa-envelope-open-text"></i> View Message Details
</a>
</div>
</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|format_datetime }}</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 %}
{% set delivered = recipient_logs_map[log.id]|selectattr('status', 'equalto', 'success')|list %}
{% set failed = recipient_logs_map[log.id]|selectattr('status', 'ne', 'success')|list %}
{% if delivered and failed %}
{% set overall_status = 'partial' %}
{% elif delivered %}
{% set overall_status = 'relayed' %}
{% else %}
{% set overall_status = 'failed' %}
{% endif %}
<div class="log-entry log-email log-{{ overall_status }}">
<div class="d-flex justify-content-between align-items-start mb-2">
<div>
<strong>{{ log.mail_from }}</strong>
{% if log.to_address %}
<span class="text-primary">To:</span> {{ log.to_address }}
{% endif %}
{% if log.cc_addresses %}
<br><span class="ms-4 text-info">CC:</span> {{ log.cc_addresses }}
{% endif %}
{% if log.bcc_addresses %}
<br><span class="ms-4 text-warning">BCC:</span> {{ log.bcc_addresses }}
{% endif %}
{% 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.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}</small>
</div>
<div class="row">
<div class="col-md-3">
<strong>Status:</strong>
{% if overall_status == 'relayed' %}
<span class="text-success">Sent</span>
{% elif overall_status == 'partial' %}
<span class="text-warning">Partial Fail</span>
{% else %}
<span class="text-danger">Failed</span>
{% endif %}
</div>
<div class="col-md-3">
<strong>Peer:</strong> <code>{{ log.peer_ip }}</code>
</div>
<div class="col-md-6">
<strong>Message ID:</strong> <code>{{ log.message_id }}</code>
</div>
</div>
<div class="row mt-2">
<div class="col-md-4">
<strong>Username:</strong> {{ log.username or 'N/A' }}
</div>
<div class="col-md-4">
<strong>CC:</strong> {{ log.cc_addresses or 'None' }}
</div>
<div class="col-md-4">
<strong>BCC:</strong> {{ log.bcc_addresses or 'None' }}
</div>
</div>
{% if recipient_logs_map and log.id in recipient_logs_map and recipient_logs_map[log.id] %}
<div class="mt-2">
<strong>Recipient Delivery Results:</strong>
<ul class="list-group">
{% for r in recipient_logs_map[log.id] %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<span>
<strong>{{ r.recipient_type|upper }}:</strong> {{ r.recipient }}
{% if r.status == 'success' %}
<span class="badge bg-success ms-2">Delivered</span>
{% else %}
<span class="badge bg-danger ms-2">Failed</span>
{% endif %}
</span>
{% if r.error_code or r.error_message %}
<span class="text-danger ms-2">
{{ r.error_code }} {{ r.error_message }}
</span>
{% endif %}
{% if r.server_response %}
<span class="text-muted ms-2">{{ r.server_response }}</span>
{% endif %}
</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% 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 %}
{% if log.has_message_content %}
<div class="mt-2">
<a href="{{ url_for('email.view_message_content', log_id=log.id) }}" class="btn btn-outline-info btn-sm">
<i class="bi bi-file-earmark-text me-1"></i> View Full Message
</a>
</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|format_datetime }}</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 %}