762 lines
36 KiB
HTML
762 lines
36 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}DKIM Keys - Email Server{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.dns-record {
|
|
font-family: 'Courier New', monospace;
|
|
color: black;
|
|
background-color: var(--bs-gray-100);
|
|
border-radius: 0.375rem;
|
|
padding: 0.75rem;
|
|
border: 1px solid var(--bs-border-color);
|
|
word-break: break-all;
|
|
}
|
|
.status-indicator {
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 50%;
|
|
display: inline-block;
|
|
margin-right: 0.5rem;
|
|
}
|
|
.status-success { background-color: #28a745; }
|
|
.status-warning { background-color: #ffc107; }
|
|
.status-danger { background-color: #dc3545; }
|
|
</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-shield-check me-2"></i>
|
|
DKIM Key Management
|
|
</h2>
|
|
<div class="btn-group">
|
|
<button class="btn btn-outline-primary me-2" data-bs-toggle="modal" data-bs-target="#createDKIMModal">
|
|
<i class="bi bi-plus-circle me-2"></i>
|
|
Create DKIM
|
|
</button>
|
|
<button class="btn btn-outline-info" data-action="check-all-dns">
|
|
<i class="bi bi-arrow-clockwise me-2"></i>
|
|
Check All DNS
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Create DKIM Modal -->
|
|
<div class="modal fade" id="createDKIMModal" tabindex="-1" aria-labelledby="createDKIMModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<form id="createDKIMForm">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="createDKIMModalLabel">Create New DKIM Key</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label for="dkimDomain" class="form-label">Domain</label>
|
|
<select class="form-select" id="dkimDomain" name="domain" required>
|
|
<option value="" disabled selected>Select domain</option>
|
|
{% for item in dkim_data %}
|
|
<option value="{{ item.domain.domain_name }}">{{ item.domain.domain_name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="dkimSelector" class="form-label">Selector (optional)</label>
|
|
<input type="text" class="form-control" id="dkimSelector" name="selector" maxlength="32" placeholder="Leave blank for random selector">
|
|
</div>
|
|
<div id="createDKIMError" class="alert alert-danger d-none"></div>
|
|
<div id="createDKIMSuccess" class="alert alert-success d-none"></div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-primary">Create</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% for item in dkim_data %}
|
|
<div class="card mb-4" id="domain-{{ item.domain.domain_name.replace('.', '-') }}" data-is-active="{{ item.dkim_key.is_active|tojson }}">
|
|
<div class="card-header">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div class="flex-grow-1 card-header-clickable" style="cursor: pointer;" data-bs-toggle="collapse" data-bs-target="#collapse-{{ item.domain.domain_name.replace('.', '-') }}" aria-expanded="false" aria-controls="collapse-{{ item.domain.domain_name.replace('.', '-') }}">
|
|
<h5 class="mb-0">
|
|
<i class="bi bi-server me-2"></i>
|
|
{{ item.domain.domain_name }}
|
|
{% if item.dkim_key.is_active %}
|
|
<span class="badge bg-success ms-2">Active</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary ms-2">Inactive</span>
|
|
{% endif %}
|
|
</h5>
|
|
</div>
|
|
<div class="btn-group btn-group-sm me-2">
|
|
<button class="btn btn-outline-primary"
|
|
data-action="check-dns"
|
|
data-domain="{{ item.domain.domain_name }}"
|
|
data-selector="{{ item.dkim_key.selector }}"
|
|
onclick="event.stopPropagation();">
|
|
<i class="bi bi-search me-1"></i>
|
|
Check DNS
|
|
</button>
|
|
<div class="btn-group btn-group-sm" role="group">
|
|
<a href="{{ url_for('email.edit_dkim', dkim_id=item.dkim_key.id) }}"
|
|
class="btn btn-outline-info"
|
|
onclick="event.stopPropagation();">
|
|
<i class="bi bi-pencil me-1"></i>
|
|
Edit
|
|
</a>
|
|
<form method="post" action="{{ url_for('email.toggle_dkim', dkim_id=item.dkim_key.id) }}" class="d-inline">
|
|
{% if item.dkim_key.is_active %}
|
|
<button type="submit" class="btn btn-outline-warning" onclick="event.stopPropagation();" title="Disable DKIM">
|
|
<i class="bi bi-pause-circle me-1"></i>
|
|
Disable
|
|
</button>
|
|
{% else %}
|
|
<button type="submit" class="btn btn-outline-success" onclick="event.stopPropagation();" title="Enable DKIM">
|
|
<i class="bi bi-play-circle me-1"></i>
|
|
Enable
|
|
</button>
|
|
{% endif %}
|
|
</form>
|
|
<form method="post" action="{{ url_for('email.remove_dkim', dkim_id=item.dkim_key.id) }}" class="d-inline" onsubmit="return handleFormSubmit(event, 'Are you sure you want to permanently remove the DKIM key for {{ item.domain.domain_name }}? This action cannot be undone and you will lose the ability to sign emails until you regenerate a new key.')">
|
|
<button type="submit"
|
|
class="btn btn-outline-danger"
|
|
onclick="event.stopPropagation();">
|
|
<i class="bi bi-trash me-1"></i>
|
|
Remove
|
|
</button>
|
|
</form>
|
|
</div>
|
|
<button class="btn btn-outline-warning"
|
|
onclick="event.stopPropagation(); regenerateDKIM({{ item.domain.id }}, '{{ item.domain.domain_name }}')">
|
|
<i class="bi bi-arrow-clockwise me-1"></i>
|
|
Regenerate
|
|
</button>
|
|
</div>
|
|
<div class="card-header-clickable" style="cursor: pointer;" data-bs-toggle="collapse" data-bs-target="#collapse-{{ item.domain.domain_name.replace('.', '-') }}" aria-expanded="false" aria-controls="collapse-{{ item.domain.domain_name.replace('.', '-') }}">
|
|
<i class="bi bi-chevron-down" id="chevron-{{ item.domain.domain_name.replace('.', '-') }}"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="collapse" id="collapse-{{ item.domain.domain_name.replace('.', '-') }}">
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<!-- DKIM DNS Record -->
|
|
<div class="col-lg-6 mb-3">
|
|
<h6>
|
|
<i class="bi bi-key me-2"></i>
|
|
DKIM DNS Record
|
|
<span class="dns-status" id="dkim-status-{{ item.domain.domain_name.replace('.', '-') }}">
|
|
<span class="status-indicator status-warning"></span>
|
|
<small class="text-muted">Active (DNS not checked)</small>
|
|
</span>
|
|
</h6>
|
|
<div class="mb-2">
|
|
<strong>Name:</strong>
|
|
<div class="dns-record">{{ item.dns_record.name }}</div>
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>Type:</strong> TXT
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>Value:</strong>
|
|
<div class="dns-record">{{ item.dns_record.value }}</div>
|
|
</div>
|
|
<button class="btn btn-outline-secondary btn-sm" onclick="copyToClipboard('{{ item.dns_record.value }}')">
|
|
<i class="bi bi-clipboard me-1"></i>
|
|
Copy Value
|
|
</button>
|
|
</div>
|
|
|
|
<!-- SPF DNS Record -->
|
|
<div class="col-lg-6 mb-3">
|
|
<h6>
|
|
<i class="bi bi-shield-lock me-2"></i>
|
|
SPF DNS Record
|
|
<span class="dns-status" id="spf-status-{{ item.domain.domain_name.replace('.', '-') }}">
|
|
<span class="status-indicator status-warning"></span>
|
|
<small class="text-muted">Not checked</small>
|
|
</span>
|
|
</h6>
|
|
<div class="mb-2">
|
|
<strong>Name:</strong>
|
|
<div class="dns-record">{{ item.domain.domain_name }}</div>
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>Type:</strong> TXT
|
|
</div>
|
|
{% if item.existing_spf %}
|
|
<div class="mb-2">
|
|
<strong>Current SPF:</strong>
|
|
<div class="dns-record">{{ item.existing_spf }}</div>
|
|
</div>
|
|
{% endif %}
|
|
<div class="mb-2">
|
|
<strong>Recommended SPF:</strong>
|
|
<div class="dns-record">{{ item.recommended_spf }}</div>
|
|
</div>
|
|
<button class="btn btn-outline-secondary btn-sm" onclick="copyToClipboard('{{ item.recommended_spf }}')">
|
|
<i class="bi bi-clipboard me-1"></i>
|
|
Copy SPF
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Key Information -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<h6><i class="bi bi-info-circle me-2"></i>Key Information</h6>
|
|
<div class="row">
|
|
<div class="col-md-3">
|
|
<strong>Selector:</strong><br>
|
|
<code>{{ item.dkim_key.selector }}</code>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<strong>Created:</strong><br>
|
|
{{ item.dkim_key.created_at.strftime('%Y-%m-%d %H:%M') }}
|
|
</div>
|
|
<div class="col-md-3">
|
|
<strong>Server IP:</strong><br>
|
|
<code>{{ item.public_ip }}</code>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<strong>Status:</strong><br>
|
|
{% if item.dkim_key.is_active %}
|
|
<span class="text-success">Active</span>
|
|
{% else %}
|
|
<span class="text-secondary">Inactive</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
|
|
<!-- Old DKIM Keys Section -->
|
|
{% if old_dkim_data %}
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h4 class="mb-0">
|
|
<i class="bi bi-archive me-2"></i>
|
|
Old DKIM Keys
|
|
<span class="badge bg-secondary ms-2">{{ old_dkim_data|length }}</span>
|
|
</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="text-muted mb-3">These keys have been replaced or disabled. They are kept for reference and can be permanently removed.</p>
|
|
|
|
{% for item in old_dkim_data %}
|
|
<div class="card mb-3 border-secondary">
|
|
<div class="card-header bg-dark">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h6 class="mb-0">
|
|
<i class="bi bi-server me-2"></i>
|
|
{{ item.domain.domain_name }}
|
|
<span class="badge bg-secondary ms-2">{{ item.status_text }}</span>
|
|
</h6>
|
|
<small class="text-muted">
|
|
Selector: <code>{{ item.dkim_key.selector }}</code> |
|
|
Created: {{ item.dkim_key.created_at.strftime('%Y-%m-%d %H:%M') }}
|
|
{% if item.dkim_key.replaced_at %}
|
|
| Replaced: {{ item.dkim_key.replaced_at.strftime('%Y-%m-%d %H:%M') }}
|
|
{% endif %}
|
|
</small>
|
|
</div>
|
|
<div class="btn-group btn-group-sm">
|
|
<form method="post" action="{{ url_for('email.toggle_dkim', dkim_id=item.dkim_key.id) }}" class="d-inline">
|
|
<button type="submit" class="btn btn-outline-success btn-sm">
|
|
<i class="bi bi-play-circle me-1"></i>
|
|
Reactivate
|
|
</button>
|
|
</form>
|
|
<form method="post" action="{{ url_for('email.remove_dkim', dkim_id=item.dkim_key.id) }}" class="d-inline" onsubmit="return handleFormSubmit(event, 'Are you sure you want to permanently remove this old DKIM key? This action cannot be undone.')">
|
|
<button type="submit"
|
|
class="btn btn-outline-danger btn-sm">
|
|
<i class="bi bi-trash me-1"></i>
|
|
Remove
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if not dkim_data %}
|
|
<div class="card">
|
|
<div class="card-body text-center py-5">
|
|
<i class="bi bi-shield-x text-muted" style="font-size: 4rem;"></i>
|
|
<h4 class="text-muted mt-3">No DKIM Keys Found</h4>
|
|
<p class="text-muted">Add domains first to automatically generate DKIM keys</p>
|
|
<a href="{{ url_for('email.add_domain') }}" class="btn btn-primary">
|
|
<i class="bi bi-plus-circle me-2"></i>
|
|
Add Domain
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- DNS Check Results Modal -->
|
|
<div class="modal fade" id="dnsResultModal" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">DNS Check Results</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body" id="dnsResults">
|
|
<!-- Results will be populated here -->
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
// Show toast notification
|
|
function showToast(message, type = 'info') {
|
|
const toastContainer = document.getElementById('toastContainer');
|
|
if (!toastContainer) {
|
|
// Create toast container if it doesn't exist
|
|
const container = document.createElement('div');
|
|
container.id = 'toastContainer';
|
|
container.style.cssText = 'position: fixed; top: 20px; right: 20px; z-index: 1050;';
|
|
document.body.appendChild(container);
|
|
}
|
|
|
|
const toast = document.createElement('div');
|
|
toast.className = `toast align-items-center border-0 bg-${type}`;
|
|
toast.setAttribute('role', 'alert');
|
|
toast.setAttribute('aria-live', 'assertive');
|
|
toast.setAttribute('aria-atomic', 'true');
|
|
|
|
const toastContent = document.createElement('div');
|
|
toastContent.className = 'd-flex';
|
|
|
|
const toastBody = document.createElement('div');
|
|
toastBody.className = 'toast-body text-white';
|
|
toastBody.textContent = message;
|
|
|
|
const closeButton = document.createElement('button');
|
|
closeButton.type = 'button';
|
|
closeButton.className = 'btn-close btn-close-white me-2 m-auto';
|
|
closeButton.setAttribute('data-bs-dismiss', 'toast');
|
|
closeButton.setAttribute('aria-label', 'Close');
|
|
|
|
toastContent.appendChild(toastBody);
|
|
toastContent.appendChild(closeButton);
|
|
toast.appendChild(toastContent);
|
|
|
|
document.getElementById('toastContainer').appendChild(toast);
|
|
|
|
const bsToast = new bootstrap.Toast(toast, {
|
|
animation: true,
|
|
autohide: true,
|
|
delay: 5000
|
|
});
|
|
bsToast.show();
|
|
|
|
// Remove toast after it's hidden
|
|
toast.addEventListener('hidden.bs.toast', function() {
|
|
toast.remove();
|
|
});
|
|
}
|
|
|
|
// Check DNS records for a domain
|
|
async function checkDomainDNS(domain, selector) {
|
|
const dkimStatus = document.getElementById(`dkim-status-${domain.replace('.', '-')}`);
|
|
const spfStatus = document.getElementById(`spf-status-${domain.replace('.', '-')}`);
|
|
|
|
// Show loading state
|
|
dkimStatus.innerHTML = '<span class="status-indicator status-warning"></span><small class="text-muted">Checking...</small>';
|
|
spfStatus.innerHTML = '<span class="status-indicator status-warning"></span><small class="text-muted">Checking...</small>';
|
|
|
|
try {
|
|
// Check DKIM DNS
|
|
const dkimResponse = await fetch("{{ url_for('email.check_dkim_dns') }}", {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|
},
|
|
body: new URLSearchParams({
|
|
domain: domain,
|
|
selector: selector
|
|
})
|
|
});
|
|
const dkimResult = await dkimResponse.json();
|
|
|
|
// Check SPF DNS
|
|
const spfResponse = await fetch("{{ url_for('email.check_spf_dns') }}", {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|
},
|
|
body: new URLSearchParams({
|
|
domain: domain
|
|
})
|
|
});
|
|
const spfResult = await spfResponse.json();
|
|
|
|
// Get DKIM key status from data attribute
|
|
const domainCard = document.getElementById(`domain-${domain.replace('.', '-')}`);
|
|
const isActive = domainCard && domainCard.dataset.isActive === 'true';
|
|
|
|
// Update DKIM status based on active state and DNS visibility
|
|
if (isActive) {
|
|
if (dkimResult.success) {
|
|
dkimStatus.innerHTML = '<span class="status-indicator status-success"></span><small class="text-success">✓ Active & Configured</small>';
|
|
} else {
|
|
dkimStatus.innerHTML = '<span class="status-indicator" style="background-color: #fd7e14;"></span><small class="text-warning">Active but DNS not found</small>';
|
|
}
|
|
} else {
|
|
dkimStatus.innerHTML = '<span class="status-indicator" style="background-color: #6c757d;"></span><small class="text-muted">Disabled</small>';
|
|
}
|
|
|
|
// Update SPF status
|
|
if (spfResult.success) {
|
|
spfStatus.innerHTML = '<span class="status-indicator status-success"></span><small class="text-success">✓ Found</small>';
|
|
} else {
|
|
spfStatus.innerHTML = '<span class="status-indicator status-danger"></span><small class="text-danger">✗ Not found</small>';
|
|
}
|
|
|
|
// Show detailed results in modal
|
|
showDNSResults(domain, dkimResult, spfResult);
|
|
|
|
} catch (error) {
|
|
console.error('DNS check error:', error);
|
|
dkimStatus.innerHTML = '<span class="status-indicator status-danger"></span><small class="text-danger">Error</small>';
|
|
spfStatus.innerHTML = '<span class="status-indicator status-danger"></span><small class="text-danger">Error</small>';
|
|
}
|
|
}
|
|
|
|
// Show DNS check results in modal
|
|
function showDNSResults(domain, dkimResult, spfResult) {
|
|
// Clean up record strings by removing extra quotes and normalizing whitespace
|
|
function cleanRecordDisplay(record) {
|
|
if (!record) return '';
|
|
return record
|
|
.replace(/^["']|["']$/g, '') // Remove outer quotes
|
|
.replace(/\\n/g, '') // Remove newlines
|
|
.replace(/\s+/g, ' ') // Normalize whitespace
|
|
.trim(); // Remove leading/trailing space
|
|
}
|
|
|
|
const dkimRecordsHtml = dkimResult.records ?
|
|
dkimResult.records.map(record =>
|
|
`<div class="record-value" style="word-break: break-all; font-family: monospace; background: #f8f9fa; padding: 8px; border-radius: 4px;">
|
|
${cleanRecordDisplay(record)}
|
|
</div>`
|
|
).join('') : '';
|
|
|
|
const spfRecordHtml = spfResult.spf_record ?
|
|
`<div class="record-value mt-2" style="word-break: break-all; font-family: monospace; background: #f8f9fa; padding: 8px; border-radius: 4px;">
|
|
${cleanRecordDisplay(spfResult.spf_record)}
|
|
</div>` : '';
|
|
|
|
const resultsHtml = `
|
|
<h6>DNS Check Results for ${domain}</h6>
|
|
|
|
<div class="mb-3">
|
|
<h6 class="text-primary">DKIM Record</h6>
|
|
<div class="alert ${dkimResult.success ? 'alert-success' : 'alert-danger'}">
|
|
<strong>Status:</strong> ${dkimResult.success ? 'Found' : 'Not Found'}<br>
|
|
<strong>Message:</strong> ${dkimResult.message}
|
|
${dkimResult.records ? `
|
|
<br><strong>Records:</strong>
|
|
<div class="records-container mt-2">
|
|
${dkimRecordsHtml}
|
|
</div>
|
|
` : ''}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<h6 class="text-primary">SPF Record</h6>
|
|
<div class="alert ${spfResult.success ? 'alert-success' : 'alert-danger'}">
|
|
<strong>Status:</strong> ${spfResult.success ? 'Found' : 'Not Found'}<br>
|
|
<strong>Message:</strong> ${spfResult.message}
|
|
${spfResult.spf_record ? `
|
|
<br><strong>Current SPF:</strong>
|
|
${spfRecordHtml}
|
|
` : ''}
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
document.getElementById('dnsResults').innerHTML = resultsHtml;
|
|
new bootstrap.Modal(document.getElementById('dnsResultModal')).show();
|
|
}
|
|
|
|
// Check all domains' DNS records
|
|
async function checkAllDNS() {
|
|
const domains = document.querySelectorAll('[id^="domain-"]');
|
|
const results = [];
|
|
|
|
// Show a progress indicator
|
|
showToast('Checking DNS records for all domains...', 'info');
|
|
|
|
for (const domainCard of domains) {
|
|
try {
|
|
const domainId = domainCard.id.split('-')[1];
|
|
// Extract domain name from the card header
|
|
const domainHeaderText = domainCard.querySelector('h5').textContent.trim();
|
|
const domainName = domainHeaderText.split('\n')[0].trim().replace(/^\s*\S+\s+/, ''); // Remove icon
|
|
const selectorElement = domainCard.querySelector('code');
|
|
|
|
if (selectorElement) {
|
|
const selector = selectorElement.textContent;
|
|
|
|
// Check DKIM DNS
|
|
const dkimResponse = await fetch('{{ url_for("email.check_dkim_dns") }}', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: `domain=${encodeURIComponent(domainName)}&selector=${encodeURIComponent(selector)}`
|
|
});
|
|
const dkimResult = await dkimResponse.json();
|
|
|
|
// Check SPF DNS
|
|
const spfResponse = await fetch('{{ url_for("email.check_spf_dns") }}', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: `domain=${encodeURIComponent(domainName)}`
|
|
});
|
|
const spfResult = await spfResponse.json();
|
|
|
|
results.push({
|
|
domain: domainName,
|
|
dkim: dkimResult,
|
|
spf: spfResult
|
|
});
|
|
|
|
// Update individual status indicators
|
|
const dkimStatus = document.getElementById(`dkim-status-${domainName.replace('.', '-')}`);
|
|
const spfStatus = document.getElementById(`spf-status-${domainName.replace('.', '-')}`);
|
|
|
|
if (dkimStatus) {
|
|
if (dkimResult.success) {
|
|
dkimStatus.innerHTML = '<span class="status-indicator status-success"></span><small class="text-success">✓ Configured</small>';
|
|
} else {
|
|
dkimStatus.innerHTML = '<span class="status-indicator status-danger"></span><small class="text-danger">✗ Not found</small>';
|
|
}
|
|
}
|
|
|
|
if (spfStatus) {
|
|
if (spfResult.success) {
|
|
spfStatus.innerHTML = '<span class="status-indicator status-success"></span><small class="text-success">✓ Found</small>';
|
|
} else {
|
|
spfStatus.innerHTML = '<span class="status-indicator status-danger"></span><small class="text-danger">✗ Not found</small>';
|
|
}
|
|
}
|
|
|
|
// Small delay between checks to avoid overwhelming the DNS server
|
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
}
|
|
} catch (error) {
|
|
console.error('Error checking DNS for domain:', error);
|
|
}
|
|
}
|
|
|
|
// Show combined results in modal
|
|
showAllDNSResults(results);
|
|
}
|
|
|
|
// Show combined DNS check results
|
|
function showAllDNSResults(results) {
|
|
let tableRows = '';
|
|
|
|
results.forEach(result => {
|
|
const dkimIcon = result.dkim.success ? '<i class="bi bi-check-circle-fill text-success"></i>' : '<i class="bi bi-x-circle-fill text-danger"></i>';
|
|
const spfIcon = result.spf.success ? '<i class="bi bi-check-circle-fill text-success"></i>' : '<i class="bi bi-x-circle-fill text-danger"></i>';
|
|
|
|
tableRows += `
|
|
<tr>
|
|
<td><strong>${result.domain}</strong></td>
|
|
<td class="text-center">
|
|
${dkimIcon}
|
|
<small class="d-block">${result.dkim.success ? 'Configured' : 'Not Found'}</small>
|
|
</td>
|
|
<td class="text-center">
|
|
${spfIcon}
|
|
<small class="d-block">${result.spf.success ? 'Found' : 'Not Found'}</small>
|
|
</td>
|
|
<td>
|
|
<small class="text-muted">
|
|
DKIM: ${result.dkim.message}<br>
|
|
SPF: ${result.spf.message}
|
|
</small>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
});
|
|
|
|
const resultsHtml = `
|
|
<h6>DNS Check Results for All Domains</h6>
|
|
<div class="table-responsive">
|
|
<table class="table table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Domain</th>
|
|
<th class="text-center">DKIM Status</th>
|
|
<th class="text-center">SPF Status</th>
|
|
<th>Details</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
${tableRows}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="mt-3">
|
|
<div class="alert alert-info">
|
|
<small>
|
|
<i class="bi bi-info-circle me-1"></i>
|
|
<strong>DKIM:</strong> Verifies email signatures for authenticity<br>
|
|
<i class="bi bi-info-circle me-1"></i>
|
|
<strong>SPF:</strong> Authorizes servers that can send email for your domain
|
|
</small>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
document.getElementById('dnsResults').innerHTML = resultsHtml;
|
|
new bootstrap.Modal(document.getElementById('dnsResultModal')).show();
|
|
}
|
|
|
|
// Initialize event handlers
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Add click handler for DNS check buttons
|
|
document.querySelectorAll('[data-action="check-dns"]').forEach(button => {
|
|
button.addEventListener('click', function(event) {
|
|
event.stopPropagation();
|
|
const domain = this.dataset.domain;
|
|
const selector = this.dataset.selector;
|
|
checkDomainDNS(domain, selector);
|
|
});
|
|
});
|
|
|
|
// Add click handler for check all DNS button
|
|
const checkAllButton = document.querySelector('[data-action="check-all-dns"]');
|
|
if (checkAllButton) {
|
|
checkAllButton.addEventListener('click', function() {
|
|
checkAllDNS();
|
|
});
|
|
}
|
|
|
|
// Add click handlers for card headers - only for clickable areas
|
|
document.querySelectorAll('.card-header-clickable[data-bs-toggle="collapse"]').forEach(function(element) {
|
|
element.addEventListener('click', function() {
|
|
const targetId = this.getAttribute('data-bs-target');
|
|
const chevronId = targetId.replace('#collapse-', '#chevron-');
|
|
const chevron = document.querySelector(chevronId);
|
|
|
|
// Toggle chevron direction
|
|
if (chevron) {
|
|
setTimeout(() => {
|
|
const collapseElement = document.querySelector(targetId);
|
|
if (collapseElement && collapseElement.classList.contains('show')) {
|
|
chevron.className = 'bi bi-chevron-up';
|
|
} else {
|
|
chevron.className = 'bi bi-chevron-down';
|
|
}
|
|
}, 100);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Add submit handler for DKIM toggle forms
|
|
document.querySelectorAll('form[action*="toggle_dkim"]').forEach(form => {
|
|
form.addEventListener('submit', async function(event) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
|
|
const formData = new FormData(this);
|
|
const response = await fetch(this.action, {
|
|
method: 'POST',
|
|
body: formData,
|
|
headers: {
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
}
|
|
});
|
|
|
|
if (response.ok) {
|
|
const result = await response.json();
|
|
if (result.success) {
|
|
// Get the domain card
|
|
const domainCard = this.closest('.card');
|
|
if (domainCard) {
|
|
// Update the data-is-active attribute
|
|
domainCard.dataset.isActive = result.is_active.toString();
|
|
|
|
// Update the badge
|
|
const badge = domainCard.querySelector('.badge');
|
|
if (badge) {
|
|
if (result.is_active) {
|
|
badge.className = 'badge bg-success ms-2';
|
|
badge.textContent = 'Active';
|
|
} else {
|
|
badge.className = 'badge bg-secondary ms-2';
|
|
badge.textContent = 'Inactive';
|
|
}
|
|
}
|
|
|
|
// Update the button
|
|
const button = this.querySelector('button');
|
|
if (button) {
|
|
if (result.is_active) {
|
|
button.className = 'btn btn-outline-warning';
|
|
button.innerHTML = '<i class="bi bi-pause-circle me-1"></i>Disable';
|
|
button.title = 'Disable DKIM';
|
|
} else {
|
|
button.className = 'btn btn-outline-success';
|
|
button.innerHTML = '<i class="bi bi-play-circle me-1"></i>Enable';
|
|
button.title = 'Enable DKIM';
|
|
}
|
|
}
|
|
|
|
// Update the status indicator
|
|
const dkimStatus = domainCard.querySelector('.dns-status');
|
|
if (dkimStatus) {
|
|
if (result.is_active) {
|
|
dkimStatus.innerHTML = '<span class="status-indicator status-warning"></span><small class="text-muted">Active (DNS not checked)</small>';
|
|
} else {
|
|
dkimStatus.innerHTML = '<span class="status-indicator" style="background-color: #6c757d;"></span><small class="text-muted">Disabled</small>';
|
|
}
|
|
}
|
|
|
|
// Show success message
|
|
showToast(result.message, 'success');
|
|
}
|
|
} else {
|
|
showToast(result.message, 'error');
|
|
}
|
|
} else {
|
|
showToast('Error toggling DKIM status', 'error');
|
|
}
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %}
|