feat: initial WooCow plugin — Mailcow/WooCommerce integration
- Mailcow API client wrapping domains, mailboxes, aliases endpoints - Admin backend: server management, customer-domain assignments, mailbox overview - WooCommerce My Account: email hosting tab with mailbox/alias management - Per-mailbox password change (independent of WP account password) - Optional WP account password sync to all customer mailboxes - Installer creates wp_woocow_servers and wp_woocow_assignments DB tables - Full nonce + capability + ownership verification on all AJAX endpoints Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
281
assets/js/woocow-account.js
Normal file
281
assets/js/woocow-account.js
Normal file
@@ -0,0 +1,281 @@
|
||||
/**
|
||||
* WooCow – My Account frontend JavaScript
|
||||
*/
|
||||
(function ($) {
|
||||
'use strict';
|
||||
|
||||
if (!$('#woocow-account').length) return;
|
||||
|
||||
const ajax = (action, data) =>
|
||||
$.post(woocowAcct.ajax_url, { action, nonce: woocowAcct.nonce, ...data });
|
||||
|
||||
const notice = (msg, type = 'success') => {
|
||||
const $n = $('#woocow-acct-notices');
|
||||
$n.html(`<div class="woocommerce-${type === 'success' ? 'message' : 'error'}">${msg}</div>`);
|
||||
setTimeout(() => $n.find('> div').fadeOut(400, function () { $(this).remove(); }), 5000);
|
||||
};
|
||||
|
||||
function esc(str) {
|
||||
return String(str).replace(/[&<>"']/g, m => ({
|
||||
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
|
||||
})[m]);
|
||||
}
|
||||
|
||||
function formatMB(bytes) {
|
||||
if (!bytes) return '0 MB';
|
||||
const mb = bytes / 1024 / 1024;
|
||||
return mb >= 1024 ? (mb / 1024).toFixed(1) + ' GB' : mb.toFixed(0) + ' MB';
|
||||
}
|
||||
|
||||
// ── Load Mailboxes ────────────────────────────────────────────────────────
|
||||
|
||||
$(document).on('click', '.woocow-load-mailboxes', function () {
|
||||
const $panel = $(this).closest('.woocow-domain-panel');
|
||||
const sid = $panel.data('server-id');
|
||||
const domain = $panel.data('domain');
|
||||
const $wrap = $panel.find('.woocow-mailboxes-wrap');
|
||||
const $list = $panel.find('.woocow-mailboxes-list');
|
||||
|
||||
$(this).prop('disabled', true).text('Loading…');
|
||||
$wrap.show();
|
||||
$list.html('<p class="woocow-loading">Fetching mailboxes…</p>');
|
||||
|
||||
ajax('woocow_acct_mailboxes', { server_id: sid, domain }).done(res => {
|
||||
$(this).prop('disabled', false).text('Refresh');
|
||||
if (!res.success) {
|
||||
$list.html(`<p class="woocow-error">${esc(res.data)}</p>`);
|
||||
return;
|
||||
}
|
||||
|
||||
const boxes = res.data.mailboxes || [];
|
||||
const webmail = res.data.webmail_url;
|
||||
|
||||
if (!boxes.length) {
|
||||
$list.html('<p class="woocow-muted">No mailboxes yet. Create one below.</p>');
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
boxes.forEach(m => {
|
||||
const pct = parseFloat(m.quota_used_in_percent || 0);
|
||||
const used = formatMB(m.quota_used);
|
||||
const max = formatMB(m.quota);
|
||||
const col = pct > 85 ? '#e74c3c' : pct > 60 ? '#f39c12' : '#27ae60';
|
||||
|
||||
html += `<div class="woocow-mailbox-row" data-email="${esc(m.username)}">
|
||||
<div class="woocow-mbox-main">
|
||||
<div class="woocow-mbox-address">
|
||||
<span class="woocow-mbox-icon">✉</span>
|
||||
<strong>${esc(m.username)}</strong>
|
||||
${m.name ? `<span class="woocow-mbox-name">(${esc(m.name)})</span>` : ''}
|
||||
</div>
|
||||
<div class="woocow-quota-wrap">
|
||||
<div class="woocow-quota-bar-outer">
|
||||
<div class="woocow-quota-bar-inner" style="width:${pct}%;background:${col}"></div>
|
||||
</div>
|
||||
<span class="woocow-quota-text">${used} / ${max} (${pct}%)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="woocow-mbox-actions">
|
||||
<button class="woocow-btn woocow-btn-sm wc-change-pw"
|
||||
data-server="${esc(sid)}" data-domain="${esc(domain)}" data-email="${esc(m.username)}">
|
||||
Change Password
|
||||
</button>
|
||||
<button class="woocow-btn woocow-btn-sm woocow-btn-outline wc-toggle-aliases"
|
||||
data-server="${esc(sid)}" data-domain="${esc(domain)}">
|
||||
Aliases
|
||||
</button>
|
||||
<a href="${esc(webmail)}" target="_blank" rel="noopener" class="woocow-btn woocow-btn-sm woocow-btn-ghost">
|
||||
Webmail ↗
|
||||
</a>
|
||||
</div>
|
||||
<div class="woocow-aliases-wrap" style="display:none">
|
||||
<div class="woocow-aliases-list"></div>
|
||||
<div class="woocow-alias-create-form" style="display:none">
|
||||
<div class="woocow-alias-fields">
|
||||
<input type="email" class="wc-alias-addr woocow-input" placeholder="alias@${esc(domain)}">
|
||||
<span class="woocow-arrow">→</span>
|
||||
<input type="email" class="wc-alias-goto woocow-input" placeholder="destination@example.com" value="${esc(m.username)}">
|
||||
<button class="woocow-btn woocow-btn-primary woocow-btn-sm wc-alias-save"
|
||||
data-server="${esc(sid)}" data-domain="${esc(domain)}">Add</button>
|
||||
<button class="woocow-btn woocow-btn-sm wc-alias-cancel">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
<button class="woocow-btn woocow-btn-sm woocow-btn-outline wc-alias-add-btn">+ Add Alias</button>
|
||||
</div>
|
||||
</div>`;
|
||||
});
|
||||
|
||||
$list.html(html);
|
||||
});
|
||||
});
|
||||
|
||||
// ── Aliases ───────────────────────────────────────────────────────────────
|
||||
|
||||
$(document).on('click', '.wc-toggle-aliases', function () {
|
||||
const $row = $(this).closest('.woocow-mailbox-row');
|
||||
const $wrap = $row.find('.woocow-aliases-wrap');
|
||||
const sid = $(this).data('server');
|
||||
const domain = $(this).data('domain');
|
||||
|
||||
if ($wrap.is(':visible')) {
|
||||
$wrap.slideUp();
|
||||
return;
|
||||
}
|
||||
|
||||
const $list = $row.find('.woocow-aliases-list').html('<p class="woocow-muted">Loading aliases…</p>');
|
||||
$wrap.slideDown();
|
||||
|
||||
ajax('woocow_acct_aliases', { server_id: sid, domain }).done(res => {
|
||||
if (!res.success) {
|
||||
$list.html(`<p class="woocow-error">${esc(res.data)}</p>`);
|
||||
return;
|
||||
}
|
||||
const aliases = res.data;
|
||||
if (!aliases.length) {
|
||||
$list.html('<p class="woocow-muted">No aliases for this domain yet.</p>');
|
||||
return;
|
||||
}
|
||||
let html = '<ul class="woocow-alias-list">';
|
||||
aliases.forEach(a => {
|
||||
html += `<li>
|
||||
<span class="woocow-alias-addr">${esc(a.address)}</span>
|
||||
<span class="woocow-arrow">→</span>
|
||||
<span class="woocow-alias-goto">${esc(a.goto)}</span>
|
||||
<button class="woocow-btn woocow-btn-danger woocow-btn-xs wc-alias-del"
|
||||
data-id="${esc(a.id)}" data-server="${esc(sid)}" data-domain="${esc(domain)}">✕</button>
|
||||
</li>`;
|
||||
});
|
||||
html += '</ul>';
|
||||
$list.html(html);
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.wc-alias-add-btn', function () {
|
||||
$(this).closest('.woocow-aliases-wrap').find('.woocow-alias-create-form').slideToggle();
|
||||
});
|
||||
|
||||
$(document).on('click', '.wc-alias-cancel', function () {
|
||||
$(this).closest('.woocow-alias-create-form').slideUp();
|
||||
});
|
||||
|
||||
$(document).on('click', '.wc-alias-save', function () {
|
||||
const $form = $(this).closest('.woocow-alias-create-form');
|
||||
const sid = $(this).data('server');
|
||||
const domain = $(this).data('domain');
|
||||
const addr = $form.find('.wc-alias-addr').val().trim();
|
||||
const goto_ = $form.find('.wc-alias-goto').val().trim();
|
||||
|
||||
if (!addr || !goto_) { alert('Both alias and destination are required.'); return; }
|
||||
|
||||
ajax('woocow_acct_alias_create', { server_id: sid, domain, address: addr, goto: goto_ }).done(res => {
|
||||
if (res.success) {
|
||||
// Refresh alias list
|
||||
$(this).closest('.woocow-mailbox-row').find('.wc-toggle-aliases').trigger('click');
|
||||
setTimeout(() => { $(this).closest('.woocow-mailbox-row').find('.wc-toggle-aliases').trigger('click'); }, 300);
|
||||
$form.slideUp();
|
||||
} else {
|
||||
alert('Error: ' + res.data);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.wc-alias-del', function () {
|
||||
if (!confirm('Delete this alias?')) return;
|
||||
const $li = $(this).closest('li');
|
||||
const sid = $(this).data('server');
|
||||
const domain = $(this).data('domain');
|
||||
const id = $(this).data('id');
|
||||
ajax('woocow_acct_alias_delete', { server_id: sid, domain, alias_id: id }).done(res => {
|
||||
if (res.success) $li.fadeOut(300, function () { $(this).remove(); });
|
||||
else alert('Delete failed: ' + res.data);
|
||||
});
|
||||
});
|
||||
|
||||
// ── Create Mailbox ────────────────────────────────────────────────────────
|
||||
|
||||
$(document).on('click', '.woocow-create-mbox-btn', function () {
|
||||
const $panel = $(this).closest('.woocow-domain-panel');
|
||||
$panel.find('.woocow-create-mbox-form').slideToggle();
|
||||
});
|
||||
|
||||
$(document).on('click', '.wc-mbox-cancel', function () {
|
||||
$(this).closest('.woocow-create-mbox-form').slideUp();
|
||||
});
|
||||
|
||||
$(document).on('click', '.wc-mbox-submit', function () {
|
||||
const $panel = $(this).closest('.woocow-domain-panel');
|
||||
const sid = $panel.data('server-id');
|
||||
const domain = $panel.data('domain');
|
||||
const $form = $(this).closest('.woocow-create-mbox-form');
|
||||
const $note = $form.find('.wc-mbox-notice');
|
||||
|
||||
const local = $form.find('.wc-mbox-local').val().trim();
|
||||
const name = $form.find('.wc-mbox-name').val().trim();
|
||||
const pass = $form.find('.wc-mbox-pass').val();
|
||||
const pass2 = $form.find('.wc-mbox-pass2').val();
|
||||
const quota = $form.find('.wc-mbox-quota').val();
|
||||
|
||||
if (!local || !pass) { $note.html('<span class="woocow-error">Username and password required.</span>'); return; }
|
||||
if (pass !== pass2) { $note.html('<span class="woocow-error">Passwords do not match.</span>'); return; }
|
||||
|
||||
$note.text('Creating…');
|
||||
ajax('woocow_acct_mailbox_create', { server_id: sid, domain, local_part: local, name, password: pass, password2: pass2, quota }).done(res => {
|
||||
if (res.success) {
|
||||
$note.html('<span style="color:green">✓ Mailbox created!</span>');
|
||||
$form.slideUp();
|
||||
// Refresh mailbox list
|
||||
$panel.find('.woocow-load-mailboxes').trigger('click');
|
||||
} else {
|
||||
$note.html(`<span class="woocow-error">${esc(res.data)}</span>`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ── Change Password Modal ─────────────────────────────────────────────────
|
||||
|
||||
$(document).on('click', '.wc-change-pw', function () {
|
||||
const sid = $(this).data('server');
|
||||
const domain = $(this).data('domain');
|
||||
const email = $(this).data('email');
|
||||
$('#woocow-pw-server-id').val(sid);
|
||||
$('#woocow-pw-mailbox').val(email);
|
||||
$('#woocow-pw-email').text(email);
|
||||
$('#woocow-pw-new, #woocow-pw-new2').val('');
|
||||
$('#woocow-pw-notice').text('');
|
||||
$('#woocow-pw-modal').fadeIn(200);
|
||||
// Store domain on modal for verification
|
||||
$('#woocow-pw-modal').data('domain', domain);
|
||||
});
|
||||
|
||||
$('#woocow-pw-cancel').on('click', () => $('#woocow-pw-modal').fadeOut(200));
|
||||
|
||||
$(document).on('click', '#woocow-pw-modal', function (e) {
|
||||
if ($(e.target).is('#woocow-pw-modal')) $(this).fadeOut(200);
|
||||
});
|
||||
|
||||
$('#woocow-pw-save').on('click', function () {
|
||||
const sid = $('#woocow-pw-server-id').val();
|
||||
const email = $('#woocow-pw-mailbox').val();
|
||||
const domain = $('#woocow-pw-modal').data('domain');
|
||||
const pass = $('#woocow-pw-new').val();
|
||||
const pass2 = $('#woocow-pw-new2').val();
|
||||
const $note = $('#woocow-pw-notice');
|
||||
|
||||
if (!pass || pass !== pass2) {
|
||||
$note.html('<span class="woocow-error">Passwords do not match or are empty.</span>');
|
||||
return;
|
||||
}
|
||||
|
||||
$note.text('Updating…');
|
||||
ajax('woocow_acct_mailbox_password', { server_id: sid, domain, email, password: pass, password2: pass2 }).done(res => {
|
||||
if (res.success) {
|
||||
$note.html('<span style="color:green">✓ Password updated!</span>');
|
||||
setTimeout(() => $('#woocow-pw-modal').fadeOut(200), 1500);
|
||||
} else {
|
||||
$note.html(`<span class="woocow-error">${esc(res.data)}</span>`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
398
assets/js/woocow-admin.js
Normal file
398
assets/js/woocow-admin.js
Normal file
@@ -0,0 +1,398 @@
|
||||
/**
|
||||
* 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.quota_used_in_percent || 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>`;
|
||||
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 wc-mb-del" data-email="${esc(m.username)}" style="color:#a00">Delete</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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ── Utilities ─────────────────────────────────────────────────────────────
|
||||
|
||||
function esc(str) {
|
||||
return String(str).replace(/[&<>"']/g, m => ({
|
||||
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
|
||||
})[m]);
|
||||
}
|
||||
|
||||
function formatMB(bytes) {
|
||||
if (!bytes) return '0 MB';
|
||||
const mb = bytes / 1024 / 1024;
|
||||
return mb >= 1024 ? (mb / 1024).toFixed(1) + ' GB' : mb.toFixed(0) + ' MB';
|
||||
}
|
||||
|
||||
})(jQuery);
|
||||
Reference in New Issue
Block a user