Merge branch 'origin/QA_4_8' into Weblate.
[phpmyadmin.git] / js / server_status_monitor.js
blob7ad61eb1e3b1aa9c36a29a05a2ffc9400d2eb3fe
1 /* vim: set expandtab sw=4 ts=4 sts=4: */
2 var runtime = {};
3 var server_time_diff;
4 var server_os;
5 var is_superuser;
6 var server_db_isLocal;
7 var chartSize;
8 AJAX.registerOnload('server_status_monitor.js', function () {
9     var $js_data_form = $('#js_data');
10     server_time_diff  = new Date().getTime() - $js_data_form.find('input[name=server_time]').val();
11     server_os =         $js_data_form.find('input[name=server_os]').val();
12     is_superuser =      $js_data_form.find('input[name=is_superuser]').val();
13     server_db_isLocal = $js_data_form.find('input[name=server_db_isLocal]').val();
14 });
16 /**
17  * Unbind all event handlers before tearing down a page
18  */
19 AJAX.registerTeardown('server_status_monitor.js', function () {
20     $('#emptyDialog').remove();
21     $('#addChartDialog').remove();
22     $('a.popupLink').off('click');
23     $('body').off('click');
24 });
25 /**
26  * Popup behaviour
27  */
28 AJAX.registerOnload('server_status_monitor.js', function () {
29     $('<div />')
30         .attr('id', 'emptyDialog')
31         .appendTo('#page_content');
32     $('#addChartDialog')
33         .appendTo('#page_content');
35     $('a.popupLink').click(function () {
36         var $link = $(this);
37         $('div.' + $link.attr('href').substr(1))
38             .show()
39             .offset({ top: $link.offset().top + $link.height() + 5, left: $link.offset().left })
40             .addClass('openedPopup');
42         return false;
43     });
44     $('body').click(function (event) {
45         $('div.openedPopup').each(function () {
46             var $cnt = $(this);
47             var pos = $cnt.offset();
48             // Hide if the mouseclick is outside the popupcontent
49             if (event.pageX < pos.left ||
50                 event.pageY < pos.top ||
51                 event.pageX > pos.left + $cnt.outerWidth() ||
52                 event.pageY > pos.top + $cnt.outerHeight()
53             ) {
54                 $cnt.hide().removeClass('openedPopup');
55             }
56         });
57     });
58 });
60 AJAX.registerTeardown('server_status_monitor.js', function () {
61     $('a[href="#rearrangeCharts"], a[href="#endChartEditMode"]').off('click');
62     $('div.popupContent select[name="chartColumns"]').off('change');
63     $('div.popupContent select[name="gridChartRefresh"]').off('change');
64     $('a[href="#addNewChart"]').off('click');
65     $('a[href="#exportMonitorConfig"]').off('click');
66     $('a[href="#importMonitorConfig"]').off('click');
67     $('a[href="#clearMonitorConfig"]').off('click');
68     $('a[href="#pauseCharts"]').off('click');
69     $('a[href="#monitorInstructionsDialog"]').off('click');
70     $('input[name="chartType"]').off('click');
71     $('input[name="useDivisor"]').off('click');
72     $('input[name="useUnit"]').off('click');
73     $('select[name="varChartList"]').off('click');
74     $('a[href="#kibDivisor"]').off('click');
75     $('a[href="#mibDivisor"]').off('click');
76     $('a[href="#submitClearSeries"]').off('click');
77     $('a[href="#submitAddSeries"]').off('click');
78     // $("input#variableInput").destroy();
79     $('#chartPreset').off('click');
80     $('#chartStatusVar').off('click');
81     destroyGrid();
82 });
84 AJAX.registerOnload('server_status_monitor.js', function () {
85     // Show tab links
86     $('div.tabLinks').show();
87     $('#loadingMonitorIcon').remove();
88     // Codemirror is loaded on demand so we might need to initialize it
89     if (! codemirror_editor) {
90         var $elm = $('#sqlquery');
91         if ($elm.length > 0 && typeof CodeMirror !== 'undefined') {
92             codemirror_editor = CodeMirror.fromTextArea(
93                 $elm[0],
94                 {
95                     lineNumbers: true,
96                     matchBrackets: true,
97                     indentUnit: 4,
98                     mode: 'text/x-mysql',
99                     lineWrapping: true
100                 }
101             );
102         }
103     }
104     // Timepicker is loaded on demand so we need to initialize
105     // datetime fields from the 'load log' dialog
106     $('#logAnalyseDialog').find('.datetimefield').each(function () {
107         PMA_addDatepicker($(this));
108     });
110     /** ** Monitor charting implementation ****/
111     /* Saves the previous ajax response for differential values */
112     var oldChartData = null;
113     // Holds about to be created chart
114     var newChart = null;
115     var chartSpacing;
117     // Whenever the monitor object (runtime.charts) or the settings object
118     // (monitorSettings) changes in a way incompatible to the previous version,
119     // increase this number. It will reset the users monitor and settings object
120     // in his localStorage to the default configuration
121     var monitorProtocolVersion = '1.0';
123     // Runtime parameter of the monitor, is being fully set in initGrid()
124     runtime = {
125         // Holds all visible charts in the grid
126         charts: null,
127         // Stores the timeout handler so it can be cleared
128         refreshTimeout: null,
129         // Stores the GET request to refresh the charts
130         refreshRequest: null,
131         // Chart auto increment
132         chartAI: 0,
133         // To play/pause the monitor
134         redrawCharts: false,
135         // Object that contains a list of nodes that need to be retrieved
136         // from the server for chart updates
137         dataList: [],
138         // Current max points per chart (needed for auto calculation)
139         gridMaxPoints: 20,
140         // displayed time frame
141         xmin: -1,
142         xmax: -1
143     };
144     var monitorSettings = null;
146     var defaultMonitorSettings = {
147         columns: 3,
148         chartSize: { width: 295, height: 250 },
149         // Max points in each chart. Settings it to 'auto' sets
150         // gridMaxPoints to (chartwidth - 40) / 12
151         gridMaxPoints: 'auto',
152         /* Refresh rate of all grid charts in ms */
153         gridRefresh: 5000
154     };
156     // Allows drag and drop rearrange and print/edit icons on charts
157     var editMode = false;
159     /* List of preconfigured charts that the user may select */
160     var presetCharts = {
161         // Query cache efficiency
162         'qce': {
163             title: PMA_messages.strQueryCacheEfficiency,
164             series: [{
165                 label: PMA_messages.strQueryCacheEfficiency
166             }],
167             nodes: [{
168                 dataPoints: [{ type: 'statusvar', name: 'Qcache_hits' }, { type: 'statusvar', name: 'Com_select' }],
169                 transformFn: 'qce'
170             }],
171             maxYLabel: 0
172         },
173         // Query cache usage
174         'qcu': {
175             title: PMA_messages.strQueryCacheUsage,
176             series: [{
177                 label: PMA_messages.strQueryCacheUsed
178             }],
179             nodes: [{
180                 dataPoints: [{ type: 'statusvar', name: 'Qcache_free_memory' }, { type: 'servervar', name: 'query_cache_size' }],
181                 transformFn: 'qcu'
182             }],
183             maxYLabel: 0
184         }
185     };
187     // time span selection
188     var selectionTimeDiff = [];
189     var selectionStartX;
190     var selectionStartY;
191     var selectionEndX;
192     var selectionEndY;
193     var drawTimeSpan = false;
195     // chart tooltip
196     var tooltipBox;
198     /* Add OS specific system info charts to the preset chart list */
199     switch (server_os) {
200     case 'WINNT':
201         $.extend(presetCharts, {
202             'cpu': {
203                 title: PMA_messages.strSystemCPUUsage,
204                 series: [{
205                     label: PMA_messages.strAverageLoad
206                 }],
207                 nodes: [{
208                     dataPoints: [{ type: 'cpu', name: 'loadavg' }]
209                 }],
210                 maxYLabel: 100
211             },
213             'memory': {
214                 title: PMA_messages.strSystemMemory,
215                 series: [{
216                     label: PMA_messages.strTotalMemory,
217                     fill: true
218                 }, {
219                     dataType: 'memory',
220                     label: PMA_messages.strUsedMemory,
221                     fill: true
222                 }],
223                 nodes: [{ dataPoints: [{ type: 'memory', name: 'MemTotal' }], valueDivisor: 1024 },
224                     { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 }
225                 ],
226                 maxYLabel: 0
227             },
229             'swap': {
230                 title: PMA_messages.strSystemSwap,
231                 series: [{
232                     label: PMA_messages.strTotalSwap,
233                     fill: true
234                 }, {
235                     label: PMA_messages.strUsedSwap,
236                     fill: true
237                 }],
238                 nodes: [{ dataPoints: [{ type: 'memory', name: 'SwapTotal' }] },
239                     { dataPoints: [{ type: 'memory', name: 'SwapUsed' }] }
240                 ],
241                 maxYLabel: 0
242             }
243         });
244         break;
246     case 'Linux':
247         $.extend(presetCharts, {
248             'cpu': {
249                 title: PMA_messages.strSystemCPUUsage,
250                 series: [{
251                     label: PMA_messages.strAverageLoad
252                 }],
253                 nodes: [{ dataPoints: [{ type: 'cpu', name: 'irrelevant' }], transformFn: 'cpu-linux' }],
254                 maxYLabel: 0
255             },
256             'memory': {
257                 title: PMA_messages.strSystemMemory,
258                 series: [
259                     { label: PMA_messages.strBufferedMemory, fill: true },
260                     { label: PMA_messages.strUsedMemory, fill: true },
261                     { label: PMA_messages.strCachedMemory, fill: true },
262                     { label: PMA_messages.strFreeMemory, fill: true }
263                 ],
264                 nodes: [
265                     { dataPoints: [{ type: 'memory', name: 'Buffers' }], valueDivisor: 1024 },
266                     { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 },
267                     { dataPoints: [{ type: 'memory', name: 'Cached' }],  valueDivisor: 1024 },
268                     { dataPoints: [{ type: 'memory', name: 'MemFree' }], valueDivisor: 1024 }
269                 ],
270                 maxYLabel: 0
271             },
272             'swap': {
273                 title: PMA_messages.strSystemSwap,
274                 series: [
275                     { label: PMA_messages.strCachedSwap, fill: true },
276                     { label: PMA_messages.strUsedSwap, fill: true },
277                     { label: PMA_messages.strFreeSwap, fill: true }
278                 ],
279                 nodes: [
280                     { dataPoints: [{ type: 'memory', name: 'SwapCached' }], valueDivisor: 1024 },
281                     { dataPoints: [{ type: 'memory', name: 'SwapUsed' }], valueDivisor: 1024 },
282                     { dataPoints: [{ type: 'memory', name: 'SwapFree' }], valueDivisor: 1024 }
283                 ],
284                 maxYLabel: 0
285             }
286         });
287         break;
289     case 'SunOS':
290         $.extend(presetCharts, {
291             'cpu': {
292                 title: PMA_messages.strSystemCPUUsage,
293                 series: [{
294                     label: PMA_messages.strAverageLoad
295                 }],
296                 nodes: [{
297                     dataPoints: [{ type: 'cpu', name: 'loadavg' }]
298                 }],
299                 maxYLabel: 0
300             },
301             'memory': {
302                 title: PMA_messages.strSystemMemory,
303                 series: [
304                     { label: PMA_messages.strUsedMemory, fill: true },
305                     { label: PMA_messages.strFreeMemory, fill: true }
306                 ],
307                 nodes: [
308                     { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 },
309                     { dataPoints: [{ type: 'memory', name: 'MemFree' }], valueDivisor: 1024 }
310                 ],
311                 maxYLabel: 0
312             },
313             'swap': {
314                 title: PMA_messages.strSystemSwap,
315                 series: [
316                     { label: PMA_messages.strUsedSwap, fill: true },
317                     { label: PMA_messages.strFreeSwap, fill: true }
318                 ],
319                 nodes: [
320                     { dataPoints: [{ type: 'memory', name: 'SwapUsed' }], valueDivisor: 1024 },
321                     { dataPoints: [{ type: 'memory', name: 'SwapFree' }], valueDivisor: 1024 }
322                 ],
323                 maxYLabel: 0
324             }
325         });
326         break;
327     }
329     // Default setting for the chart grid
330     var defaultChartGrid = {
331         'c0': {
332             title: PMA_messages.strQuestions,
333             series: [
334                 { label: PMA_messages.strQuestions }
335             ],
336             nodes: [
337                 { dataPoints: [{ type: 'statusvar', name: 'Questions' }], display: 'differential' }
338             ],
339             maxYLabel: 0
340         },
341         'c1': {
342             title: PMA_messages.strChartConnectionsTitle,
343             series: [
344                 { label: PMA_messages.strConnections },
345                 { label: PMA_messages.strProcesses }
346             ],
347             nodes: [
348                 { dataPoints: [{ type: 'statusvar', name: 'Connections' }], display: 'differential' },
349                 { dataPoints: [{ type: 'proc', name: 'processes' }] }
350             ],
351             maxYLabel: 0
352         },
353         'c2': {
354             title: PMA_messages.strTraffic,
355             series: [
356                 { label: PMA_messages.strBytesSent },
357                 { label: PMA_messages.strBytesReceived }
358             ],
359             nodes: [
360                 { dataPoints: [{ type: 'statusvar', name: 'Bytes_sent' }], display: 'differential', valueDivisor: 1024 },
361                 { dataPoints: [{ type: 'statusvar', name: 'Bytes_received' }], display: 'differential', valueDivisor: 1024 }
362             ],
363             maxYLabel: 0
364         }
365     };
367     // Server is localhost => We can add cpu/memory/swap to the default chart
368     if (server_db_isLocal && typeof presetCharts.cpu !== 'undefined') {
369         defaultChartGrid.c3 = presetCharts.cpu;
370         defaultChartGrid.c4 = presetCharts.memory;
371         defaultChartGrid.c5 = presetCharts.swap;
372     }
374     $('a[href="#rearrangeCharts"], a[href="#endChartEditMode"]').click(function (event) {
375         event.preventDefault();
376         editMode = !editMode;
377         if ($(this).attr('href') === '#endChartEditMode') {
378             editMode = false;
379         }
381         $('a[href="#endChartEditMode"]').toggle(editMode);
383         if (editMode) {
384             // Close the settings popup
385             $('div.popupContent').hide().removeClass('openedPopup');
387             $('#chartGrid').sortableTable({
388                 ignoreRect: {
389                     top: 8,
390                     left: chartSize.width - 63,
391                     width: 54,
392                     height: 24
393                 }
394             });
395         } else {
396             $('#chartGrid').sortableTable('destroy');
397         }
398         saveMonitor(); // Save settings
399         return false;
400     });
402     // global settings
403     $('div.popupContent select[name="chartColumns"]').change(function () {
404         monitorSettings.columns = parseInt(this.value, 10);
406         calculateChartSize();
407         // Empty cells should keep their size so you can drop onto them
408         $('#chartGrid').find('tr td').css('width', chartSize.width + 'px');
409         $('#chartGrid').find('.monitorChart').css({
410             width: chartSize.width + 'px',
411             height: chartSize.height + 'px'
412         });
414         /* Reorder all charts that it fills all column cells */
415         var numColumns;
416         var $tr = $('#chartGrid').find('tr:first');
417         var row = 0;
419         var tempManageCols = function () {
420             if (numColumns > monitorSettings.columns) {
421                 if ($tr.next().length === 0) {
422                     $tr.after('<tr></tr>');
423                 }
424                 $tr.next().prepend($(this));
425             }
426             numColumns++;
427         };
429         var tempAddCol = function () {
430             if ($(this).next().length !== 0) {
431                 $(this).append($(this).next().find('td:first'));
432             }
433         };
435         while ($tr.length !== 0) {
436             numColumns = 1;
437             // To many cells in one row => put into next row
438             $tr.find('td').each(tempManageCols);
440             // To little cells in one row => for each cell to little,
441             // move all cells backwards by 1
442             if ($tr.next().length > 0) {
443                 var cnt = monitorSettings.columns - $tr.find('td').length;
444                 for (var i = 0; i < cnt; i++) {
445                     $tr.append($tr.next().find('td:first'));
446                     $tr.nextAll().each(tempAddCol);
447                 }
448             }
450             $tr = $tr.next();
451             row++;
452         }
454         if (monitorSettings.gridMaxPoints === 'auto') {
455             runtime.gridMaxPoints = Math.round((chartSize.width - 40) / 12);
456         }
458         runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
459         runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh;
461         if (editMode) {
462             $('#chartGrid').sortableTable('refresh');
463         }
465         refreshChartGrid();
466         saveMonitor(); // Save settings
467     });
469     $('div.popupContent select[name="gridChartRefresh"]').change(function () {
470         monitorSettings.gridRefresh = parseInt(this.value, 10) * 1000;
471         clearTimeout(runtime.refreshTimeout);
473         if (runtime.refreshRequest) {
474             runtime.refreshRequest.abort();
475         }
477         runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
478         // fixing chart shift towards left on refresh rate change
479         // runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh;
480         runtime.refreshTimeout = setTimeout(refreshChartGrid, monitorSettings.gridRefresh);
482         saveMonitor(); // Save settings
483     });
485     $('a[href="#addNewChart"]').click(function (event) {
486         event.preventDefault();
487         var dlgButtons = { };
489         dlgButtons[PMA_messages.strAddChart] = function () {
490             var type = $('input[name="chartType"]:checked').val();
492             if (type === 'preset') {
493                 newChart = presetCharts[$('#addChartDialog').find('select[name="presetCharts"]').prop('value')];
494             } else {
495                 // If user builds his own chart, it's being set/updated
496                 // each time he adds a series
497                 // So here we only warn if he didn't add a series yet
498                 if (! newChart || ! newChart.nodes || newChart.nodes.length === 0) {
499                     alert(PMA_messages.strAddOneSeriesWarning);
500                     return;
501                 }
502             }
504             newChart.title = $('input[name="chartTitle"]').val();
505             // Add a cloned object to the chart grid
506             addChart($.extend(true, {}, newChart));
508             newChart = null;
510             saveMonitor(); // Save settings
512             $(this).dialog('close');
513         };
515         dlgButtons[PMA_messages.strClose] = function () {
516             newChart = null;
517             $('span#clearSeriesLink').hide();
518             $('#seriesPreview').html('');
519             $(this).dialog('close');
520         };
522         var $presetList = $('#addChartDialog').find('select[name="presetCharts"]');
523         if ($presetList.html().length === 0) {
524             $.each(presetCharts, function (key, value) {
525                 $presetList.append('<option value="' + key + '">' + value.title + '</option>');
526             });
527             $presetList.change(function () {
528                 $('input[name="chartTitle"]').val(
529                     $presetList.find(':selected').text()
530                 );
531                 $('#chartPreset').prop('checked', true);
532             });
533             $('#chartPreset').click(function () {
534                 $('input[name="chartTitle"]').val(
535                     $presetList.find(':selected').text()
536                 );
537             });
538             $('#chartStatusVar').click(function () {
539                 $('input[name="chartTitle"]').val(
540                     $('#chartSeries').find(':selected').text().replace(/_/g, ' ')
541                 );
542             });
543             $('#chartSeries').change(function () {
544                 $('input[name="chartTitle"]').val(
545                     $('#chartSeries').find(':selected').text().replace(/_/g, ' ')
546                 );
547             });
548         }
550         $('#addChartDialog').dialog({
551             width: 'auto',
552             height: 'auto',
553             buttons: dlgButtons
554         });
556         $('#seriesPreview').html('<i>' + PMA_messages.strNone + '</i>');
558         return false;
559     });
561     $('a[href="#exportMonitorConfig"]').click(function (event) {
562         event.preventDefault();
563         var gridCopy = {};
564         $.each(runtime.charts, function (key, elem) {
565             gridCopy[key] = {};
566             gridCopy[key].nodes = elem.nodes;
567             gridCopy[key].settings = elem.settings;
568             gridCopy[key].title = elem.title;
569         });
570         var exportData = {
571             monitorCharts: gridCopy,
572             monitorSettings: monitorSettings
573         };
575         var blob = new Blob([JSON.stringify(exportData)], { type: 'application/octet-stream' });
576         var url = window.URL.createObjectURL(blob);
577         window.location.href = url;
578         window.URL.revokeObjectURL(url);
579     });
581     $('a[href="#importMonitorConfig"]').click(function (event) {
582         event.preventDefault();
583         $('#emptyDialog').dialog({ title: PMA_messages.strImportDialogTitle });
584         $('#emptyDialog').html(PMA_messages.strImportDialogMessage + ':<br/><form>' +
585             '<input type="file" name="file" id="import_file"> </form>');
587         var dlgBtns = {};
589         dlgBtns[PMA_messages.strImport] = function () {
590             var input = $('#emptyDialog').find('#import_file')[0];
591             var reader = new FileReader();
593             reader.onerror = function (event) {
594                 alert(PMA_messages.strFailedParsingConfig + '\n' + event.target.error.code);
595             };
596             reader.onload = function (e) {
597                 var data = e.target.result;
599                 // Try loading config
600                 try {
601                     json = JSON.parse(data);
602                 } catch (err) {
603                     alert(PMA_messages.strFailedParsingConfig);
604                     $('#emptyDialog').dialog('close');
605                     return;
606                 }
608                 // Basic check, is this a monitor config json?
609                 if (!json || ! json.monitorCharts || ! json.monitorCharts) {
610                     alert(PMA_messages.strFailedParsingConfig);
611                     $('#emptyDialog').dialog('close');
612                     return;
613                 }
615                 // If json ok, try applying config
616                 try {
617                     window.localStorage.monitorCharts = JSON.stringify(json.monitorCharts);
618                     window.localStorage.monitorSettings = JSON.stringify(json.monitorSettings);
619                     rebuildGrid();
620                 } catch (err) {
621                     console.log(err);
622                     alert(PMA_messages.strFailedBuildingGrid);
623                     // If an exception is thrown, load default again
624                     if (isStorageSupported('localStorage')) {
625                         window.localStorage.removeItem('monitorCharts');
626                         window.localStorage.removeItem('monitorSettings');
627                     }
628                     rebuildGrid();
629                 }
631                 $('#emptyDialog').dialog('close');
632             };
633             reader.readAsText(input.files[0]);
634         };
636         dlgBtns[PMA_messages.strCancel] = function () {
637             $(this).dialog('close');
638         };
640         $('#emptyDialog').dialog({
641             width: 'auto',
642             height: 'auto',
643             buttons: dlgBtns
644         });
645     });
647     $('a[href="#clearMonitorConfig"]').click(function (event) {
648         event.preventDefault();
649         if (isStorageSupported('localStorage')) {
650             window.localStorage.removeItem('monitorCharts');
651             window.localStorage.removeItem('monitorSettings');
652             window.localStorage.removeItem('monitorVersion');
653         }
654         $(this).hide();
655         rebuildGrid();
656     });
658     $('a[href="#pauseCharts"]').click(function (event) {
659         event.preventDefault();
660         runtime.redrawCharts = ! runtime.redrawCharts;
661         if (! runtime.redrawCharts) {
662             $(this).html(PMA_getImage('play') + PMA_messages.strResumeMonitor);
663         } else {
664             $(this).html(PMA_getImage('pause') + PMA_messages.strPauseMonitor);
665             if (! runtime.charts) {
666                 initGrid();
667                 $('a[href="#settingsPopup"]').show();
668             }
669         }
670         return false;
671     });
673     $('a[href="#monitorInstructionsDialog"]').click(function (event) {
674         event.preventDefault();
676         var $dialog = $('#monitorInstructionsDialog');
678         $dialog.dialog({
679             width: 595,
680             height: 'auto'
681         }).find('img.ajaxIcon').show();
683         var loadLogVars = function (getvars) {
684             var vars = { ajax_request: true, logging_vars: true };
685             if (getvars) {
686                 $.extend(vars, getvars);
687             }
689             $.post('server_status_monitor.php' + PMA_commonParams.get('common_query'), vars,
690                 function (data) {
691                     var logVars;
692                     if (typeof data !== 'undefined' && data.success === true) {
693                         logVars = data.message;
694                     } else {
695                         return serverResponseError();
696                     }
697                     var icon = PMA_getImage('s_success');
698                     var msg = '';
699                     var str = '';
701                     if (logVars.general_log === 'ON') {
702                         if (logVars.slow_query_log === 'ON') {
703                             msg = PMA_messages.strBothLogOn;
704                         } else {
705                             msg = PMA_messages.strGenLogOn;
706                         }
707                     }
709                     if (msg.length === 0 && logVars.slow_query_log === 'ON') {
710                         msg = PMA_messages.strSlowLogOn;
711                     }
713                     if (msg.length === 0) {
714                         icon = PMA_getImage('s_error');
715                         msg = PMA_messages.strBothLogOff;
716                     }
718                     str = '<b>' + PMA_messages.strCurrentSettings + '</b><br/><div class="smallIndent">';
719                     str += icon + msg + '<br />';
721                     if (logVars.log_output !== 'TABLE') {
722                         str += PMA_getImage('s_error') + ' ' + PMA_messages.strLogOutNotTable + '<br />';
723                     } else {
724                         str += PMA_getImage('s_success') + ' ' + PMA_messages.strLogOutIsTable + '<br />';
725                     }
727                     if (logVars.slow_query_log === 'ON') {
728                         if (logVars.long_query_time > 2) {
729                             str += PMA_getImage('s_attention') + ' ';
730                             str += PMA_sprintf(PMA_messages.strSmallerLongQueryTimeAdvice, logVars.long_query_time);
731                             str += '<br />';
732                         }
734                         if (logVars.long_query_time < 2) {
735                             str += PMA_getImage('s_success') + ' ';
736                             str += PMA_sprintf(PMA_messages.strLongQueryTimeSet, logVars.long_query_time);
737                             str += '<br />';
738                         }
739                     }
741                     str += '</div>';
743                     if (is_superuser) {
744                         str += '<p></p><b>' + PMA_messages.strChangeSettings + '</b>';
745                         str += '<div class="smallIndent">';
746                         str += PMA_messages.strSettingsAppliedGlobal + '<br/>';
748                         var varValue = 'TABLE';
749                         if (logVars.log_output === 'TABLE') {
750                             varValue = 'FILE';
751                         }
753                         str += '- <a class="set" href="#log_output-' + varValue + '">';
754                         str += PMA_sprintf(PMA_messages.strSetLogOutput, varValue);
755                         str += ' </a><br />';
757                         if (logVars.general_log !== 'ON') {
758                             str += '- <a class="set" href="#general_log-ON">';
759                             str += PMA_sprintf(PMA_messages.strEnableVar, 'general_log');
760                             str += ' </a><br />';
761                         } else {
762                             str += '- <a class="set" href="#general_log-OFF">';
763                             str += PMA_sprintf(PMA_messages.strDisableVar, 'general_log');
764                             str += ' </a><br />';
765                         }
767                         if (logVars.slow_query_log !== 'ON') {
768                             str += '- <a class="set" href="#slow_query_log-ON">';
769                             str +=  PMA_sprintf(PMA_messages.strEnableVar, 'slow_query_log');
770                             str += ' </a><br />';
771                         } else {
772                             str += '- <a class="set" href="#slow_query_log-OFF">';
773                             str +=  PMA_sprintf(PMA_messages.strDisableVar, 'slow_query_log');
774                             str += ' </a><br />';
775                         }
777                         varValue = 5;
778                         if (logVars.long_query_time > 2) {
779                             varValue = 1;
780                         }
782                         str += '- <a class="set" href="#long_query_time-' + varValue + '">';
783                         str += PMA_sprintf(PMA_messages.setSetLongQueryTime, varValue);
784                         str += ' </a><br />';
785                     } else {
786                         str += PMA_messages.strNoSuperUser + '<br/>';
787                     }
789                     str += '</div>';
791                     $dialog.find('div.monitorUse').toggle(
792                         logVars.log_output === 'TABLE' && (logVars.slow_query_log === 'ON' || logVars.general_log === 'ON')
793                     );
795                     $dialog.find('div.ajaxContent').html(str);
796                     $dialog.find('img.ajaxIcon').hide();
797                     $dialog.find('a.set').click(function () {
798                         var nameValue = $(this).attr('href').split('-');
799                         loadLogVars({ varName: nameValue[0].substr(1), varValue: nameValue[1] });
800                         $dialog.find('img.ajaxIcon').show();
801                     });
802                 }
803             );
804         };
807         loadLogVars();
809         return false;
810     });
812     $('input[name="chartType"]').change(function () {
813         $('#chartVariableSettings').toggle(this.checked && this.value === 'variable');
814         var title = $('input[name="chartTitle"]').val();
815         if (title === PMA_messages.strChartTitle ||
816             title === $('label[for="' + $('input[name="chartTitle"]').data('lastRadio') + '"]').text()
817         ) {
818             $('input[name="chartTitle"]')
819                 .data('lastRadio', $(this).attr('id'))
820                 .val($('label[for="' + $(this).attr('id') + '"]').text());
821         }
822     });
824     $('input[name="useDivisor"]').change(function () {
825         $('span.divisorInput').toggle(this.checked);
826     });
828     $('input[name="useUnit"]').change(function () {
829         $('span.unitInput').toggle(this.checked);
830     });
832     $('select[name="varChartList"]').change(function () {
833         if (this.selectedIndex !== 0) {
834             $('#variableInput').val(this.value);
835         }
836     });
838     $('a[href="#kibDivisor"]').click(function (event) {
839         event.preventDefault();
840         $('input[name="valueDivisor"]').val(1024);
841         $('input[name="valueUnit"]').val(PMA_messages.strKiB);
842         $('span.unitInput').toggle(true);
843         $('input[name="useUnit"]').prop('checked', true);
844         return false;
845     });
847     $('a[href="#mibDivisor"]').click(function (event) {
848         event.preventDefault();
849         $('input[name="valueDivisor"]').val(1024 * 1024);
850         $('input[name="valueUnit"]').val(PMA_messages.strMiB);
851         $('span.unitInput').toggle(true);
852         $('input[name="useUnit"]').prop('checked', true);
853         return false;
854     });
856     $('a[href="#submitClearSeries"]').click(function (event) {
857         event.preventDefault();
858         $('#seriesPreview').html('<i>' + PMA_messages.strNone + '</i>');
859         newChart = null;
860         $('#clearSeriesLink').hide();
861     });
863     $('a[href="#submitAddSeries"]').click(function (event) {
864         event.preventDefault();
865         if ($('#variableInput').val() === '') {
866             return false;
867         }
869         if (newChart === null) {
870             $('#seriesPreview').html('');
872             newChart = {
873                 title: $('input[name="chartTitle"]').val(),
874                 nodes: [],
875                 series: [],
876                 maxYLabel: 0
877             };
878         }
880         var serie = {
881             dataPoints: [{ type: 'statusvar', name: $('#variableInput').val() }],
882             display: $('input[name="differentialValue"]').prop('checked') ? 'differential' : ''
883         };
885         if (serie.dataPoints[0].name === 'Processes') {
886             serie.dataPoints[0].type = 'proc';
887         }
889         if ($('input[name="useDivisor"]').prop('checked')) {
890             serie.valueDivisor = parseInt($('input[name="valueDivisor"]').val(), 10);
891         }
893         if ($('input[name="useUnit"]').prop('checked')) {
894             serie.unit = $('input[name="valueUnit"]').val();
895         }
897         var str = serie.display === 'differential' ? ', ' + PMA_messages.strDifferential : '';
898         str += serie.valueDivisor ? (', ' + PMA_sprintf(PMA_messages.strDividedBy, serie.valueDivisor)) : '';
899         str += serie.unit ? (', ' + PMA_messages.strUnit + ': ' + serie.unit) : '';
901         var newSeries = {
902             label: $('#variableInput').val().replace(/_/g, ' ')
903         };
904         newChart.series.push(newSeries);
905         $('#seriesPreview').append('- ' + escapeHtml(newSeries.label + str) + '<br/>');
906         newChart.nodes.push(serie);
907         $('#variableInput').val('');
908         $('input[name="differentialValue"]').prop('checked', true);
909         $('input[name="useDivisor"]').prop('checked', false);
910         $('input[name="useUnit"]').prop('checked', false);
911         $('input[name="useDivisor"]').trigger('change');
912         $('input[name="useUnit"]').trigger('change');
913         $('select[name="varChartList"]').get(0).selectedIndex = 0;
915         $('#clearSeriesLink').show();
917         return false;
918     });
920     $('#variableInput').autocomplete({
921         source: variableNames
922     });
924     /* Initializes the monitor, called only once */
925     function initGrid () {
926         var i;
928         /* Apply default values & config */
929         if (isStorageSupported('localStorage')) {
930             if (typeof window.localStorage.monitorCharts !== 'undefined') {
931                 runtime.charts = JSON.parse(window.localStorage.monitorCharts);
932             }
933             if (typeof window.localStorage.monitorSettings !== 'undefined') {
934                 monitorSettings = JSON.parse(window.localStorage.monitorSettings);
935             }
937             $('a[href="#clearMonitorConfig"]').toggle(runtime.charts !== null);
939             if (runtime.charts !== null
940                 && typeof window.localStorage.monitorVersion !== 'undefined'
941                 && monitorProtocolVersion !== window.localStorage.monitorVersion
942             ) {
943                 $('#emptyDialog').dialog({ title: PMA_messages.strIncompatibleMonitorConfig });
944                 $('#emptyDialog').html(PMA_messages.strIncompatibleMonitorConfigDescription);
946                 var dlgBtns = {};
947                 dlgBtns[PMA_messages.strClose] = function () {
948                     $(this).dialog('close');
949                 };
951                 $('#emptyDialog').dialog({
952                     width: 400,
953                     buttons: dlgBtns
954                 });
955             }
956         }
958         if (runtime.charts === null) {
959             runtime.charts = defaultChartGrid;
960         }
961         if (monitorSettings === null) {
962             monitorSettings = defaultMonitorSettings;
963         }
965         $('select[name="gridChartRefresh"]').val(monitorSettings.gridRefresh / 1000);
966         $('select[name="chartColumns"]').val(monitorSettings.columns);
968         if (monitorSettings.gridMaxPoints === 'auto') {
969             runtime.gridMaxPoints = Math.round((monitorSettings.chartSize.width - 40) / 12);
970         } else {
971             runtime.gridMaxPoints = monitorSettings.gridMaxPoints;
972         }
974         runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
975         runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh;
977         /* Calculate how much spacing there is between each chart */
978         $('#chartGrid').html('<tr><td></td><td></td></tr><tr><td></td><td></td></tr>');
979         chartSpacing = {
980             width: $('#chartGrid').find('td:nth-child(2)').offset().left -
981                 $('#chartGrid').find('td:nth-child(1)').offset().left,
982             height: $('#chartGrid').find('tr:nth-child(2) td:nth-child(2)').offset().top -
983                 $('#chartGrid').find('tr:nth-child(1) td:nth-child(1)').offset().top
984         };
985         $('#chartGrid').html('');
987         /* Add all charts - in correct order */
988         var keys = [];
989         $.each(runtime.charts, function (key, value) {
990             keys.push(key);
991         });
992         keys.sort();
993         for (i = 0; i < keys.length; i++) {
994             addChart(runtime.charts[keys[i]], true);
995         }
997         /* Fill in missing cells */
998         var numCharts = $('#chartGrid').find('.monitorChart').length;
999         var numMissingCells = (monitorSettings.columns - numCharts % monitorSettings.columns) % monitorSettings.columns;
1000         for (i = 0; i < numMissingCells; i++) {
1001             $('#chartGrid').find('tr:last').append('<td></td>');
1002         }
1004         // Empty cells should keep their size so you can drop onto them
1005         calculateChartSize();
1006         $('#chartGrid').find('tr td').css('width', chartSize.width + 'px');
1008         buildRequiredDataList();
1009         refreshChartGrid();
1010     }
1012     /* Calls destroyGrid() and initGrid(), but before doing so it saves the chart
1013      * data from each chart and restores it after the monitor is initialized again */
1014     function rebuildGrid () {
1015         var oldData = null;
1016         if (runtime.charts) {
1017             oldData = {};
1018             $.each(runtime.charts, function (key, chartObj) {
1019                 for (var i = 0, l = chartObj.nodes.length; i < l; i++) {
1020                     oldData[chartObj.nodes[i].dataPoint] = [];
1021                     for (var j = 0, ll = chartObj.chart.series[i].data.length; j < ll; j++) {
1022                         oldData[chartObj.nodes[i].dataPoint].push([chartObj.chart.series[i].data[j].x, chartObj.chart.series[i].data[j].y]);
1023                     }
1024                 }
1025             });
1026         }
1028         destroyGrid();
1029         initGrid();
1030     }
1032     /* Calculactes the dynamic chart size that depends on the column width */
1033     function calculateChartSize () {
1034         var panelWidth;
1035         if ($('body').height() > $(window).height()) { // has vertical scroll bar
1036             panelWidth = $('#logTable').innerWidth();
1037         } else {
1038             panelWidth = $('#logTable').innerWidth() - 10; // leave some space for vertical scroll bar
1039         }
1041         var wdt = panelWidth;
1042         var windowWidth = $(window).width();
1044         if (windowWidth > 768) {
1045             wdt = (panelWidth - monitorSettings.columns * chartSpacing.width) / monitorSettings.columns;
1046         }
1048         chartSize = {
1049             width: Math.floor(wdt),
1050             height: Math.floor(0.75 * wdt)
1051         };
1052     }
1054     /* Adds a chart to the chart grid */
1055     function addChart (chartObj, initialize) {
1056         var i;
1057         var settings = {
1058             title: escapeHtml(chartObj.title),
1059             grid: {
1060                 drawBorder: false,
1061                 shadow: false,
1062                 background: 'rgba(0,0,0,0)'
1063             },
1064             axes: {
1065                 xaxis: {
1066                     renderer: $.jqplot.DateAxisRenderer,
1067                     tickOptions: {
1068                         formatString: '%H:%M:%S',
1069                         showGridline: false
1070                     },
1071                     min: runtime.xmin,
1072                     max: runtime.xmax
1073                 },
1074                 yaxis: {
1075                     min: 0,
1076                     max: 100,
1077                     tickInterval: 20
1078                 }
1079             },
1080             seriesDefaults: {
1081                 rendererOptions: {
1082                     smooth: true
1083                 },
1084                 showLine: true,
1085                 lineWidth: 2,
1086                 markerOptions: {
1087                     size: 6
1088                 }
1089             },
1090             highlighter: {
1091                 show: true
1092             }
1093         };
1095         if (settings.title === PMA_messages.strSystemCPUUsage ||
1096             settings.title === PMA_messages.strQueryCacheEfficiency
1097         ) {
1098             settings.axes.yaxis.tickOptions = {
1099                 formatString: '%d %%'
1100             };
1101         } else if (settings.title === PMA_messages.strSystemMemory ||
1102             settings.title === PMA_messages.strSystemSwap
1103         ) {
1104             settings.stackSeries = true;
1105             settings.axes.yaxis.tickOptions = {
1106                 formatter: $.jqplot.byteFormatter(2) // MiB
1107             };
1108         } else if (settings.title === PMA_messages.strTraffic) {
1109             settings.axes.yaxis.tickOptions = {
1110                 formatter: $.jqplot.byteFormatter(1) // KiB
1111             };
1112         } else if (settings.title === PMA_messages.strQuestions ||
1113             settings.title === PMA_messages.strConnections
1114         ) {
1115             settings.axes.yaxis.tickOptions = {
1116                 formatter: function (format, val) {
1117                     if (Math.abs(val) >= 1000000) {
1118                         return $.jqplot.sprintf('%.3g M', val / 1000000);
1119                     } else if (Math.abs(val) >= 1000) {
1120                         return $.jqplot.sprintf('%.3g k', val / 1000);
1121                     } else {
1122                         return $.jqplot.sprintf('%d', val);
1123                     }
1124                 }
1125             };
1126         }
1128         settings.series = chartObj.series;
1130         if ($('#' + 'gridchart' + runtime.chartAI).length === 0) {
1131             var numCharts = $('#chartGrid').find('.monitorChart').length;
1133             if (numCharts === 0 || (numCharts % monitorSettings.columns === 0)) {
1134                 $('#chartGrid').append('<tr></tr>');
1135             }
1137             if (!chartSize) {
1138                 calculateChartSize();
1139             }
1140             $('#chartGrid').find('tr:last').append(
1141                 '<td><div id="gridChartContainer' + runtime.chartAI + '" class="">' +
1142                 '<div class="ui-state-default monitorChart"' +
1143                 ' id="gridchart' + runtime.chartAI + '"' +
1144                 ' style="width:' + chartSize.width + 'px; height:' + chartSize.height + 'px;"></div>' +
1145                 '</div></td>'
1146             );
1147         }
1149         // Set series' data as [0,0], smooth lines won't plot with data array having null values.
1150         // also chart won't plot initially with no data and data comes on refreshChartGrid()
1151         var series = [];
1152         for (i in chartObj.series) {
1153             series.push([[0, 0]]);
1154         }
1156         var tempTooltipContentEditor = function (str, seriesIndex, pointIndex, plot) {
1157             var j;
1158             // TODO: move style to theme CSS
1159             var tooltipHtml = '<div style="font-size:12px;background-color:#FFFFFF;' +
1160                 'opacity:0.95;filter:alpha(opacity=95);padding:5px;">';
1161             // x value i.e. time
1162             var timeValue = str.split(',')[0];
1163             var seriesValue;
1164             tooltipHtml += 'Time: ' + timeValue;
1165             tooltipHtml += '<span style="font-weight:bold;">';
1166             // Add y values to the tooltip per series
1167             for (j in plot.series) {
1168                 // get y value if present
1169                 if (plot.series[j].data.length > pointIndex) {
1170                     seriesValue = plot.series[j].data[pointIndex][1];
1171                 } else {
1172                     return;
1173                 }
1174                 var seriesLabel = plot.series[j].label;
1175                 var seriesColor = plot.series[j].color;
1176                 // format y value
1177                 if (plot.series[0]._yaxis.tickOptions.formatter) {
1178                     // using formatter function
1179                     seriesValue = plot.series[0]._yaxis.tickOptions.formatter('%s', seriesValue);
1180                 } else if (plot.series[0]._yaxis.tickOptions.formatString) {
1181                     // using format string
1182                     seriesValue = PMA_sprintf(plot.series[0]._yaxis.tickOptions.formatString, seriesValue);
1183                 }
1184                 tooltipHtml += '<br /><span style="color:' + seriesColor + '">' +
1185                     seriesLabel + ': ' + seriesValue + '</span>';
1186             }
1187             tooltipHtml += '</span></div>';
1188             return tooltipHtml;
1189         };
1191         // set Tooltip for each series
1192         for (i in settings.series) {
1193             settings.series[i].highlighter = {
1194                 show: true,
1195                 tooltipContentEditor: tempTooltipContentEditor
1196             };
1197         }
1199         chartObj.chart = $.jqplot('gridchart' + runtime.chartAI, series, settings);
1200         // remove [0,0] after plotting
1201         for (i in chartObj.chart.series) {
1202             chartObj.chart.series[i].data.shift();
1203         }
1205         var $legend = $('<div />').css('padding', '0.5em');
1206         for (i in chartObj.chart.series) {
1207             $legend.append(
1208                 $('<div />').append(
1209                     $('<div>').css({
1210                         width: '1em',
1211                         height: '1em',
1212                         background: chartObj.chart.seriesColors[i]
1213                     }).addClass('floatleft')
1214                 ).append(
1215                     $('<div>').text(
1216                         chartObj.chart.series[i].label
1217                     ).addClass('floatleft')
1218                 ).append(
1219                     $('<div class="clearfloat">')
1220                 ).addClass('floatleft')
1221             );
1222         }
1223         $('#gridchart' + runtime.chartAI)
1224             .parent()
1225             .append($legend);
1227         if (initialize !== true) {
1228             runtime.charts['c' + runtime.chartAI] = chartObj;
1229             buildRequiredDataList();
1230         }
1232         // time span selection
1233         $('#gridchart' + runtime.chartAI).on('jqplotMouseDown', function (ev, gridpos, datapos, neighbor, plot) {
1234             drawTimeSpan = true;
1235             selectionTimeDiff.push(datapos.xaxis);
1236             if ($('#selection_box').length) {
1237                 $('#selection_box').remove();
1238             }
1239             var selectionBox = $('<div id="selection_box" style="z-index:1000;height:205px;position:absolute;background-color:#87CEEB;opacity:0.4;filter:alpha(opacity=40);pointer-events:none;">');
1240             $(document.body).append(selectionBox);
1241             selectionStartX = ev.pageX;
1242             selectionStartY = ev.pageY;
1243             selectionBox
1244                 .attr({ id: 'selection_box' })
1245                 .css({
1246                     top: selectionStartY - gridpos.y,
1247                     left: selectionStartX
1248                 })
1249                 .fadeIn();
1250         });
1252         $('#gridchart' + runtime.chartAI).on('jqplotMouseUp', function (ev, gridpos, datapos, neighbor, plot) {
1253             if (! drawTimeSpan || editMode) {
1254                 return;
1255             }
1257             selectionTimeDiff.push(datapos.xaxis);
1259             if (selectionTimeDiff[1] <= selectionTimeDiff[0]) {
1260                 selectionTimeDiff = [];
1261                 return;
1262             }
1263             // get date from timestamp
1264             var min = new Date(Math.ceil(selectionTimeDiff[0]));
1265             var max = new Date(Math.ceil(selectionTimeDiff[1]));
1266             PMA_getLogAnalyseDialog(min, max);
1267             selectionTimeDiff = [];
1268             drawTimeSpan = false;
1269         });
1271         $('#gridchart' + runtime.chartAI).on('jqplotMouseMove', function (ev, gridpos, datapos, neighbor, plot) {
1272             if (! drawTimeSpan || editMode) {
1273                 return;
1274             }
1275             if (selectionStartX !== undefined) {
1276                 $('#selection_box')
1277                     .css({
1278                         width: Math.ceil(ev.pageX - selectionStartX)
1279                     })
1280                     .fadeIn();
1281             }
1282         });
1284         $('#gridchart' + runtime.chartAI).on('jqplotMouseLeave', function (ev, gridpos, datapos, neighbor, plot) {
1285             drawTimeSpan = false;
1286         });
1288         $(document.body).mouseup(function () {
1289             if ($('#selection_box').length) {
1290                 $('#selection_box').remove();
1291             }
1292         });
1294         // Edit, Print icon only in edit mode
1295         $('#chartGrid').find('div svg').find('*[zIndex=20], *[zIndex=21], *[zIndex=19]').toggle(editMode);
1297         runtime.chartAI++;
1298     }
1300     function PMA_getLogAnalyseDialog (min, max) {
1301         var $logAnalyseDialog = $('#logAnalyseDialog');
1302         var $dateStart = $logAnalyseDialog.find('input[name="dateStart"]');
1303         var $dateEnd = $logAnalyseDialog.find('input[name="dateEnd"]');
1304         $dateStart.prop('readonly', true);
1305         $dateEnd.prop('readonly', true);
1307         var dlgBtns = { };
1309         dlgBtns[PMA_messages.strFromSlowLog] = function () {
1310             loadLog('slow', min, max);
1311             $(this).dialog('close');
1312         };
1314         dlgBtns[PMA_messages.strFromGeneralLog] = function () {
1315             loadLog('general', min, max);
1316             $(this).dialog('close');
1317         };
1319         $logAnalyseDialog.dialog({
1320             width: 'auto',
1321             height: 'auto',
1322             buttons: dlgBtns
1323         });
1325         PMA_addDatepicker($dateStart, 'datetime', {
1326             showMillisec: false,
1327             showMicrosec: false,
1328             timeFormat: 'HH:mm:ss'
1329         });
1330         PMA_addDatepicker($dateEnd, 'datetime', {
1331             showMillisec: false,
1332             showMicrosec: false,
1333             timeFormat: 'HH:mm:ss'
1334         });
1335         $dateStart.datepicker('setDate', min);
1336         $dateEnd.datepicker('setDate', max);
1337     }
1339     function loadLog (type, min, max) {
1340         var dateStart = Date.parse($('#logAnalyseDialog').find('input[name="dateStart"]').datepicker('getDate')) || min;
1341         var dateEnd = Date.parse($('#logAnalyseDialog').find('input[name="dateEnd"]').datepicker('getDate')) || max;
1343         loadLogStatistics({
1344             src: type,
1345             start: dateStart,
1346             end: dateEnd,
1347             removeVariables: $('#removeVariables').prop('checked'),
1348             limitTypes: $('#limitTypes').prop('checked')
1349         });
1350     }
1352     /* Called in regular intervals, this function updates the values of each chart in the grid */
1353     function refreshChartGrid () {
1354         /* Send to server */
1355         runtime.refreshRequest = $.post('server_status_monitor.php' + PMA_commonParams.get('common_query'), {
1356             ajax_request: true,
1357             chart_data: 1,
1358             type: 'chartgrid',
1359             requiredData: JSON.stringify(runtime.dataList),
1360             server: PMA_commonParams.get('server')
1361         }, function (data) {
1362             var chartData;
1363             if (typeof data !== 'undefined' && data.success === true) {
1364                 chartData = data.message;
1365             } else {
1366                 return serverResponseError();
1367             }
1368             var value;
1369             var i = 0;
1370             var diff;
1371             var total;
1373             /* Update values in each graph */
1374             $.each(runtime.charts, function (orderKey, elem) {
1375                 var key = elem.chartID;
1376                 // If newly added chart, we have no data for it yet
1377                 if (! chartData[key]) {
1378                     return;
1379                 }
1380                 // Draw all series
1381                 total = 0;
1382                 for (var j = 0; j < elem.nodes.length; j++) {
1383                     // Update x-axis
1384                     if (i === 0 && j === 0) {
1385                         if (oldChartData === null) {
1386                             diff = chartData.x - runtime.xmax;
1387                         } else {
1388                             diff = parseInt(chartData.x - oldChartData.x, 10);
1389                         }
1391                         runtime.xmin += diff;
1392                         runtime.xmax += diff;
1393                     }
1395                     // elem.chart.xAxis[0].setExtremes(runtime.xmin, runtime.xmax, false);
1396                     /* Calculate y value */
1398                     // If transform function given, use it
1399                     if (elem.nodes[j].transformFn) {
1400                         value = chartValueTransform(
1401                             elem.nodes[j].transformFn,
1402                             chartData[key][j],
1403                             // Check if first iteration (oldChartData==null), or if newly added chart oldChartData[key]==null
1404                             (
1405                                 oldChartData === null ||
1406                                 oldChartData[key] === null ||
1407                                 oldChartData[key] === undefined ? null : oldChartData[key][j]
1408                             )
1409                         );
1411                     // Otherwise use original value and apply differential and divisor if given,
1412                     // in this case we have only one data point per series - located at chartData[key][j][0]
1413                     } else {
1414                         value = parseFloat(chartData[key][j][0].value);
1416                         if (elem.nodes[j].display === 'differential') {
1417                             if (oldChartData === null ||
1418                                 oldChartData[key] === null ||
1419                                 oldChartData[key] === undefined
1420                             ) {
1421                                 continue;
1422                             }
1423                             value -= oldChartData[key][j][0].value;
1424                         }
1426                         if (elem.nodes[j].valueDivisor) {
1427                             value = value / elem.nodes[j].valueDivisor;
1428                         }
1429                     }
1431                     // Set y value, if defined
1432                     if (value !== undefined) {
1433                         elem.chart.series[j].data.push([chartData.x, value]);
1434                         if (value > elem.maxYLabel) {
1435                             elem.maxYLabel = value;
1436                         } else if (elem.maxYLabel === 0) {
1437                             elem.maxYLabel = 0.5;
1438                         }
1439                         // free old data point values and update maxYLabel
1440                         if (elem.chart.series[j].data.length > runtime.gridMaxPoints &&
1441                             elem.chart.series[j].data[0][0] < runtime.xmin
1442                         ) {
1443                             // check if the next freeable point is highest
1444                             if (elem.maxYLabel <= elem.chart.series[j].data[0][1]) {
1445                                 elem.chart.series[j].data.splice(0, elem.chart.series[j].data.length - runtime.gridMaxPoints);
1446                                 elem.maxYLabel = getMaxYLabel(elem.chart.series[j].data);
1447                             } else {
1448                                 elem.chart.series[j].data.splice(0, elem.chart.series[j].data.length - runtime.gridMaxPoints);
1449                             }
1450                         }
1451                         if (elem.title === PMA_messages.strSystemMemory ||
1452                             elem.title === PMA_messages.strSystemSwap
1453                         ) {
1454                             total += value;
1455                         }
1456                     }
1457                 }
1459                 // update chart options
1460                 // keep ticks number/positioning consistent while refreshrate changes
1461                 var tickInterval = (runtime.xmax - runtime.xmin) / 5;
1462                 elem.chart.axes.xaxis.ticks = [(runtime.xmax - tickInterval * 4),
1463                     (runtime.xmax - tickInterval * 3), (runtime.xmax - tickInterval * 2),
1464                     (runtime.xmax - tickInterval), runtime.xmax];
1466                 if (elem.title !== PMA_messages.strSystemCPUUsage &&
1467                     elem.title !== PMA_messages.strQueryCacheEfficiency &&
1468                     elem.title !== PMA_messages.strSystemMemory &&
1469                     elem.title !== PMA_messages.strSystemSwap
1470                 ) {
1471                     elem.chart.axes.yaxis.max = Math.ceil(elem.maxYLabel * 1.1);
1472                     elem.chart.axes.yaxis.tickInterval = Math.ceil(elem.maxYLabel * 1.1 / 5);
1473                 } else if (elem.title === PMA_messages.strSystemMemory ||
1474                     elem.title === PMA_messages.strSystemSwap
1475                 ) {
1476                     elem.chart.axes.yaxis.max = Math.ceil(total * 1.1 / 100) * 100;
1477                     elem.chart.axes.yaxis.tickInterval = Math.ceil(total * 1.1 / 5);
1478                 }
1479                 i++;
1481                 if (runtime.redrawCharts) {
1482                     elem.chart.replot();
1483                 }
1484             });
1486             oldChartData = chartData;
1488             runtime.refreshTimeout = setTimeout(refreshChartGrid, monitorSettings.gridRefresh);
1489         });
1490     }
1492     /* Function to get highest plotted point's y label, to scale the chart,
1493      * TODO: make jqplot's autoscale:true work here
1494      */
1495     function getMaxYLabel (dataValues) {
1496         var maxY = dataValues[0][1];
1497         $.each(dataValues, function (k, v) {
1498             maxY = (v[1] > maxY) ? v[1] : maxY;
1499         });
1500         return maxY;
1501     }
1503     /* Function that supplies special value transform functions for chart values */
1504     function chartValueTransform (name, cur, prev) {
1505         switch (name) {
1506         case 'cpu-linux':
1507             if (prev === null) {
1508                 return undefined;
1509             }
1510             // cur and prev are datapoint arrays, but containing
1511             // only 1 element for cpu-linux
1512             cur = cur[0];
1513             prev = prev[0];
1515             var diff_total = cur.busy + cur.idle - (prev.busy + prev.idle);
1516             var diff_idle = cur.idle - prev.idle;
1517             return 100 * (diff_total - diff_idle) / diff_total;
1519         // Query cache efficiency (%)
1520         case 'qce':
1521             if (prev === null) {
1522                 return undefined;
1523             }
1524             // cur[0].value is Qcache_hits, cur[1].value is Com_select
1525             var diffQHits = cur[0].value - prev[0].value;
1526             // No NaN please :-)
1527             if (cur[1].value - prev[1].value === 0) {
1528                 return 0;
1529             }
1531             return diffQHits / (cur[1].value - prev[1].value + diffQHits) * 100;
1533         // Query cache usage (%)
1534         case 'qcu':
1535             if (cur[1].value === 0) {
1536                 return 0;
1537             }
1538             // cur[0].value is Qcache_free_memory, cur[1].value is query_cache_size
1539             return 100 - cur[0].value / cur[1].value * 100;
1540         }
1541         return undefined;
1542     }
1544     /* Build list of nodes that need to be retrieved from server.
1545      * It creates something like a stripped down version of the runtime.charts object.
1546      */
1547     function buildRequiredDataList () {
1548         runtime.dataList = {};
1549         // Store an own id, because the property name is subject of reordering,
1550         // thus destroying our mapping with runtime.charts <=> runtime.dataList
1551         var chartID = 0;
1552         $.each(runtime.charts, function (key, chart) {
1553             runtime.dataList[chartID] = [];
1554             for (var i = 0, l = chart.nodes.length; i < l; i++) {
1555                 runtime.dataList[chartID][i] = chart.nodes[i].dataPoints;
1556             }
1557             runtime.charts[key].chartID = chartID;
1558             chartID++;
1559         });
1560     }
1562     /* Loads the log table data, generates the table and handles the filters */
1563     function loadLogStatistics (opts) {
1564         var tableStr = '';
1565         var logRequest = null;
1567         if (! opts.removeVariables) {
1568             opts.removeVariables = false;
1569         }
1570         if (! opts.limitTypes) {
1571             opts.limitTypes = false;
1572         }
1574         $('#emptyDialog').dialog({ title: PMA_messages.strAnalysingLogsTitle });
1575         $('#emptyDialog').html(PMA_messages.strAnalysingLogs +
1576                                 ' <img class="ajaxIcon" src="' + pmaThemeImage +
1577                                 'ajax_clock_small.gif" alt="">');
1578         var dlgBtns = {};
1580         dlgBtns[PMA_messages.strCancelRequest] = function () {
1581             if (logRequest !== null) {
1582                 logRequest.abort();
1583             }
1585             $(this).dialog('close');
1586         };
1588         $('#emptyDialog').dialog({
1589             width: 'auto',
1590             height: 'auto',
1591             buttons: dlgBtns
1592         });
1594         logRequest = $.post(
1595             'server_status_monitor.php' + PMA_commonParams.get('common_query'),
1596             {
1597                 ajax_request: true,
1598                 log_data: 1,
1599                 type: opts.src,
1600                 time_start: Math.round(opts.start / 1000),
1601                 time_end: Math.round(opts.end / 1000),
1602                 removeVariables: opts.removeVariables,
1603                 limitTypes: opts.limitTypes
1604             },
1605             function (data) {
1606                 var logData;
1607                 var dlgBtns = {};
1608                 if (typeof data !== 'undefined' && data.success === true) {
1609                     logData = data.message;
1610                 } else {
1611                     return serverResponseError();
1612                 }
1614                 if (logData.rows.length === 0) {
1615                     $('#emptyDialog').dialog({ title: PMA_messages.strNoDataFoundTitle });
1616                     $('#emptyDialog').html('<p>' + PMA_messages.strNoDataFound + '</p>');
1618                     dlgBtns[PMA_messages.strClose] = function () {
1619                         $(this).dialog('close');
1620                     };
1622                     $('#emptyDialog').dialog('option', 'buttons', dlgBtns);
1623                     return;
1624                 }
1626                 runtime.logDataCols = buildLogTable(logData, opts.removeVariables);
1628                 /* Show some stats in the dialog */
1629                 $('#emptyDialog').dialog({ title: PMA_messages.strLoadingLogs });
1630                 $('#emptyDialog').html('<p>' + PMA_messages.strLogDataLoaded + '</p>');
1631                 $.each(logData.sum, function (key, value) {
1632                     key = key.charAt(0).toUpperCase() + key.slice(1).toLowerCase();
1633                     if (key === 'Total') {
1634                         key = '<b>' + key + '</b>';
1635                     }
1636                     $('#emptyDialog').append(key + ': ' + value + '<br/>');
1637                 });
1639                 /* Add filter options if more than a bunch of rows there to filter */
1640                 if (logData.numRows > 12) {
1641                     $('#logTable').prepend(
1642                         '<fieldset id="logDataFilter">' +
1643                         '    <legend>' + PMA_messages.strFiltersForLogTable + '</legend>' +
1644                         '    <div class="formelement">' +
1645                         '        <label for="filterQueryText">' + PMA_messages.strFilterByWordRegexp + '</label>' +
1646                         '        <input name="filterQueryText" type="text" id="filterQueryText" style="vertical-align: baseline;" />' +
1647                         '    </div>' +
1648                         ((logData.numRows > 250) ? ' <div class="formelement"><button name="startFilterQueryText" id="startFilterQueryText">' + PMA_messages.strFilter + '</button></div>' : '') +
1649                         '    <div class="formelement">' +
1650                         '       <input type="checkbox" id="noWHEREData" name="noWHEREData" value="1" /> ' +
1651                         '       <label for="noWHEREData"> ' + PMA_messages.strIgnoreWhereAndGroup + '</label>' +
1652                         '   </div' +
1653                         '</fieldset>'
1654                     );
1656                     $('#noWHEREData').change(function () {
1657                         filterQueries(true);
1658                     });
1660                     if (logData.numRows > 250) {
1661                         $('#startFilterQueryText').click(filterQueries);
1662                     } else {
1663                         $('#filterQueryText').keyup(filterQueries);
1664                     }
1665                 }
1667                 dlgBtns[PMA_messages.strJumpToTable] = function () {
1668                     $(this).dialog('close');
1669                     $(document).scrollTop($('#logTable').offset().top);
1670                 };
1672                 $('#emptyDialog').dialog('option', 'buttons', dlgBtns);
1673             }
1674         );
1676         /* Handles the actions performed when the user uses any of the
1677          * log table filters which are the filter by name and grouping
1678          * with ignoring data in WHERE clauses
1679          *
1680          * @param boolean Should be true when the users enabled or disabled
1681          *                to group queries ignoring data in WHERE clauses
1682         */
1683         function filterQueries (varFilterChange) {
1684             var cell;
1685             var textFilter;
1686             var val = $('#filterQueryText').val();
1688             if (val.length === 0) {
1689                 textFilter = null;
1690             } else {
1691                 try {
1692                     textFilter = new RegExp(val, 'i');
1693                     $('#filterQueryText').removeClass('error');
1694                 } catch (e) {
1695                     if (e instanceof SyntaxError) {
1696                         $('#filterQueryText').addClass('error');
1697                         textFilter = null;
1698                     }
1699                 }
1700             }
1702             var rowSum = 0;
1703             var totalSum = 0;
1704             var i = 0;
1705             var q;
1706             var noVars = $('#noWHEREData').prop('checked');
1707             var equalsFilter = /([^=]+)=(\d+|((\'|"|).*?[^\\])\4((\s+)|$))/gi;
1708             var functionFilter = /([a-z0-9_]+)\(.+?\)/gi;
1709             var filteredQueries = {};
1710             var filteredQueriesLines = {};
1711             var hide = false;
1712             var rowData;
1713             var queryColumnName = runtime.logDataCols[runtime.logDataCols.length - 2];
1714             var sumColumnName = runtime.logDataCols[runtime.logDataCols.length - 1];
1715             var isSlowLog = opts.src === 'slow';
1716             var columnSums = {};
1718             // For the slow log we have to count many columns (query_time, lock_time, rows_examined, rows_sent, etc.)
1719             var countRow = function (query, row) {
1720                 var cells = row.match(/<td>(.*?)<\/td>/gi);
1721                 if (!columnSums[query]) {
1722                     columnSums[query] = [0, 0, 0, 0];
1723                 }
1725                 // lock_time and query_time and displayed in timespan format
1726                 columnSums[query][0] += timeToSec(cells[2].replace(/(<td>|<\/td>)/gi, ''));
1727                 columnSums[query][1] += timeToSec(cells[3].replace(/(<td>|<\/td>)/gi, ''));
1728                 // rows_examind and rows_sent are just numbers
1729                 columnSums[query][2] += parseInt(cells[4].replace(/(<td>|<\/td>)/gi, ''), 10);
1730                 columnSums[query][3] += parseInt(cells[5].replace(/(<td>|<\/td>)/gi, ''), 10);
1731             };
1733             // We just assume the sql text is always in the second last column, and that the total count is right of it
1734             $('#logTable').find('table tbody tr td:nth-child(' + (runtime.logDataCols.length - 1) + ')').each(function () {
1735                 var $t = $(this);
1736                 // If query is a SELECT and user enabled or disabled to group
1737                 // queries ignoring data in where statements, we
1738                 // need to re-calculate the sums of each row
1739                 if (varFilterChange && $t.html().match(/^SELECT/i)) {
1740                     if (noVars) {
1741                         // Group on => Sum up identical columns, and hide all but 1
1743                         q = $t.text().replace(equalsFilter, '$1=...$6').trim();
1744                         q = q.replace(functionFilter, ' $1(...)');
1746                         // Js does not specify a limit on property name length,
1747                         // so we can abuse it as index :-)
1748                         if (filteredQueries[q]) {
1749                             filteredQueries[q] += parseInt($t.next().text(), 10);
1750                             totalSum += parseInt($t.next().text(), 10);
1751                             hide = true;
1752                         } else {
1753                             filteredQueries[q] = parseInt($t.next().text(), 10);
1754                             filteredQueriesLines[q] = i;
1755                             $t.text(q);
1756                         }
1757                         if (isSlowLog) {
1758                             countRow(q, $t.parent().html());
1759                         }
1760                     } else {
1761                         // Group off: Restore original columns
1763                         rowData = $t.parent().data('query');
1764                         // Restore SQL text
1765                         $t.text(rowData[queryColumnName]);
1766                         // Restore total count
1767                         $t.next().text(rowData[sumColumnName]);
1768                         // Restore slow log columns
1769                         if (isSlowLog) {
1770                             $t.parent().children('td:nth-child(3)').text(rowData.query_time);
1771                             $t.parent().children('td:nth-child(4)').text(rowData.lock_time);
1772                             $t.parent().children('td:nth-child(5)').text(rowData.rows_sent);
1773                             $t.parent().children('td:nth-child(6)').text(rowData.rows_examined);
1774                         }
1775                     }
1776                 }
1778                 // If not required to be hidden, do we need
1779                 // to hide because of a not matching text filter?
1780                 if (! hide && (textFilter !== null && ! textFilter.exec($t.text()))) {
1781                     hide = true;
1782                 }
1784                 // Now display or hide this column
1785                 if (hide) {
1786                     $t.parent().css('display', 'none');
1787                 } else {
1788                     totalSum += parseInt($t.next().text(), 10);
1789                     rowSum++;
1790                     $t.parent().css('display', '');
1791                 }
1793                 hide = false;
1794                 i++;
1795             });
1797             // We finished summarizing counts => Update count values of all grouped entries
1798             if (varFilterChange) {
1799                 if (noVars) {
1800                     var numCol;
1801                     var row;
1802                     var $table = $('#logTable').find('table tbody');
1803                     $.each(filteredQueriesLines, function (key, value) {
1804                         if (filteredQueries[key] <= 1) {
1805                             return;
1806                         }
1808                         row =  $table.children('tr:nth-child(' + (value + 1) + ')');
1809                         numCol = row.children(':nth-child(' + (runtime.logDataCols.length) + ')');
1810                         numCol.text(filteredQueries[key]);
1812                         if (isSlowLog) {
1813                             row.children('td:nth-child(3)').text(secToTime(columnSums[key][0]));
1814                             row.children('td:nth-child(4)').text(secToTime(columnSums[key][1]));
1815                             row.children('td:nth-child(5)').text(columnSums[key][2]);
1816                             row.children('td:nth-child(6)').text(columnSums[key][3]);
1817                         }
1818                     });
1819                 }
1821                 $('#logTable').find('table').trigger('update');
1822                 setTimeout(function () {
1823                     $('#logTable').find('table').trigger('sorton', [[[runtime.logDataCols.length - 1, 1]]]);
1824                 }, 0);
1825             }
1827             // Display some stats at the bottom of the table
1828             $('#logTable').find('table tfoot tr')
1829                 .html('<th colspan="' + (runtime.logDataCols.length - 1) + '">' +
1830                       PMA_messages.strSumRows + ' ' + rowSum + '<span class="floatright">' +
1831                       PMA_messages.strTotal + '</span></th><th class="right">' + totalSum + '</th>');
1832         }
1833     }
1835     /* Turns a timespan (12:12:12) into a number */
1836     function timeToSec (timeStr) {
1837         var time = timeStr.split(':');
1838         return (parseInt(time[0], 10) * 3600) + (parseInt(time[1], 10) * 60) + parseInt(time[2], 10);
1839     }
1841     /* Turns a number into a timespan (100 into 00:01:40) */
1842     function secToTime (timeInt) {
1843         var hours = Math.floor(timeInt / 3600);
1844         timeInt -= hours * 3600;
1845         var minutes = Math.floor(timeInt / 60);
1846         timeInt -= minutes * 60;
1848         if (hours < 10) {
1849             hours = '0' + hours;
1850         }
1851         if (minutes < 10) {
1852             minutes = '0' + minutes;
1853         }
1854         if (timeInt < 10) {
1855             timeInt = '0' + timeInt;
1856         }
1858         return hours + ':' + minutes + ':' + timeInt;
1859     }
1861     /* Constructs the log table out of the retrieved server data */
1862     function buildLogTable (data, groupInserts) {
1863         var rows = data.rows;
1864         var cols = [];
1865         var $table = $('<table class="sortable"></table>');
1866         var $tBody;
1867         var $tRow;
1868         var $tCell;
1870         $('#logTable').html($table);
1872         var tempPushKey = function (key, value) {
1873             cols.push(key);
1874         };
1876         var formatValue = function (name, value) {
1877             if (name === 'user_host') {
1878                 return value.replace(/(\[.*?\])+/g, '');
1879             }
1880             return escapeHtml(value);
1881         };
1883         for (var i = 0, l = rows.length; i < l; i++) {
1884             if (i === 0) {
1885                 $.each(rows[0], tempPushKey);
1886                 $table.append('<thead>' +
1887                               '<tr><th class="nowrap">' + cols.join('</th><th class="nowrap">') + '</th></tr>' +
1888                               '</thead>'
1889                 );
1891                 $table.append($tBody = $('<tbody></tbody>'));
1892             }
1894             $tBody.append($tRow = $('<tr class="noclick"></tr>'));
1895             var cl = '';
1896             for (var j = 0, ll = cols.length; j < ll; j++) {
1897                 // Assuming the query column is the second last
1898                 if (j === cols.length - 2 && rows[i][cols[j]].match(/^SELECT/i)) {
1899                     $tRow.append($tCell = $('<td class="linkElem">' + formatValue(cols[j], rows[i][cols[j]]) + '</td>'));
1900                     $tCell.click(openQueryAnalyzer);
1901                 } else {
1902                     $tRow.append('<td>' + formatValue(cols[j], rows[i][cols[j]]) + '</td>');
1903                 }
1905                 $tRow.data('query', rows[i]);
1906             }
1907         }
1909         $table.append('<tfoot>' +
1910                     '<tr><th colspan="' + (cols.length - 1) + '">' + PMA_messages.strSumRows +
1911                     ' ' + data.numRows + '<span class="floatright">' + PMA_messages.strTotal +
1912                     '</span></th><th class="right">' + data.sum.TOTAL + '</th></tr></tfoot>');
1914         // Append a tooltip to the count column, if there exist one
1915         if ($('#logTable').find('tr:first th:last').text().indexOf('#') > -1) {
1916             $('#logTable').find('tr:first th:last').append('&nbsp;' + PMA_getImage('b_help', '', { 'class': 'qroupedQueryInfoIcon' }));
1918             var tooltipContent = PMA_messages.strCountColumnExplanation;
1919             if (groupInserts) {
1920                 tooltipContent += '<p>' + PMA_messages.strMoreCountColumnExplanation + '</p>';
1921             }
1923             PMA_tooltip(
1924                 $('img.qroupedQueryInfoIcon'),
1925                 'img',
1926                 tooltipContent
1927             );
1928         }
1930         $('#logTable').find('table').tablesorter({
1931             sortList: [[cols.length - 1, 1]],
1932             widgets: ['fast-zebra']
1933         });
1935         $('#logTable').find('table thead th')
1936             .append('<div class="sorticon"></div>');
1938         return cols;
1939     }
1941     /* Opens the query analyzer dialog */
1942     function openQueryAnalyzer () {
1943         var rowData = $(this).parent().data('query');
1944         var query = rowData.argument || rowData.sql_text;
1946         if (codemirror_editor) {
1947             // TODO: somehow PMA_SQLPrettyPrint messes up the query, needs be fixed
1948             // query = PMA_SQLPrettyPrint(query);
1949             codemirror_editor.setValue(query);
1950             // Codemirror is bugged, it doesn't refresh properly sometimes.
1951             // Following lines seem to fix that
1952             setTimeout(function () {
1953                 codemirror_editor.refresh();
1954             }, 50);
1955         } else {
1956             $('#sqlquery').val(query);
1957         }
1959         var profilingChart = null;
1960         var dlgBtns = {};
1962         dlgBtns[PMA_messages.strAnalyzeQuery] = function () {
1963             loadQueryAnalysis(rowData);
1964         };
1965         dlgBtns[PMA_messages.strClose] = function () {
1966             $(this).dialog('close');
1967         };
1969         $('#queryAnalyzerDialog').dialog({
1970             width: 'auto',
1971             height: 'auto',
1972             resizable: false,
1973             buttons: dlgBtns,
1974             close: function () {
1975                 if (profilingChart !== null) {
1976                     profilingChart.destroy();
1977                 }
1978                 $('#queryAnalyzerDialog').find('div.placeHolder').html('');
1979                 if (codemirror_editor) {
1980                     codemirror_editor.setValue('');
1981                 } else {
1982                     $('#sqlquery').val('');
1983                 }
1984             }
1985         });
1986     }
1988     /* Loads and displays the analyzed query data */
1989     function loadQueryAnalysis (rowData) {
1990         var db = rowData.db || '';
1992         $('#queryAnalyzerDialog').find('div.placeHolder').html(
1993             PMA_messages.strAnalyzing + ' <img class="ajaxIcon" src="' +
1994             pmaThemeImage + 'ajax_clock_small.gif" alt="">');
1996         $.post('server_status_monitor.php' + PMA_commonParams.get('common_query'), {
1997             ajax_request: true,
1998             query_analyzer: true,
1999             query: codemirror_editor ? codemirror_editor.getValue() : $('#sqlquery').val(),
2000             database: db,
2001             server: PMA_commonParams.get('server')
2002         }, function (data) {
2003             var i;
2004             var l;
2005             if (typeof data !== 'undefined' && data.success === true) {
2006                 data = data.message;
2007             }
2008             if (data.error) {
2009                 if (data.error.indexOf('1146') !== -1 || data.error.indexOf('1046') !== -1) {
2010                     data.error = PMA_messages.strServerLogError;
2011                 }
2012                 $('#queryAnalyzerDialog').find('div.placeHolder').html('<div class="error">' + data.error + '</div>');
2013                 return;
2014             }
2015             var totalTime = 0;
2016             // Float sux, I'll use table :(
2017             $('#queryAnalyzerDialog').find('div.placeHolder')
2018                 .html('<table width="100%" border="0"><tr><td class="explain"></td><td class="chart"></td></tr></table>');
2020             var explain = '<b>' + PMA_messages.strExplainOutput + '</b> ' + $('#explain_docu').html();
2021             if (data.explain.length > 1) {
2022                 explain += ' (';
2023                 for (i = 0; i < data.explain.length; i++) {
2024                     if (i > 0) {
2025                         explain += ', ';
2026                     }
2027                     explain += '<a href="#showExplain-' + i + '">' + i + '</a>';
2028                 }
2029                 explain += ')';
2030             }
2031             explain += '<p></p>';
2033             var tempExplain = function (key, value) {
2034                 value = (value === null) ? 'null' : escapeHtml(value);
2036                 if (key === 'type' && value.toLowerCase() === 'all') {
2037                     value = '<span class="attention">' + value + '</span>';
2038                 }
2039                 if (key === 'Extra') {
2040                     value = value.replace(/(using (temporary|filesort))/gi, '<span class="attention">$1</span>');
2041                 }
2042                 explain += key + ': ' + value + '<br />';
2043             };
2045             for (i = 0, l = data.explain.length; i < l; i++) {
2046                 explain += '<div class="explain-' + i + '"' + (i > 0 ?  'style="display:none;"' : '') + '>';
2047                 $.each(data.explain[i], tempExplain);
2048                 explain += '</div>';
2049             }
2051             explain += '<p><b>' + PMA_messages.strAffectedRows + '</b> ' + data.affectedRows;
2053             $('#queryAnalyzerDialog').find('div.placeHolder td.explain').append(explain);
2055             $('#queryAnalyzerDialog').find('div.placeHolder a[href*="#showExplain"]').click(function () {
2056                 var id = $(this).attr('href').split('-')[1];
2057                 $(this).parent().find('div[class*="explain"]').hide();
2058                 $(this).parent().find('div[class*="explain-' + id + '"]').show();
2059             });
2061             if (data.profiling) {
2062                 var chartData = [];
2063                 var numberTable = '<table class="queryNums"><thead><tr><th>' + PMA_messages.strStatus + '</th><th>' + PMA_messages.strTime + '</th></tr></thead><tbody>';
2064                 var duration;
2065                 var otherTime = 0;
2067                 for (i = 0, l = data.profiling.length; i < l; i++) {
2068                     duration = parseFloat(data.profiling[i].duration);
2070                     totalTime += duration;
2072                     numberTable += '<tr><td>' + data.profiling[i].state + ' </td><td> ' + PMA_prettyProfilingNum(duration, 2) + '</td></tr>';
2073                 }
2075                 // Only put those values in the pie which are > 2%
2076                 for (i = 0, l = data.profiling.length; i < l; i++) {
2077                     duration = parseFloat(data.profiling[i].duration);
2079                     if (duration / totalTime > 0.02) {
2080                         chartData.push([PMA_prettyProfilingNum(duration, 2) + ' ' + data.profiling[i].state, duration]);
2081                     } else {
2082                         otherTime += duration;
2083                     }
2084                 }
2086                 if (otherTime > 0) {
2087                     chartData.push([PMA_prettyProfilingNum(otherTime, 2) + ' ' + PMA_messages.strOther, otherTime]);
2088                 }
2090                 numberTable += '<tr><td><b>' + PMA_messages.strTotalTime + '</b></td><td>' + PMA_prettyProfilingNum(totalTime, 2) + '</td></tr>';
2091                 numberTable += '</tbody></table>';
2093                 $('#queryAnalyzerDialog').find('div.placeHolder td.chart').append(
2094                     '<b>' + PMA_messages.strProfilingResults + ' ' + $('#profiling_docu').html() + '</b> ' +
2095                     '(<a href="#showNums">' + PMA_messages.strTable + '</a>, <a href="#showChart">' + PMA_messages.strChart + '</a>)<br/>' +
2096                     numberTable + ' <div id="queryProfiling"></div>');
2098                 $('#queryAnalyzerDialog').find('div.placeHolder a[href="#showNums"]').click(function () {
2099                     $('#queryAnalyzerDialog').find('#queryProfiling').hide();
2100                     $('#queryAnalyzerDialog').find('table.queryNums').show();
2101                     return false;
2102                 });
2104                 $('#queryAnalyzerDialog').find('div.placeHolder a[href="#showChart"]').click(function () {
2105                     $('#queryAnalyzerDialog').find('#queryProfiling').show();
2106                     $('#queryAnalyzerDialog').find('table.queryNums').hide();
2107                     return false;
2108                 });
2110                 profilingChart = PMA_createProfilingChart(
2111                     'queryProfiling',
2112                     chartData
2113                 );
2115                 // $('#queryProfiling').resizable();
2116             }
2117         });
2118     }
2120     /* Saves the monitor to localstorage */
2121     function saveMonitor () {
2122         var gridCopy = {};
2124         $.each(runtime.charts, function (key, elem) {
2125             gridCopy[key] = {};
2126             gridCopy[key].nodes = elem.nodes;
2127             gridCopy[key].settings = elem.settings;
2128             gridCopy[key].title = elem.title;
2129             gridCopy[key].series = elem.series;
2130             gridCopy[key].maxYLabel = elem.maxYLabel;
2131         });
2133         if (isStorageSupported('localStorage')) {
2134             window.localStorage.monitorCharts = JSON.stringify(gridCopy);
2135             window.localStorage.monitorSettings = JSON.stringify(monitorSettings);
2136             window.localStorage.monitorVersion = monitorProtocolVersion;
2137         }
2139         $('a[href="#clearMonitorConfig"]').show();
2140     }
2143 // Run the monitor once loaded
2144 AJAX.registerOnload('server_status_monitor.js', function () {
2145     $('a[href="#pauseCharts"]').trigger('click');
2148 function serverResponseError () {
2149     var btns = {};
2150     btns[PMA_messages.strReloadPage] = function () {
2151         window.location.reload();
2152     };
2153     $('#emptyDialog').dialog({ title: PMA_messages.strRefreshFailed });
2154     $('#emptyDialog').html(
2155         PMA_getImage('s_attention') +
2156         PMA_messages.strInvalidResponseExplanation
2157     );
2158     $('#emptyDialog').dialog({ buttons: btns });
2161 /* Destroys all monitor related resources */
2162 function destroyGrid () {
2163     if (runtime.charts) {
2164         $.each(runtime.charts, function (key, value) {
2165             try {
2166                 value.chart.destroy();
2167             } catch (err) {}
2168         });
2169     }
2171     try {
2172         runtime.refreshRequest.abort();
2173     } catch (err) {}
2174     try {
2175         clearTimeout(runtime.refreshTimeout);
2176     } catch (err) {}
2177     $('#chartGrid').html('');
2178     runtime.charts = null;
2179     runtime.chartAI = 0;
2180     monitorSettings = null; // TODO:this not global variable