Files
WooCow/assets/js/woocow-admin.js
Malin 1ea2ed7e74 feat: admin reset password and set quota for existing mailboxes
- Add Reset PW and Set Quota action buttons to each mailbox row
- Shared edit modal that switches between password / quota modes
- New woocow_admin_mailbox_edit AJAX handler in PHP
- Quota reloads mailbox list after save; password closes modal silently
- Customer-facing Change Password was already implemented in account JS

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

468 lines
21 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 wc-mb-reset-pw"
data-email="${esc(m.username)}">Reset PW</button>
<button class="button button-small wc-mb-set-quota"
data-email="${esc(m.username)}" data-quota="${quotaMB}">Set Quota</button>
<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);
});
});
// ── 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) return '0 MB';
const mb = bytes / 1024 / 1024;
return mb >= 1024 ? (mb / 1024).toFixed(1) + ' GB' : mb.toFixed(0) + ' MB';
}
})(jQuery);