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>
This commit is contained in:
@@ -476,12 +476,14 @@
|
||||
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 = () => {
|
||||
@@ -491,18 +493,44 @@
|
||||
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>Active</th><th>Actions</th></tr></thead><tbody>`;
|
||||
<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></td>
|
||||
<td>${d.active == 1 ? '<span class="woocow-badge woocow-badge-green">Active</span>' : '<span class="woocow-badge woocow-badge-grey">Inactive</span>'}</td>
|
||||
<td>
|
||||
<button class="button button-small wc-dom-dns" data-domain="${esc(d.domain)}">
|
||||
<span class="dashicons dashicons-admin-site"></span> DNS Records
|
||||
<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>`;
|
||||
@@ -548,6 +576,82 @@
|
||||
});
|
||||
});
|
||||
|
||||
// ── 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');
|
||||
@@ -697,4 +801,84 @@
|
||||
});
|
||||
}
|
||||
|
||||
// ── 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);
|
||||
|
||||
Reference in New Issue
Block a user