PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.65
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.65
51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 51.1.46 51.1.47 51.1.49 All 37 releases
king-addons / includes / extensions / Activity_Log / assets / script.js

script.js in King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder 51.1.65, at includes/extensions/Activity_Log/assets/script.js

568 lines 20.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Activity Log - Admin JavaScript
3 *
4 * @package King_Addons
5 */
6
7 (function ($) {
8 'use strict';
9
10 const ActivityLog = {
11 state: {
12 page: 1,
13 perPage: 20,
14 filters: {},
15 loading: false
16 },
17
18 /**
19 * Initialize the Activity Log module.
20 */
21 init: function () {
22 this.bindEvents();
23
24 // Load logs if on logs tab
25 if ($('#ka-al-table').length) {
26 this.loadLogs();
27 }
28 },
29
30 /**
31 * Bind all event handlers.
32 */
33 bindEvents: function () {
34 // Filter events
35 $('#ka-al-filter-apply').on('click', this.applyFilters.bind(this));
36 $('#ka-al-filter-reset').on('click', this.resetFilters.bind(this));
37 $('#ka-al-search').on('keypress', function (e) {
38 if (e.which === 13) {
39 ActivityLog.applyFilters();
40 }
41 });
42
43 // Pagination
44 $(document).on('click', '.ka-al-page-btn', this.handlePagination.bind(this));
45 $(document).on('click', '.ka-al-page-num', this.handlePageNumber.bind(this));
46
47 // View event details
48 $(document).on('click', '.ka-al-view-btn', this.openDrawer.bind(this));
49
50 // Close drawer
51 $('.ka-al-drawer-close, .ka-al-drawer-backdrop').on('click', this.closeDrawer.bind(this));
52 $(document).on('keydown', function (e) {
53 if (e.key === 'Escape') {
54 ActivityLog.closeDrawer();
55 }
56 });
57
58 // Settings
59 $('#ka-al-save-settings').on('click', this.saveSettings.bind(this));
60 $('#ka-al-purge-logs').on('click', this.purgeLogs.bind(this));
61
62 // Export
63 $('#ka-al-export-btn').on('click', this.exportLogs.bind(this));
64 },
65
66 /**
67 * Apply filters and reload logs.
68 */
69 applyFilters: function () {
70 this.state.page = 1;
71 this.state.filters = {
72 search: $('#ka-al-search').val(),
73 event_key: $('#ka-al-filter-event').val(),
74 severity: $('#ka-al-filter-severity').val(),
75 user_id: $('#ka-al-filter-user').val(),
76 date_from: $('#ka-al-filter-date-from').val(),
77 date_to: $('#ka-al-filter-date-to').val()
78 };
79 this.loadLogs();
80 },
81
82 /**
83 * Reset all filters.
84 */
85 resetFilters: function () {
86 $('#ka-al-search').val('');
87 $('#ka-al-filter-event').val('');
88 $('#ka-al-filter-severity').val('');
89 $('#ka-al-filter-user').val('');
90 $('#ka-al-filter-date-from').val('');
91 $('#ka-al-filter-date-to').val('');
92 this.state.filters = {};
93 this.state.page = 1;
94 this.loadLogs();
95 },
96
97 /**
98 * Load logs via AJAX.
99 */
100 loadLogs: function () {
101 if (this.state.loading) {
102 return;
103 }
104
105 this.state.loading = true;
106 const $tbody = $('#ka-al-table-body');
107
108 // Show loading state
109 $tbody.html(`
110 <tr class="ka-al-loading">
111 <td colspan="7">
112 <div class="ka-al-spinner"></div>
113 ${kngActivityLog.i18n.loading}
114 </td>
115 </tr>
116 `);
117
118 const data = {
119 action: 'kng_activity_log_get_logs',
120 nonce: kngActivityLog.nonce,
121 page: this.state.page,
122 per_page: this.state.perPage,
123 ...this.state.filters
124 };
125
126 $.post(kngActivityLog.ajaxUrl, data, (response) => {
127 this.state.loading = false;
128
129 if (!response.success) {
130 $tbody.html(`
131 <tr>
132 <td colspan="7" class="ka-al-empty">${kngActivityLog.i18n.error}</td>
133 </tr>
134 `);
135 return;
136 }
137
138 this.renderLogs(response.data);
139 }).fail(() => {
140 this.state.loading = false;
141 $tbody.html(`
142 <tr>
143 <td colspan="7" class="ka-al-empty">${kngActivityLog.i18n.error}</td>
144 </tr>
145 `);
146 });
147 },
148
149 /**
150 * Render logs table.
151 *
152 * @param {Object} data Response data with items, total, pages.
153 */
154 renderLogs: function (data) {
155 const $tbody = $('#ka-al-table-body');
156
157 if (!data.items || data.items.length === 0) {
158 $tbody.html(`
159 <tr>
160 <td colspan="7" class="ka-al-empty">${kngActivityLog.i18n.noResults}</td>
161 </tr>
162 `);
163 this.renderPagination(0, 0);
164 return;
165 }
166
167 let html = '';
168 data.items.forEach((item) => {
169 const date = new Date(item.created_at);
170 const formattedDate = date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
171
172 html += `
173 <tr>
174 <td>${this.escapeHtml(formattedDate)}</td>
175 <td>${this.escapeHtml(item.event_label)}</td>
176 <td>
177 <span class="ka-al-severity ka-al-severity--${item.severity_class}">
178 ${this.escapeHtml(item.severity)}
179 </span>
180 </td>
181 <td>${this.escapeHtml(item.user_login || 'System')}</td>
182 <td>${this.escapeHtml(item.object_title || '-')}</td>
183 <td>${this.escapeHtml(item.ip || '-')}</td>
184 <td>
185 <button type="button" class="ka-al-view-btn" data-id="${item.id}">
186 View
187 </button>
188 </td>
189 </tr>
190 `;
191 });
192
193 $tbody.html(html);
194 this.renderPagination(data.total, data.pages);
195 },
196
197 /**
198 * Render pagination controls.
199 *
200 * @param {number} total Total items.
201 * @param {number} pages Total pages.
202 */
203 renderPagination: function (total, pages) {
204 const $pagination = $('#ka-al-pagination');
205 const $info = $pagination.find('.ka-al-pagination-info');
206 const $numbers = $pagination.find('.ka-al-page-numbers');
207 const $prevBtn = $pagination.find('[data-page="prev"]');
208 const $nextBtn = $pagination.find('[data-page="next"]');
209
210 // Update info
211 const start = ((this.state.page - 1) * this.state.perPage) + 1;
212 const end = Math.min(this.state.page * this.state.perPage, total);
213 $info.text(`Showing ${start}-${end} of ${total} events`);
214
215 // Update prev/next buttons
216 $prevBtn.prop('disabled', this.state.page <= 1);
217 $nextBtn.prop('disabled', this.state.page >= pages);
218
219 // Render page numbers
220 let numbersHtml = '';
221 const maxVisible = 5;
222 let startPage = Math.max(1, this.state.page - Math.floor(maxVisible / 2));
223 let endPage = Math.min(pages, startPage + maxVisible - 1);
224
225 if (endPage - startPage < maxVisible - 1) {
226 startPage = Math.max(1, endPage - maxVisible + 1);
227 }
228
229 for (let i = startPage; i <= endPage; i++) {
230 numbersHtml += `
231 <button type="button" class="ka-al-page-num ${i === this.state.page ? 'active' : ''}" data-page="${i}">
232 ${i}
233 </button>
234 `;
235 }
236
237 $numbers.html(numbersHtml);
238 },
239
240 /**
241 * Handle pagination button click.
242 *
243 * @param {Event} e Click event.
244 */
245 handlePagination: function (e) {
246 const $btn = $(e.currentTarget);
247 const action = $btn.data('page');
248
249 if (action === 'prev' && this.state.page > 1) {
250 this.state.page--;
251 this.loadLogs();
252 } else if (action === 'next') {
253 this.state.page++;
254 this.loadLogs();
255 }
256 },
257
258 /**
259 * Handle page number click.
260 *
261 * @param {Event} e Click event.
262 */
263 handlePageNumber: function (e) {
264 const page = parseInt($(e.currentTarget).data('page'), 10);
265 if (page !== this.state.page) {
266 this.state.page = page;
267 this.loadLogs();
268 }
269 },
270
271 /**
272 * Open event details drawer.
273 *
274 * @param {Event} e Click event.
275 */
276 openDrawer: function (e) {
277 const id = $(e.currentTarget).data('id');
278 const $drawer = $('#ka-al-drawer');
279 const $body = $drawer.find('.ka-al-drawer-body');
280
281 // Show loading
282 $body.html(`
283 <div style="text-align: center; padding: 56px;">
284 <div class="ka-al-spinner"></div>
285 <div style="margin-top: 16px; color: #8e8e93;">${kngActivityLog.i18n.loading}</div>
286 </div>
287 `);
288
289 $drawer.addClass('open');
290 $('body').css('overflow', 'hidden');
291
292 // Load event details
293 $.post(kngActivityLog.ajaxUrl, {
294 action: 'kng_activity_log_get_event',
295 nonce: kngActivityLog.nonce,
296 id: id
297 }, (response) => {
298 if (!response.success) {
299 $body.html(`<p class="ka-al-empty">${kngActivityLog.i18n.error}</p>`);
300 return;
301 }
302
303 this.renderEventDetails(response.data, $body);
304 });
305 },
306
307 /**
308 * Render event details in drawer.
309 *
310 * @param {Object} event Event data.
311 * @param {jQuery} $container Container element.
312 */
313 renderEventDetails: function (event, $container) {
314 const date = new Date(event.created_at);
315 const formattedDate = date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
316
317 let html = `
318 <div class="ka-al-detail-row">
319 <span class="ka-al-detail-label">Event</span>
320 <span class="ka-al-detail-value">${this.escapeHtml(event.event_label)}</span>
321 </div>
322 <div class="ka-al-detail-row">
323 <span class="ka-al-detail-label">Time</span>
324 <span class="ka-al-detail-value">${this.escapeHtml(formattedDate)}</span>
325 </div>
326 <div class="ka-al-detail-row">
327 <span class="ka-al-detail-label">Severity</span>
328 <span class="ka-al-detail-value">
329 <span class="ka-al-severity ka-al-severity--${event.severity_class}">
330 ${this.escapeHtml(event.severity_label)}
331 </span>
332 </span>
333 </div>
334 <div class="ka-al-detail-row">
335 <span class="ka-al-detail-label">User</span>
336 <span class="ka-al-detail-value">${this.escapeHtml(event.user_login || 'System')} (${event.user_role || 'N/A'})</span>
337 </div>
338 <div class="ka-al-detail-row">
339 <span class="ka-al-detail-label">IP Address</span>
340 <span class="ka-al-detail-value">${this.escapeHtml(event.ip || 'N/A')}</span>
341 </div>
342 <div class="ka-al-detail-row">
343 <span class="ka-al-detail-label">Object Type</span>
344 <span class="ka-al-detail-value">${this.escapeHtml(event.object_type || 'N/A')}</span>
345 </div>
346 <div class="ka-al-detail-row">
347 <span class="ka-al-detail-label">Object</span>
348 <span class="ka-al-detail-value">${this.escapeHtml(event.object_title || 'N/A')} (ID: ${event.object_id || 'N/A'})</span>
349 </div>
350 <div class="ka-al-detail-row">
351 <span class="ka-al-detail-label">Context</span>
352 <span class="ka-al-detail-value">${this.escapeHtml(event.context)}</span>
353 </div>
354 <div class="ka-al-detail-row">
355 <span class="ka-al-detail-label">Source</span>
356 <span class="ka-al-detail-value">${this.escapeHtml(event.source)}</span>
357 </div>
358 <div class="ka-al-detail-message">
359 ${this.escapeHtml(event.message)}
360 </div>
361 `;
362
363 // Add user agent if available
364 if (event.user_agent) {
365 html += `
366 <div class="ka-al-detail-row" style="margin-top: 20px;">
367 <span class="ka-al-detail-label">User Agent</span>
368 <span class="ka-al-detail-value" style="font-size: 12px; opacity: 0.7;">
369 ${this.escapeHtml(event.user_agent)}
370 </span>
371 </div>
372 `;
373 }
374
375 // Add data JSON if available
376 if (event.data && Object.keys(event.data).length > 0) {
377 html += `
378 <div class="ka-al-detail-data">
379 <h4>Additional Data</h4>
380 <pre class="ka-al-detail-json">${JSON.stringify(event.data, null, 2)}</pre>
381 </div>
382 `;
383 }
384
385 $container.html(html);
386 },
387
388 /**
389 * Close event details drawer.
390 */
391 closeDrawer: function () {
392 $('#ka-al-drawer').removeClass('open');
393 $('body').css('overflow', '');
394 },
395
396 /**
397 * Save settings via AJAX.
398 */
399 saveSettings: function () {
400 const $btn = $('#ka-al-save-settings');
401 const $form = $('#ka-al-settings-form');
402
403 // Collect settings
404 const settings = {
405 enabled: $form.find('[name="enabled"]').is(':checked'),
406 retention_days: $form.find('[name="retention_days"]').val() || 14,
407 modules: {
408 auth: $form.find('[name="modules[auth]"]').is(':checked'),
409 content: $form.find('[name="modules[content]"]').is(':checked'),
410 users: $form.find('[name="modules[users]"]').is(':checked'),
411 plugins: $form.find('[name="modules[plugins]"]').is(':checked'),
412 themes: $form.find('[name="modules[themes]"]').is(':checked')
413 },
414 excluded_roles: $form.find('[name="excluded_roles[]"]').val() || [],
415 ip_storage: $form.find('[name="ip_storage"]').val(),
416 store_user_agent: $form.find('[name="store_user_agent"]').is(':checked')
417 };
418
419 $btn.prop('disabled', true).text('Saving...');
420
421 $.post(kngActivityLog.ajaxUrl, {
422 action: 'kng_activity_log_save_settings',
423 nonce: kngActivityLog.nonce,
424 settings: settings
425 }, (response) => {
426 $btn.prop('disabled', false).text('Save Settings');
427
428 if (response.success) {
429 this.showNotice('success', kngActivityLog.i18n.settingsSaved);
430 } else {
431 this.showNotice('error', response.data?.message || kngActivityLog.i18n.error);
432 }
433 }).fail(() => {
434 $btn.prop('disabled', false).text('Save Settings');
435 this.showNotice('error', kngActivityLog.i18n.error);
436 });
437 },
438
439 /**
440 * Purge all logs.
441 */
442 purgeLogs: function () {
443 if (!confirm(kngActivityLog.i18n.confirmPurge)) {
444 return;
445 }
446
447 const $btn = $('#ka-al-purge-logs');
448 $btn.prop('disabled', true).text('Purging...');
449
450 $.post(kngActivityLog.ajaxUrl, {
451 action: 'kng_activity_log_purge',
452 nonce: kngActivityLog.nonce
453 }, (response) => {
454 $btn.prop('disabled', false).text('Purge All Logs');
455
456 if (response.success) {
457 this.showNotice('success', response.data.message);
458 } else {
459 this.showNotice('error', response.data?.message || kngActivityLog.i18n.error);
460 }
461 }).fail(() => {
462 $btn.prop('disabled', false).text('Purge All Logs');
463 this.showNotice('error', kngActivityLog.i18n.error);
464 });
465 },
466
467 /**
468 * Export logs to CSV.
469 */
470 exportLogs: function () {
471 const $btn = $('#ka-al-export-btn');
472 const format = $('input[name="export_format"]:checked').val() || 'csv';
473
474 $btn.prop('disabled', true);
475
476 $.post(kngActivityLog.ajaxUrl, {
477 action: 'kng_activity_log_export',
478 nonce: kngActivityLog.nonce,
479 ...this.state.filters
480 }, (response) => {
481 $btn.prop('disabled', false);
482
483 if (!response.success) {
484 this.showNotice('error', response.data?.message || kngActivityLog.i18n.error);
485 return;
486 }
487
488 // Create and trigger download
489 const blob = new Blob([response.data.content], { type: 'text/csv;charset=utf-8;' });
490 const link = document.createElement('a');
491 const url = URL.createObjectURL(blob);
492
493 link.setAttribute('href', url);
494 link.setAttribute('download', response.data.filename);
495 link.style.visibility = 'hidden';
496 document.body.appendChild(link);
497 link.click();
498 document.body.removeChild(link);
499
500 this.showNotice('success', kngActivityLog.i18n.exportSuccess);
501 }).fail(() => {
502 $btn.prop('disabled', false);
503 this.showNotice('error', kngActivityLog.i18n.error);
504 });
505 },
506
507 /**
508 * Show a temporary notice.
509 *
510 * @param {string} type Notice type (success, error).
511 * @param {string} message Message text.
512 */
513 showNotice: function (type, message) {
514 // Remove any existing notices
515 $('.ka-al-notice').remove();
516
517 const bgColor = type === 'success' ? 'rgba(48, 209, 88, 0.95)' : 'rgba(255, 69, 58, 0.95)';
518
519 const $notice = $(`
520 <div class="ka-al-notice" style="
521 position: fixed;
522 top: 50px;
523 right: 20px;
524 z-index: 100001;
525 max-width: 400px;
526 padding: 16px 24px;
527 background: ${bgColor};
528 color: #fff;
529 border-radius: 14px;
530 font-size: 14px;
531 font-weight: 500;
532 box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
533 backdrop-filter: blur(10px);
534 ">
535 ${this.escapeHtml(message)}
536 </div>
537 `);
538
539 $('body').append($notice);
540
541 setTimeout(() => {
542 $notice.fadeOut(300, function () {
543 $(this).remove();
544 });
545 }, 3000);
546 },
547
548 /**
549 * Escape HTML for safe rendering.
550 *
551 * @param {string} str String to escape.
552 * @returns {string} Escaped string.
553 */
554 escapeHtml: function (str) {
555 if (!str) return '';
556 const div = document.createElement('div');
557 div.textContent = str;
558 return div.innerHTML;
559 }
560 };
561
562 // Initialize on DOM ready
563 $(document).ready(function () {
564 ActivityLog.init();
565 });
566
567 })(jQuery);
568