1window.addEventListener('load', function () {
2 window.objectcache.groups.init();
3 window.objectcache.latency.init();
4 window.objectcache.flushlog.init();
5 window.objectcache.slowlog.init();
6 window.objectcache.commands.init();
7 window.objectcache.diagnostics.init();
8 window.objectcache.adaptive.init();
9});
10
11jQuery.extend(window.objectcache, {
12 latency: {
13 init: function () {
14 this.fetchData();
15 setInterval(this.fetchData, 10000);
16 },
17
18 fetchData: function () {
19 jQuery
20 .ajax({
21 url: objectcache.rest.url + 'objectcache/v1/latency',
22 beforeSend: function (xhr) {
23 xhr.setRequestHeader('X-WP-Nonce', objectcache.rest.nonce);
24 },
25 })
26 .done(function (data, status, xhr) {
27 var header = xhr.getResponseHeader('X-WP-Nonce');
28
29 if (header) {
30 objectcache.rest.nonce = header;
31 }
32
33 var widget = document.querySelector('.objectcache\\:latency-widget');
34
35 var table = widget.querySelector('table');
36 table && widget.removeChild(table);
37
38 var error = widget.querySelector('.error');
39 error && widget.removeChild(error);
40
41 table = document.createElement('table');
42 widget.prepend(table);
43
44 var content = '';
45
46 var formatLatency = function (us) {
47 var ms = Math.round((us / 1000 + Number.EPSILON) * 100) / 100;
48 if (us < 500) return '<strong>' + ms + '</strong> ms';
49 if (us < 1000) return '<strong class="warning">' + ms + '</strong> ms';
50 return '<strong class="error">' + ms + '</strong> ms';
51 };
52
53 data.forEach(function (item) {
54 var note = item.note ? ' (' + item.note + ')' : '';
55
56 content += '<tr>';
57 content += ' <td>' + item.url + note + '</td>';
58 content += ' <td>';
59 content += item.error ? '<span class="error">' + item.error + '</span>' : formatLatency(item.latency);
60 content += ' </td>';
61 content += '</tr>';
62 });
63
64 document.querySelector('.objectcache\\:latency-widget table').innerHTML = content;
65 })
66 .fail(function (error) {
67 var widget = document.querySelector('.objectcache\\:latency-widget');
68
69 var table = widget.querySelector('table');
70 table && widget.removeChild(table);
71
72 var container = widget.querySelector('.error');
73
74 if (! container) {
75 container = document.createElement('p');
76 container.classList.add('error');
77
78 widget.append(container);
79 }
80
81 if (error.responseJSON && error.responseJSON.message) {
82 container.textContent = error.responseJSON.message;
83 } else {
84 container.textContent = 'Request failed (' + error.status + ').';
85 }
86 });
87 },
88 },
89
90 groups: {
91 init: function () {
92 document.querySelector('.objectcache\\:groups-widget button')
93 .addEventListener('click', window.objectcache.groups.fetchData);
94
95 if (! ClipboardJS.isSupported()) {
96 return;
97 }
98
99 var widget = document.querySelector('.objectcache\\:groups-widget');
100 var downloadButton = widget.querySelector('.button[data-download-target]');
101
102 downloadButton.addEventListener('click', function (event) {
103 var groups = widget.querySelector(event.target.dataset.downloadTarget);
104 var hostname = window.location.hostname.replace('.', '-');
105
106 if (groups) {
107 var data = groups.innerText.replace(/ *\t/g, ',');
108 var anchor = window.document.createElement('a');
109 anchor.href = window.URL.createObjectURL(new Blob([data], { type: 'text/plain' }));
110 anchor.download = 'cache-groups-' + hostname + '.csv';
111 anchor.click();
112 }
113 });
114 },
115
116 fetchData: function () {
117 var widget = document.querySelector('.objectcache\\:groups-widget');
118 var checkbox = widget.querySelector('input[type=checkbox]');
119 var button = widget.querySelector('.button');
120 button.blur();
121 button.classList.add('disabled');
122 button.textContent = button.dataset.loading;
123
124 var download = widget.querySelector('.button[data-download-target]');
125 download.classList.add('hidden');
126
127 var container = widget.querySelector('.table-container');
128 container && widget.removeChild(container);
129
130 var error = widget.querySelector('.error');
131 error && widget.removeChild(error);
132
133 var title = document.querySelector('#objectcache_groups .hndle');
134
135 if (title) {
136 if ('label' in title.dataset) {
137 title.textContent = title.dataset.label;
138 } else {
139 title.dataset.label = title.textContent;
140 }
141 }
142
143 jQuery
144 .ajax({
145 url: objectcache.rest.url + 'objectcache/v1/groups' + (
146 objectcache.rest.url.indexOf('?') < 0 ? '?' : '&'
147 ) + 'memory=' + (+(checkbox && checkbox.checked)),
148 beforeSend: function (xhr) {
149 xhr.setRequestHeader('X-WP-Nonce', objectcache.rest.nonce);
150 },
151 })
152 .done(function (data, status, xhr) {
153 var header = xhr.getResponseHeader('X-WP-Nonce');
154
155 if (header) {
156 objectcache.rest.nonce = header;
157 }
158
159 var info = widget.querySelector('p:first-child');
160 info && widget.removeChild(info);
161
162 var container = document.createElement('div');
163 container.classList.add('table-container');
164 widget.prepend(container);
165
166 var table = document.createElement('table');
167 container.prepend(table);
168
169 var escapeHtml = function (text) {
170 var div = document.createElement('div');
171 div.innerText = text;
172
173 return div.innerHTML.replace(/"/g, '"').replace(/'/g, ''');
174 };
175
176 var formatNumber = function (number) {
177 return new Intl.NumberFormat('en-US').format(number);
178 };
179
180 var formatBytes = function (bytes) {
181 return new Intl.NumberFormat('en-US', {
182 style: 'unit',
183 unit: 'byte',
184 unitDisplay: 'narrow',
185 notation: 'compact',
186 }).format(bytes).replace(/([\d.]+)(\D+)/, '$1 $2');
187 };
188
189 var content = '';
190
191 if (data.length) {
192 title.textContent = title.dataset.label + ' (' + data.length + ')';
193 title.dataset.keys = data.length;
194
195 data.forEach(function (item) {
196 var title = formatNumber(item.keys) + ' objects found in `' + escapeHtml(item.group) + '` cache group';
197
198 content += '<tr title="' + title + '">';
199 content += ' <td data-group="' + item.group + '">';
200 content += ' <span class="group-name">' + escapeHtml(item.group) + '</span>';
201 content += ' <button class="objectcache:flush-group button-link">Flush</button>';
202 content += ' </td>';
203 if (checkbox && checkbox.checked) {
204 content += '<td>';
205 content += formatBytes(item.bytes);
206 content += '</td>';
207 }
208 content += ' <td>';
209 content += formatNumber(item.keys);
210 content += ' </td>';
211 content += '</tr>';
212 });
213
214 download.classList.remove('hidden');
215 } else {
216 content += '<tr>';
217 content += ' <td colspan="2">No cache groups found.</td>';
218 content += '</tr>';
219 }
220
221 table.innerHTML = content;
222
223 document.querySelectorAll(
224 '.objectcache\\:groups-widget .objectcache\\:flush-group'
225 ).forEach(function (button) {
226 button.addEventListener('click', window.objectcache.groups.flushGroup);
227 });
228 })
229 .fail(function (error) {
230 var container = widget.querySelector('.error');
231
232 if (! container) {
233 container = document.createElement('p');
234 container.classList.add('error');
235
236 widget.append(container);
237 }
238
239 if (error.responseJSON && error.responseJSON.message) {
240 container.textContent = error.responseJSON.message;
241 } else {
242 container.textContent = 'Request failed (' + error.status + ').';
243 }
244 })
245 .always(function () {
246 var button = widget.querySelector('.objectcache\\:groups-widget .button');
247 button.textContent = button.dataset.text;
248 button.classList.remove('disabled');
249 });
250 },
251
252 flushGroup: function (event) {
253 event.preventDefault();
254
255 var table = event.target.closest('table');
256
257 if (table.classList.contains('busy')) {
258 return;
259 }
260
261 table.classList.add('busy');
262
263 event.target.disabled = true;
264
265 var groupLabel = event.target.previousElementSibling;
266
267 groupLabel.classList.remove('error');
268 groupLabel.textContent = 'Flushing...';
269
270 jQuery
271 .ajax({
272 type: 'DELETE',
273 url: objectcache.rest.url + 'objectcache/v1/groups',
274 data: {
275 group: event.target.parentElement.dataset.group,
276 },
277 beforeSend: function (xhr) {
278 xhr.setRequestHeader('X-WP-Nonce', objectcache.rest.nonce);
279 },
280 })
281 .done(function(data, status, xhr) {
282 var header = xhr.getResponseHeader('X-WP-Nonce');
283
284 if (header) {
285 objectcache.rest.nonce = header;
286 }
287
288 var title = document.querySelector('#objectcache_groups .hndle');
289 title.dataset.keys = title.dataset.keys - 1;
290
291 title.textContent = title.dataset.label + ' (' + title.dataset.keys + ')';
292
293 event.target.closest('tr').remove();
294 })
295 .fail(function (error) {
296 groupLabel.classList.add('error');
297
298 if (error.responseJSON && error.responseJSON.message) {
299 groupLabel.textContent = error.responseJSON.message;
300 } else {
301 groupLabel.textContent = 'Request failed (' + error.status + ').';
302 }
303
304 setTimeout(function() {
305 groupLabel.classList.remove('error');
306 groupLabel.textContent = groupLabel.parentElement.dataset.group;
307 }, 3000);
308 })
309 .always(function () {
310 table.classList.remove('busy');
311 event.target.disabled = false;
312 });
313 }
314 },
315
316 flushlog: {
317 init: function () {
318 var inputs = document.querySelectorAll('.objectcache\\:flushlog-widget input');
319
320 if (inputs) {
321 for (var i = 0; i < inputs.length; i++) {
322 inputs[i].addEventListener('click', window.objectcache.flushlog.save);
323 }
324 }
325 },
326
327 save: function (event) {
328 event.target.disabled = true;
329
330 jQuery
331 .ajax({
332 type: 'POST',
333 url: objectcache.rest.url + 'objectcache/v1/options',
334 data: {
335 [event.target.name]: event.target.checked ? 1 : 0,
336 },
337 beforeSend: function (xhr) {
338 xhr.setRequestHeader('X-WP-Nonce', objectcache.rest.nonce);
339 },
340 })
341 .fail(function (error) {
342 if (error.responseJSON && error.responseJSON.message) {
343 window.alert(error.responseJSON.message);
344 } else {
345 window.alert('Request failed (' + error.status + ').');
346 }
347 })
348 .always(function () {
349 event.target.disabled = false;
350 });
351 },
352 },
353
354 diagnostics: {
355 init: function () {
356 var widget = document.querySelector('.objectcache\\:health-widget');
357 var downloadButton = widget.querySelector('.button[data-download-target]');
358
359 downloadButton.addEventListener('click', function (event) {
360 var diagnostics = widget.querySelector(event.target.dataset.downloadTarget);
361 var hostname = window.location.hostname.replace('.', '-');
362
363 if (diagnostics) {
364 var anchor = window.document.createElement('a');
365 anchor.href = window.URL.createObjectURL(new Blob([diagnostics.value], { type: 'text/plain' }));
366 anchor.download = 'diagnostics-' + hostname + '.txt';
367 anchor.click();
368 }
369 });
370 },
371 },
372
373 slowlog: {
374 init: function () {
375 document.querySelector(
376 '#objectcache_slowlog .handle-actions'
377 )?.addEventListener('click', function (event) {
378 if (
379 event.target.classList.contains('handle-reset') ||
380 event.target.closest('.handle-reset')
381 ) {
382 event.preventDefault();
383 event.stopPropagation();
384
385 if (window.confirm("Are you sure you want to reset the slowlog?")) {
386 window.objectcache.slowlog.reset();
387 }
388 }
389 });
390 },
391
392 reset: function () {
393 jQuery
394 .ajax({
395 type: 'DELETE',
396 url: document.querySelector('#objectcache_slowlog .handle-reset').dataset.href,
397 beforeSend: function (xhr) {
398 xhr.setRequestHeader('X-WP-Nonce', objectcache.rest.nonce);
399 },
400 })
401 .done(function (data, status, xhr) {
402 window.location.reload();
403 })
404 .fail(function (error) {
405 if (error.responseJSON && error.responseJSON.message) {
406 window.alert(error.responseJSON.message);
407 } else {
408 window.alert('Request failed (' + error.status + ').');
409 }
410 });
411 },
412 },
413
414 commands: {
415 init: function () {
416 document.querySelector(
417 '#objectcache_commandstats .handle-actions'
418 )?.addEventListener('click', function (event) {
419 if (
420 event.target.classList.contains('handle-reset') ||
421 event.target.closest('.handle-reset')
422 ) {
423 event.preventDefault();
424 event.stopPropagation();
425
426 if (window.confirm("Are you sure you want to reset the command statistics?")) {
427 window.objectcache.commands.reset();
428 }
429 }
430 });
431 },
432
433 reset: function () {
434 jQuery
435 .ajax({
436 type: 'DELETE',
437 url: document.querySelector('#objectcache_commandstats .handle-reset').dataset.href,
438 beforeSend: function (xhr) {
439 xhr.setRequestHeader('X-WP-Nonce', objectcache.rest.nonce);
440 },
441 })
442 .done(function (data, status, xhr) {
443 window.location.reload();
444 })
445 .fail(function (error) {
446 if (error.responseJSON && error.responseJSON.message) {
447 window.alert(error.responseJSON.message);
448 } else {
449 window.alert('Request failed (' + error.status + ').');
450 }
451 });
452 },
453
454 sort: function (event) {
455 var sortBy = (event.target.closest('[data-sort]') || { dataset: {} }).dataset.sort;
456 var sortable = ['calls', 'rejected', 'failed', 'usec'];
457
458 if (sortBy && sortable.includes(sortBy)) {
459 event.preventDefault();
460
461 var container = document.querySelector('.objectcache\\:commands-widget');
462 var items = Array.from(container.querySelectorAll('details'));
463
464 var sorted = items.slice().sort((a, b) => {
465 return parseInt(b.dataset[sortBy]) - parseInt(a.dataset[sortBy]);
466 });
467
468 sorted.forEach(item => container.appendChild(item));
469 }
470 },
471 },
472
473 adaptive: {
474 init: function () {
475 var button = document.querySelector('.objectcache\\:adaptive-widget button');
476 if (button) {
477 button.addEventListener('click', window.objectcache.adaptive.fetchData);
478 }
479 },
480 fetchData: function (event) {
481 event.preventDefault();
482
483 var widget = document.querySelector('.objectcache\\:adaptive-widget');
484 var button = widget.querySelector('.button');
485
486 button.blur();
487 button.classList.add('disabled');
488 button.textContent = button.dataset.loading;
489
490 jQuery
491 .ajax({
492 url: objectcache.rest.url + 'objectcache/v1/relay/adaptive',
493 beforeSend: function (xhr) {
494 xhr.setRequestHeader('X-WP-Nonce', objectcache.rest.nonce);
495 },
496 })
497 .done(function (data, status, xhr) {
498 var header = xhr.getResponseHeader('X-WP-Nonce');
499
500 if (header) {
501 objectcache.rest.nonce = header;
502 }
503
504 var content = '';
505
506 if (data.length > 0) {
507 data.forEach(function (item) {
508 content += '<details>';
509 content += ' <summary>';
510 content += ' <span class="dashicons dashicons-arrow-right-alt2"></span>';
511 content += ' <code title="' + item.key + '">' + item.key + '</code>';
512 content += ' <time title="Reads/Writes ratio">1:' + Math.min(Math.round(item.ratio), 1000) + '</time>';
513 content += ' </summary>';
514 content += ' <ul>';
515 content += ' <li title="Number of reads">';
516 content += ' <span class="dashicons dashicons-visibility"></span>';
517 content += ' <time>' + item.reads + '</time>';
518 content += ' </li>';
519 content += ' <li title="Number of writes">';
520 content += ' <span class="dashicons dashicons-edit"></span>';
521 content += ' <time>' + item.writes + '</time>';
522 content += ' </li>';
523 content += ' </ul>';
524 content += '</details>';
525 });
526 } else {
527 content = 'No statistics available, yet.';
528 }
529
530 var table = widget.querySelector('p,div.table-container');
531 table && widget.removeChild(table);
532
533 var container = document.createElement('div');
534 container.classList.add('table-container');
535 container.innerHTML = content;
536
537 widget.prepend(container);
538 })
539 .fail(function (error) {
540 var container = widget.querySelector('.error');
541
542 if (! container) {
543 container = document.createElement('p');
544 container.classList.add('error');
545
546 widget.prepend(container);
547 }
548
549 if (error.responseJSON && error.responseJSON.message) {
550 container.textContent = error.responseJSON.message;
551 } else {
552 container.textContent = 'Request failed (' + error.status + ').';
553 }
554 })
555 .always(function () {
556 button.textContent = button.dataset.text;
557 button.classList.remove('disabled');
558 });
559 }
560 }
561});
562