first commit

This commit is contained in:
2026-08-12 12:56:22 +01:00
commit ff96be2708
153 changed files with 27779 additions and 0 deletions
+266
View File
@@ -0,0 +1,266 @@
// DKIM Management functionality
const DKIMManagement = {
// Check DNS records for a domain
checkDomainDNS: async function(domain, selector, checkDkimUrl, checkSpfUrl) {
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(checkDkimUrl, {
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(checkSpfUrl, {
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 the card class
const domainCard = document.getElementById(`domain-${domain.replace('.', '-')}`);
const isActive = domainCard && domainCard.classList.contains('dkim-active');
// 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
this.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
showDNSResults: function(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
checkAllDNS: async function(checkDkimUrl, checkSpfUrl) {
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(checkDkimUrl, {
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(checkSpfUrl, {
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
this.showAllDNSResults(results);
},
// Show combined DNS check results
showAllDNSResults: function(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();
}
};
+285
View File
@@ -0,0 +1,285 @@
/* Custom JavaScript for SMTP Management Frontend */
// Global utilities
const SMTPManagement = {
// Copy text to clipboard
copyToClipboard: function(text, button) {
navigator.clipboard.writeText(text).then(() => {
this.showCopySuccess(button);
}).catch(err => {
console.error('Failed to copy: ', err);
this.showCopyError(button);
});
},
// Show copy success feedback
showCopySuccess: function(button) {
const originalText = button.innerHTML;
button.innerHTML = '<i class="fas fa-check me-1"></i>Copied!';
button.classList.remove('btn-outline-light');
button.classList.add('btn-success');
setTimeout(() => {
button.innerHTML = originalText;
button.classList.remove('btn-success');
button.classList.add('btn-outline-light');
}, 2000);
},
// Show copy error feedback
showCopyError: function(button) {
const originalText = button.innerHTML;
button.innerHTML = '<i class="fas fa-times me-1"></i>Failed!';
button.classList.remove('btn-outline-light');
button.classList.add('btn-danger');
setTimeout(() => {
button.innerHTML = originalText;
button.classList.remove('btn-danger');
button.classList.add('btn-outline-light');
}, 2000);
},
// Format timestamps
formatTimestamp: function(timestamp) {
const date = new Date(timestamp);
return date.toLocaleString();
},
// Validate email address
validateEmail: function(email) {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(email);
},
// Validate IP address
validateIP: function(ip) {
const ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
const ipv6Regex = /^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
return ipv4Regex.test(ip) || ipv6Regex.test(ip);
},
// Show loading state
showLoading: function(element) {
element.classList.add('loading');
const spinner = element.querySelector('.spinner-border');
if (spinner) {
spinner.style.display = 'inline-block';
}
},
// Hide loading state
hideLoading: function(element) {
element.classList.remove('loading');
const spinner = element.querySelector('.spinner-border');
if (spinner) {
spinner.style.display = 'none';
}
},
// Show toast notification
showToast: function(message, type = 'info') {
const toastContainer = document.getElementById('toast-container') || this.createToastContainer();
const toast = this.createToast(message, type);
toastContainer.appendChild(toast);
// Auto-remove after 5 seconds
setTimeout(() => {
toast.remove();
}, 5000);
},
// Create toast container
createToastContainer: function() {
const container = document.createElement('div');
container.id = 'toast-container';
container.className = 'position-fixed top-0 end-0 p-3';
container.style.zIndex = '1056';
document.body.appendChild(container);
return container;
},
// Create toast element
createToast: function(message, type) {
const toast = document.createElement('div');
toast.className = `toast align-items-center text-white bg-${type} border-0`;
toast.setAttribute('role', 'alert');
toast.innerHTML = `
<div class="d-flex">
<div class="toast-body">${message}</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
</div>
`;
// Initialize Bootstrap toast
const bsToast = new bootstrap.Toast(toast);
bsToast.show();
return toast;
},
// Auto-refresh functionality
autoRefresh: function(url, interval = 30000) {
setInterval(() => {
fetch(url, {
method: 'GET',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.text())
.then(html => {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const newContent = doc.querySelector('#refresh-content');
const currentContent = document.querySelector('#refresh-content');
if (newContent && currentContent) {
currentContent.innerHTML = newContent.innerHTML;
}
})
.catch(error => {
console.error('Auto-refresh failed:', error);
});
}, interval);
}
};
// DNS verification functionality
const DNSVerification = {
// Check DNS record
checkDNSRecord: function(domain, recordType, expectedValue) {
return fetch('/email/check-dns', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify({
domain: domain,
record_type: recordType,
expected_value: expectedValue
})
})
.then(response => response.json());
},
// Update DNS status indicator
updateDNSStatus: function(element, status, message) {
const statusIcon = element.querySelector('.dns-status-icon');
const statusText = element.querySelector('.dns-status-text');
if (statusIcon && statusText) {
statusIcon.className = `dns-status-icon fas ${status === 'valid' ? 'fa-check-circle text-success' : 'fa-times-circle text-danger'}`;
statusText.textContent = message;
}
}
};
// Form validation
const FormValidation = {
// Real-time email validation
validateEmailField: function(input) {
const isValid = SMTPManagement.validateEmail(input.value);
this.updateFieldStatus(input, isValid, 'Please enter a valid email address');
return isValid;
},
// Real-time IP validation
validateIPField: function(input) {
const isValid = SMTPManagement.validateIP(input.value);
this.updateFieldStatus(input, isValid, 'Please enter a valid IP address');
return isValid;
},
// Update field validation status
updateFieldStatus: function(input, isValid, errorMessage) {
const feedback = input.parentNode.querySelector('.invalid-feedback');
if (isValid) {
input.classList.remove('is-invalid');
input.classList.add('is-valid');
if (feedback) feedback.textContent = '';
} else {
input.classList.remove('is-valid');
input.classList.add('is-invalid');
if (feedback) feedback.textContent = errorMessage;
}
}
};
// Initialize on DOM load
document.addEventListener('DOMContentLoaded', function() {
// Initialize tooltips
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
tooltipTriggerList.map(function(tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
// Initialize form validation
const emailInputs = document.querySelectorAll('input[type="email"]');
emailInputs.forEach(input => {
input.addEventListener('blur', () => FormValidation.validateEmailField(input));
});
const ipInputs = document.querySelectorAll('input[data-validate="ip"]');
ipInputs.forEach(input => {
input.addEventListener('blur', () => FormValidation.validateIPField(input));
});
// Initialize auto-refresh for logs page
if (document.querySelector('#logs-page')) {
SMTPManagement.autoRefresh(window.location.href, 30000);
}
// Initialize current IP detection
const currentIPSpan = document.querySelector('#current-ip');
if (currentIPSpan) {
fetch('https://api.ipify.org?format=json')
.then(response => response.json())
.then(data => {
currentIPSpan.textContent = data.ip;
})
.catch(() => {
currentIPSpan.textContent = 'Unable to detect';
});
}
// Initialize copy buttons
const copyButtons = document.querySelectorAll('.copy-btn');
copyButtons.forEach(button => {
button.addEventListener('click', function() {
const textToCopy = this.getAttribute('data-copy') || this.nextElementSibling.textContent;
SMTPManagement.copyToClipboard(textToCopy, this);
});
});
// Initialize DNS check buttons
const dnsCheckButtons = document.querySelectorAll('.dns-check-btn');
dnsCheckButtons.forEach(button => {
button.addEventListener('click', function() {
const domain = this.getAttribute('data-domain');
const recordType = this.getAttribute('data-record-type');
const expectedValue = this.getAttribute('data-expected-value');
const statusElement = this.closest('.dns-record').querySelector('.dns-status');
SMTPManagement.showLoading(this);
DNSVerification.checkDNSRecord(domain, recordType, expectedValue)
.then(result => {
DNSVerification.updateDNSStatus(statusElement, result.status, result.message);
SMTPManagement.hideLoading(this);
})
.catch(error => {
console.error('DNS check failed:', error);
DNSVerification.updateDNSStatus(statusElement, 'error', 'DNS check failed');
SMTPManagement.hideLoading(this);
});
});
});
});
// Export for use in other scripts
window.SMTPManagement = SMTPManagement;
window.DNSVerification = DNSVerification;
window.FormValidation = FormValidation;