PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.2
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.2
6.2.2 6.2.1 6.2 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1 6.0.12 6.0.11 4.6.0.1 5.0 5.0.1 5.0.2 5.0.3 5.0.3.1 5.0.3.2 5.1 5.1.1 5.2 5.2.1 All 155 releases
mainwp / assets / js / mainwp-rest-api.js

mainwp-rest-api.js in MainWP Dashboard: Self-hosted WordPress Management for Agencies 6.2.2, at assets/js/mainwp-rest-api.js

737 lines 25.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 let bulk_RestAPIMaxThreads = 1;
2 let bulk_RestAPICurrentThreads = 0;
3 let bulk_RestAPITotal = 0;
4 let bulk_RestAPIFinished = 0;
5 let bulk_RestAPITaskRunning = false;
6
7 jQuery(function ($) {
8 $('body').on('click', '.copy-to-clipboard', function () {
9 $('#mainwp-api-key-copied-confirm-modal').modal('show');
10 });
11 // Trigger Manage Bulk Actions
12 jQuery(document).on('click', '#mainwp-do-rest-api-bulk-actions', function () {
13 let action = jQuery("#mainwp-rest-api-bulk-actions-menu").dropdown("get value");
14 if (action == 'delete' && !bulk_RestAPITaskRunning) {
15 mainwp_restapi_bulk_remove_keys_confirm();
16 }
17 return false;
18 });
19
20 // Initialize Application Passwords functionality
21 if ($("#rest-application-passwords-settings").length > 0) {
22 init_application_passwords($);
23 }
24 });
25
26 let mainwp_restapi_remove_key_confirm = function (pCheckedBox) {
27 let confirmMsg = MainWP.I18n.t("You are about to delete the selected REST API Key?");
28 mainwp_confirm(confirmMsg, function () { mainwp_restapi_bulk_remove_specific(pCheckedBox); });
29 }
30
31 let mainwp_restapi_bulk_remove_keys_confirm = function () {
32 let confirmMsg = MainWP.I18n.t("You are about to delete the selected REST API Key(s)?");
33 mainwp_confirm(confirmMsg, function () { mainwp_restapi_bulk_init(); mainwp_restapi_remove_keys_next(); });
34 }
35
36 let mainwp_restapi_bulk_init = function () {
37 jQuery('#mainwp-message-zone-apikeys').hide();
38 if (!bulk_RestAPITaskRunning) {
39 bulk_RestAPICurrentThreads = 0;
40 bulk_RestAPITotal = 0;
41 bulk_RestAPIFinished = 0;
42 jQuery('.mainwp-rest-api-body-table-manage .check-column INPUT:checkbox').each(function () {
43 jQuery(this).attr('status', 'queue')
44 });
45 }
46 };
47
48 let mainwp_restapi_remove_keys_next = function () {
49 while ((checkedBox = jQuery('.mainwp-rest-api-body-table-manage .check-column INPUT:checkbox:checked[status="queue"]:first')) && (checkedBox.length > 0) && (bulk_RestAPICurrentThreads < bulk_RestAPIMaxThreads)) { // NOSONAR - variables modified in other functions.
50 mainwp_restapi_bulk_remove_specific(checkedBox);
51 }
52 if ((bulk_RestAPITotal > 0) && (bulk_RestAPIFinished == bulk_RestAPITotal)) { // NOSONAR - modified outside the function.
53 setHtml('#mainwp-message-zone-apikeys', MainWP.I18n.t("Process completed. Reloading page..."));
54 setTimeout(function () {
55 mainwp_forceReload();
56 }, 3000);
57 }
58 }
59
60 let mainwp_restapi_bulk_remove_specific = function (pCheckedBox) {
61 pCheckedBox.attr('status', 'running');
62 let rowObj = pCheckedBox.closest('tr');
63 bulk_RestAPICurrentThreads++;
64
65 let id = rowObj.attr('key-ck-id');
66 rowObj.html('<td colspan="999"><i class="notched circle loading icon"></i> ' + 'Deleting ...' + '</td>');
67 let data = mainwp_secure_data({
68 action: 'mainwp_rest_api_remove_keys',
69 keyId: id,
70 api_ver: rowObj.closest('tbody').attr('id') === 'mainwp-rest-api-v2-body-table' ? 'v2' : 'v1'
71 });
72
73 jQuery.post(ajaxurl, data, function (response) {
74 bulk_RestAPICurrentThreads--;
75 bulk_RestAPIFinished++;
76 rowObj.html('<td colspan="999"></td>');
77 let result = '';
78 let error = '';
79 if (response.error != undefined) {
80 error = response.error;
81 } else if (response.success == 'SUCCESS') {
82 result = MainWP.I18n.t('The REST API Key has been deleted.');
83 }
84 if (error != '') {
85 rowObj.html('<td colspan="999"><i class="red times icon"></i>' + error + '</td>');
86 } else {
87 rowObj.html('<td colspan="999"><i class="green check icon"></i>' + result + '</td>');
88 }
89 setTimeout(function () {
90 jQuery('tr[key-ck-id=' + id + ']').fadeOut(1000);
91 }, 3000);
92
93 mainwp_restapi_remove_keys_next();
94 },
95 "json"
96 );
97 };
98
99 /**
100 * Application Passwords functionality
101 */
102 const init_application_passwords = ($) => {
103 const app_pass_section = $("#rest-application-passwords-settings");
104 const create_modal = $("#mainwp-create-application-password-modal");
105 const success_modal = $("#mainwp-application-password-success-modal");
106 const app_pass_name_input = $("#mainwp-app-password-name-input");
107 const app_pass_tbody = $("#mainwp-application-password-table-body");
108 const edit_modal = $("#mainwp-edit-application-password-modal");
109 const edit_name_input = $("#mainwp-app-password-edit-name-input");
110 const edit_uuid_input = $("#mainwp-app-password-edit-uuid");
111 const edit_user_id_input = $("#mainwp-app-password-edit-user-id");
112 const has_user_col = $('#mainwp-application-password-table thead th.mainwp-col-user').length > 0;
113 const current_user_name = $('#rest-application-passwords-settings').data('current-user-name') || '';
114 const message_zone = $("#mainwp-message-zone-app-passwords");
115
116 /**
117 * Helper function to escape HTML
118 */
119 const escape_html = (text) => {
120 const map = {
121 "&": "&amp;",
122 "<": "&lt;",
123 ">": "&gt;",
124 '"': "&quot;",
125 "'": "&#039;",
126 };
127 return text.replace(/[&<>"']/g, (m) => map[m]); // NOSONAR ok.
128 };
129
130 /**
131 * Helper function to clear messages
132 */
133 const clear_messages = () => {
134 message_zone.hide().html("");
135 };
136
137 /**
138 * Helper function to show messages
139 */
140 const show_message = (message, type) => {
141 clear_messages();
142
143 const class_name = type === "error" ? "red" : "green";
144 const html = `<div class="ui ${class_name} message">
145 <i class="close icon"></i>
146 ${message}
147 </div>`;
148
149 message_zone.html(html).show();
150 message_zone.find(".close.icon").on("click", () => {
151 message_zone.fadeOut();
152 });
153
154 setTimeout(() => {
155 message_zone.fadeOut();
156 }, 3000);
157 };
158
159 /**
160 * Helper function to format date like PHP format_timestamp
161 * Format: "January 1, 2026 10:15 am"
162 */
163 const format_timestamp = (timestamp) => {
164 const date = new Date(timestamp * 1000);
165
166 // Get month name
167 const month_names = [
168 "January",
169 "February",
170 "March",
171 "April",
172 "May",
173 "June",
174 "July",
175 "August",
176 "September",
177 "October",
178 "November",
179 "December",
180 ];
181 const month = month_names[date.getMonth()];
182
183 // Get day, year
184 const day = date.getDate();
185 const year = date.getFullYear();
186
187 // Get hours and minutes
188 let hours = date.getHours();
189 const minutes = date.getMinutes().toString().padStart(2, "0");
190 const ampm = hours >= 12 ? "pm" : "am";
191 hours = hours % 12;
192 hours = hours ? hours : 12; // NOSONAR - noopener - hours 0 should be 12.
193
194 return `${month} ${day}, ${year} ${hours}:${minutes} ${ampm}`;
195 };
196
197 /**
198 * Helper function to add password row to table
199 */
200 const add_password_row = (item) => {
201 const created_str = format_timestamp(item.created);
202 const table_el = $('#mainwp-application-password-table');
203 // Get DataTable instance
204 const table = window.mainwp_app_passwords_table; // NOSONAR - noopener - global variable.
205
206 if (!table) {
207 console.error("DataTable not initialized");
208 return;
209 }
210
211 // Check manage capability from table data attribute
212 const can_manage_table = table_el.data('can-manage') == 1;
213 const can_edit_table = table_el.data('can-edit') == 1;
214
215 // Create row HTML with proper data-order attributes
216 const row_html = `<tr data-uuid="${item.uuid}" data-user-id="${item.user_id || ''}">
217 <td class="check-column">
218 <div class="ui checkbox">
219 <input type="checkbox" value="${item.uuid}" name="" ${can_manage_table ? '' : 'disabled'} />
220 </div>
221 </td>
222 <td>${escape_html(item.name)}</td>
223 ${has_user_col ? `<td>${escape_html(current_user_name)}</td>` : ''}
224 <td data-order="${item.created}">${created_str}</td>
225 <td data-order="0">&mdash;</td>
226 <td>&mdash;</td>
227 <td class="right aligned">
228 ${can_edit_table ? `<button type="button" class="ui mini basic button mainwp-edit-application-password" data-uuid="${item.uuid}" data-user-id="${item.user_id || ''}" data-name="${escape_html(item.name)}">${MainWP.I18n.t("Edit")}</button>` : ''}
229 ${can_manage_table ? `<button type="button" class="ui mini grey basic button mainwp-revoke-application-password" data-uuid="${item.uuid}">${MainWP.I18n.t("Revoke")}</button>` : ''}
230 </td>
231 </tr>`;
232
233 // Add row using DataTables API with DOM node
234 const row_node = table.row.add($(row_html)[0]).draw(false).node();
235
236 // Initialize Semantic UI checkbox
237 $(row_node).find(".ui.checkbox").checkbox();
238 };
239
240 /**
241 * Open edit modal
242 */
243 app_pass_tbody.on('click', '.mainwp-edit-application-password', (e) => {
244 e.preventDefault();
245 const btn = $(e.currentTarget);
246 const uuid = btn.data('uuid');
247 const user_id = btn.data('user-id');
248 const name = btn.data('name');
249
250 edit_uuid_input.val(uuid);
251 edit_user_id_input.val(user_id);
252 edit_name_input.val(name);
253
254 edit_modal.modal('show');
255 });
256
257 /**
258 * Submit edit application password
259 */
260 $('#mainwp-edit-app-password-submit').on('click', (e) => {
261 e.preventDefault();
262 const uuid = edit_uuid_input.val();
263 const user_id = edit_user_id_input.val();
264 const name = (edit_name_input.val() || '').trim();
265 if (!name) {
266 edit_name_input.trigger('focus');
267 show_message(MainWP.I18n.t('Please enter an application name.'), 'error');
268 return false;
269 }
270
271 const data = mainwp_secure_data({
272 action: 'mainwp_application_password_update',
273 uuid: uuid,
274 user_id: user_id,
275 name: name,
276 });
277
278 $.post(ajaxurl, data, (resp) => { // NOSONAR - noopener - complex.
279 if (resp?.success) {
280 try {
281 const table = window.mainwp_app_passwords_table; // NOSONAR - noopener - global variable.
282 const tr = $('#mainwp-application-password-table tbody tr[data-uuid="' + uuid + '"]');
283 if (tr.length) {
284 // Update name cell
285 const name_cell = tr.find('td').eq(1);
286 name_cell.text(name);
287 // Update buttons aria/data
288 const edit_btn = tr.find('.mainwp-edit-application-password');
289 if (edit_btn.length) {
290 edit_btn.attr('data-name', name);
291 edit_btn.attr('aria-label', MainWP.I18n.t('Rename "%s"').replace('%s', name));
292 }
293 const revoke_btn = tr.find('.mainwp-revoke-application-password');
294 if (revoke_btn.length) {
295 revoke_btn.attr('aria-label', MainWP.I18n.t('Revoke "%s"').replace('%s', name));
296 }
297 if (table) {
298 table.row(tr).invalidate().draw(false);
299 }
300 }
301 show_message(MainWP.I18n.t('Application password updated successfully.'), 'success');
302 edit_modal.modal('hide');
303 } catch {
304 mainwp_forceReload();
305 }
306 } else {
307 const msg = (resp?.data?.message) ?? MainWP.I18n.t('Failed to update Application Password.');
308 show_message(msg, 'error');
309 }
310 }, 'json');
311 });
312
313 /**
314 * Helper function to revoke multiple passwords
315 */
316 const revoke_multiple_passwords = (items) => {
317 const data = mainwp_secure_data({
318 action: "mainwp_application_password_delete_multiple",
319 items: items,
320 });
321
322 $.post(
323 ajaxurl,
324 data,
325 (response) => {
326 if (response.success) {
327 const table = window.mainwp_app_passwords_table; // NOSONAR - noopener - global variable.
328
329 if (table) {
330 // Remove all selected rows using DataTables API
331 items.forEach((it) => {
332 const row = table.row(`[data-uuid="${it.uuid}"]`);
333 if (row.length) {
334 row.remove();
335 }
336 });
337
338 // Redraw table
339 table.draw(false);
340 }
341
342 show_message(
343 MainWP.I18n.t("Selected application passwords have been revoked."),
344 "success"
345 );
346 } else {
347 show_message(
348 response.data.message || MainWP.I18n.t("An error occurred."),
349 "error"
350 );
351 }
352 },
353 "json"
354 ).fail(() => {
355 show_message(
356 MainWP.I18n.t(
357 "An error occurred while revoking the application passwords."
358 ),
359 "error"
360 );
361 });
362 };
363
364 /**
365 * Open create modal
366 */
367 $("#mainwp-create-application-password-button-top, #mainwp-create-application-password-button").on("click", () => {
368 app_pass_name_input.val("");
369 const submit_btn = $("#mainwp-create-app-password-submit");
370 submit_btn.addClass("disabled");
371
372 app_pass_name_input.off("input.create_modal").on("input.create_modal", () => {
373 if (app_pass_name_input.val().trim().length > 0) {
374 submit_btn.removeClass("disabled");
375 } else {
376 submit_btn.addClass("disabled");
377 }
378 });
379
380 create_modal
381 .modal({
382 onApprove: () => { // NOSONAR - noopener - prevent close.
383 const name = app_pass_name_input.val().trim();
384
385 if (name.length === 0) {
386 app_pass_name_input.trigger("focus");
387 show_message(
388 MainWP.I18n.t("Please enter an application name."),
389 "error"
390 );
391 return false;
392 }
393
394 create_application_password(name);
395 return false; // Prevent modal from closing
396 },
397 })
398 .modal("show");
399 });
400
401 /**
402 * Create new application password
403 */
404 const create_application_password = (name) => {
405 clear_messages();
406
407 const submit_button = $("#mainwp-create-app-password-submit");
408 submit_button.addClass("loading disabled");
409
410 const data = mainwp_secure_data({
411 action: "mainwp_application_password_create",
412 name: name,
413 });
414
415 $.post(
416 ajaxurl,
417 data,
418 (response) => {
419 submit_button.removeClass("loading disabled");
420
421 if (response.success) {
422 // Close create modal
423 create_modal.modal("hide");
424
425 // Show success modal with password
426 $("#app-pass-success-name").text(response.data.item.name);
427 $("#app-pass-success-value").val(response.data.password);
428
429 success_modal
430 .modal({
431 closable: false,
432 onHidden: () => {
433 location.reload();
434 },
435 })
436 .modal("show");
437
438 show_message(
439 MainWP.I18n.t("Application password created successfully!"),
440 "success"
441 );
442 } else {
443 create_modal.modal("hide");
444 show_message(
445 response.data.message || MainWP.I18n.t("An error occurred."),
446 "error"
447 );
448 }
449 },
450 "json"
451 ).fail(() => {
452 submit_button.removeClass("loading disabled");
453 create_modal.modal("hide");
454 show_message(
455 MainWP.I18n.t(
456 "An error occurred while creating the application password."
457 ),
458 "error"
459 );
460 });
461 };
462
463 /**
464 * Copy password to clipboard
465 */
466 $(document).on("click", ".copy-app-password", function () {
467 const input = $("#app-pass-success-value");
468 const value = input.val();
469
470 if (navigator.clipboard && globalThis.isSecureContext) {
471 navigator.clipboard.writeText(value);
472 }
473
474 const button = $(this);
475 const original_text = button.html();
476 button.html('<i class="check icon"></i> ' + MainWP.I18n.t("Copied!"));
477
478 setTimeout(() => {
479 button.html(original_text);
480 }, 2000);
481 });
482
483 /**
484 * Revoke single application password
485 */
486 app_pass_tbody.on("click", ".mainwp-revoke-application-password", (e) => {
487 e.preventDefault();
488
489 if (
490 !confirm(
491 MainWP.I18n.t(
492 "Are you sure you want to revoke this password? This action cannot be undone."
493 )
494 )
495 ) {
496 return;
497 }
498
499 const button = $(e.currentTarget);
500 const tr = button.closest("tr");
501 const uuid = button.data("uuid");
502 const user_id = button.data("user-id") || button.closest('tr').data('user-id') || '';
503
504 clear_messages();
505 button.prop("disabled", true).addClass("disabled loading");
506
507 const data = mainwp_secure_data({
508 action: "mainwp_application_password_delete",
509 uuid: uuid,
510 user_id: user_id,
511 });
512
513 $.post(
514 ajaxurl,
515 data,
516 (response) => {
517 button.prop("disabled", false).removeClass("disabled loading");
518
519 if (response.success) {
520 const table = window.mainwp_app_passwords_table; // NOSONAR - noopener - global variable.
521
522 if (table) {
523 // Remove row using DataTables API
524 table.row(tr).remove().draw(false);
525 } else {
526 // Fallback to direct DOM manipulation
527 tr.fadeOut(300, function () {
528 tr.remove();
529 });
530 }
531
532 show_message(
533 MainWP.I18n.t("Application password revoked successfully."),
534 "success"
535 );
536 } else {
537 show_message(
538 response.data.message || MainWP.I18n.t("An error occurred."),
539 "error"
540 );
541 }
542 },
543 "json"
544 ).fail(() => {
545 button.prop("disabled", false).removeClass("disabled loading");
546 show_message(
547 MainWP.I18n.t(
548 "An error occurred while revoking the application password."
549 ),
550 "error"
551 );
552 });
553 });
554
555 /**
556 * Select checkboxes and disable/enable bulk actions
557 */
558 $('.mainwp-application-password-checkbox').on('change', function () {
559 const all_checked = $('.mainwp-application-password-checkbox').is(':checked');
560 if (all_checked > 0) {
561 $("#mainwp-do-application-passwords-bulk-actions").removeClass('disabled');
562 } else {
563 $("#mainwp-do-application-passwords-bulk-actions").addClass('disabled');
564 }
565 });
566
567 /**
568 * Bulk actions
569 */
570 $("#mainwp-do-application-passwords-bulk-actions").on("click", (e) => {
571 const selected = [];
572 app_pass_tbody
573 .find('.check-column input[type="checkbox"]:checked')
574 .each(function () {
575 const uuid = $(this).val();
576 const user_id = $(this).data('user-id') || $(this).closest('tr').data('user-id') || '';
577 if (uuid) {
578 selected.push({ uuid: uuid, user_id: user_id });
579 }
580 });
581
582 if (selected.length === 0) {
583 show_message(
584 MainWP.I18n.t("Please select at least one application password."),
585 "error"
586 );
587 return false;
588 }
589
590 if (
591 !confirm(
592 MainWP.I18n.t(
593 "Are you sure you want to revoke the selected passwords? This action cannot be undone."
594 )
595 )
596 ) {
597 return false;
598 }
599
600 revoke_multiple_passwords(selected);
601 e.preventDefault();
602 e.stopPropagation();
603 return true;
604 });
605
606 /**
607 * Close new password notice
608 */
609 app_pass_section.on(
610 "click",
611 ".new-application-password-notice .close.icon",
612 function () {
613 $(this).closest(".new-application-password-notice").fadeOut();
614 }
615 );
616
617 /**
618 * Enter key on name field
619 */
620 app_pass_name_input.on("keypress", (e) => {
621 if (e.which === 13) {
622 e.preventDefault();
623 create_button.trigger("click");
624 }
625 });
626 };
627
628 /**
629 * Initialize REST API Permission Chips
630 */
631 jQuery(document).ready(function($) { // NOSONAR - complex.
632 let initPermissionChips = function() {
633 $('.mainwp-rest-api-permission-chips').each(function() {
634 let container = $(this);
635 let hiddenInput = container.find('input[type="hidden"]');
636 let chips = container.find('.mainwp-permission-chip:visible');
637 let initValue = container.data('init-value') || '';
638
639 let updateHiddenInput = function() {
640 let selectedPermissions = [];
641 container.find('.mainwp-permission-chip:visible').each(function() { // NOSONAR - levels deep.
642 if ($(this).hasClass('active')) {
643 let perms = $(this).data('permission').toString().split(',');
644 perms.forEach(function(p) {
645 if (p && !selectedPermissions.includes(p)) {
646 selectedPermissions.push(p);
647 }
648 });
649 }
650 });
651 hiddenInput.val(selectedPermissions.join(','));
652 hiddenInput.trigger('change');
653 };
654
655 let initPermissions = new Set(
656 initValue
657 .split(',')
658 .filter(function (p) { return p !== ''; })
659 );
660
661 chips.each(function() {
662 let chip = $(this);
663 let chipPerms = chip.data('permission').toString().split(',');
664 let isActive = false;
665
666 chipPerms.forEach(function(perm) {
667 if (initPermissions.has(perm)) {
668 isActive = true;
669 }
670 });
671
672 if (isActive) {
673 chip.addClass('active green');
674 } else {
675 chip.addClass('inactive grey');
676 }
677 });
678
679 updateHiddenInput();
680
681 chips.off('click.permission-chip').on('click.permission-chip', function(e) {
682 e.preventDefault();
683 let chip = $(this);
684
685 if (chip.hasClass('active')) {
686 chip.removeClass('active green').addClass('inactive grey');
687 } else {
688 chip.removeClass('inactive grey').addClass('active green');
689 }
690
691 updateHiddenInput();
692 });
693 });
694 };
695
696 initPermissionChips();
697
698 $('#mainwp_rest_api_keys_compatible_v1').on('change', function() {
699 let isV1 = $(this).is(':checked');
700 let container = $('.mainwp-rest-api-permission-chips[data-api-version]');
701
702 if (isV1) {
703 container.attr('data-api-version', 'v1');
704 container.find('.mainwp-v2-chip').hide();
705 container.find('.mainwp-v1-chip').show();
706 } else {
707 container.attr('data-api-version', 'v2');
708 container.find('.mainwp-v1-chip').hide();
709 container.find('.mainwp-v2-chip').show();
710 }
711
712 container.find('.mainwp-permission-chip').removeClass('active');
713 initPermissionChips();
714 });
715
716 let updateBulkActionsState = function() {
717 let checkedCount = $('.mainwp-rest-api-body-table-manage .check-column INPUT:checkbox:checked').length;
718 let dropdown = $('#mainwp-rest-api-bulk-actions-menu');
719 let applyButton = $('#mainwp-do-rest-api-bulk-actions');
720
721 if (checkedCount > 0) {
722 dropdown.removeClass('disabled');
723 applyButton.removeClass('disabled');
724 } else {
725 dropdown.addClass('disabled');
726 dropdown.dropdown('clear');
727 applyButton.addClass('disabled');
728 }
729 };
730
731 updateBulkActionsState();
732
733 $(document).on('change', '.mainwp-rest-api-body-table-manage .check-column INPUT:checkbox', function() {
734 updateBulkActionsState();
735 });
736 });
737