fixed layout - rename functions for user to sender
This commit is contained in:
@@ -131,6 +131,20 @@
|
||||
<!-- Custom SMTP Management CSS -->
|
||||
<link href="{{ url_for('email.static', filename='css/smtp-management.css') }}" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
/* Ensure tooltip text is visible on dark backgrounds */
|
||||
.tooltip-inner {
|
||||
color: #fff !important;
|
||||
background-color: #222 !important;
|
||||
font-size: 1rem;
|
||||
text-align: left;
|
||||
}
|
||||
.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,
|
||||
.bs-tooltip-top .tooltip-arrow::before {
|
||||
border-top-color: #222 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
{% block extra_css %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
@@ -337,6 +351,16 @@
|
||||
<!-- Custom SMTP Management JavaScript -->
|
||||
<script src="{{ url_for('email.static', filename='js/smtp-management.js') }}"></script>
|
||||
|
||||
<!-- Bootstrap Tooltip Initialization -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
||||
tooltipTriggerList.forEach(function (tooltipTriggerEl) {
|
||||
new bootstrap.Tooltip(tooltipTriggerEl);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{% block extra_js %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<div class="row">
|
||||
<!-- Statistics Cards -->
|
||||
<div class="col-lg-3 col-md-6 mb-4">
|
||||
<a href="{{ url_for('email.domains_list') }}" class="dashboard-card-link text-decoration-none">
|
||||
<div class="card border-primary">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
@@ -24,19 +25,21 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-3 col-md-6 mb-4">
|
||||
<a href="{{ url_for('email.senders_list') }}" class="dashboard-card-link text-decoration-none">
|
||||
<div class="card border-success">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="flex-grow-1">
|
||||
<h5 class="card-title text-success mb-1">
|
||||
<i class="bi bi-people me-2"></i>
|
||||
Users
|
||||
Senders
|
||||
</h5>
|
||||
<h3 class="mb-0">{{ user_count }}</h3>
|
||||
<small class="text-muted">Authenticated users</small>
|
||||
<h3 class="mb-0">{{ sender_count }}</h3>
|
||||
<small class="text-muted">Authenticated senders</small>
|
||||
</div>
|
||||
<div class="fs-2 text-success opacity-50">
|
||||
<i class="bi bi-people"></i>
|
||||
@@ -44,9 +47,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-3 col-md-6 mb-4">
|
||||
<a href="{{ url_for('email.dkim_list') }}" class="dashboard-card-link text-decoration-none">
|
||||
<div class="card border-warning">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
@@ -64,6 +69,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-3 col-md-6 mb-4">
|
||||
@@ -75,11 +81,28 @@
|
||||
<i class="bi bi-activity me-2"></i>
|
||||
Status
|
||||
</h5>
|
||||
<h6 class="text-success mb-0">
|
||||
{% set health = check_health() %}
|
||||
<h6 class="{% if health.status == 'healthy' %}text-success{% else %}text-warning{% endif %} mb-0 status-indicator"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-html="true"
|
||||
data-bs-placement="top"
|
||||
title="{{ (
|
||||
"<div class='text-start'><strong>Service Status:</strong><br>" +
|
||||
'SMTP Server: ' + health.services.smtp_server|title + '<br>' +
|
||||
'Web Frontend: ' + health.services.web_frontend|title + '<br>' +
|
||||
'Database: ' + health.services.database|title + '</div>'
|
||||
) | safe }}">
|
||||
<i class="bi bi-circle-fill me-1" style="font-size: 0.5rem;"></i>
|
||||
Online
|
||||
{{ health.status|title }}
|
||||
</h6>
|
||||
<small class="text-muted">Server running</small>
|
||||
<small class="text-muted">
|
||||
{% if health.services.smtp_server == 'running' and health.services.database == 'ok' %}
|
||||
All services running
|
||||
{% else %}
|
||||
{% if health.services.smtp_server == 'stopped' %}SMTP Server stopped{% endif %}
|
||||
{% if health.services.database == 'error' %}Database error{% endif %}
|
||||
{% endif %}
|
||||
</small>
|
||||
</div>
|
||||
<div class="fs-2 text-info opacity-50">
|
||||
<i class="bi bi-activity"></i>
|
||||
@@ -248,7 +271,7 @@
|
||||
<div class="d-grid">
|
||||
<a href="{{ url_for('email.add_sender') }}" class="btn btn-outline-success">
|
||||
<i class="bi bi-person-plus me-2"></i>
|
||||
Add User
|
||||
Add Sender
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -282,4 +305,31 @@
|
||||
location.reload();
|
||||
}, 30000);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.status-indicator {
|
||||
cursor: pointer;
|
||||
}
|
||||
.dashboard-card-link {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
}
|
||||
.dashboard-card-link:hover .card {
|
||||
box-shadow: 0 0 0 2px #0d6efd33;
|
||||
filter: brightness(1.05);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
||||
tooltipTriggerList.forEach(function(tooltipTriggerEl) {
|
||||
new bootstrap.Tooltip(tooltipTriggerEl, {
|
||||
html: true,
|
||||
placement: 'top',
|
||||
trigger: 'hover'
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -331,6 +331,30 @@
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
// Clipboard copy function for all copy buttons
|
||||
function copyToClipboard(text) {
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(text).then(function() {
|
||||
showToast('Copied to clipboard!', 'success');
|
||||
}, function(err) {
|
||||
showToast('Failed to copy: ' + err, 'danger');
|
||||
});
|
||||
} else {
|
||||
// Fallback for older browsers
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = text;
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
showToast('Copied to clipboard!', 'success');
|
||||
} catch (err) {
|
||||
showToast('Failed to copy: ' + err, 'danger');
|
||||
}
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
}
|
||||
|
||||
// Show toast notification
|
||||
function showToast(message, type = 'info') {
|
||||
const toastContainer = document.getElementById('toastContainer');
|
||||
@@ -433,6 +457,14 @@
|
||||
// Update SPF status
|
||||
if (spfResult.success) {
|
||||
spfStatus.innerHTML = '<span class="status-indicator status-success"></span><small class="text-success">✓ Found</small>';
|
||||
// Show additional SPF check message if available
|
||||
if (typeof spfResult.spf_valid_for_server !== 'undefined') {
|
||||
if (spfResult.spf_valid_for_server) {
|
||||
spfStatus.innerHTML += '<br><span class="text-success"><i class="bi bi-check-circle me-1"></i> SPF is valid for this server</span>';
|
||||
} else {
|
||||
spfStatus.innerHTML += '<br><span class="text-warning"><i class="bi bi-exclamation-triangle me-1"></i> SPF missing server IP</span>';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
spfStatus.innerHTML = '<span class="status-indicator status-danger"></span><small class="text-danger">✗ Not found</small>';
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Edit User - SMTP Management{% endblock %}
|
||||
{% block title %}Edit Sender - SMTP Management{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
@@ -9,7 +9,7 @@
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">
|
||||
<i class="bi bi-person-fill-gear me-2"></i>
|
||||
Edit User
|
||||
Edit Sender
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@@ -20,7 +20,7 @@
|
||||
class="form-control"
|
||||
id="email"
|
||||
name="email"
|
||||
value="{{ user.email }}"
|
||||
value="{{ sender.email }}"
|
||||
required>
|
||||
</div>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<option value="">Select a domain</option>
|
||||
{% for domain in domains %}
|
||||
<option value="{{ domain.id }}"
|
||||
{% if domain.id == user.domain_id %}selected{% endif %}>
|
||||
{% if domain.id == sender.domain_id %}selected{% endif %}>
|
||||
{{ domain.domain_name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
@@ -55,12 +55,12 @@
|
||||
type="checkbox"
|
||||
id="can_send_as_domain"
|
||||
name="can_send_as_domain"
|
||||
{% if user.can_send_as_domain %}checked{% endif %}>
|
||||
{% if sender.can_send_as_domain %}checked{% endif %}>
|
||||
<label class="form-check-label" for="can_send_as_domain">
|
||||
<strong>Can send as any email from domain</strong>
|
||||
</label>
|
||||
<div class="form-text">
|
||||
Allow this user to send emails using any address within their domain
|
||||
Allow this sender to send emails using any address within their domain
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -68,7 +68,7 @@
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-check-lg me-1"></i>
|
||||
Update User
|
||||
Update Sender
|
||||
</button>
|
||||
<a href="{{ url_for('email.senders_list') }}" class="btn btn-secondary">
|
||||
<i class="bi bi-x-lg me-1"></i>
|
||||
@@ -85,26 +85,26 @@
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
Current User Details
|
||||
Current Sender Details
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<dl class="row mb-0">
|
||||
<dt class="col-sm-4">Email:</dt>
|
||||
<dd class="col-sm-8">
|
||||
<code>{{ user.email }}</code>
|
||||
<code>{{ sender.email }}</code>
|
||||
</dd>
|
||||
<dt class="col-sm-4">Domain:</dt>
|
||||
<dd class="col-sm-8">
|
||||
{% for domain in domains %}
|
||||
{% if domain.id == user.domain_id %}
|
||||
{% if domain.id == sender.domain_id %}
|
||||
<span class="badge bg-secondary">{{ domain.domain_name }}</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</dd>
|
||||
<dt class="col-sm-4">Status:</dt>
|
||||
<dd class="col-sm-8">
|
||||
{% if user.is_active %}
|
||||
{% if sender.is_active %}
|
||||
<span class="badge bg-success">
|
||||
<i class="bi bi-check-circle me-1"></i>
|
||||
Active
|
||||
@@ -118,7 +118,7 @@
|
||||
</dd>
|
||||
<dt class="col-sm-4">Domain Sender:</dt>
|
||||
<dd class="col-sm-8">
|
||||
{% if user.can_send_as_domain %}
|
||||
{% if sender.can_send_as_domain %}
|
||||
<span class="badge bg-success">
|
||||
<i class="bi bi-check-circle me-1"></i>
|
||||
Yes
|
||||
@@ -133,7 +133,7 @@
|
||||
<dt class="col-sm-4">Created:</dt>
|
||||
<dd class="col-sm-8">
|
||||
<small class="text-muted">
|
||||
{{ user.created_at.strftime('%Y-%m-%d %H:%M') }}
|
||||
{{ sender.created_at.strftime('%Y-%m-%d %H:%M') }}
|
||||
</small>
|
||||
</dd>
|
||||
</dl>
|
||||
@@ -144,7 +144,7 @@
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">
|
||||
<i class="bi bi-shield-check me-2"></i>
|
||||
User Permissions
|
||||
Sender Permissions
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@@ -154,8 +154,8 @@
|
||||
Domain Sender Permission
|
||||
</h6>
|
||||
<ul class="mb-0 small">
|
||||
<li><strong>Enabled:</strong> User can send emails using any address in their domain (e.g., admin@domain.com, support@domain.com)</li>
|
||||
<li><strong>Disabled:</strong> User can only send emails from their own email address</li>
|
||||
<li><strong>Enabled:</strong> Sender can send emails using any address in their domain (e.g., admin@domain.com, support@domain.com)</li>
|
||||
<li><strong>Disabled:</strong> Sender can only send emails from their own email address</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
{% if users %}
|
||||
{% if senders %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-dark table-hover mb-0">
|
||||
<thead>
|
||||
@@ -90,16 +90,16 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user, domain in users %}
|
||||
{% for sender, domain in senders %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="fw-bold">{{ user.email }}</div>
|
||||
<div class="fw-bold">{{ sender.email }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-secondary">{{ domain.domain_name }}</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if user.can_send_as_domain %}
|
||||
{% if sender.can_send_as_domain %}
|
||||
<span class="badge bg-warning" style="color: black;">
|
||||
<i class="bi bi-star me-1"></i>
|
||||
Domain Sender
|
||||
@@ -112,11 +112,11 @@
|
||||
Regular Sender
|
||||
</span>
|
||||
<br>
|
||||
<small class="text-muted">Can only send as {{ user.email }}</small>
|
||||
<small class="text-muted">Can only send as {{ sender.email }}</small>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if user.is_active %}
|
||||
{% if sender.is_active %}
|
||||
<span class="badge bg-success">
|
||||
<i class="bi bi-check-circle me-1"></i>
|
||||
Active
|
||||
@@ -130,45 +130,45 @@
|
||||
</td>
|
||||
<td>
|
||||
<small class="text-muted">
|
||||
{{ user.created_at.strftime('%Y-%m-%d %H:%M') }}
|
||||
{{ sender.created_at.strftime('%Y-%m-%d %H:%M') }}
|
||||
</small>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group" role="group">
|
||||
<!-- Edit Button -->
|
||||
<a href="{{ url_for('email.edit_sender', user_id=user.id) }}"
|
||||
<a href="{{ url_for('email.edit_sender', user_id=sender.id) }}"
|
||||
class="btn btn-outline-primary btn-sm"
|
||||
title="Edit Sender">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
|
||||
<!-- Enable/Disable Button -->
|
||||
{% if user.is_active %}
|
||||
<form method="post" action="{{ url_for('email.delete_sender', user_id=user.id) }}" class="d-inline">
|
||||
{% if sender.is_active %}
|
||||
<form method="post" action="{{ url_for('email.delete_sender', user_id=sender.id) }}" class="d-inline">
|
||||
<button type="submit"
|
||||
class="btn btn-outline-warning btn-sm"
|
||||
title="Disable Sender"
|
||||
onclick="return confirm('Disable user {{ user.email }}?')">
|
||||
onclick="return confirm('Disable user {{ sender.email }}?')">
|
||||
<i class="bi bi-pause-circle"></i>
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<form method="post" action="{{ url_for('email.enable_sender', user_id=user.id) }}" class="d-inline">
|
||||
<form method="post" action="{{ url_for('email.enable_sender', user_id=sender.id) }}" class="d-inline">
|
||||
<button type="submit"
|
||||
class="btn btn-outline-success btn-sm"
|
||||
title="Enable Sender"
|
||||
onclick="return confirm('Enable user {{ user.email }}?')">
|
||||
onclick="return confirm('Enable user {{ sender.email }}?')">
|
||||
<i class="bi bi-play-circle"></i>
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<!-- Permanent Remove Button -->
|
||||
<form method="post" action="{{ url_for('email.remove_sender', user_id=user.id) }}" class="d-inline">
|
||||
<form method="post" action="{{ url_for('email.remove_sender', user_id=sender.id) }}" class="d-inline">
|
||||
<button type="submit"
|
||||
class="btn btn-outline-danger btn-sm"
|
||||
title="Permanently Remove Sender"
|
||||
onclick="return confirm('Permanently remove user {{ user.email }}? This cannot be undone!')">
|
||||
onclick="return confirm('Permanently remove user {{ sender.email }}? This cannot be undone!')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
class="nav-link text-white {{ 'active' if request.endpoint in ['email.senders_list', 'email.add_user'] else '' }}">
|
||||
<i class="bi bi-people me-2"></i>
|
||||
Allowed Senders
|
||||
<span class="badge bg-secondary ms-auto">{{ user_count if user_count is defined else '' }}</span>
|
||||
<span class="badge bg-secondary ms-auto">{{ sender_count if sender_count is defined else '' }}</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -122,12 +122,22 @@
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="flex-grow-1">
|
||||
<small class="text-muted d-block">Server Status</small>
|
||||
<small class="text-success">
|
||||
{% set health = check_health() %}
|
||||
<small class="{% if health.status == 'healthy' %}text-success{% else %}text-warning{% endif %} status-indicator"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-html="true"
|
||||
data-bs-placement="top"
|
||||
title="{{ (
|
||||
"<div class='text-start'><strong>Service Status:</strong><br>" +
|
||||
'SMTP Server: ' + health.services.smtp_server|title + '<br>' +
|
||||
'Web Frontend: ' + health.services.web_frontend|title + '<br>' +
|
||||
'Database: ' + health.services.database|title + '</div>'
|
||||
) | safe }}">
|
||||
<i class="bi bi-circle-fill me-1" style="font-size: 0.5rem;"></i>
|
||||
Online
|
||||
{{ health.status|title }}
|
||||
</small>
|
||||
</div>
|
||||
<button class="btn btn-outline-secondary btn-sm" title="Refresh Status">
|
||||
<button class="btn btn-outline-secondary btn-sm" title="Refresh Status" onclick="location.reload()">
|
||||
<i class="bi bi-arrow-clockwise"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -170,6 +180,10 @@
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.sidebar {
|
||||
@@ -186,3 +200,16 @@
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
||||
tooltipTriggerList.forEach(function(tooltipTriggerEl) {
|
||||
new bootstrap.Tooltip(tooltipTriggerEl, {
|
||||
html: true,
|
||||
placement: 'top',
|
||||
trigger: 'hover'
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user