2026-02-27 08:06:22 +01:00
/ * *
* WooCow – Admin JavaScript
* /
( function ( $ ) {
'use strict' ;
const ajax = ( action , data ) =>
$ . post ( woocow . ajax _url , { action , nonce : woocow . nonce , ... data } ) ;
const notice = ( $el , type , msg , autohide = true ) => {
$el . html ( ` <div class="notice notice- ${ type } is-dismissible"><p> ${ msg } </p></div> ` ) ;
if ( autohide ) setTimeout ( ( ) => $el . find ( '.notice' ) . fadeOut ( ) , 4000 ) ;
} ;
// ── Servers Page ──────────────────────────────────────────────────────────
if ( $ ( '#wc-servers-table-wrap' ) . length ) {
let editId = 0 ;
const loadServers = ( ) => {
ajax ( 'woocow_servers_list' ) . done ( res => {
if ( ! res . success ) return ;
const rows = res . data ;
if ( ! rows . length ) {
$ ( '#wc-servers-table-wrap' ) . html ( '<p>No servers yet. Add one above.</p>' ) ;
return ;
}
let html = ` <table class="wp-list-table widefat fixed striped woocow-table">
< thead > < tr >
< th > Name < / t h > < t h > U R L < / t h > < t h > S t a t u s < / t h > < t h > A d d e d < / t h > < t h > A c t i o n s < / t h >
< / t r > < / t h e a d > < t b o d y > ` ;
rows . forEach ( s => {
const badge = s . active == 1
? '<span class="woocow-badge woocow-badge-green">Active</span>'
: '<span class="woocow-badge woocow-badge-grey">Inactive</span>' ;
html += ` <tr data-id=" ${ s . id } " data-name=" ${ esc ( s . name ) } " data-url=" ${ esc ( s . url ) } ">
< td > < strong > $ { esc ( s . name ) } < / s t r o n g > < / t d >
< td > < a href = "${esc(s.url)}" target = "_blank" rel = "noopener" > $ { esc ( s . url ) } < / a > < / t d >
< td > $ { badge } < / t d >
< td > $ { s . created _at . split ( ' ' ) [ 0 ] } < / t d >
< td class = "woocow-actions" >
< button class = "button button-small wc-srv-edit" data - id = "${s.id}" > Edit < / b u t t o n >
< button class = "button button-small wc-srv-test" data - id = "${s.id}" > Test < / b u t t o n >
< button class = "button button-small wc-srv-del" data - id = "${s.id}" style = "color:#a00" > Delete < / b u t t o n >
< / t d >
< / t r > ` ;
} ) ;
html += '</tbody></table>' ;
$ ( '#wc-servers-table-wrap' ) . html ( html ) ;
$ ( '#wc-servers-loading' ) . hide ( ) ;
} ) ;
} ;
loadServers ( ) ;
// Show add form
$ ( '#wc-add-server' ) . on ( 'click' , ( ) => {
editId = 0 ;
$ ( '#wc-server-id' ) . val ( '' ) ;
$ ( '#wc-server-name, #wc-server-url, #wc-server-key' ) . val ( '' ) ;
$ ( '#wc-server-active' ) . prop ( 'checked' , true ) ;
$ ( '#wc-server-form-title' ) . text ( 'Add Server' ) ;
$ ( '#wc-server-form' ) . slideDown ( ) ;
} ) ;
// Edit row
$ ( document ) . on ( 'click' , '.wc-srv-edit' , function ( ) {
editId = $ ( this ) . data ( 'id' ) ;
const $row = $ ( this ) . closest ( 'tr' ) ;
$ ( '#wc-server-id' ) . val ( editId ) ;
$ ( '#wc-server-name' ) . val ( $row . data ( 'name' ) ) ;
$ ( '#wc-server-url' ) . val ( $row . data ( 'url' ) ) ;
$ ( '#wc-server-key' ) . val ( '' ) ;
$ ( '#wc-server-active' ) . prop ( 'checked' , true ) ;
$ ( '#wc-server-form-title' ) . text ( 'Edit Server' ) ;
$ ( '#wc-server-form' ) . slideDown ( ) ;
$ ( 'html, body' ) . animate ( { scrollTop : 0 } , 300 ) ;
} ) ;
// Save
$ ( '#wc-server-save' ) . on ( 'click' , ( ) => {
const data = {
id : $ ( '#wc-server-id' ) . val ( ) ,
name : $ ( '#wc-server-name' ) . val ( ) . trim ( ) ,
url : $ ( '#wc-server-url' ) . val ( ) . trim ( ) ,
api _key : $ ( '#wc-server-key' ) . val ( ) . trim ( ) ,
active : $ ( '#wc-server-active' ) . is ( ':checked' ) ? 1 : 0 ,
} ;
if ( ! data . name || ! data . url || ( ! data . api _key && ! editId ) ) {
notice ( $ ( '#wc-notices' ) , 'error' , 'Please fill in all required fields.' ) ;
return ;
}
ajax ( 'woocow_server_save' , data ) . done ( res => {
if ( res . success ) {
notice ( $ ( '#wc-notices' ) , 'success' , 'Server saved.' ) ;
$ ( '#wc-server-form' ) . slideUp ( ) ;
loadServers ( ) ;
} else {
notice ( $ ( '#wc-notices' ) , 'error' , res . data || 'Save failed.' ) ;
}
} ) ;
} ) ;
// Test connection
$ ( '#wc-server-test' ) . on ( 'click' , ( ) => {
const $result = $ ( '#wc-server-test-result' ) . text ( 'Testing…' ) ;
ajax ( 'woocow_server_test' , {
id : $ ( '#wc-server-id' ) . val ( ) ,
url : $ ( '#wc-server-url' ) . val ( ) . trim ( ) ,
api _key : $ ( '#wc-server-key' ) . val ( ) . trim ( ) ,
} ) . done ( res => {
if ( res . success ) {
$result . html ( ` <span style="color:green">✓ Connected – Mailcow ${ esc ( res . data . version ) } </span> ` ) ;
} else {
$result . html ( ` <span style="color:red">✗ ${ esc ( res . data ) } </span> ` ) ;
}
} ) ;
} ) ;
// Test from row
$ ( document ) . on ( 'click' , '.wc-srv-test' , function ( ) {
const id = $ ( this ) . data ( 'id' ) ;
const $td = $ ( this ) . closest ( 'td' ) ;
$td . append ( '<span class="wc-inline-test"> Testing…</span>' ) ;
ajax ( 'woocow_server_test' , { id } ) . done ( res => {
$td . find ( '.wc-inline-test' ) . html (
res . success
? ` <span style="color:green"> ✓ v ${ esc ( res . data . version ) } </span> `
: ` <span style="color:red"> ✗ ${ esc ( res . data ) } </span> `
) ;
} ) ;
} ) ;
// Delete
$ ( document ) . on ( 'click' , '.wc-srv-del' , function ( ) {
if ( ! confirm ( 'Delete this server? All domain assignments for it will also be removed.' ) ) return ;
ajax ( 'woocow_server_delete' , { id : $ ( this ) . data ( 'id' ) } ) . done ( res => {
if ( res . success ) loadServers ( ) ;
else notice ( $ ( '#wc-notices' ) , 'error' , res . data ) ;
} ) ;
} ) ;
$ ( '#wc-server-cancel' ) . on ( 'click' , ( ) => $ ( '#wc-server-form' ) . slideUp ( ) ) ;
}
// ── Assignments Page ──────────────────────────────────────────────────────
if ( $ ( '#wc-assignments-table-wrap' ) . length ) {
const loadAssignments = ( ) => {
ajax ( 'woocow_assignments_list' ) . done ( res => {
if ( ! res . success ) return ;
const rows = res . data ;
if ( ! rows . length ) {
$ ( '#wc-assignments-table-wrap' ) . html ( '<p>No assignments yet.</p>' ) ;
$ ( '#wc-assignments-loading' ) . hide ( ) ;
return ;
}
let html = ` <table class="wp-list-table widefat fixed striped woocow-table">
< thead > < tr >
< th > Customer < / t h > < t h > E m a i l < / t h > < t h > D o m a i n < / t h > < t h > S e r v e r < / t h > < t h > A s s i g n e d < / t h > < t h > A c t i o n s < / t h >
< / t r > < / t h e a d > < t b o d y > ` ;
rows . forEach ( r => {
html += ` <tr>
< td > $ { esc ( r . display _name ) } < / t d >
< td > $ { esc ( r . user _email ) } < / t d >
< td > < strong > $ { esc ( r . domain ) } < / s t r o n g > < / t d >
< td > $ { esc ( r . server _name ) } < / t d >
< td > $ { r . created _at . split ( ' ' ) [ 0 ] } < / t d >
< td > < button class = "button button-small wc-assign-del" data - id = "${r.id}" style = "color:#a00" > Remove < / b u t t o n > < / t d >
< / t r > ` ;
} ) ;
html += '</tbody></table>' ;
$ ( '#wc-assignments-table-wrap' ) . html ( html ) ;
$ ( '#wc-assignments-loading' ) . hide ( ) ;
} ) ;
} ;
// Load servers into select
ajax ( 'woocow_servers_list' ) . done ( res => {
if ( ! res . success ) return ;
res . data . filter ( s => s . active == 1 ) . forEach ( s => {
$ ( '#wc-assign-server' ) . append ( ` <option value=" ${ s . id } "> ${ esc ( s . name ) } </option> ` ) ;
} ) ;
} ) ;
// Server → load domains
$ ( '#wc-assign-server' ) . on ( 'change' , function ( ) {
const sid = $ ( this ) . val ( ) ;
$ ( '#wc-assign-domain' ) . html ( '<option value="">— Loading —</option>' ) ;
if ( ! sid ) { $ ( '#wc-domain-row' ) . hide ( ) ; return ; }
ajax ( 'woocow_server_domains' , { server _id : sid } ) . done ( res => {
if ( ! res . success ) {
alert ( 'Could not load domains: ' + res . data ) ;
return ;
}
$ ( '#wc-assign-domain' ) . html ( '<option value="">— Select domain —</option>' ) ;
res . data . forEach ( d => {
$ ( '#wc-assign-domain' ) . append ( ` <option value=" ${ esc ( d . domain ) } "> ${ esc ( d . domain ) } </option> ` ) ;
} ) ;
$ ( '#wc-domain-row' ) . show ( ) ;
} ) ;
} ) ;
// Customer autocomplete
let searchTimer ;
$ ( '#wc-cust-search' ) . on ( 'input' , function ( ) {
clearTimeout ( searchTimer ) ;
const term = $ ( this ) . val ( ) . trim ( ) ;
if ( term . length < 2 ) { $ ( '#wc-cust-results' ) . hide ( ) ; return ; }
searchTimer = setTimeout ( ( ) => {
ajax ( 'woocow_customers_search' , { term } ) . done ( res => {
if ( ! res . success || ! res . data . length ) { $ ( '#wc-cust-results' ) . hide ( ) ; return ; }
let html = '' ;
res . data . forEach ( c => {
html += ` <div class="woocow-ac-item" data-id=" ${ c . id } " data-label=" ${ esc ( c . label ) } "> ${ esc ( c . label ) } </div> ` ;
} ) ;
$ ( '#wc-cust-results' ) . html ( html ) . show ( ) ;
} ) ;
} , 250 ) ;
} ) ;
$ ( document ) . on ( 'click' , '.woocow-ac-item' , function ( ) {
$ ( '#wc-cust-id' ) . val ( $ ( this ) . data ( 'id' ) ) ;
$ ( '#wc-cust-search' ) . val ( '' ) ;
$ ( '#wc-cust-selected' ) . text ( $ ( this ) . data ( 'label' ) ) ;
$ ( '#wc-cust-results' ) . hide ( ) ;
} ) ;
$ ( document ) . on ( 'click' , function ( e ) {
if ( ! $ ( e . target ) . closest ( '#wc-cust-results, #wc-cust-search' ) . length ) {
$ ( '#wc-cust-results' ) . hide ( ) ;
}
} ) ;
// Save assignment
$ ( '#wc-assign-save' ) . on ( 'click' , ( ) => {
const customer _id = $ ( '#wc-cust-id' ) . val ( ) ;
const server _id = $ ( '#wc-assign-server' ) . val ( ) ;
const domain = $ ( '#wc-assign-domain' ) . val ( ) ;
if ( ! customer _id || ! server _id || ! domain ) {
notice ( $ ( '#wc-assign-notice' ) , 'error' , 'Please select a customer, server, and domain.' ) ;
return ;
}
ajax ( 'woocow_assignment_save' , { customer _id , server _id , domain } ) . done ( res => {
if ( res . success ) {
notice ( $ ( '#wc-assign-notice' ) , 'success' , ` Domain <strong> ${ esc ( domain ) } </strong> assigned. ` ) ;
$ ( '#wc-cust-id' ) . val ( '' ) ;
$ ( '#wc-cust-selected' ) . text ( '' ) ;
loadAssignments ( ) ;
} else {
notice ( $ ( '#wc-assign-notice' ) , 'error' , res . data ) ;
}
} ) ;
} ) ;
// Delete assignment
$ ( document ) . on ( 'click' , '.wc-assign-del' , function ( ) {
if ( ! confirm ( 'Remove this domain assignment?' ) ) return ;
ajax ( 'woocow_assignment_delete' , { id : $ ( this ) . data ( 'id' ) } ) . done ( res => {
if ( res . success ) loadAssignments ( ) ;
} ) ;
} ) ;
loadAssignments ( ) ;
}
// ── Mailboxes Page ────────────────────────────────────────────────────────
if ( $ ( '#wc-mb-table-wrap' ) . length ) {
let currentServerId = null ;
let currentDomain = null ;
// Server → load domains
$ ( '#wc-mb-server' ) . on ( 'change' , function ( ) {
const sid = $ ( this ) . val ( ) ;
$ ( '#wc-mb-domain' ) . hide ( ) . html ( '<option value="">— Select domain —</option>' ) ;
$ ( '#wc-mb-load' ) . prop ( 'disabled' , true ) ;
if ( ! sid ) return ;
ajax ( 'woocow_server_domains' , { server _id : sid } ) . done ( res => {
if ( ! res . success ) return ;
res . data . forEach ( d => {
$ ( '#wc-mb-domain' ) . append ( ` <option value=" ${ esc ( d . domain ) } "> ${ esc ( d . domain ) } </option> ` ) ;
} ) ;
$ ( '#wc-mb-domain' ) . show ( ) ;
} ) ;
} ) ;
$ ( '#wc-mb-domain' ) . on ( 'change' , function ( ) {
$ ( '#wc-mb-load' ) . prop ( 'disabled' , ! $ ( this ) . val ( ) ) ;
} ) ;
const loadMailboxes = ( ) => {
currentServerId = $ ( '#wc-mb-server' ) . val ( ) ;
currentDomain = $ ( '#wc-mb-domain' ) . val ( ) ;
if ( ! currentServerId || ! currentDomain ) return ;
$ ( '#wc-mb-table-wrap' ) . html ( '<p>Loading…</p>' ) ;
ajax ( 'woocow_admin_mailboxes' , { server _id : currentServerId , domain : currentDomain } ) . done ( res => {
if ( ! res . success ) {
$ ( '#wc-mb-table-wrap' ) . html ( ` <div class="notice notice-error"><p> ${ esc ( res . data ) } </p></div> ` ) ;
return ;
}
const boxes = res . data . mailboxes || [ ] ;
const webmail = res . data . webmail _url ;
if ( ! boxes . length ) {
$ ( '#wc-mb-table-wrap' ) . html ( '<p>No mailboxes found for this domain.</p>' ) ;
} else {
let html = ` <table class="wp-list-table widefat fixed striped woocow-table">
< thead > < tr >
< th > Email < / t h > < t h > N a m e < / t h > < t h > Q u o t a U s e d < / t h > < t h > Q u o t a M a x < / t h > < t h > A c t i v e < / t h > < t h > A c t i o n s < / t h >
< / t r > < / t h e a d > < t b o d y > ` ;
boxes . forEach ( m => {
2026-02-27 08:21:46 +01:00
const pct = m . percent _in _use || 0 ;
const used = formatMB ( m . quota _used ) ;
const max = formatMB ( m . quota ) ;
const bar = ` <div class="woocow-quota-bar"><div style="width: ${ pct } %"></div></div> ` ;
const quotaMB = Math . round ( ( m . quota || 0 ) / 1024 / 1024 ) ;
2026-02-27 08:06:22 +01:00
html += ` <tr>
< td > < a href = "${esc(webmail)}" target = "_blank" > $ { esc ( m . username ) } < / a > < / t d >
< td > $ { esc ( m . name ) } < / t d >
< td > $ { used } $ { bar } < / t d >
< td > $ { max } < / t d >
< td > $ { m . active == 1 ? '✓' : '– ' } < / t d >
< td class = "woocow-actions" >
feat: domains, transports, logs, quarantine, spam filter, i18n + UX fixes
Features added:
- Admin > Domains: add domains to Mailcow servers, auto-generate DKIM,
display full DNS record set (MX, SPF, DMARC, DKIM, autoconfig CNAMEs)
with one-click copy per record
- Admin > Transports: manage sender-dependent relay hosts (add/delete)
- Admin > Logs: view Postfix, Dovecot, Rspamd, Ratelimit, API and other
server logs in a dark scrollable panel
- My Account: per-domain Quarantine panel — view score, sender, subject,
date; permanently delete quarantined messages
- My Account: per-mailbox Spam Filter slider (1–15 threshold) saved via API
- My Account: Aliases & Forwarders (alias creation doubles as forwarder
to any external address)
UX fixes:
- Quota 0 now displays ∞ (unlimited) in both admin and account views
- Admin mailbox action buttons replaced with Dashicon icon buttons
(lock, chart-bar, trash) with title tooltips
i18n:
- load_plugin_textdomain registered on init hook
- All user-facing PHP strings wrapped in __() / esc_html__()
- Translated strings array passed to account JS via wp_localize_script
- woocow-es_ES.po/.mo — Spanish translation
- woocow-ro_RO.po/.mo — Romanian translation (with correct plural forms)
- English remains the fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 08:38:52 +01:00
< button class = "button button-small woocow-icon-btn wc-mb-reset-pw"
title = "Reset Password" data - email = "${esc(m.username)}" >
< span class = "dashicons dashicons-lock" > < / s p a n >
< / b u t t o n >
< button class = "button button-small woocow-icon-btn wc-mb-set-quota"
title = "Set Quota" data - email = "${esc(m.username)}" data - quota = "${quotaMB}" >
< span class = "dashicons dashicons-chart-bar" > < / s p a n >
< / b u t t o n >
< button class = "button button-small woocow-icon-btn wc-mb-del"
title = "Delete" data - email = "${esc(m.username)}" style = "color:#a00" >
< span class = "dashicons dashicons-trash" > < / s p a n >
< / b u t t o n >
2026-02-27 08:06:22 +01:00
< / t d >
< / t r > ` ;
} ) ;
html += '</tbody></table>' ;
$ ( '#wc-mb-table-wrap' ) . html ( html ) ;
}
$ ( '#wc-mb-domain-label' ) . text ( currentDomain ) ;
$ ( '#wc-mb-create' ) . show ( ) ;
} ) ;
} ;
$ ( '#wc-mb-load' ) . on ( 'click' , loadMailboxes ) ;
// Create mailbox modal
$ ( '#wc-mb-create' ) . on ( 'click' , ( ) => {
$ ( '#wc-mb-local, #wc-mb-fullname, #wc-mb-pass, #wc-mb-pass2' ) . val ( '' ) ;
$ ( '#wc-mb-quota' ) . val ( 1024 ) ;
$ ( '#wc-mb-modal-notice' ) . text ( '' ) ;
$ ( '#wc-mb-modal' ) . show ( ) ;
} ) ;
$ ( '#wc-mb-modal-cancel' ) . on ( 'click' , ( ) => $ ( '#wc-mb-modal' ) . hide ( ) ) ;
$ ( document ) . on ( 'keydown' , e => { if ( e . key === 'Escape' ) $ ( '#wc-mb-modal' ) . hide ( ) ; } ) ;
$ ( '#wc-mb-modal-save' ) . on ( 'click' , ( ) => {
const data = {
server _id : currentServerId ,
domain : currentDomain ,
local _part : $ ( '#wc-mb-local' ) . val ( ) . trim ( ) ,
name : $ ( '#wc-mb-fullname' ) . val ( ) . trim ( ) ,
password : $ ( '#wc-mb-pass' ) . val ( ) ,
password2 : $ ( '#wc-mb-pass2' ) . val ( ) ,
quota : $ ( '#wc-mb-quota' ) . val ( ) ,
} ;
$ ( '#wc-mb-modal-notice' ) . text ( 'Creating…' ) ;
ajax ( 'woocow_admin_mailbox_create' , data ) . done ( res => {
if ( res . success ) {
$ ( '#wc-mb-modal' ) . hide ( ) ;
notice ( $ ( '#wc-mb-notices' ) , 'success' , ` Mailbox <strong> ${ esc ( res . data . email ) } </strong> created. ` ) ;
loadMailboxes ( ) ;
} else {
$ ( '#wc-mb-modal-notice' ) . html ( ` <span style="color:red"> ${ esc ( res . data ) } </span> ` ) ;
}
} ) ;
} ) ;
// Delete mailbox
$ ( document ) . on ( 'click' , '.wc-mb-del' , function ( ) {
const email = $ ( this ) . data ( 'email' ) ;
if ( ! confirm ( ` Delete mailbox ${ email } ? This cannot be undone. ` ) ) return ;
ajax ( 'woocow_admin_mailbox_delete' , { server _id : currentServerId , email } ) . done ( res => {
if ( res . success ) loadMailboxes ( ) ;
else notice ( $ ( '#wc-mb-notices' ) , 'error' , res . data ) ;
} ) ;
} ) ;
2026-02-27 08:21:46 +01:00
// ── Edit modal: Reset PW ──────────────────────────────────────────────
let editEmail = '' ;
let editType = '' ;
const openEditModal = ( email , type , currentQuota ) => {
editEmail = email ;
editType = type ;
$ ( '#wc-mb-edit-subtitle' ) . text ( email ) ;
$ ( '#wc-mb-edit-notice' ) . text ( '' ) ;
$ ( '#wc-mb-edit-pw-section, #wc-mb-edit-quota-section' ) . hide ( ) ;
if ( type === 'password' ) {
$ ( '#wc-mb-edit-title' ) . text ( 'Reset Password' ) ;
$ ( '#wc-mb-edit-pass, #wc-mb-edit-pass2' ) . val ( '' ) ;
$ ( '#wc-mb-edit-pw-section' ) . show ( ) ;
setTimeout ( ( ) => $ ( '#wc-mb-edit-pass' ) . trigger ( 'focus' ) , 100 ) ;
} else {
$ ( '#wc-mb-edit-title' ) . text ( 'Set Quota' ) ;
$ ( '#wc-mb-edit-quota' ) . val ( currentQuota || 1024 ) ;
$ ( '#wc-mb-edit-quota-section' ) . show ( ) ;
setTimeout ( ( ) => $ ( '#wc-mb-edit-quota' ) . trigger ( 'focus' ) , 100 ) ;
}
$ ( '#wc-mb-edit-modal' ) . show ( ) ;
} ;
$ ( document ) . on ( 'click' , '.wc-mb-reset-pw' , function ( ) {
openEditModal ( $ ( this ) . data ( 'email' ) , 'password' , null ) ;
} ) ;
$ ( document ) . on ( 'click' , '.wc-mb-set-quota' , function ( ) {
openEditModal ( $ ( this ) . data ( 'email' ) , 'quota' , $ ( this ) . data ( 'quota' ) ) ;
} ) ;
$ ( '#wc-mb-edit-cancel' ) . on ( 'click' , ( ) => $ ( '#wc-mb-edit-modal' ) . hide ( ) ) ;
$ ( document ) . on ( 'keydown' , e => { if ( e . key === 'Escape' ) $ ( '#wc-mb-edit-modal' ) . hide ( ) ; } ) ;
$ ( '#wc-mb-edit-save' ) . on ( 'click' , ( ) => {
const $note = $ ( '#wc-mb-edit-notice' ) . text ( 'Saving…' ) ;
const data = { server _id : currentServerId , email : editEmail , type : editType } ;
if ( editType === 'password' ) {
data . password = $ ( '#wc-mb-edit-pass' ) . val ( ) ;
data . password2 = $ ( '#wc-mb-edit-pass2' ) . val ( ) ;
if ( ! data . password ) { $note . html ( '<span style="color:red">Password cannot be empty.</span>' ) ; return ; }
if ( data . password !== data . password2 ) { $note . html ( '<span style="color:red">Passwords do not match.</span>' ) ; return ; }
} else {
data . quota = $ ( '#wc-mb-edit-quota' ) . val ( ) ;
if ( ! data . quota || data . quota < 1 ) { $note . html ( '<span style="color:red">Enter a valid quota.</span>' ) ; return ; }
}
ajax ( 'woocow_admin_mailbox_edit' , data ) . done ( res => {
if ( res . success ) {
$ ( '#wc-mb-edit-modal' ) . hide ( ) ;
const msg = editType === 'password' ? 'Password updated.' : 'Quota updated.' ;
notice ( $ ( '#wc-mb-notices' ) , 'success' , ` <strong> ${ esc ( editEmail ) } </strong> — ${ msg } ` ) ;
if ( editType === 'quota' ) loadMailboxes ( ) ; // refresh to show new quota
} else {
$note . html ( ` <span style="color:red"> ${ esc ( res . data ) } </span> ` ) ;
}
} ) ;
} ) ;
2026-02-27 08:06:22 +01:00
}
// ── Utilities ─────────────────────────────────────────────────────────────
function esc ( str ) {
return String ( str ) . replace ( /[&<>"']/g , m => ( {
'&' : '&' , '<' : '<' , '>' : '>' , '"' : '"' , "'" : '''
} ) [ m ] ) ;
}
function formatMB ( bytes ) {
feat: domains, transports, logs, quarantine, spam filter, i18n + UX fixes
Features added:
- Admin > Domains: add domains to Mailcow servers, auto-generate DKIM,
display full DNS record set (MX, SPF, DMARC, DKIM, autoconfig CNAMEs)
with one-click copy per record
- Admin > Transports: manage sender-dependent relay hosts (add/delete)
- Admin > Logs: view Postfix, Dovecot, Rspamd, Ratelimit, API and other
server logs in a dark scrollable panel
- My Account: per-domain Quarantine panel — view score, sender, subject,
date; permanently delete quarantined messages
- My Account: per-mailbox Spam Filter slider (1–15 threshold) saved via API
- My Account: Aliases & Forwarders (alias creation doubles as forwarder
to any external address)
UX fixes:
- Quota 0 now displays ∞ (unlimited) in both admin and account views
- Admin mailbox action buttons replaced with Dashicon icon buttons
(lock, chart-bar, trash) with title tooltips
i18n:
- load_plugin_textdomain registered on init hook
- All user-facing PHP strings wrapped in __() / esc_html__()
- Translated strings array passed to account JS via wp_localize_script
- woocow-es_ES.po/.mo — Spanish translation
- woocow-ro_RO.po/.mo — Romanian translation (with correct plural forms)
- English remains the fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 08:38:52 +01:00
if ( bytes === 0 || bytes === '0' ) return '∞' ;
2026-02-27 08:06:22 +01:00
if ( ! bytes ) return '0 MB' ;
const mb = bytes / 1024 / 1024 ;
return mb >= 1024 ? ( mb / 1024 ) . toFixed ( 1 ) + ' GB' : mb . toFixed ( 0 ) + ' MB' ;
}
feat: domains, transports, logs, quarantine, spam filter, i18n + UX fixes
Features added:
- Admin > Domains: add domains to Mailcow servers, auto-generate DKIM,
display full DNS record set (MX, SPF, DMARC, DKIM, autoconfig CNAMEs)
with one-click copy per record
- Admin > Transports: manage sender-dependent relay hosts (add/delete)
- Admin > Logs: view Postfix, Dovecot, Rspamd, Ratelimit, API and other
server logs in a dark scrollable panel
- My Account: per-domain Quarantine panel — view score, sender, subject,
date; permanently delete quarantined messages
- My Account: per-mailbox Spam Filter slider (1–15 threshold) saved via API
- My Account: Aliases & Forwarders (alias creation doubles as forwarder
to any external address)
UX fixes:
- Quota 0 now displays ∞ (unlimited) in both admin and account views
- Admin mailbox action buttons replaced with Dashicon icon buttons
(lock, chart-bar, trash) with title tooltips
i18n:
- load_plugin_textdomain registered on init hook
- All user-facing PHP strings wrapped in __() / esc_html__()
- Translated strings array passed to account JS via wp_localize_script
- woocow-es_ES.po/.mo — Spanish translation
- woocow-ro_RO.po/.mo — Romanian translation (with correct plural forms)
- English remains the fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 08:38:52 +01:00
// ── Domains Page ──────────────────────────────────────────────────────────
if ( $ ( '#wc-dom-server' ) . length ) {
let domServerId = null ;
let domServerUrl = null ;
2026-02-27 08:53:11 +01:00
let domainsData = { } ; // domain name → full data object
feat: domains, transports, logs, quarantine, spam filter, i18n + UX fixes
Features added:
- Admin > Domains: add domains to Mailcow servers, auto-generate DKIM,
display full DNS record set (MX, SPF, DMARC, DKIM, autoconfig CNAMEs)
with one-click copy per record
- Admin > Transports: manage sender-dependent relay hosts (add/delete)
- Admin > Logs: view Postfix, Dovecot, Rspamd, Ratelimit, API and other
server logs in a dark scrollable panel
- My Account: per-domain Quarantine panel — view score, sender, subject,
date; permanently delete quarantined messages
- My Account: per-mailbox Spam Filter slider (1–15 threshold) saved via API
- My Account: Aliases & Forwarders (alias creation doubles as forwarder
to any external address)
UX fixes:
- Quota 0 now displays ∞ (unlimited) in both admin and account views
- Admin mailbox action buttons replaced with Dashicon icon buttons
(lock, chart-bar, trash) with title tooltips
i18n:
- load_plugin_textdomain registered on init hook
- All user-facing PHP strings wrapped in __() / esc_html__()
- Translated strings array passed to account JS via wp_localize_script
- woocow-es_ES.po/.mo — Spanish translation
- woocow-ro_RO.po/.mo — Romanian translation (with correct plural forms)
- English remains the fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 08:38:52 +01:00
$ ( '#wc-dom-server' ) . on ( 'change' , function ( ) {
domServerId = $ ( this ) . val ( ) ;
domServerUrl = $ ( this ) . find ( 'option:selected' ) . data ( 'url' ) ;
$ ( '#wc-dom-load' ) . prop ( 'disabled' , ! domServerId ) ;
$ ( '#wc-dom-add-btn, #wc-dom-table-wrap' ) . hide ( ) ;
2026-02-27 08:53:11 +01:00
domainsData = { } ;
feat: domains, transports, logs, quarantine, spam filter, i18n + UX fixes
Features added:
- Admin > Domains: add domains to Mailcow servers, auto-generate DKIM,
display full DNS record set (MX, SPF, DMARC, DKIM, autoconfig CNAMEs)
with one-click copy per record
- Admin > Transports: manage sender-dependent relay hosts (add/delete)
- Admin > Logs: view Postfix, Dovecot, Rspamd, Ratelimit, API and other
server logs in a dark scrollable panel
- My Account: per-domain Quarantine panel — view score, sender, subject,
date; permanently delete quarantined messages
- My Account: per-mailbox Spam Filter slider (1–15 threshold) saved via API
- My Account: Aliases & Forwarders (alias creation doubles as forwarder
to any external address)
UX fixes:
- Quota 0 now displays ∞ (unlimited) in both admin and account views
- Admin mailbox action buttons replaced with Dashicon icon buttons
(lock, chart-bar, trash) with title tooltips
i18n:
- load_plugin_textdomain registered on init hook
- All user-facing PHP strings wrapped in __() / esc_html__()
- Translated strings array passed to account JS via wp_localize_script
- woocow-es_ES.po/.mo — Spanish translation
- woocow-ro_RO.po/.mo — Romanian translation (with correct plural forms)
- English remains the fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 08:38:52 +01:00
} ) ;
const loadDomains = ( ) => {
ajax ( 'woocow_server_domains' , { server _id : domServerId } ) . done ( res => {
if ( ! res . success ) {
notice ( $ ( '#wc-dom-notices' ) , 'error' , res . data ) ;
return ;
}
const domains = res . data ;
2026-02-27 08:53:11 +01:00
domainsData = { } ;
domains . forEach ( d => { domainsData [ d . domain ] = d ; } ) ;
feat: domains, transports, logs, quarantine, spam filter, i18n + UX fixes
Features added:
- Admin > Domains: add domains to Mailcow servers, auto-generate DKIM,
display full DNS record set (MX, SPF, DMARC, DKIM, autoconfig CNAMEs)
with one-click copy per record
- Admin > Transports: manage sender-dependent relay hosts (add/delete)
- Admin > Logs: view Postfix, Dovecot, Rspamd, Ratelimit, API and other
server logs in a dark scrollable panel
- My Account: per-domain Quarantine panel — view score, sender, subject,
date; permanently delete quarantined messages
- My Account: per-mailbox Spam Filter slider (1–15 threshold) saved via API
- My Account: Aliases & Forwarders (alias creation doubles as forwarder
to any external address)
UX fixes:
- Quota 0 now displays ∞ (unlimited) in both admin and account views
- Admin mailbox action buttons replaced with Dashicon icon buttons
(lock, chart-bar, trash) with title tooltips
i18n:
- load_plugin_textdomain registered on init hook
- All user-facing PHP strings wrapped in __() / esc_html__()
- Translated strings array passed to account JS via wp_localize_script
- woocow-es_ES.po/.mo — Spanish translation
- woocow-ro_RO.po/.mo — Romanian translation (with correct plural forms)
- English remains the fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 08:38:52 +01:00
if ( ! domains . length ) {
$ ( '#wc-dom-table-wrap' ) . html ( '<p>No domains on this server yet.</p>' ) . show ( ) ;
} else {
let html = ` <table class="wp-list-table widefat fixed striped woocow-table">
2026-02-27 08:53:11 +01:00
< thead > < tr >
< th > Domain < / t h >
< th > Mailboxes < / t h >
< th > Quota Used < / t h >
< th > Active < / t h >
< th > Actions < / t h >
< / t r > < / t h e a d > < t b o d y > ` ;
feat: domains, transports, logs, quarantine, spam filter, i18n + UX fixes
Features added:
- Admin > Domains: add domains to Mailcow servers, auto-generate DKIM,
display full DNS record set (MX, SPF, DMARC, DKIM, autoconfig CNAMEs)
with one-click copy per record
- Admin > Transports: manage sender-dependent relay hosts (add/delete)
- Admin > Logs: view Postfix, Dovecot, Rspamd, Ratelimit, API and other
server logs in a dark scrollable panel
- My Account: per-domain Quarantine panel — view score, sender, subject,
date; permanently delete quarantined messages
- My Account: per-mailbox Spam Filter slider (1–15 threshold) saved via API
- My Account: Aliases & Forwarders (alias creation doubles as forwarder
to any external address)
UX fixes:
- Quota 0 now displays ∞ (unlimited) in both admin and account views
- Admin mailbox action buttons replaced with Dashicon icon buttons
(lock, chart-bar, trash) with title tooltips
i18n:
- load_plugin_textdomain registered on init hook
- All user-facing PHP strings wrapped in __() / esc_html__()
- Translated strings array passed to account JS via wp_localize_script
- woocow-es_ES.po/.mo — Spanish translation
- woocow-ro_RO.po/.mo — Romanian translation (with correct plural forms)
- English remains the fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 08:38:52 +01:00
domains . forEach ( d => {
2026-02-27 08:53:11 +01:00
const mboxes = ` ${ d . mboxes _in } / ${ d . mailboxes || '∞' } ` ;
const qUsed = formatMB ( d . quota _used * 1024 * 1024 ) ;
const qTotal = d . quota ? formatMB ( d . quota * 1024 * 1024 ) : '∞' ;
const active = d . active == 1
? '<span class="woocow-badge woocow-badge-green">Active</span>'
: '<span class="woocow-badge woocow-badge-grey">Inactive</span>' ;
feat: domains, transports, logs, quarantine, spam filter, i18n + UX fixes
Features added:
- Admin > Domains: add domains to Mailcow servers, auto-generate DKIM,
display full DNS record set (MX, SPF, DMARC, DKIM, autoconfig CNAMEs)
with one-click copy per record
- Admin > Transports: manage sender-dependent relay hosts (add/delete)
- Admin > Logs: view Postfix, Dovecot, Rspamd, Ratelimit, API and other
server logs in a dark scrollable panel
- My Account: per-domain Quarantine panel — view score, sender, subject,
date; permanently delete quarantined messages
- My Account: per-mailbox Spam Filter slider (1–15 threshold) saved via API
- My Account: Aliases & Forwarders (alias creation doubles as forwarder
to any external address)
UX fixes:
- Quota 0 now displays ∞ (unlimited) in both admin and account views
- Admin mailbox action buttons replaced with Dashicon icon buttons
(lock, chart-bar, trash) with title tooltips
i18n:
- load_plugin_textdomain registered on init hook
- All user-facing PHP strings wrapped in __() / esc_html__()
- Translated strings array passed to account JS via wp_localize_script
- woocow-es_ES.po/.mo — Spanish translation
- woocow-ro_RO.po/.mo — Romanian translation (with correct plural forms)
- English remains the fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 08:38:52 +01:00
html += ` <tr>
2026-02-27 08:53:11 +01:00
< td > < strong > $ { esc ( d . domain ) } < / s t r o n g > $ { d . d e s c r i p t i o n ? ` < b r > < s m a l l c l a s s = " d e s c r i p t i o n " > $ { e s c ( d . d e s c r i p t i o n ) } < / s m a l l > ` : ' ' } < / t d >
< td > $ { mboxes } < / t d >
< td > $ { qUsed } / $ { qTotal } < / t d >
< td > $ { active } < / t d >
< td class = "woocow-actions" >
< button class = "button button-small woocow-icon-btn wc-dom-dns"
title = "DNS Records" data - domain = "${esc(d.domain)}" >
< span class = "dashicons dashicons-admin-site" > < / s p a n >
< / b u t t o n >
< button class = "button button-small woocow-icon-btn wc-dom-edit"
title = "Edit" data - domain = "${esc(d.domain)}" >
< span class = "dashicons dashicons-edit" > < / s p a n >
< / b u t t o n >
< button class = "button button-small woocow-icon-btn wc-dom-del"
title = "Delete" data - domain = "${esc(d.domain)}" style = "color:#a00" >
< span class = "dashicons dashicons-trash" > < / s p a n >
feat: domains, transports, logs, quarantine, spam filter, i18n + UX fixes
Features added:
- Admin > Domains: add domains to Mailcow servers, auto-generate DKIM,
display full DNS record set (MX, SPF, DMARC, DKIM, autoconfig CNAMEs)
with one-click copy per record
- Admin > Transports: manage sender-dependent relay hosts (add/delete)
- Admin > Logs: view Postfix, Dovecot, Rspamd, Ratelimit, API and other
server logs in a dark scrollable panel
- My Account: per-domain Quarantine panel — view score, sender, subject,
date; permanently delete quarantined messages
- My Account: per-mailbox Spam Filter slider (1–15 threshold) saved via API
- My Account: Aliases & Forwarders (alias creation doubles as forwarder
to any external address)
UX fixes:
- Quota 0 now displays ∞ (unlimited) in both admin and account views
- Admin mailbox action buttons replaced with Dashicon icon buttons
(lock, chart-bar, trash) with title tooltips
i18n:
- load_plugin_textdomain registered on init hook
- All user-facing PHP strings wrapped in __() / esc_html__()
- Translated strings array passed to account JS via wp_localize_script
- woocow-es_ES.po/.mo — Spanish translation
- woocow-ro_RO.po/.mo — Romanian translation (with correct plural forms)
- English remains the fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 08:38:52 +01:00
< / b u t t o n >
< / t d >
< / t r > ` ;
} ) ;
html += '</tbody></table>' ;
$ ( '#wc-dom-table-wrap' ) . html ( html ) . show ( ) ;
}
$ ( '#wc-dom-add-btn' ) . show ( ) ;
} ) ;
} ;
$ ( '#wc-dom-load' ) . on ( 'click' , loadDomains ) ;
$ ( '#wc-dom-add-btn' ) . on ( 'click' , ( ) => {
$ ( '#wc-dom-name, #wc-dom-desc' ) . val ( '' ) ;
$ ( '#wc-dom-form-notice' ) . text ( '' ) ;
$ ( '#wc-dom-form' ) . slideDown ( ) ;
} ) ;
$ ( '#wc-dom-cancel' ) . on ( 'click' , ( ) => $ ( '#wc-dom-form' ) . slideUp ( ) ) ;
$ ( '#wc-dom-save' ) . on ( 'click' , ( ) => {
const $note = $ ( '#wc-dom-form-notice' ) . text ( 'Adding domain…' ) ;
const data = {
server _id : domServerId ,
domain : $ ( '#wc-dom-name' ) . val ( ) . trim ( ) ,
description : $ ( '#wc-dom-desc' ) . val ( ) . trim ( ) ,
mailboxes : $ ( '#wc-dom-mailboxes' ) . val ( ) ,
aliases : $ ( '#wc-dom-aliases' ) . val ( ) ,
quota : $ ( '#wc-dom-quota' ) . val ( ) ,
defquota : $ ( '#wc-dom-defquota' ) . val ( ) ,
dkim _size : $ ( '#wc-dom-dkim-size' ) . val ( ) ,
} ;
if ( ! data . domain ) { $note . html ( '<span style="color:red">Domain name required.</span>' ) ; return ; }
ajax ( 'woocow_admin_domain_add' , data ) . done ( res => {
if ( res . success ) {
$note . html ( '<span style="color:green">✓ Domain added with DKIM generated!</span>' ) ;
$ ( '#wc-dom-form' ) . slideUp ( ) ;
loadDomains ( ) ;
} else {
$note . html ( ` <span style="color:red"> ${ esc ( res . data ) } </span> ` ) ;
}
} ) ;
} ) ;
2026-02-27 08:53:11 +01:00
// ── Edit Domain modal ─────────────────────────────────────────────────
$ ( document ) . on ( 'click' , '.wc-dom-edit' , function ( ) {
const domain = $ ( this ) . data ( 'domain' ) ;
const d = domainsData [ domain ] ;
if ( ! d ) return ;
$ ( '#wc-dom-edit-domain' ) . val ( domain ) ;
$ ( '#wc-dom-edit-name' ) . text ( domain ) ;
$ ( '#wc-dom-edit-desc' ) . val ( d . description || '' ) ;
$ ( '#wc-dom-edit-mboxes' ) . val ( d . mailboxes || 10 ) ;
$ ( '#wc-dom-edit-aliases' ) . val ( d . aliases || 400 ) ;
$ ( '#wc-dom-edit-quota' ) . val ( d . quota || 10240 ) ;
$ ( '#wc-dom-edit-defquota' ) . val ( d . defquota || 3072 ) ;
$ ( '#wc-dom-edit-rl-value' ) . val ( d . rl _value || 0 ) ;
$ ( '#wc-dom-edit-rl-frame' ) . val ( d . rl _frame || 's' ) ;
$ ( '#wc-dom-edit-active' ) . prop ( 'checked' , d . active == 1 ) ;
$ ( '#wc-dom-edit-notice' ) . text ( '' ) ;
// Load relayhosts into transport dropdown
const $sel = $ ( '#wc-dom-edit-relayhost' ) . html ( '<option value="0">— Direct delivery (no relay) —</option>' ) ;
ajax ( 'woocow_admin_relayhosts_list' , { server _id : domServerId } ) . done ( rh => {
if ( rh . success && Array . isArray ( rh . data ) ) {
rh . data . forEach ( r => {
$sel . append ( ` <option value=" ${ r . id } "> ${ esc ( r . hostname ) } </option> ` ) ;
} ) ;
$sel . val ( d . relayhost || '0' ) ;
}
} ) ;
$ ( '#wc-dom-edit-modal' ) . show ( ) ;
} ) ;
$ ( '#wc-dom-edit-cancel' ) . on ( 'click' , ( ) => $ ( '#wc-dom-edit-modal' ) . hide ( ) ) ;
$ ( '#wc-dom-edit-save' ) . on ( 'click' , ( ) => {
const $note = $ ( '#wc-dom-edit-notice' ) . text ( 'Saving…' ) ;
const domain = $ ( '#wc-dom-edit-domain' ) . val ( ) ;
ajax ( 'woocow_admin_domain_edit' , {
server _id : domServerId ,
domain ,
description : $ ( '#wc-dom-edit-desc' ) . val ( ) ,
mailboxes : $ ( '#wc-dom-edit-mboxes' ) . val ( ) ,
aliases : $ ( '#wc-dom-edit-aliases' ) . val ( ) ,
quota : $ ( '#wc-dom-edit-quota' ) . val ( ) ,
defquota : $ ( '#wc-dom-edit-defquota' ) . val ( ) ,
rl _value : $ ( '#wc-dom-edit-rl-value' ) . val ( ) ,
rl _frame : $ ( '#wc-dom-edit-rl-frame' ) . val ( ) ,
relayhost : $ ( '#wc-dom-edit-relayhost' ) . val ( ) ,
active : $ ( '#wc-dom-edit-active' ) . is ( ':checked' ) ? 1 : 0 ,
} ) . done ( res => {
if ( res . success ) {
$ ( '#wc-dom-edit-modal' ) . hide ( ) ;
notice ( $ ( '#wc-dom-notices' ) , 'success' , ` Domain <strong> ${ esc ( domain ) } </strong> updated. ` ) ;
loadDomains ( ) ;
} else {
$note . html ( ` <span style="color:red"> ${ esc ( res . data ) } </span> ` ) ;
}
} ) ;
} ) ;
// ── Delete Domain ─────────────────────────────────────────────────────
$ ( document ) . on ( 'click' , '.wc-dom-del' , function ( ) {
const domain = $ ( this ) . data ( 'domain' ) ;
if ( ! confirm ( ` Delete domain ${ domain } ? This will also delete all mailboxes, aliases, and data on the Mailcow server. This cannot be undone! ` ) ) return ;
ajax ( 'woocow_admin_domain_delete' , { server _id : domServerId , domain } ) . done ( res => {
if ( res . success ) {
notice ( $ ( '#wc-dom-notices' ) , 'success' , ` Domain <strong> ${ esc ( domain ) } </strong> deleted. ` ) ;
loadDomains ( ) ;
} else {
notice ( $ ( '#wc-dom-notices' ) , 'error' , res . data ) ;
}
} ) ;
} ) ;
feat: domains, transports, logs, quarantine, spam filter, i18n + UX fixes
Features added:
- Admin > Domains: add domains to Mailcow servers, auto-generate DKIM,
display full DNS record set (MX, SPF, DMARC, DKIM, autoconfig CNAMEs)
with one-click copy per record
- Admin > Transports: manage sender-dependent relay hosts (add/delete)
- Admin > Logs: view Postfix, Dovecot, Rspamd, Ratelimit, API and other
server logs in a dark scrollable panel
- My Account: per-domain Quarantine panel — view score, sender, subject,
date; permanently delete quarantined messages
- My Account: per-mailbox Spam Filter slider (1–15 threshold) saved via API
- My Account: Aliases & Forwarders (alias creation doubles as forwarder
to any external address)
UX fixes:
- Quota 0 now displays ∞ (unlimited) in both admin and account views
- Admin mailbox action buttons replaced with Dashicon icon buttons
(lock, chart-bar, trash) with title tooltips
i18n:
- load_plugin_textdomain registered on init hook
- All user-facing PHP strings wrapped in __() / esc_html__()
- Translated strings array passed to account JS via wp_localize_script
- woocow-es_ES.po/.mo — Spanish translation
- woocow-ro_RO.po/.mo — Romanian translation (with correct plural forms)
- English remains the fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 08:38:52 +01:00
// DNS Records panel
$ ( document ) . on ( 'click' , '.wc-dom-dns' , function ( ) {
const domain = $ ( this ) . data ( 'domain' ) ;
$ ( '#wc-dom-dns-domain' ) . text ( domain ) ;
$ ( '#wc-dom-dns-content' ) . html ( '<p>Loading…</p>' ) ;
$ ( '#wc-dom-dns-panel' ) . show ( ) ;
$ ( 'html,body' ) . animate ( { scrollTop : $ ( '#wc-dom-dns-panel' ) . offset ( ) . top - 40 } , 300 ) ;
ajax ( 'woocow_admin_domain_dns' , { server _id : domServerId , domain } ) . done ( res => {
if ( ! res . success ) {
$ ( '#wc-dom-dns-content' ) . html ( ` <p style="color:red"> ${ esc ( res . data ) } </p> ` ) ;
return ;
}
const d = res . data ;
let html = ` <table class="wp-list-table widefat fixed striped woocow-table woocow-dns-table">
< thead > < tr > < th > Type < /th><th>Host / Name < / t h > < t h > V a l u e < / t h > < t h > P r i o r i t y < / t h > < t h > T T L < / t h > < t h > N o t e < / t h > < t h > < / t h > < / t r > < / t h e a d > < t b o d y > ` ;
d . records . forEach ( r => {
html += ` <tr>
< td > < code > $ { esc ( r . type ) } < / c o d e > < / t d >
< td class = "woocow-dns-host" > < code > $ { esc ( r . host ) } < / c o d e > < / t d >
< td class = "woocow-dns-val" > < code class = "woocow-dns-value" > $ { esc ( r . value ) } < / c o d e > < / t d >
< td > $ { esc ( r . prio ) } < / t d >
< td > $ { esc ( r . ttl ) } < / t d >
< td > < em > $ { esc ( r . note || '' ) } < / e m > < / t d >
< td > < button class = "button button-small wc-copy-dns" data - val = "${esc(r.value)}" > Copy < / b u t t o n > < / t d >
< / t r > ` ;
} ) ;
html += '</tbody></table>' ;
if ( ! d . dkim _txt ) {
html += ` <p class="description" style="color:orange">⚠ DKIM key not yet generated for this domain. Add the domain first, then view DNS records again.</p> ` ;
}
$ ( '#wc-dom-dns-content' ) . html ( html ) ;
} ) ;
} ) ;
$ ( document ) . on ( 'click' , '.wc-copy-dns' , function ( ) {
const val = $ ( this ) . data ( 'val' ) ;
navigator . clipboard . writeText ( val ) . then ( ( ) => {
$ ( this ) . text ( 'Copied!' ) ;
setTimeout ( ( ) => $ ( this ) . text ( 'Copy' ) , 1500 ) ;
} ) ;
} ) ;
$ ( '#wc-dom-dns-close' ) . on ( 'click' , ( ) => $ ( '#wc-dom-dns-panel' ) . hide ( ) ) ;
}
// ── Transports Page ───────────────────────────────────────────────────────
if ( $ ( '#wc-tr-server' ) . length ) {
let trServerId = null ;
$ ( '#wc-tr-server' ) . on ( 'change' , function ( ) {
trServerId = $ ( this ) . val ( ) ;
$ ( '#wc-tr-load' ) . prop ( 'disabled' , ! trServerId ) ;
} ) ;
const loadTransports = ( ) => {
ajax ( 'woocow_admin_relayhosts_list' , { server _id : trServerId } ) . done ( res => {
if ( ! res . success ) { notice ( $ ( '#wc-tr-notices' ) , 'error' , res . data ) ; return ; }
const rows = res . data ;
if ( ! Array . isArray ( rows ) || ! rows . length ) {
$ ( '#wc-tr-table-wrap' ) . html ( '<p>No transports configured on this server.</p>' ) ;
} else {
let html = ` <table class="wp-list-table widefat fixed striped woocow-table">
< thead > < tr > < th > Hostname : Port < / t h > < t h > U s e r n a m e < / t h > < t h > U s e d b y D o m a i n s < / t h > < t h > A c t i v e < / t h > < t h > A c t i o n s < / t h > < / t r > < / t h e a d > < t b o d y > ` ;
rows . forEach ( r => {
html += ` <tr>
< td > < code > $ { esc ( r . hostname ) } < / c o d e > < / t d >
< td > $ { esc ( r . username ) } < / t d >
< td > $ { esc ( r . used _by _domains || '—' ) } < / t d >
< td > $ { r . active == 1 ? '✓' : '– ' } < / t d >
< td > < button class = "button button-small woocow-icon-btn wc-tr-del" title = "Delete" data - id = "${r.id}" style = "color:#a00" >
< span class = "dashicons dashicons-trash" > < / s p a n >
< / b u t t o n > < / t d >
< / t r > ` ;
} ) ;
html += '</tbody></table>' ;
$ ( '#wc-tr-table-wrap' ) . html ( html ) ;
}
$ ( '#wc-tr-add-btn' ) . show ( ) ;
} ) ;
} ;
$ ( '#wc-tr-load' ) . on ( 'click' , loadTransports ) ;
$ ( '#wc-tr-add-btn' ) . on ( 'click' , ( ) => { $ ( '#wc-tr-hostname,#wc-tr-user,#wc-tr-pass' ) . val ( '' ) ; $ ( '#wc-tr-form' ) . slideDown ( ) ; } ) ;
$ ( '#wc-tr-cancel' ) . on ( 'click' , ( ) => $ ( '#wc-tr-form' ) . slideUp ( ) ) ;
$ ( '#wc-tr-save' ) . on ( 'click' , ( ) => {
const $note = $ ( '#wc-tr-form-notice' ) . text ( 'Saving…' ) ;
ajax ( 'woocow_admin_relayhost_save' , {
server _id : trServerId ,
hostname : $ ( '#wc-tr-hostname' ) . val ( ) . trim ( ) ,
username : $ ( '#wc-tr-user' ) . val ( ) . trim ( ) ,
password : $ ( '#wc-tr-pass' ) . val ( ) ,
active : $ ( '#wc-tr-active' ) . is ( ':checked' ) ? 1 : 0 ,
} ) . done ( res => {
if ( res . success ) { $note . html ( '<span style="color:green">✓ Transport added.</span>' ) ; $ ( '#wc-tr-form' ) . slideUp ( ) ; loadTransports ( ) ; }
else $note . html ( ` <span style="color:red"> ${ esc ( res . data ) } </span> ` ) ;
} ) ;
} ) ;
$ ( document ) . on ( 'click' , '.wc-tr-del' , function ( ) {
if ( ! confirm ( 'Delete this transport?' ) ) return ;
ajax ( 'woocow_admin_relayhost_delete' , { server _id : trServerId , id : $ ( this ) . data ( 'id' ) } ) . done ( res => {
if ( res . success ) loadTransports ( ) ;
else notice ( $ ( '#wc-tr-notices' ) , 'error' , res . data ) ;
} ) ;
} ) ;
}
// ── Logs Page ─────────────────────────────────────────────────────────────
if ( $ ( '#wc-log-server' ) . length ) {
$ ( '#wc-log-server' ) . on ( 'change' , function ( ) {
$ ( '#wc-log-load' ) . prop ( 'disabled' , ! $ ( this ) . val ( ) ) ;
} ) ;
$ ( '#wc-log-load' ) . on ( 'click' , ( ) => {
const sid = $ ( '#wc-log-server' ) . val ( ) ;
const type = $ ( '#wc-log-type' ) . val ( ) ;
$ ( '#wc-log-wrap' ) . html ( '<p>Loading…</p>' ) ;
ajax ( 'woocow_admin_logs' , { server _id : sid , log _type : type } ) . done ( res => {
if ( ! res . success ) {
$ ( '#wc-log-wrap' ) . html ( ` <p style="color:red"> ${ esc ( res . data ) } </p> ` ) ;
return ;
}
const entries = Array . isArray ( res . data ) ? res . data : Object . values ( res . data ) ;
if ( ! entries . length ) {
$ ( '#wc-log-wrap' ) . html ( '<p>No log entries.</p>' ) ;
return ;
}
// Render as a scrollable pre block
const text = entries . map ( e => {
if ( typeof e === 'string' ) return e ;
if ( e . time && e . message ) return ` [ ${ e . time } ] ${ e . message } ` ;
return JSON . stringify ( e ) ;
} ) . join ( '\n' ) ;
$ ( '#wc-log-wrap' ) . html ( `
< div class = "woocow-log-toolbar" >
< strong > $ { esc ( type . charAt ( 0 ) . toUpperCase ( ) + type . slice ( 1 ) ) } log < / s t r o n g >
< span style = "color:#666;font-size:12px" > $ { entries . length } entries < / s p a n >
< / d i v >
< pre class = "woocow-log-pre" > $ { esc ( text ) } < / p r e >
` );
} ) ;
} ) ;
}
2026-02-27 08:53:11 +01:00
// ── Admin Quarantine Page ─────────────────────────────────────────────────
if ( $ ( '#wc-quar-server' ) . length ) {
let quarServerId = null ;
$ ( '#wc-quar-server' ) . on ( 'change' , function ( ) {
quarServerId = $ ( this ) . val ( ) ;
$ ( '#wc-quar-load' ) . prop ( 'disabled' , ! quarServerId ) ;
} ) ;
const loadQuarantine = ( ) => {
$ ( '#wc-quar-wrap' ) . html ( '<p>Loading…</p>' ) ;
ajax ( 'woocow_admin_quarantine' , { server _id : quarServerId } ) . done ( res => {
if ( ! res . success ) {
$ ( '#wc-quar-wrap' ) . html ( ` <div class="notice notice-error"><p> ${ esc ( res . data ) } </p></div> ` ) ;
return ;
}
const msgs = Array . isArray ( res . data ) ? res . data : [ ] ;
if ( ! msgs . length ) {
$ ( '#wc-quar-wrap' ) . html ( '<p>No quarantined messages.</p>' ) ;
return ;
}
let html = ` <table class="wp-list-table widefat fixed striped woocow-quarantine-table">
< thead > < tr >
< th > Date < / t h > < t h > S e n d e r < / t h > < t h > R e c i p i e n t < / t h > < t h > S u b j e c t < / t h > < t h > S c o r e < / t h > < t h > A c t i o n s < / t h >
< / t r > < / t h e a d > < t b o d y > ` ;
msgs . forEach ( m => {
const date = m . created ? new Date ( m . created * 1000 ) . toLocaleString ( ) : '—' ;
const domain = ( m . rcpt || '' ) . split ( '@' ) [ 1 ] || '' ;
html += ` <tr>
< td > $ { esc ( date ) } < / t d >
< td > < code > $ { esc ( m . sender ) } < / c o d e > < / t d >
< td > $ { esc ( m . rcpt ) } < / t d >
< td > $ { esc ( m . subject ) } < / t d >
< td > $ { esc ( m . score ) } < / t d >
< td class = "woocow-actions" >
< button class = "button button-small woocow-icon-btn wc-quar-del"
title = "Delete" data - qid = "${m.id}" style = "color:#a00" >
< span class = "dashicons dashicons-trash" > < / s p a n >
< / b u t t o n >
< button class = "button button-small woocow-icon-btn wc-quar-block"
title = "Blacklist sender" data - sender = "${esc(m.sender)}" data - domain = "${esc(domain)}" >
< span class = "dashicons dashicons-shield-alt" > < / s p a n >
< / b u t t o n >
< / t d >
< / t r > ` ;
} ) ;
html += '</tbody></table>' ;
html += ` <p class="description"> ${ msgs . length } quarantined message(s). <em>Note: To release a message to inbox, use the link in the quarantine notification email or Webmail.</em></p> ` ;
$ ( '#wc-quar-wrap' ) . html ( html ) ;
} ) ;
} ;
$ ( '#wc-quar-load' ) . on ( 'click' , loadQuarantine ) ;
$ ( document ) . on ( 'click' , '.wc-quar-del' , function ( ) {
if ( ! confirm ( 'Permanently delete this quarantined message?' ) ) return ;
const qid = $ ( this ) . data ( 'qid' ) ;
ajax ( 'woocow_admin_quarantine_delete' , { server _id : quarServerId , qid } ) . done ( res => {
if ( res . success ) loadQuarantine ( ) ;
else notice ( $ ( '#wc-quar-notices' ) , 'error' , res . data ) ;
} ) ;
} ) ;
$ ( document ) . on ( 'click' , '.wc-quar-block' , function ( ) {
const sender = $ ( this ) . data ( 'sender' ) ;
const domain = $ ( this ) . data ( 'domain' ) ;
if ( ! domain ) { notice ( $ ( '#wc-quar-notices' ) , 'error' , 'Could not determine recipient domain.' ) ; return ; }
if ( ! confirm ( ` Add ${ sender } to the blacklist for domain ${ domain } ? ` ) ) return ;
ajax ( 'woocow_admin_quarantine_block' , {
server _id : quarServerId ,
domain ,
object _from : sender ,
} ) . done ( res => {
if ( res . success ) notice ( $ ( '#wc-quar-notices' ) , 'success' , ` Sender <strong> ${ esc ( sender ) } </strong> blacklisted for <strong> ${ esc ( domain ) } </strong>. ` ) ;
else notice ( $ ( '#wc-quar-notices' ) , 'error' , res . data ) ;
} ) ;
} ) ;
}
2026-02-27 08:06:22 +01:00
} ) ( jQuery ) ;