adding message viewer - fixing log view, change to aiosmtplib

This commit is contained in:
nahakubuilde
2025-06-14 00:35:24 +01:00
parent 38672bea0b
commit e300eb82d5
33 changed files with 1239 additions and 381 deletions

View File

@@ -67,6 +67,18 @@
</div>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="store_message_content" name="store_message_content">
<label class="form-check-label" for="store_message_content">
<strong>Store Full Message Content</strong>
</label>
<div class="form-text">
If enabled, the full message body and attachments will be stored and viewable in logs. Otherwise, only headers and subject are stored.
</div>
</div>
</div>
<div class="alert alert-warning">
<h6 class="alert-heading">
<i class="bi bi-exclamation-triangle me-2"></i>

View File

@@ -70,6 +70,18 @@
</div>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="store_message_content" name="store_message_content">
<label class="form-check-label" for="store_message_content">
<strong>Store Full Message Content</strong>
</label>
<div class="form-text">
If enabled, the full message body and attachments will be stored and viewable in logs. Otherwise, only headers and subject are stored.
</div>
</div>
</div>
<div class="alert alert-info">
<h6 class="alert-heading">
<i class="bi bi-info-circle me-2"></i>

View File

@@ -144,7 +144,7 @@
<tr>
<td>
<small class="text-muted">
{{ email.created_at.strftime('%H:%M:%S') }}
{{ email.created_at|format_datetime }}
</small>
</td>
<td>
@@ -153,22 +153,38 @@
</span>
</td>
<td>
<span class="text-truncate d-inline-block" style="max-width: 150px;" title="{{ email.to_address }}">
{{ email.to_address }}
<span class="text-truncate d-inline-block" style="max-width: 150px;" title="{{ email.rcpt_tos }}">
{{ email.rcpt_tos }}
</span>
</td>
<td>
{% if email.status == 'relayed' %}
<span class="badge bg-success">
<i class="bi bi-check-circle me-1"></i>
Sent
</span>
{% set delivered = recipient_logs_map[email.id]|selectattr('status', 'equalto', 'success')|list %}
{% set failed = recipient_logs_map[email.id]|selectattr('status', 'ne', 'success')|list %}
{% if delivered and failed %}
{% set overall_status = 'partial' %}
{% elif delivered %}
{% set overall_status = 'relayed' %}
{% else %}
<span class="badge bg-danger">
<i class="bi bi-x-circle me-1"></i>
Failed
</span>
{% set overall_status = 'failed' %}
{% endif %}
<td>
{% if overall_status == 'relayed' %}
<span class="badge bg-success">
<i class="bi bi-check-circle me-1"></i>
Sent
</span>
{% elif overall_status == 'partial' %}
<span class="badge bg-warning text-dark">
<i class="bi bi-exclamation-triangle me-1"></i>
Partial Fail
</span>
{% else %}
<span class="badge bg-danger">
<i class="bi bi-x-circle me-1"></i>
Failed
</span>
{% endif %}
</td>
</td>
<td>
{% if email.dkim_signed %}
@@ -227,7 +243,7 @@
</small>
<br>
<small class="text-muted">
{{ auth.created_at.strftime('%H:%M:%S') }}
{{ auth.created_at|format_datetime }}
</small>
</div>
<small class="text-muted">

View File

@@ -44,6 +44,18 @@
</div>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="store_message_content" name="store_message_content" {% if ip_record.store_message_content %}checked{% endif %}>
<label class="form-check-label" for="store_message_content">
<strong>Store Full Message Content</strong>
</label>
<div class="form-text">
If enabled, the full message body and attachments will be stored and viewable in logs. Otherwise, only headers and subject are stored.
</div>
</div>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">
<i class="bi bi-check-lg me-1"></i>
@@ -95,6 +107,20 @@
</span>
{% endif %}
</dd>
<dt class="col-sm-4">Store Message:</dt>
<dd class="col-sm-8">
{% if ip_record.store_message_content %}
<span class="badge bg-info text-dark">
<i class="bi bi-file-earmark-text me-1"></i>
Full Message
</span>
{% else %}
<span class="badge bg-secondary">
<i class="bi bi-file-earmark me-1"></i>
Headers Only
</span>
{% endif %}
</dd>
<dt class="col-sm-4">Created:</dt>
<dd class="col-sm-8">
<small class="text-muted">

View File

@@ -65,6 +65,18 @@
</div>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="store_message_content" name="store_message_content" {% if sender.store_message_content %}checked{% endif %}>
<label class="form-check-label" for="store_message_content">
<strong>Store Full Message Content</strong>
</label>
<div class="form-text">
If enabled, the full message body and attachments will be stored and viewable in logs. Otherwise, only headers and subject are stored.
</div>
</div>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">
<i class="bi bi-check-lg me-1"></i>
@@ -130,6 +142,20 @@
</span>
{% endif %}
</dd>
<dt class="col-sm-4">Store Message:</dt>
<dd class="col-sm-8">
{% if sender.store_message_content %}
<span class="badge bg-info text-dark">
<i class="bi bi-file-earmark-text me-1"></i>
Full Message
</span>
{% else %}
<span class="badge bg-secondary">
<i class="bi bi-file-earmark me-1"></i>
Headers Only
</span>
{% endif %}
</dd>
<dt class="col-sm-4">Created:</dt>
<dd class="col-sm-8">
<small class="text-muted">

View File

@@ -33,6 +33,7 @@
<th>IP Address</th>
<th>Domain</th>
<th>Status</th>
<th>Storage Type</th>
<th>Added</th>
<th>Actions</th>
</tr>
@@ -59,6 +60,19 @@
</span>
{% endif %}
</td>
<td>
{% if ip.store_message_content %}
<span class="badge bg-info text-dark">
<i class="bi bi-file-earmark-text me-1"></i>
Stores Full Message
</span>
{% else %}
<span class="badge bg-secondary">
<i class="bi bi-file-earmark me-1"></i>
Headers Only
</span>
{% endif %}
</td>
<td>
<small class="text-muted">
{{ ip.created_at.strftime('%Y-%m-%d %H:%M') }}

View File

@@ -16,16 +16,9 @@
.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 */
.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;
}
/* Message display styles are now in view_message_content.html */
</style>
{% endblock %}
@@ -83,11 +76,30 @@
{% 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' }}">
{% 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> → {{ log.rcpt_tos }}
<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>
@@ -95,13 +107,15 @@
</span>
{% endif %}
</div>
<small class="text-muted">{{ log.created_at.strftime('%Y-%m-%d %H:%M:%S') }}</small>
<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 log.status == 'relayed' %}
{% 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 %}
@@ -115,6 +129,11 @@
<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 %}
@@ -127,7 +146,7 @@
{{ 'Success' if log.success else 'Failed' }}
</span>
</div>
<small class="text-muted">{{ log.created_at.strftime('%Y-%m-%d %H:%M:%S') }}</small>
<small class="text-muted">{{ log.created_at|format_datetime }}</small>
</div>
<div class="row">
<div class="col-md-6">
@@ -148,10 +167,28 @@
{% 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' }}">
{% 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> → {{ log.rcpt_tos }}
<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>
@@ -159,24 +196,64 @@
</span>
{% endif %}
</div>
<small class="text-muted">{{ log.created_at.strftime('%Y-%m-%d %H:%M:%S') }}</small>
<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 log.status == 'relayed' %}
{% 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 }}</code>
<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 }}
@@ -195,6 +272,13 @@
</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 %}
@@ -208,7 +292,7 @@
{{ 'Success' if log.success else 'Failed' }}
</span>
</div>
<small class="text-muted">{{ log.created_at.strftime('%Y-%m-%d %H:%M:%S') }}</small>
<small class="text-muted">{{ log.created_at|format_datetime }}</small>
</div>
<div class="row">
<div class="col-md-4">

View File

@@ -85,6 +85,7 @@
<th>Domain</th>
<th>Permissions</th>
<th>Status</th>
<th>Storage</th>
<th>Created</th>
<th>Actions</th>
</tr>
@@ -128,6 +129,19 @@
</span>
{% endif %}
</td>
<td>
{% if sender.store_message_content %}
<span class="badge bg-info text-dark">
<i class="bi bi-file-earmark-text me-1"></i>
Stores Full Message
</span>
{% else %}
<span class="badge bg-secondary">
<i class="bi bi-file-earmark me-1"></i>
Headers Only
</span>
{% endif %}
</td>
<td>
<small class="text-muted">
{{ sender.created_at.strftime('%Y-%m-%d %H:%M') }}

View File

@@ -0,0 +1,98 @@
{% extends "base.html" %}
{% block title %}View Full Message - Email Log{% endblock %}
{% block content %}
<div class="container mt-4">
<h2>Full Message Content</h2>
<div class="mb-3">
<strong>From:</strong> {{ log.mail_from }}<br>
<strong>To:</strong> {{ log.to_address }}<br>
<strong>CC:</strong> {{ log.cc_addresses or 'None' }}<br>
<strong>BCC:</strong> {{ log.bcc_addresses or 'None' }}<br>
<strong>Subject:</strong> {{ log.subject or 'N/A' }}<br>
<strong>Date:</strong> {{ log.created_at.strftime('%Y-%m-%d %H:%M:%S') }}<br>
</div>
{% if log.attachments %}
<div class="card mb-3">
<div class="card-header">
<strong>Attachments:</strong>
</div>
<div class="card-body">
<ul class="list-group">
{% for attachment in log.attachments %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<div>
<i class="fas fa-paperclip"></i> {{ attachment.filename }}
<small class="text-muted">({{ attachment.size|filesizeformat }})</small>
</div>
<div class="btn-group" role="group">
{% set content_type = attachment.content_type.lower() if attachment.content_type else 'application/octet-stream' %}
{% set extension = attachment.filename.split('.')[-1].lower() if '.' in attachment.filename else '' %}
{% set is_image = content_type.startswith('image/') or extension in ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'] %}
{% set is_text = content_type.startswith('text/') or extension in ['txt', 'log', 'json', 'xml', 'csv', 'md'] %}
{% set is_pdf = content_type == 'application/pdf' or extension == 'pdf' %}
{% set is_html = content_type in ['text/html', 'application/xhtml+xml'] or extension in ['html', 'htm'] %}
{% if is_image or is_text or is_pdf or is_html %}
<a href="{{ url_for('email.download_attachment', attachment_id=attachment.id) }}"
class="btn btn-sm btn-outline-primary"
target="_blank"
data-bs-toggle="tooltip"
title="Open in new tab">
<i class="fas fa-external-link-alt"></i>
{% if is_image %}<i class="fas fa-image"></i> View Image
{% elif is_pdf %}<i class="fas fa-file-pdf"></i> View PDF
{% elif extension == 'csv' %}<i class="fas fa-table"></i> View CSV
{% elif is_text %}<i class="fas fa-file-alt"></i> View Text
{% elif is_html %}<i class="fas fa-file-code"></i> View HTML
{% else %}View in Browser
{% endif %}
</a>
{% endif %}
<a href="{{ url_for('email.download_attachment', attachment_id=attachment.id, download='true') }}"
class="btn btn-sm btn-outline-secondary"
title="Download file">
<i class="fas fa-download"></i> Download
</a>
<form method="POST"
action="{{ url_for('email.delete_attachment', attachment_id=attachment.id) }}"
style="display: inline;"
onsubmit="return confirm('Are you sure you want to delete this attachment?');">
<button type="submit"
class="btn btn-sm btn-outline-danger"
title="Delete attachment">
<i class="fas fa-trash-alt"></i> Delete
</button>
</form>
</div>
</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
<div class="card">
<div class="card-header">
<strong>Message Content:</strong>
</div>
<div class="card-body">
<pre style="white-space: pre-wrap; word-break: break-all;">{{ log.message_body }}</pre>
</div>
</div>
<div class="card mt-3">
<div class="card-header">
<strong>Message Headers:</strong>
</div>
<div class="card-body">
<pre style="white-space: pre-wrap;">{{ log.email_headers }}</pre>
</div>
</div>
<a href="{{ url_for('email.logs', type='emails') }}" class="btn btn-secondary mt-3">Back to Logs</a>
</div>
{% endblock %}