Files
WooCow/assets/js/woocow-admin.js
Malin dbe4abccf7 feat: domain edit/delete, fix logs, add admin quarantine with block
- Add ajax_woocow_admin_domain_edit and _delete PHP handlers
- Domain table: richer columns (mailboxes used/limit, quota), icon buttons
- Edit domain modal: pre-populates fields, loads relayhosts for transport select
- Fix logs: correct Mailcow API slugs (rspamd-history, ratelimited) and add /{count} suffix to endpoint
- Add admin Quarantine submenu: view all quarantined messages, delete, blacklist sender via domain policy
- Add domain policy methods to API class (add/delete/get_bl)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 08:53:11 +01:00

885 lines
42 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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</th><th>URL</th><th>Status</th><th>Added</th><th>Actions</th>
</tr></thead><tbody>`;
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)}</strong></td>
<td><a href="${esc(s.url)}" target="_blank" rel="noopener">${esc(s.url)}</a></td>
<td>${badge}</td>
<td>${s.created_at.split(' ')[0]}</td>
<td class="woocow-actions">
<button class="button button-small wc-srv-edit" data-id="${s.id}">Edit</button>
<button class="button button-small wc-srv-test" data-id="${s.id}">Test</button>
<button class="button button-small wc-srv-del" data-id="${s.id}" style="color:#a00">Delete</button>
</td>
</tr>`;
});
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</th><th>Email</th><th>Domain</th><th>Server</th><th>Assigned</th><th>Actions</th>
</tr></thead><tbody>`;
rows.forEach(r => {
html += `<tr>
<td>${esc(r.display_name)}</td>
<td>${esc(r.user_email)}</td>
<td><strong>${esc(r.domain)}</strong></td>
<td>${esc(r.server_name)}</td>
<td>${r.created_at.split(' ')[0]}</td>
<td><button class="button button-small wc-assign-del" data-id="${r.id}" style="color:#a00">Remove</button></td>
</tr>`;
});
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</th><th>Name</th><th>Quota Used</th><th>Quota Max</th><th>Active</th><th>Actions</th>
</tr></thead><tbody>`;
boxes.forEach(m => {
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);
html += `<tr>
<td><a href="${esc(webmail)}" target="_blank">${esc(m.username)}</a></td>
<td>${esc(m.name)}</td>
<td>${used} ${bar}</td>
<td>${max}</td>
<td>${m.active == 1 ? '✓' : ''}</td>
<td class="woocow-actions">
<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"></span>
</button>
<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"></span>
</button>
<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"></span>
</button>
</td>
</tr>`;
});
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);
});
});
// ── 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>`);
}
});
});
}
// ── Utilities ─────────────────────────────────────────────────────────────
function esc(str) {
return String(str).replace(/[&<>"']/g, m => ({
'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
})[m]);
}
function formatMB(bytes) {
if (bytes === 0 || bytes === '0') return '∞';
if (!bytes) return '0 MB';
const mb = bytes / 1024 / 1024;
return mb >= 1024 ? (mb / 1024).toFixed(1) + ' GB' : mb.toFixed(0) + ' MB';
}
// ── Domains Page ──────────────────────────────────────────────────────────
if ($('#wc-dom-server').length) {
let domServerId = null;
let domServerUrl = null;
let domainsData = {}; // domain name → full data object
$('#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();
domainsData = {};
});
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;
domainsData = {};
domains.forEach(d => { domainsData[d.domain] = d; });
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">
<thead><tr>
<th>Domain</th>
<th>Mailboxes</th>
<th>Quota Used</th>
<th>Active</th>
<th>Actions</th>
</tr></thead><tbody>`;
domains.forEach(d => {
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>';
html += `<tr>
<td><strong>${esc(d.domain)}</strong>${d.description ? `<br><small class="description">${esc(d.description)}</small>` : ''}</td>
<td>${mboxes}</td>
<td>${qUsed} / ${qTotal}</td>
<td>${active}</td>
<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"></span>
</button>
<button class="button button-small woocow-icon-btn wc-dom-edit"
title="Edit" data-domain="${esc(d.domain)}">
<span class="dashicons dashicons-edit"></span>
</button>
<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"></span>
</button>
</td>
</tr>`;
});
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>`);
}
});
});
// ── 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);
}
});
});
// 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</th><th>Value</th><th>Priority</th><th>TTL</th><th>Note</th><th></th></tr></thead><tbody>`;
d.records.forEach(r => {
html += `<tr>
<td><code>${esc(r.type)}</code></td>
<td class="woocow-dns-host"><code>${esc(r.host)}</code></td>
<td class="woocow-dns-val"><code class="woocow-dns-value">${esc(r.value)}</code></td>
<td>${esc(r.prio)}</td>
<td>${esc(r.ttl)}</td>
<td><em>${esc(r.note || '')}</em></td>
<td><button class="button button-small wc-copy-dns" data-val="${esc(r.value)}">Copy</button></td>
</tr>`;
});
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</th><th>Username</th><th>Used by Domains</th><th>Active</th><th>Actions</th></tr></thead><tbody>`;
rows.forEach(r => {
html += `<tr>
<td><code>${esc(r.hostname)}</code></td>
<td>${esc(r.username)}</td>
<td>${esc(r.used_by_domains || '—')}</td>
<td>${r.active == 1 ? '✓' : ''}</td>
<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"></span>
</button></td>
</tr>`;
});
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</strong>
<span style="color:#666;font-size:12px">${entries.length} entries</span>
</div>
<pre class="woocow-log-pre">${esc(text)}</pre>
`);
});
});
}
// ── 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</th><th>Sender</th><th>Recipient</th><th>Subject</th><th>Score</th><th>Actions</th>
</tr></thead><tbody>`;
msgs.forEach(m => {
const date = m.created ? new Date(m.created * 1000).toLocaleString() : '—';
const domain = (m.rcpt || '').split('@')[1] || '';
html += `<tr>
<td>${esc(date)}</td>
<td><code>${esc(m.sender)}</code></td>
<td>${esc(m.rcpt)}</td>
<td>${esc(m.subject)}</td>
<td>${esc(m.score)}</td>
<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"></span>
</button>
<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"></span>
</button>
</td>
</tr>`;
});
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);
});
});
}
})(jQuery);