407 lines
24 KiB
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 %}
|