2026-08-12 12:56:22 +01:00
{{define "title"}}DKIM Keys - Email Server{{end}}
{{define "extra_css"}}
< style >
. dns-record { font-family : 'Courier New' , monospace ; color : black ; background-color : var ( -- bs - gray -100 ); border-radius : 0.375 rem ; padding : 0.75 rem ; border : 1 px solid var ( -- bs - border - color ); word-break : break-all ; }
. status-indicator { width : 12 px ; height : 12 px ; border-radius : 50 % ; display : inline-block ; margin-right : 0.5 rem ; }
. status-success { background-color : #28a745 ; }
. status-warning { background-color : #ffc107 ; }
. status-danger { background-color : #dc3545 ; }
</ style >
{{end}}
{{define "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 >
< div class = "modal fade" id = "createDKIMModal" tabindex = "-1" aria-hidden = "true" >
< div class = "modal-dialog" >
< div class = "modal-content" >
2026-08-15 21:51:43 +01:00
< form id = "createDKIMForm" method = "post" action = "/smtp-server/dkim/create" >
2026-08-12 12:56:22 +01:00
< div class = "modal-header" >
< h5 class = "modal-title" > 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 >
{{range .dkim_data}}< option value = "{{.domain.domain_name}}" > {{.domain.domain_name}}</ option > {{end}}
</ 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 >
< 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 >
{{range .dkim_data}}
{{$domain := .domain}}{{$key := .dkim_key}}{{$slug := dotToDash $domain.domain_name}}
< div class = "card mb-4" id = "domain-{{$slug}}" data-is-active = "{{$key.IsActive}}" >
< 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-{{$slug}}" >
< h5 class = "mb-0" >
< i class = "bi bi-server me-2" ></ i >
{{$domain.domain_name}}
{{if $key.IsActive}}< span class = "badge bg-success ms-2" > Active</ span > {{else}}< span class = "badge bg-secondary ms-2" > Inactive</ span > {{end}}
</ h5 >
</ div >
< div class = "btn-group btn-group-sm me-2" >
< button class = "btn btn-outline-primary" data-action = "check-dns" data-domain = "{{$domain.domain_name}}" data-selector = "{{$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" >
2026-08-15 21:51:43 +01:00
< a href = "/smtp-server/dkim/{{$key.ID}}/edit" class = "btn btn-outline-info" onclick = "event.stopPropagation();" >< i class = "bi bi-pencil me-1" ></ i > Edit</ a >
< form method = "post" action = "/smtp-server/dkim/{{$key.ID}}/toggle" class = "d-inline" >
2026-08-12 12:56:22 +01:00
{{if $key.IsActive}}
< 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 >
{{end}}
</ form >
2026-08-15 21:51:43 +01:00
< form method = "post" action = "/smtp-server/dkim/{{$key.ID}}/remove" class = "d-inline" >
2026-08-12 12:56:22 +01:00
< button type = "submit" class = "btn btn-outline-danger" onclick = "event.stopPropagation();" data-confirm = "Permanently remove the DKIM key for {{$domain.domain_name}}? You will lose the ability to sign emails until you regenerate a new key." >
< i class = "bi bi-trash me-1" ></ i > Remove
</ button >
</ form >
</ div >
2026-08-15 21:51:43 +01:00
< form method = "post" action = "/smtp-server/dkim/{{$domain.id}}/regenerate" class = "d-inline" onsubmit = "event.stopPropagation();" >
2026-08-12 12:56:22 +01:00
< button type = "submit" class = "btn btn-outline-warning" onclick = "event.stopPropagation();" >< i class = "bi bi-arrow-clockwise me-1" ></ i > Regenerate</ button >
</ form >
</ div >
< div class = "card-header-clickable" style = "cursor: pointer;" data-bs-toggle = "collapse" data-bs-target = "#collapse-{{$slug}}" >
< i class = "bi bi-chevron-down" id = "chevron-{{$slug}}" ></ i >
</ div >
</ div >
</ div >
< div class = "collapse" id = "collapse-{{$slug}}" >
< div class = "card-body" >
< div class = "row" >
< 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-{{$slug}}" >< 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" > {{.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" > {{.dns_record.value}}</ div ></ div >
< button class = "btn btn-outline-secondary btn-sm" onclick = "copyToClipboard('{{.dns_record.value}}')" >< i class = "bi bi-clipboard me-1" ></ i > Copy Value</ button >
</ div >
< 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-{{$slug}}" >< 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" > {{$domain.domain_name}}</ div ></ div >
< div class = "mb-2" >< strong > Type:</ strong > TXT</ div >
{{if .existing_spf}}< div class = "mb-2" >< strong > Current SPF:</ strong >< div class = "dns-record" > {{.existing_spf}}</ div ></ div > {{end}}
< div class = "mb-2" >< strong > Recommended SPF:</ strong >< div class = "dns-record" > {{.recommended_spf}}</ div ></ div >
< button class = "btn btn-outline-secondary btn-sm" onclick = "copyToClipboard('{{.recommended_spf}}')" >< i class = "bi bi-clipboard me-1" ></ i > Copy SPF</ button >
</ div >
</ div >
< 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 > {{$key.Selector}}</ code ></ div >
< div class = "col-md-3" >< strong > Created:</ strong >< br > {{strftime "%Y-%m-%d %H:%M" $key.CreatedAt}}</ div >
< div class = "col-md-3" >< strong > Server IP:</ strong >< br >< code > {{.public_ip}}</ code ></ div >
< div class = "col-md-3" >< strong > Status:</ strong >< br > {{if $key.IsActive}}< span class = "text-success" > Active</ span > {{else}}< span class = "text-secondary" > Inactive</ span > {{end}}</ div >
</ div >
</ div >
</ div >
</ div >
</ div >
</ div >
{{end}}
{{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" > {{len .old_dkim_data}}</ 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 >
{{range .old_dkim_data}}
{{$domain := .domain}}{{$key := .dkim_key}}
< 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 > {{$domain.domain_name}}< span class = "badge bg-secondary ms-2" > {{.status_text}}</ span ></ h6 >
< small class = "text-muted" > Selector: < code > {{$key.Selector}}</ code > | Created: {{strftime "%Y-%m-%d %H:%M" $key.CreatedAt}}</ small >
</ div >
< div class = "btn-group btn-group-sm" >
2026-08-15 21:51:43 +01:00
< form method = "post" action = "/smtp-server/dkim/{{$key.ID}}/toggle" class = "d-inline" >
2026-08-12 12:56:22 +01:00
< button type = "submit" class = "btn btn-outline-success btn-sm" >< i class = "bi bi-play-circle me-1" ></ i > Reactivate</ button >
</ form >
2026-08-15 21:51:43 +01:00
< form method = "post" action = "/smtp-server/dkim/{{$key.ID}}/remove" class = "d-inline" >
2026-08-12 12:56:22 +01:00
< button type = "submit" class = "btn btn-outline-danger btn-sm" data-confirm = "Permanently remove this old DKIM key? This action cannot be undone." >< i class = "bi bi-trash me-1" ></ i > Remove</ button >
</ form >
</ div >
</ div >
</ div >
</ div >
{{end}}
</ div >
</ div >
{{end}}
{{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 >
2026-08-15 21:51:43 +01:00
< a href = "/smtp-server/domains/add" class = "btn btn-primary" >< i class = "bi bi-plus-circle me-2" ></ i > Add Domain</ a >
2026-08-12 12:56:22 +01:00
</ div >
</ div >
{{end}}
</ div >
< 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" ></ div >
< div class = "modal-footer" >< button type = "button" class = "btn btn-secondary" data-bs-dismiss = "modal" > Close</ button ></ div >
</ div >
</ div >
</ div >
{{end}}
{{define "extra_js"}}
< script >
function copyToClipboard ( text ) {
navigator . clipboard . writeText ( text ). then ( function () { showToast ( 'Copied to clipboard!' , 'success' ); }, function ( err ) { showToast ( 'Failed to copy: ' + err , 'danger' ); });
}
async function checkDomainDNS ( domain , selector ) {
const dkimStatus = document . getElementById ( `dkim-status- ${ domain . replace ( '.' , '-' ) } ` );
const spfStatus = document . getElementById ( `spf-status- ${ domain . replace ( '.' , '-' ) } ` );
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 {
2026-08-15 21:51:43 +01:00
const dkimResponse = await fetch ( '/smtp-server/dkim/check_dns' , { method : 'POST' , headers : { 'Content-Type' : 'application/x-www-form-urlencoded' }, body : new URLSearchParams ({ domain , selector }) });
2026-08-12 12:56:22 +01:00
const dkimResult = await dkimResponse . json ();
2026-08-15 21:51:43 +01:00
const spfResponse = await fetch ( '/smtp-server/dkim/check_spf' , { method : 'POST' , headers : { 'Content-Type' : 'application/x-www-form-urlencoded' }, body : new URLSearchParams ({ domain }) });
2026-08-12 12:56:22 +01:00
const spfResult = await spfResponse . json ();
const domainCard = document . getElementById ( `domain- ${ domain . replace ( '.' , '-' ) } ` );
const isActive = domainCard && domainCard . dataset . isActive === 'true' ;
if ( isActive ) {
dkimStatus . innerHTML = dkimResult . success
? '<span class="status-indicator status-success"></span><small class="text-success">Active & Configured</small>'
: '<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>' ;
}
spfStatus . innerHTML = spfResult . success
? '<span class="status-indicator status-success"></span><small class="text-success">Found</small>'
: '<span class="status-indicator status-danger"></span><small class="text-danger">Not found</small>' ;
showDNSResults ( domain , dkimResult , spfResult );
} catch ( 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>' ;
}
}
function showDNSResults ( domain , dkimResult , spfResult ) {
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 }
</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 }
</div>
</div>` ;
document . getElementById ( 'dnsResults' ). innerHTML = resultsHtml ;
new bootstrap . Modal ( document . getElementById ( 'dnsResultModal' )). show ();
}
async function checkAllDNS () {
const cards = document . querySelectorAll ( '[data-action="check-dns"]' );
for ( const btn of cards ) {
await checkDomainDNS ( btn . dataset . domain , btn . dataset . selector );
}
}
document . addEventListener ( 'DOMContentLoaded' , function () {
const checkAllBtn = document . querySelector ( '[data-action="check-all-dns"]' );
if ( checkAllBtn ) { checkAllBtn . addEventListener ( 'click' , checkAllDNS ); }
document . querySelectorAll ( '[data-action="check-dns"]' ). forEach ( button => {
button . addEventListener ( 'click' , function ( event ) {
event . stopPropagation ();
checkDomainDNS ( this . dataset . domain , this . dataset . selector );
});
});
document . querySelectorAll ( '.card-header-clickable[data-bs-toggle="collapse"]' ). forEach ( function ( element ) {
element . addEventListener ( 'click' , function () {
const targetId = this . getAttribute ( 'data-bs-target' );
const chevron = document . querySelector ( targetId . replace ( '#collapse-' , '#chevron-' ));
if ( chevron ) {
setTimeout (() => {
const collapseElement = document . querySelector ( targetId );
chevron . className = ( collapseElement && collapseElement . classList . contains ( 'show' )) ? 'bi bi-chevron-up' : 'bi bi-chevron-down' ;
}, 100 );
}
});
});
document . querySelectorAll ( 'form[action*="toggle"]' ). forEach ( form => {
if ( ! form . action . includes ( '/dkim/' )) return ;
form . addEventListener ( 'submit' , async function ( event ) {
event . preventDefault ();
const response = await fetch ( this . action , { method : 'POST' , body : new FormData ( this ), headers : { 'X-Requested-With' : 'XMLHttpRequest' } });
if ( response . ok ) {
const result = await response . json ();
if ( result . success ) { showToast ( result . message , 'success' ); setTimeout (() => location . reload (), 600 ); }
else { showToast ( result . message , 'danger' ); }
}
});
});
document . getElementById ( 'createDKIMForm' ). addEventListener ( 'submit' , async function ( event ) {
event . preventDefault ();
const response = await fetch ( this . action , { method : 'POST' , body : new FormData ( this ) });
const result = await response . json ();
if ( result . success ) { showToast ( result . message , 'success' ); setTimeout (() => location . reload (), 600 ); }
else { showToast ( result . message , 'danger' ); }
});
});
</ script >
{{end}}