Merge pull request #431 from xmujay/0609_monitor
[phpmyadmin/aamir.git] / js / server_status_monitor.js
blobeacac37c6f9566ec9c1b4c54fb2c5f6f054730fc
1 /* vim: set expandtab sw=4 ts=4 sts=4: */
2 var runtime = {},
3     server_time_diff,
4     server_os,
5     is_superuser,
6     server_db_isLocal;
7 AJAX.registerOnload('server_status_monitor.js', function () {
8     var $js_data_form = $('#js_data');
9     server_time_diff  = new Date().getTime() - $js_data_form.find("input[name=server_time]").val();
10     server_os =         $js_data_form.find("input[name=server_os]").val();
11     is_superuser =      $js_data_form.find("input[name=is_superuser]").val();
12     server_db_isLocal = $js_data_form.find("input[name=server_db_isLocal]").val();
13 });
15 /**
16  * Unbind all event handlers before tearing down a page
17  */
18 AJAX.registerTeardown('server_status_monitor.js', function () {
19     $('#emptyDialog').remove();
20     $('#addChartDialog').remove();
21     $('a.popupLink').unbind('click');
22     $('body').unbind('click');
23 });
24 /**
25  * Popup behaviour
26  */
27 AJAX.registerOnload('server_status_monitor.js', function () {
28     $('<div />')
29         .attr('id', 'emptyDialog')
30         .appendTo('#page_content');
31     $('#addChartDialog')
32         .appendTo('#page_content');
34     $('a.popupLink').click(function () {
35         var $link = $(this);
36         $('div.' + $link.attr('href').substr(1))
37             .show()
38             .offset({ top: $link.offset().top + $link.height() + 5, left: $link.offset().left })
39             .addClass('openedPopup');
41         return false;
42     });
43     $('body').click(function (event) {
44         $('div.openedPopup').each(function () {
45             var $cnt = $(this);
46             var pos = $cnt.offset();
47             // Hide if the mouseclick is outside the popupcontent
48             if (event.pageX < pos.left ||
49                 event.pageY < pos.top ||
50                 event.pageX > pos.left + $cnt.outerWidth() ||
51                 event.pageY > pos.top + $cnt.outerHeight()
52             ) {
53                 $cnt.hide().removeClass('openedPopup');
54             }
55         });
56     });
57 });
59 AJAX.registerTeardown('server_status_monitor.js', function () {
60     $('a[href="#rearrangeCharts"], a[href="#endChartEditMode"]').unbind('click');
61     $('div.popupContent select[name="chartColumns"]').unbind('change');
62     $('div.popupContent select[name="gridChartRefresh"]').unbind('change');
63     $('a[href="#addNewChart"]').unbind('click');
64     $('a[href="#exportMonitorConfig"]').unbind('click');
65     $('a[href="#importMonitorConfig"]').unbind('click');
66     $('a[href="#clearMonitorConfig"]').unbind('click');
67     $('a[href="#pauseCharts"]').unbind('click');
68     $('a[href="#monitorInstructionsDialog"]').unbind('click');
69     $('input[name="chartType"]').unbind('click');
70     $('input[name="useDivisor"]').unbind('click');
71     $('input[name="useUnit"]').unbind('click');
72     $('select[name="varChartList"]').unbind('click');
73     $('a[href="#kibDivisor"]').unbind('click');
74     $('a[href="#mibDivisor"]').unbind('click');
75     $('a[href="#submitClearSeries"]').unbind('click');
76     $('a[href="#submitAddSeries"]').unbind('click');
77     // $("input#variableInput").destroy();
78     $('#chartPreset').unbind('click');
79     $('#chartStatusVar').unbind('click');
80     destroyGrid();
81 });
83 AJAX.registerOnload('server_status_monitor.js', function () {
84     // Show tab links
85     $('div.tabLinks').show();
86     $('#loadingMonitorIcon').remove();
87     // Codemirror is loaded on demand so we might need to initialize it
88     if (! codemirror_editor) {
89         var $elm = $('#sqlquery');
90         if ($elm.length > 0 && typeof CodeMirror != 'undefined') {
91             codemirror_editor = CodeMirror.fromTextArea(
92                 $elm[0],
93                 {
94                     lineNumbers: true,
95                     matchBrackets: true,
96                     indentUnit: 4,
97                     mode: "text/x-mysql"
98                 }
99             );
100         }
101     }
102     // Timepicker is loaded on demand so we need to initialize
103     // datetime fields from the 'load log' dialog
104     $('#logAnalyseDialog .datetimefield').each(function () {
105         PMA_addDatepicker($(this));
106     });
108     /**** Monitor charting implementation ****/
109     /* Saves the previous ajax response for differential values */
110     var oldChartData = null;
111     // Holds about to be created chart
112     var newChart = null;
113     var chartSpacing;
115     // Whenever the monitor object (runtime.charts) or the settings object
116     // (monitorSettings) changes in a way incompatible to the previous version,
117     // increase this number. It will reset the users monitor and settings object
118     // in his localStorage to the default configuration
119     var monitorProtocolVersion = '1.0';
121     // Runtime parameter of the monitor, is being fully set in initGrid()
122     runtime = {
123         // Holds all visible charts in the grid
124         charts: null,
125         // Stores the timeout handler so it can be cleared
126         refreshTimeout: null,
127         // Stores the GET request to refresh the charts
128         refreshRequest: null,
129         // Chart auto increment
130         chartAI: 0,
131         // To play/pause the monitor
132         redrawCharts: false,
133         // Object that contains a list of nodes that need to be retrieved
134         // from the server for chart updates
135         dataList: [],
136         // Current max points per chart (needed for auto calculation)
137         gridMaxPoints: 20,
138         // displayed time frame
139         xmin: -1,
140         xmax: -1
141     };
142     var monitorSettings = null;
144     var defaultMonitorSettings = {
145         columns: 3,
146         chartSize: { width: 295, height: 250 },
147         // Max points in each chart. Settings it to 'auto' sets
148         // gridMaxPoints to (chartwidth - 40) / 12
149         gridMaxPoints: 'auto',
150         /* Refresh rate of all grid charts in ms */
151         gridRefresh: 5000
152     };
154     // Allows drag and drop rearrange and print/edit icons on charts
155     var editMode = false;
157     /* List of preconfigured charts that the user may select */
158     var presetCharts = {
159         // Query cache efficiency
160         'qce': {
161             title: PMA_messages.strQueryCacheEfficiency,
162             series: [ {
163                 label: PMA_messages.strQueryCacheEfficiency
164             } ],
165             nodes: [ {
166                 dataPoints: [{type: 'statusvar', name: 'Qcache_hits'}, {type: 'statusvar', name: 'Com_select'}],
167                 transformFn: 'qce'
168             } ],
169             maxYLabel: 0
170         },
171         // Query cache usage
172         'qcu': {
173             title: PMA_messages.strQueryCacheUsage,
174             series: [ {
175                 label: PMA_messages.strQueryCacheUsed
176             } ],
177             nodes: [ {
178                 dataPoints: [{type: 'statusvar', name: 'Qcache_free_memory'}, {type: 'servervar', name: 'query_cache_size'}],
179                 transformFn: 'qcu'
180             } ],
181             maxYLabel: 0
182         }
183     };
185     // time span selection
186     var selectionTimeDiff = [];
187     var selectionStartX, selectionStartY, selectionEndX, selectionEndY;
188     var drawTimeSpan = false;
190     // chart tooltip
191     var tooltipBox;
193     /* Add OS specific system info charts to the preset chart list */
194     switch (server_os) {
195     case 'WINNT':
196         $.extend(presetCharts, {
197             'cpu': {
198                 title: PMA_messages.strSystemCPUUsage,
199                 series: [ {
200                     label: PMA_messages.strAverageLoad
201                 } ],
202                 nodes: [ {
203                     dataPoints: [{ type: 'cpu', name: 'loadavg'}]
204                 } ],
205                 maxYLabel: 100
206             },
208             'memory': {
209                 title: PMA_messages.strSystemMemory,
210                 series: [ {
211                     label: PMA_messages.strTotalMemory,
212                     fill: true
213                 }, {
214                     dataType: 'memory',
215                     label: PMA_messages.strUsedMemory,
216                     fill: true
217                 } ],
218                 nodes: [{ dataPoints: [{ type: 'memory', name: 'MemTotal' }], valueDivisor: 1024 },
219                         { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 }
220                 ],
221                 maxYLabel: 0
222             },
224             'swap': {
225                 title: PMA_messages.strSystemSwap,
226                 series: [ {
227                     label: PMA_messages.strTotalSwap,
228                     fill: true
229                 }, {
230                     label: PMA_messages.strUsedSwap,
231                     fill: true
232                 } ],
233                 nodes: [{ dataPoints: [{ type: 'memory', name: 'SwapTotal' }]},
234                         { dataPoints: [{ type: 'memory', name: 'SwapUsed' }]}
235                 ],
236                 maxYLabel: 0
237             }
238         });
239         break;
241     case 'Linux':
242         $.extend(presetCharts, {
243             'cpu': {
244                 title: PMA_messages.strSystemCPUUsage,
245                 series: [ {
246                     label: PMA_messages.strAverageLoad
247                 } ],
248                 nodes: [{ dataPoints: [{ type: 'cpu', name: 'irrelevant' }], transformFn: 'cpu-linux'}],
249                 maxYLabel: 0
250             },
251             'memory': {
252                 title: PMA_messages.strSystemMemory,
253                 series: [
254                     { label: PMA_messages.strBufferedMemory, fill: true},
255                     { label: PMA_messages.strUsedMemory, fill: true},
256                     { label: PMA_messages.strCachedMemory, fill: true},
257                     { label: PMA_messages.strFreeMemory, fill: true}
258                 ],
259                 nodes: [
260                     { dataPoints: [{ type: 'memory', name: 'Buffers' }], valueDivisor: 1024 },
261                     { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 },
262                     { dataPoints: [{ type: 'memory', name: 'Cached' }],  valueDivisor: 1024 },
263                     { dataPoints: [{ type: 'memory', name: 'MemFree' }], valueDivisor: 1024 }
264                 ],
265                 maxYLabel: 0
266             },
267             'swap': {
268                 title: PMA_messages.strSystemSwap,
269                 series: [
270                     { label: PMA_messages.strCachedSwap, fill: true},
271                     { label: PMA_messages.strUsedSwap, fill: true},
272                     { label: PMA_messages.strFreeSwap, fill: true}
273                 ],
274                 nodes: [
275                     { dataPoints: [{ type: 'memory', name: 'SwapCached' }], valueDivisor: 1024 },
276                     { dataPoints: [{ type: 'memory', name: 'SwapUsed' }], valueDivisor: 1024 },
277                     { dataPoints: [{ type: 'memory', name: 'SwapFree' }], valueDivisor: 1024 }
278                 ],
279                 maxYLabel: 0
280             }
281         });
282         break;
284     case 'SunOS':
285         $.extend(presetCharts, {
286             'cpu': {
287                 title: PMA_messages.strSystemCPUUsage,
288                 series: [ {
289                     label: PMA_messages.strAverageLoad
290                 } ],
291                 nodes: [ {
292                     dataPoints: [{ type: 'cpu', name: 'loadavg'}]
293                 } ],
294                 maxYLabel: 0
295             },
296             'memory': {
297                 title: PMA_messages.strSystemMemory,
298                 series: [
299                     { label: PMA_messages.strUsedMemory, fill: true },
300                     { label: PMA_messages.strFreeMemory, fill: true }
301                 ],
302                 nodes: [
303                     { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 },
304                     { dataPoints: [{ type: 'memory', name: 'MemFree' }], valueDivisor: 1024 }
305                 ],
306                 maxYLabel: 0
307             },
308             'swap': {
309                 title: PMA_messages.strSystemSwap,
310                 series: [
311                     { label: PMA_messages.strUsedSwap, fill: true },
312                     { label: PMA_messages.strFreeSwap, fill: true }
313                 ],
314                 nodes: [
315                     { dataPoints: [{ type: 'memory', name: 'SwapUsed' }], valueDivisor: 1024 },
316                     { dataPoints: [{ type: 'memory', name: 'SwapFree' }], valueDivisor: 1024 }
317                 ],
318                 maxYLabel: 0
319             }
320         });
321         break;
322     }
324     // Default setting for the chart grid
325     var defaultChartGrid = {
326         'c0': {
327             title: PMA_messages.strQuestions,
328             series: [
329                 {label: PMA_messages.strQuestions}
330             ],
331             nodes: [
332                 {dataPoints: [{ type: 'statusvar', name: 'Questions' }], display: 'differential' }
333             ],
334             maxYLabel: 0
335         },
336         'c1': {
337             title: PMA_messages.strChartConnectionsTitle,
338             series: [
339                 {label: PMA_messages.strConnections},
340                 {label: PMA_messages.strProcesses}
341             ],
342             nodes: [
343                 {dataPoints: [{ type: 'statusvar', name: 'Connections' }], display: 'differential' },
344                 {dataPoints: [{ type: 'proc', name: 'processes' }] }
345             ],
346             maxYLabel: 0
347         },
348         'c2': {
349             title: PMA_messages.strTraffic,
350             series: [
351                 {label: PMA_messages.strBytesSent},
352                 {label: PMA_messages.strBytesReceived}
353             ],
354             nodes: [
355                 {dataPoints: [{ type: 'statusvar', name: 'Bytes_sent' }], display: 'differential', valueDivisor: 1024 },
356                 {dataPoints: [{ type: 'statusvar', name: 'Bytes_received' }], display: 'differential', valueDivisor: 1024 }
357             ],
358             maxYLabel: 0
359         }
360     };
362     // Server is localhost => We can add cpu/memory/swap to the default chart
363     if (server_db_isLocal) {
364         defaultChartGrid['c3'] = presetCharts['cpu'];
365         defaultChartGrid['c4'] = presetCharts['memory'];
366         defaultChartGrid['c5'] = presetCharts['swap'];
367     }
369     /* Buttons that are on the top right corner of each chart */
370     var gridbuttons = {
371         cogButton: {
372             //enabled: true,
373             symbol: 'url(' + pmaThemeImage  + 's_cog.png)',
374             x: -36,
375             symbolFill: '#B5C9DF',
376             hoverSymbolFill: '#779ABF',
377             _titleKey: 'settings',
378             menuName: 'gridsettings',
379             menuItems: [{
380                 textKey: 'editChart',
381                 onclick: function () {
382                     editChart(this);
383                 }
384             }, {
385                 textKey: 'removeChart',
386                 onclick: function () {
387                     removeChart(this);
388                 }
389             }]
390         }
391     };
393     $('a[href="#rearrangeCharts"], a[href="#endChartEditMode"]').click(function (event) {
394         event.preventDefault();
395         editMode = !editMode;
396         if ($(this).attr('href') == '#endChartEditMode') {
397             editMode = false;
398         }
400         // Icon graphics have zIndex 19, 20 and 21.
401         // Let's just hope nothing else has the same zIndex
402         $('#chartGrid div svg').find('*[zIndex=20], *[zIndex=21], *[zIndex=19]').toggle(editMode);
404         $('a[href="#endChartEditMode"]').toggle(editMode);
406         if (editMode) {
407             // Close the settings popup
408             $('div.popupContent').hide().removeClass('openedPopup');
410             $("#chartGrid").sortableTable({
411                 ignoreRect: {
412                     top: 8,
413                     left: chartSize().width - 63,
414                     width: 54,
415                     height: 24
416                 },
417                 events: {
418                     // Drop event. The drag child element is moved into the drop element
419                     // and vice versa. So the parameters are switched.
420                     drop: function (drag, drop, pos) {
421                         var dragKey, dropKey, dropRender;
422                         var dragRender = $(drag).children().first().attr('id');
424                         if ($(drop).children().length > 0) {
425                             dropRender = $(drop).children().first().attr('id');
426                         }
428                         // Find the charts in the array
429                         $.each(runtime.charts, function (key, value) {
430                             if (value.chart.options.chart.renderTo == dragRender) {
431                                 dragKey = key;
432                             }
433                             if (dropRender && value.chart.options.chart.renderTo == dropRender) {
434                                 dropKey = key;
435                             }
436                         });
438                         // Case 1: drag and drop are charts -> Switch keys
439                         if (dropKey) {
440                             if (dragKey) {
441                                 dragChart = runtime.charts[dragKey];
442                                 runtime.charts[dragKey] = runtime.charts[dropKey];
443                                 runtime.charts[dropKey] = dragChart;
444                             } else {
445                                 // Case 2: drop is a empty cell => just completely rebuild the ids
446                                 var keys = [];
447                                 var dropKeyNum = parseInt(dropKey.substr(1), 10);
448                                 var insertBefore = pos.col + pos.row * monitorSettings.columns;
449                                 var values = [];
450                                 var newChartList = {};
451                                 var c = 0;
453                                 $.each(runtime.charts, function (key, value) {
454                                     if (key != dropKey) {
455                                         keys.push(key);
456                                     }
457                                 });
459                                 keys.sort();
461                                 // Rebuilds all ids, with the dragged chart correctly inserted
462                                 for (var i = 0; i < keys.length; i++) {
463                                     if (keys[i] == insertBefore) {
464                                         newChartList['c' + (c++)] = runtime.charts[dropKey];
465                                         insertBefore = -1; // Insert ok
466                                     }
467                                     newChartList['c' + (c++)] = runtime.charts[keys[i]];
468                                 }
470                                 // Not inserted => put at the end
471                                 if (insertBefore != -1) {
472                                     newChartList['c' + (c++)] = runtime.charts[dropKey];
473                                 }
475                                 runtime.charts = newChartList;
476                             }
478                             saveMonitor();
479                         }
480                     }
481                 }
482             });
484         } else {
485             $("#chartGrid").sortableTable('destroy');
486             saveMonitor(); // Save settings
487         }
489         return false;
490     });
492     // global settings
493     $('div.popupContent select[name="chartColumns"]').change(function () {
494         monitorSettings.columns = parseInt(this.value, 10);
496         var newSize = chartSize();
498         // Empty cells should keep their size so you can drop onto them
499         $('#chartGrid tr td').css('width', newSize.width + 'px');
501         /* Reorder all charts that it fills all column cells */
502         var numColumns;
503         var $tr = $('#chartGrid tr:first');
504         var row = 0;
505         while ($tr.length !== 0) {
506             numColumns = 1;
507             // To many cells in one row => put into next row
508             $tr.find('td').each(function () {
509                 if (numColumns > monitorSettings.columns) {
510                     if ($tr.next().length === 0) {
511                         $tr.after('<tr></tr>');
512                     }
513                     $tr.next().prepend($(this));
514                 }
515                 numColumns++;
516             });
518             // To little cells in one row => for each cell to little,
519             // move all cells backwards by 1
520             if ($tr.next().length > 0) {
521                 var cnt = monitorSettings.columns - $tr.find('td').length;
522                 for (var i = 0; i < cnt; i++) {
523                     $tr.append($tr.next().find('td:first'));
524                     $tr.nextAll().each(function () {
525                         if ($(this).next().length !== 0) {
526                             $(this).append($(this).next().find('td:first'));
527                         }
528                     });
529                 }
530             }
532             $tr = $tr.next();
533             row++;
534         }
536         /* Apply new chart size to all charts */
537         $.each(runtime.charts, function (key, value) {
538             value.chart.setSize(
539                 newSize.width,
540                 newSize.height,
541                 false
542             );
543         });
545         if (monitorSettings.gridMaxPoints == 'auto') {
546             runtime.gridMaxPoints = Math.round((newSize.width - 40) / 12);
547         }
549         runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
550         runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh;
552         if (editMode) {
553             $("#chartGrid").sortableTable('refresh');
554         }
556         saveMonitor(); // Save settings
557     });
559     $('div.popupContent select[name="gridChartRefresh"]').change(function () {
560         monitorSettings.gridRefresh = parseInt(this.value, 10) * 1000;
561         clearTimeout(runtime.refreshTimeout);
563         if (runtime.refreshRequest) {
564             runtime.refreshRequest.abort();
565         }
567         runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
568         // fixing chart shift towards left on refresh rate change
569         //runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh;
570         runtime.refreshTimeout = setTimeout(refreshChartGrid, monitorSettings.gridRefresh);
572         saveMonitor(); // Save settings
573     });
575     $('a[href="#addNewChart"]').click(function (event) {
576         event.preventDefault();
577         var dlgButtons = { };
579         dlgButtons[PMA_messages.strAddChart] = function () {
580             var type = $('input[name="chartType"]:checked').val();
582             if (type == 'preset') {
583                 newChart = presetCharts[$('#addChartDialog select[name="presetCharts"]').prop('value')];
584             } else {
585                 // If user builds his own chart, it's being set/updated
586                 // each time he adds a series
587                 // So here we only warn if he didn't add a series yet
588                 if (! newChart || ! newChart.nodes || newChart.nodes.length === 0) {
589                     alert(PMA_messages.strAddOneSeriesWarning);
590                     return;
591                 }
592             }
594             newChart.title = $('input[name="chartTitle"]').val();
595             // Add a cloned object to the chart grid
596             addChart($.extend(true, {}, newChart));
598             newChart = null;
600             saveMonitor(); // Save settings
602             $(this).dialog("close");
603         };
605         dlgButtons[PMA_messages.strClose] = function () {
606             newChart = null;
607             $('span#clearSeriesLink').hide();
608             $('#seriesPreview').html('');
609             $(this).dialog("close");
610         };
612         var $presetList = $('#addChartDialog select[name="presetCharts"]');
613         if ($presetList.html().length === 0) {
614             $.each(presetCharts, function (key, value) {
615                 $presetList.append('<option value="' + key + '">' + value.title + '</option>');
616             });
617             $presetList.change(function () {
618                 $('input[name="chartTitle"]').val(
619                     $presetList.find(':selected').text()
620                 );
621                 $('#chartPreset').prop('checked', true);
622             });
623             $('#chartPreset').click(function () {
624                 $('input[name="chartTitle"]').val(
625                     $presetList.find(':selected').text()
626                 );
627             });
628             $('#chartStatusVar').click(function () {
629                 $('input[name="chartTitle"]').val(
630                     $('#chartSeries').find(':selected').text().replace(/_/g, " ")
631                 );
632             });
633             $('#chartSeries').change(function () {
634                 $('input[name="chartTitle"]').val(
635                     $('#chartSeries').find(':selected').text().replace(/_/g, " ")
636                 );
637             });
638         }
640         $('#addChartDialog').dialog({
641             width: 'auto',
642             height: 'auto',
643             buttons: dlgButtons
644         });
646         $('#addChartDialog #seriesPreview').html('<i>' + PMA_messages.strNone + '</i>');
648         return false;
649     });
651     $('a[href="#exportMonitorConfig"]').click(function (event) {
652         event.preventDefault();
653         var gridCopy = {};
654         $.each(runtime.charts, function (key, elem) {
655             gridCopy[key] = {};
656             gridCopy[key].nodes = elem.nodes;
657             gridCopy[key].settings = elem.settings;
658             gridCopy[key].title = elem.title;
659         });
660         var exportData = {
661             monitorCharts: gridCopy,
662             monitorSettings: monitorSettings
663         };
664         $('<form />', {
665             "class": "disableAjax",
666             method: "post",
667             action: "file_echo.php?" + PMA_commonParams.get('common_query') + "&filename=1",
668             style: "display:none;"
669         })
670         .append(
671             $('<input />', {
672                 type: "hidden",
673                 name: "monitorconfig",
674                 value: $.toJSON(exportData)
675             })
676         )
677         .appendTo('body')
678         .submit()
679         .remove();
680     });
682     $('a[href="#importMonitorConfig"]').click(function (event) {
683         event.preventDefault();
684         $('#emptyDialog').dialog({title: PMA_messages.strImportDialogTitle});
685         $('#emptyDialog').html(PMA_messages.strImportDialogMessage + ':<br/><form action="file_echo.php?' + PMA_commonParams.get('common_query') + '&import=1" method="post" enctype="multipart/form-data">' +
686             '<input type="file" name="file"> <input type="hidden" name="import" value="1"> </form>');
688         var dlgBtns = {};
690         dlgBtns[PMA_messages.strImport] = function () {
691             var $iframe, $form;
692             $('body').append($iframe = $('<iframe id="monitorConfigUpload" style="display:none;"></iframe>'));
693             var d = $iframe[0].contentWindow.document;
694             d.open();
695             d.close();
696             mew = d;
698             $iframe.load(function () {
699                 var json;
701                 // Try loading config
702                 try {
703                     var data = $('body', $('iframe#monitorConfigUpload')[0].contentWindow.document).html();
704                     // Chrome wraps around '<pre style="word-wrap: break-word; white-space: pre-wrap;">' to any text content -.-
705                     json = $.secureEvalJSON(data.substring(data.indexOf("{"), data.lastIndexOf("}") + 1));
706                 } catch (err) {
707                     alert(PMA_messages.strFailedParsingConfig);
708                     $('#emptyDialog').dialog('close');
709                     return;
710                 }
712                 // Basic check, is this a monitor config json?
713                 if (!json || ! json.monitorCharts || ! json.monitorCharts) {
714                     alert(PMA_messages.strFailedParsingConfig);
715                     $('#emptyDialog').dialog('close');
716                     return;
717                 }
719                 // If json ok, try applying config
720                 try {
721                     window.localStorage['monitorCharts'] = $.toJSON(json.monitorCharts);
722                     window.localStorage['monitorSettings'] = $.toJSON(json.monitorSettings);
723                     rebuildGrid();
724                 } catch (err) {
725                     alert(PMA_messages.strFailedBuildingGrid);
726                     // If an exception is thrown, load default again
727                     window.localStorage.removeItem('monitorCharts');
728                     window.localStorage.removeItem('monitorSettings');
729                     rebuildGrid();
730                 }
732                 $('#emptyDialog').dialog('close');
733             });
735             $("body", d).append($form = $('#emptyDialog').find('form'));
736             $form.submit();
737             $('#emptyDialog').append('<img class="ajaxIcon" src="' + pmaThemeImage + 'ajax_clock_small.gif" alt="">');
738         };
740         dlgBtns[PMA_messages.strCancel] = function () {
741             $(this).dialog('close');
742         };
745         $('#emptyDialog').dialog({
746             width: 'auto',
747             height: 'auto',
748             buttons: dlgBtns
749         });
750     });
752     $('a[href="#clearMonitorConfig"]').click(function (event) {
753         event.preventDefault();
754         window.localStorage.removeItem('monitorCharts');
755         window.localStorage.removeItem('monitorSettings');
756         window.localStorage.removeItem('monitorVersion');
757         $(this).hide();
758         rebuildGrid();
759     });
761     $('a[href="#pauseCharts"]').click(function (event) {
762         event.preventDefault();
763         runtime.redrawCharts = ! runtime.redrawCharts;
764         if (! runtime.redrawCharts) {
765             $(this).html(PMA_getImage('play.png') + ' ' + PMA_messages.strResumeMonitor);
766         } else {
767             $(this).html(PMA_getImage('pause.png') + ' ' + PMA_messages.strPauseMonitor);
768             if (! runtime.charts) {
769                 initGrid();
770                 $('a[href="#settingsPopup"]').show();
771             }
772         }
773         return false;
774     });
776     $('a[href="#monitorInstructionsDialog"]').click(function (event) {
777         event.preventDefault();
779         var $dialog = $('#monitorInstructionsDialog');
781         $dialog.dialog({
782             width: 595,
783             height: 'auto'
784         }).find('img.ajaxIcon').show();
786         var loadLogVars = function (getvars) {
787             var vars = { ajax_request: true, logging_vars: true };
788             if (getvars) {
789                 $.extend(vars, getvars);
790             }
792             $.get('server_status_monitor.php?' + PMA_commonParams.get('common_query'), vars,
793                 function (data) {
794                     var logVars;
795                     if (data.success === true) {
796                         logVars = data.message;
797                     } else {
798                         return serverResponseError();
799                     }
800                     var icon = PMA_getImage('s_success.png'), msg = '', str = '';
802                     if (logVars['general_log'] == 'ON') {
803                         if (logVars['slow_query_log'] == 'ON') {
804                             msg = PMA_messages.strBothLogOn;
805                         } else {
806                             msg = PMA_messages.strGenLogOn;
807                         }
808                     }
810                     if (msg.length === 0 && logVars['slow_query_log'] == 'ON') {
811                         msg = PMA_messages.strSlowLogOn;
812                     }
814                     if (msg.length === 0) {
815                         icon = PMA_getImage('s_error.png');
816                         msg = PMA_messages.strBothLogOff;
817                     }
819                     str = '<b>' + PMA_messages.strCurrentSettings + '</b><br/><div class="smallIndent">';
820                     str += icon + msg + '<br />';
822                     if (logVars['log_output'] != 'TABLE') {
823                         str += PMA_getImage('s_error.png') + ' ' + PMA_messages.strLogOutNotTable + '<br />';
824                     } else {
825                         str += PMA_getImage('s_success.png') + ' ' + PMA_messages.strLogOutIsTable + '<br />';
826                     }
828                     if (logVars['slow_query_log'] == 'ON') {
829                         if (logVars['long_query_time'] > 2) {
830                             str += PMA_getImage('s_attention.png') + ' ';
831                             str += $.sprintf(PMA_messages.strSmallerLongQueryTimeAdvice, logVars['long_query_time']);
832                             str += '<br />';
833                         }
835                         if (logVars['long_query_time'] < 2) {
836                             str += PMA_getImage('s_success.png') + ' ';
837                             str += $.sprintf(PMA_messages.strLongQueryTimeSet, logVars['long_query_time']);
838                             str += '<br />';
839                         }
840                     }
842                     str += '</div>';
844                     if (is_superuser) {
845                         str += '<p></p><b>' + PMA_messages.strChangeSettings + '</b>';
846                         str += '<div class="smallIndent">';
847                         str += PMA_messages.strSettingsAppliedGlobal + '<br/>';
849                         var varValue = 'TABLE';
850                         if (logVars['log_output'] == 'TABLE') {
851                             varValue = 'FILE';
852                         }
854                         str += '- <a class="set" href="#log_output-' + varValue + '">';
855                         str += $.sprintf(PMA_messages.strSetLogOutput, varValue);
856                         str += ' </a><br />';
858                         if (logVars['general_log'] != 'ON') {
859                             str += '- <a class="set" href="#general_log-ON">';
860                             str += $.sprintf(PMA_messages.strEnableVar, 'general_log');
861                             str += ' </a><br />';
862                         } else {
863                             str += '- <a class="set" href="#general_log-OFF">';
864                             str += $.sprintf(PMA_messages.strDisableVar, 'general_log');
865                             str += ' </a><br />';
866                         }
868                         if (logVars['slow_query_log'] != 'ON') {
869                             str += '- <a class="set" href="#slow_query_log-ON">';
870                             str +=  $.sprintf(PMA_messages.strEnableVar, 'slow_query_log');
871                             str += ' </a><br />';
872                         } else {
873                             str += '- <a class="set" href="#slow_query_log-OFF">';
874                             str +=  $.sprintf(PMA_messages.strDisableVar, 'slow_query_log');
875                             str += ' </a><br />';
876                         }
878                         varValue = 5;
879                         if (logVars['long_query_time'] > 2) {
880                             varValue = 1;
881                         }
883                         str += '- <a class="set" href="#long_query_time-' + varValue + '">';
884                         str += $.sprintf(PMA_messages.setSetLongQueryTime, varValue);
885                         str += ' </a><br />';
887                     } else {
888                         str += PMA_messages.strNoSuperUser + '<br/>';
889                     }
891                     str += '</div>';
893                     $dialog.find('div.monitorUse').toggle(
894                         logVars['log_output'] == 'TABLE' && (logVars['slow_query_log'] == 'ON' || logVars['general_log'] == 'ON')
895                     );
897                     $dialog.find('div.ajaxContent').html(str);
898                     $dialog.find('img.ajaxIcon').hide();
899                     $dialog.find('a.set').click(function () {
900                         var nameValue = $(this).attr('href').split('-');
901                         loadLogVars({ varName: nameValue[0].substr(1), varValue: nameValue[1]});
902                         $dialog.find('img.ajaxIcon').show();
903                     });
904                 }
905             );
906         };
909         loadLogVars();
911         return false;
912     });
914     $('input[name="chartType"]').change(function () {
915         $('#chartVariableSettings').toggle(this.checked && this.value == 'variable');
916         var title = $('input[name="chartTitle"]').val();
917         if (title == PMA_messages.strChartTitle
918            || title == $('label[for="' + $('input[name="chartTitle"]').data('lastRadio') + '"]').text()
919         ) {
920             $('input[name="chartTitle"]')
921                 .data('lastRadio', $(this).attr('id'))
922                 .val($('label[for="' + $(this).attr('id') + '"]').text());
923         }
925     });
927     $('input[name="useDivisor"]').change(function () {
928         $('span.divisorInput').toggle(this.checked);
929     });
931     $('input[name="useUnit"]').change(function () {
932         $('span.unitInput').toggle(this.checked);
933     });
935     $('select[name="varChartList"]').change(function () {
936         if (this.selectedIndex !== 0) {
937             $('#variableInput').val(this.value);
938         }
939     });
941     $('a[href="#kibDivisor"]').click(function (event) {
942         event.preventDefault();
943         $('input[name="valueDivisor"]').val(1024);
944         $('input[name="valueUnit"]').val(PMA_messages.strKiB);
945         $('span.unitInput').toggle(true);
946         $('input[name="useUnit"]').prop('checked', true);
947         return false;
948     });
950     $('a[href="#mibDivisor"]').click(function (event) {
951         event.preventDefault();
952         $('input[name="valueDivisor"]').val(1024 * 1024);
953         $('input[name="valueUnit"]').val(PMA_messages.strMiB);
954         $('span.unitInput').toggle(true);
955         $('input[name="useUnit"]').prop('checked', true);
956         return false;
957     });
959     $('a[href="#submitClearSeries"]').click(function (event) {
960         event.preventDefault();
961         $('#seriesPreview').html('<i>' + PMA_messages.strNone + '</i>');
962         newChart = null;
963         $('#clearSeriesLink').hide();
964     });
966     $('a[href="#submitAddSeries"]').click(function (event) {
967         event.preventDefault();
968         if ($('#variableInput').val() === "") {
969             return false;
970         }
972         if (newChart === null) {
973             $('#seriesPreview').html('');
975             newChart = {
976                 title: $('input[name="chartTitle"]').val(),
977                 nodes: [],
978                 series: [],
979                 maxYLabel: 0
980             };
981         }
983         var serie = {
984             dataPoints: [{ type: 'statusvar', name: $('#variableInput').val() }],
985             display: $('input[name="differentialValue"]').prop('checked') ? 'differential' : ''
986         };
988         if (serie.dataPoints[0].name == 'Processes') {
989             serie.dataPoints[0].type = 'proc';
990         }
992         if ($('input[name="useDivisor"]').prop('checked')) {
993             serie.valueDivisor = parseInt($('input[name="valueDivisor"]').val(), 10);
994         }
996         if ($('input[name="useUnit"]').prop('checked')) {
997             serie.unit = $('input[name="valueUnit"]').val();
998         }
1000         var str = serie.display == 'differential' ? ', ' + PMA_messages.strDifferential : '';
1001         str += serie.valueDivisor ? (', ' + $.sprintf(PMA_messages.strDividedBy, serie.valueDivisor)) : '';
1002         str += serie.unit ? (', ' + PMA_messages.strUnit + ': ' + serie.unit) : '';
1004         var newSeries = {
1005             label: $('#variableInput').val().replace(/_/g, " ")
1006         };
1007         newChart.series.push(newSeries);
1008         $('#seriesPreview').append('- ' + newSeries.label + str + '<br/>');
1009         newChart.nodes.push(serie);
1010         $('#variableInput').val('');
1011         $('input[name="differentialValue"]').prop('checked', true);
1012         $('input[name="useDivisor"]').prop('checked', false);
1013         $('input[name="useUnit"]').prop('checked', false);
1014         $('input[name="useDivisor"]').trigger('change');
1015         $('input[name="useUnit"]').trigger('change');
1016         $('select[name="varChartList"]').get(0).selectedIndex = 0;
1018         $('#clearSeriesLink').show();
1020         return false;
1021     });
1023     $("#variableInput").autocomplete({
1024         source: variableNames
1025     });
1027     /* Initializes the monitor, called only once */
1028     function initGrid() {
1030         var i;
1032         /* Apply default values & config */
1033         if (window.localStorage) {
1034             if (window.localStorage['monitorCharts']) {
1035                 runtime.charts = $.parseJSON(window.localStorage['monitorCharts']);
1036             }
1037             if (window.localStorage['monitorSettings']) {
1038                 monitorSettings = $.parseJSON(window.localStorage['monitorSettings']);
1039             }
1041             $('a[href="#clearMonitorConfig"]').toggle(runtime.charts !== null);
1043             if (runtime.charts !== null && monitorProtocolVersion != window.localStorage['monitorVersion']) {
1044                 $('#emptyDialog').dialog({title: PMA_messages.strIncompatibleMonitorConfig});
1045                 $('#emptyDialog').html(PMA_messages.strIncompatibleMonitorConfigDescription);
1047                 var dlgBtns = {};
1048                 dlgBtns[PMA_messages.strClose] = function () { $(this).dialog('close'); };
1050                 $('#emptyDialog').dialog({
1051                     width: 400,
1052                     buttons: dlgBtns
1053                 });
1054             }
1055         }
1057         if (runtime.charts === null) {
1058             runtime.charts = defaultChartGrid;
1059         }
1060         if (monitorSettings === null) {
1061             monitorSettings = defaultMonitorSettings;
1062         }
1064         $('select[name="gridChartRefresh"]').val(monitorSettings.gridRefresh / 1000);
1065         $('select[name="chartColumns"]').val(monitorSettings.columns);
1067         if (monitorSettings.gridMaxPoints == 'auto') {
1068             runtime.gridMaxPoints = Math.round((monitorSettings.chartSize.width - 40) / 12);
1069         } else {
1070             runtime.gridMaxPoints = monitorSettings.gridMaxPoints;
1071         }
1073         runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
1074         runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh;
1076         /* Calculate how much spacing there is between each chart */
1077         $('#chartGrid').html('<tr><td></td><td></td></tr><tr><td></td><td></td></tr>');
1078         chartSpacing = {
1079             width: $('#chartGrid td:nth-child(2)').offset().left -
1080                 $('#chartGrid td:nth-child(1)').offset().left,
1081             height: $('#chartGrid tr:nth-child(2) td:nth-child(2)').offset().top -
1082                 $('#chartGrid tr:nth-child(1) td:nth-child(1)').offset().top
1083         };
1084         $('#chartGrid').html('');
1086         /* Add all charts - in correct order */
1087         var keys = [];
1088         $.each(runtime.charts, function (key, value) {
1089             keys.push(key);
1090         });
1091         keys.sort();
1092         for (i = 0; i < keys.length; i++) {
1093             addChart(runtime.charts[keys[i]], true);
1094         }
1096         /* Fill in missing cells */
1097         var numCharts = $('#chartGrid .monitorChart').length;
1098         var numMissingCells = (monitorSettings.columns - numCharts % monitorSettings.columns) % monitorSettings.columns;
1099         for (i = 0; i < numMissingCells; i++) {
1100             $('#chartGrid tr:last').append('<td></td>');
1101         }
1103         // Empty cells should keep their size so you can drop onto them
1104         $('#chartGrid tr td').css('width', chartSize().width + 'px');
1106         buildRequiredDataList();
1107         refreshChartGrid();
1108     }
1110     /* Calls destroyGrid() and initGrid(), but before doing so it saves the chart
1111      * data from each chart and restores it after the monitor is initialized again */
1112     function rebuildGrid() {
1113         var oldData = null;
1114         if (runtime.charts) {
1115             oldData = {};
1116             $.each(runtime.charts, function (key, chartObj) {
1117                 for (var i = 0, l = chartObj.nodes.length; i < l; i++) {
1118                     oldData[chartObj.nodes[i].dataPoint] = [];
1119                     for (var j = 0, ll = chartObj.chart.series[i].data.length; j < ll; j++) {
1120                         oldData[chartObj.nodes[i].dataPoint].push([chartObj.chart.series[i].data[j].x, chartObj.chart.series[i].data[j].y]);
1121                     }
1122                 }
1123             });
1124         }
1126         destroyGrid();
1127         initGrid();
1129         if (oldData) {
1130             $.each(runtime.charts, function (key, chartObj) {
1131                 for (var j = 0, l = chartObj.nodes.length; j < l; j++) {
1132                     if (oldData[chartObj.nodes[j].dataPoint]) {
1133                         chartObj.chart.series[j].setData(oldData[chartObj.nodes[j].dataPoint]);
1134                     }
1135                 }
1136             });
1137         }
1138     }
1140     /* Calculactes the dynamic chart size that depends on the column width */
1141     function chartSize() {
1142         var wdt = $('#logTable').innerWidth() / monitorSettings.columns - (monitorSettings.columns - 1) * chartSpacing.width;
1143         return {
1144             width: wdt,
1145             height: 0.75 * wdt
1146         };
1147     }
1149     /* Adds a chart to the chart grid */
1150     function addChart(chartObj, initialize) {
1152         var i;
1153         var settings = {
1154             title: escapeHtml(chartObj.title),
1155             grid: {
1156                 drawBorder: false,
1157                 shadow: false,
1158                 background: 'rgba(0,0,0,0)'
1159             },
1160             axes: {
1161                 xaxis: {
1162                     renderer: $.jqplot.DateAxisRenderer,
1163                     tickOptions: {
1164                         formatString: '%H:%M:%S',
1165                         showGridline: false
1166                     },
1167                     min: runtime.xmin,
1168                     max: runtime.xmax
1169                 },
1170                 yaxis: {
1171                     min: 0,
1172                     max: 100,
1173                     tickInterval: 20
1174                 }
1175             },
1176             seriesDefaults: {
1177                 rendererOptions: {
1178                     smooth: true
1179                 },
1180                 showLine: true,
1181                 lineWidth: 2
1182             },
1183             highlighter: {
1184                 show: true
1185             }
1186         };
1188         if (settings.title === PMA_messages.strSystemCPUUsage
1189             || settings.title === PMA_messages.strQueryCacheEfficiency) {
1190             settings.axes.yaxis.tickOptions = {
1191                 formatString: "%d %%"
1192             };
1193         } else if (settings.title === PMA_messages.strSystemMemory
1194             || settings.title === PMA_messages.strSystemSwap
1195         ) {
1196             settings.stackSeries = true;
1197             settings.axes.yaxis.tickOptions = {
1198                 formatter: $.jqplot.byteFormatter(2) // MiB
1199             };
1200         } else if (settings.title === PMA_messages.strTraffic) {
1201             settings.axes.yaxis.tickOptions = {
1202                 formatter: $.jqplot.byteFormatter(1) // KiB
1203             };
1204         }
1206         settings.series = chartObj.series;
1208         if ($('#' + 'gridchart' + runtime.chartAI).length === 0) {
1209             var numCharts = $('#chartGrid .monitorChart').length;
1211             if (numCharts === 0 || (numCharts % monitorSettings.columns === 0)) {
1212                 $('#chartGrid').append('<tr></tr>');
1213             }
1215             $('#chartGrid tr:last').append('<td><div id="gridChartContainer'
1216                 + runtime.chartAI + '" class=""><div class="ui-state-default monitorChart" id="'
1217                 + 'gridchart' + runtime.chartAI + '"></div></div></td>');
1218         }
1220         // Set series' data as [0,0], smooth lines won't plot with data array having null values.
1221         // also chart won't plot initially with no data and data comes on refreshChartGrid()
1222         var series = [];
1223         for (i in chartObj.series) {
1224             series.push([[0, 0]]);
1225         }
1227         // set Tooltip for each series
1228         for (i in settings.series) {
1229             settings.series[i].highlighter = {
1230                 show: true,
1231                 tooltipContentEditor: function (str, seriesIndex, pointIndex, plot) {
1232                     var j;
1233                     // TODO: move style to theme CSS
1234                     var tooltipHtml = '<div style="font-size:12px;background-color:#FFFFFF;' +
1235                         'opacity:0.95;filter:alpha(opacity=95);padding:5px;">';
1236                     // x value i.e. time
1237                     var timeValue = str.split(",")[0];
1238                     var seriesValue;
1239                     tooltipHtml += 'Time: ' + timeValue;
1240                     tooltipHtml += '<span style="font-weight:bold;">';
1241                     // Add y values to the tooltip per series
1242                     for (j in plot.series) {
1243                         // get y value if present
1244                         if (plot.series[j].data.length > pointIndex) {
1245                             seriesValue = plot.series[j].data[pointIndex][1];
1246                         } else {
1247                             return;
1248                         }
1249                         var seriesLabel = plot.series[j].label;
1250                         var seriesColor = plot.series[j].color;
1251                         // format y value
1252                         if (plot.series[0]._yaxis.tickOptions.formatter) {
1253                             // using formatter function
1254                             seriesValue = plot.series[0]._yaxis.tickOptions.formatter('%s', seriesValue);
1255                         } else if (plot.series[0]._yaxis.tickOptions.formatString) {
1256                             // using format string
1257                             seriesValue = $.sprintf(plot.series[0]._yaxis.tickOptions.formatString, seriesValue);
1258                         }
1259                         tooltipHtml += '<br /><span style="color:' + seriesColor + '">' +
1260                             seriesLabel + ': ' + seriesValue + '</span>';
1261                     }
1262                     tooltipHtml += '</span></div>';
1263                     return tooltipHtml;
1264                 }
1265             };
1266         }
1268         chartObj.chart = $.jqplot('gridchart' + runtime.chartAI, series, settings);
1269         // remove [0,0] after plotting
1270         for (i in chartObj.chart.series) {
1271             chartObj.chart.series[i].data.shift();
1272         }
1274         var $legend = $('<div />').css('padding', '0.5em');
1275         for (i in chartObj.chart.series) {
1276             $legend.append(
1277                 $('<div />').append(
1278                     $('<div>').css({
1279                         width: '1em',
1280                         height: '1em',
1281                         background: chartObj.chart.seriesColors[i]
1282                     }).addClass('floatleft')
1283                 ).append(
1284                     $('<div>').text(
1285                         chartObj.chart.series[i].label
1286                     ).addClass('floatleft')
1287                 ).append(
1288                     $('<div class="clearfloat">')
1289                 ).addClass('floatleft')
1290             );
1291         }
1292         $('#gridchart' + runtime.chartAI)
1293             .css('overflow', 'hidden')
1294             .parent()
1295             .append($legend);
1297         if (initialize !== true) {
1298             runtime.charts['c' + runtime.chartAI] = chartObj;
1299             buildRequiredDataList();
1300         }
1302         // time span selection
1303         $('#gridchart' + runtime.chartAI).bind('jqplotMouseDown', function (ev, gridpos, datapos, neighbor, plot) {
1304             drawTimeSpan = true;
1305             selectionTimeDiff.push(datapos.xaxis);
1306             if ($('#selection_box').length) {
1307                 $('#selection_box').remove();
1308             }
1309             selectionBox = $('<div id="selection_box" style="z-index:1000;height:250px;position:absolute;background-color:#87CEEB;opacity:0.4;filter:alpha(opacity=40);pointer-events:none;">');
1310             $(document.body).append(selectionBox);
1311             selectionStartX = ev.pageX;
1312             selectionStartY = ev.pageY;
1313             selectionBox
1314                 .attr({id: 'selection_box'})
1315                 .css({
1316                     top: selectionStartY - gridpos.y,
1317                     left: selectionStartX
1318                 })
1319                 .fadeIn();
1320         });
1322         $('#gridchart' + runtime.chartAI).bind('jqplotMouseUp', function (ev, gridpos, datapos, neighbor, plot) {
1323             if (! drawTimeSpan || editMode) {
1324                 return;
1325             }
1327             selectionTimeDiff.push(datapos.xaxis);
1329             if (selectionTimeDiff[1] <= selectionTimeDiff[0]) {
1330                 selectionTimeDiff = [];
1331                 return;
1332             }
1333             //get date from timestamp
1334             var min = new Date(Math.ceil(selectionTimeDiff[0]));
1335             var max = new Date(Math.ceil(selectionTimeDiff[1]));
1336             PMA_getLogAnalyseDialog(min, max);
1337             selectionTimeDiff = [];
1338             drawTimeSpan = false;
1339         });
1341         $('#gridchart' + runtime.chartAI).bind('jqplotMouseMove', function (ev, gridpos, datapos, neighbor, plot) {
1342             if (! drawTimeSpan || editMode) {
1343                 return;
1344             }
1345             if (selectionStartX !== undefined) {
1346                 $('#selection_box')
1347                     .css({
1348                         width: Math.ceil(ev.pageX - selectionStartX)
1349                     })
1350                     .fadeIn();
1351             }
1352         });
1354         $('#gridchart' + runtime.chartAI).bind('jqplotMouseLeave', function (ev, gridpos, datapos, neighbor, plot) {
1355             drawTimeSpan = false;
1356         });
1358         $(document.body).mouseup(function () {
1359             if ($('#selection_box').length) {
1360                 selectionBox.remove();
1361             }
1362         });
1364         // Edit, Print icon only in edit mode
1365         $('#chartGrid div svg').find('*[zIndex=20], *[zIndex=21], *[zIndex=19]').toggle(editMode);
1367         runtime.chartAI++;
1368     }
1370     /* Opens a dialog that allows one to edit the title and series labels of the supplied chart */
1371     function editChart(chartObj) {
1372         var htmlnode = chartObj.options.chart.renderTo;
1373         if (! htmlnode) {
1374             return;
1375         }
1377         var chart = null;
1378         var chartKey = null;
1379         $.each(runtime.charts, function (key, value) {
1380             if (value.chart.options.chart.renderTo == htmlnode) {
1381                 chart = value;
1382                 chartKey = key;
1383                 return false;
1384             }
1385         });
1387         if (chart === null) {
1388             return;
1389         }
1391         var htmlStr = '<p><b>' + PMA_messages.strChartTitle + ': </b> <br/> <input type="text" size="35" name="chartTitle" value="' + chart.title + '" />';
1392         htmlStr += '</p><p><b>' + PMA_messages.strSeries + ':</b> </p><ol>';
1393         for (var i = 0; i < chart.nodes.length; i++) {
1394             htmlStr += '<li><i>' + chart.nodes[i].dataPoints[0].name  + ': </i><br/><input type="text" name="chartSerie-' + i + '" value="' + chart.nodes[i].name + '" /></li>';
1395         }
1397         dlgBtns = {};
1398         dlgBtns[PMA_messages.strSave] = function () {
1399             runtime.charts[chartKey].title = $('#emptyDialog input[name="chartTitle"]').val();
1400             runtime.charts[chartKey].chart.setTitle({ text: runtime.charts[chartKey].title });
1402             $('#emptyDialog input[name*="chartSerie"]').each(function () {
1403                 var $t = $(this);
1404                 var idx = $t.attr('name').split('-')[1];
1405                 runtime.charts[chartKey].nodes[idx].name = $t.val();
1406                 runtime.charts[chartKey].chart.series[idx].name = $t.val();
1407             });
1409             $(this).dialog('close');
1410             saveMonitor();
1411         };
1412         dlgBtns[PMA_messages.strCancel] = function () {
1413             $(this).dialog('close');
1414         };
1416         $('#emptyDialog').html(htmlStr + '</ol>');
1417         $('#emptyDialog').dialog({
1418             title: PMA_messages.strChartEdit,
1419             width: 'auto',
1420             height: 'auto',
1421             buttons: dlgBtns
1422         });
1423     }
1425     function PMA_getLogAnalyseDialog(min, max) {
1426         $('#logAnalyseDialog input[name="dateStart"]')
1427             .val(formatDate(min, 'yyyy-MM-dd HH:mm:ss'));
1428         $('#logAnalyseDialog input[name="dateEnd"]')
1429             .val(formatDate(max, 'yyyy-MM-dd HH:mm:ss'));
1431         var dlgBtns = { };
1433         dlgBtns[PMA_messages.strFromSlowLog] = function () {
1434             loadLog('slow', min, max);
1435             $(this).dialog("close");
1436         };
1438         dlgBtns[PMA_messages.strFromGeneralLog] = function () {
1439             loadLog('general', min, max);
1440             $(this).dialog("close");
1441         };
1443         $('#logAnalyseDialog').dialog({
1444             width: 'auto',
1445             height: 'auto',
1446             buttons: dlgBtns
1447         });
1448     }
1450     function loadLog(type, min, max) {
1451         var dateStart = Date.parse($('#logAnalyseDialog input[name="dateStart"]').prop('value')) || min;
1452         var dateEnd = Date.parse($('#logAnalyseDialog input[name="dateEnd"]').prop('value')) || max;
1454         loadLogStatistics({
1455             src: type,
1456             start: dateStart,
1457             end: dateEnd,
1458             removeVariables: $('#removeVariables').prop('checked'),
1459             limitTypes: $('#limitTypes').prop('checked')
1460         });
1461     }
1463     /* Removes a chart from the grid */
1464     function removeChart(chartObj) {
1465         var htmlnode = chartObj.options.chart.renderTo;
1466         if (! htmlnode) {
1467             return;
1468         }
1470         $.each(runtime.charts, function (key, value) {
1471             if (value.chart.options.chart.renderTo == htmlnode) {
1472                 delete runtime.charts[key];
1473                 return false;
1474             }
1475         });
1477         buildRequiredDataList();
1479         // Using settimeout() because clicking the remove link fires an onclick event
1480         // which throws an error when the chart is destroyed
1481         setTimeout(function () {
1482             chartObj.destroy();
1483             $('#' + htmlnode).remove();
1484         }, 10);
1486         saveMonitor(); // Save settings
1487     }
1489     /* Called in regular intervalls, this function updates the values of each chart in the grid */
1490     function refreshChartGrid() {
1491         /* Send to server */
1492         runtime.refreshRequest = $.post('server_status_monitor.php?' + PMA_commonParams.get('common_query'), {
1493             ajax_request: true,
1494             chart_data: 1,
1495             type: 'chartgrid',
1496             requiredData: $.toJSON(runtime.dataList)
1497         }, function (data) {
1498             var chartData;
1499             if (data.success === true) {
1500                 chartData = data.message;
1501             } else {
1502                 return serverResponseError();
1503             }
1504             var value, i = 0;
1505             var diff;
1506             var total;
1508             /* Update values in each graph */
1509             $.each(runtime.charts, function (orderKey, elem) {
1510                 var key = elem.chartID;
1511                 // If newly added chart, we have no data for it yet
1512                 if (! chartData[key]) {
1513                     return;
1514                 }
1515                 // Draw all series
1516                 total = 0;
1517                 for (var j = 0; j < elem.nodes.length; j++) {
1518                     // Update x-axis
1519                     if (i === 0 && j === 0) {
1520                         if (oldChartData === null) {
1521                             diff = chartData.x - runtime.xmax;
1522                         } else {
1523                             diff = parseInt(chartData.x - oldChartData.x, 10);
1524                         }
1526                         runtime.xmin += diff;
1527                         runtime.xmax += diff;
1528                     }
1530                     //elem.chart.xAxis[0].setExtremes(runtime.xmin, runtime.xmax, false);
1531                     /* Calculate y value */
1533                     // If transform function given, use it
1534                     if (elem.nodes[j].transformFn) {
1535                         value = chartValueTransform(
1536                             elem.nodes[j].transformFn,
1537                             chartData[key][j],
1538                             // Check if first iteration (oldChartData==null), or if newly added chart oldChartData[key]==null
1539                             (oldChartData === null
1540                              || oldChartData[key] === null
1541                              || oldChartData[key] === undefined ? null : oldChartData[key][j])
1542                         );
1544                     // Otherwise use original value and apply differential and divisor if given,
1545                     // in this case we have only one data point per series - located at chartData[key][j][0]
1546                     } else {
1547                         value = parseFloat(chartData[key][j][0].value);
1549                         if (elem.nodes[j].display == 'differential') {
1550                             if (oldChartData === null
1551                              || oldChartData[key] === null
1552                              || oldChartData[key] === undefined
1553                             ) {
1554                                 continue;
1555                             }
1556                             value -= oldChartData[key][j][0].value;
1557                         }
1559                         if (elem.nodes[j].valueDivisor) {
1560                             value = value / elem.nodes[j].valueDivisor;
1561                         }
1562                     }
1564                     // Set y value, if defined
1565                     if (value !== undefined) {
1566                         elem.chart.series[j].data.push([chartData.x, value]);
1567                         if (value > elem.maxYLabel) {
1568                             elem.maxYLabel = value;
1569                         } else if (elem.maxYLabel === 0) {
1570                             elem.maxYLabel = 0.5;
1571                         }
1572                         // free old data point values and update maxYLabel
1573                         if (elem.chart.series[j].data.length > runtime.gridMaxPoints
1574                             && elem.chart.series[j].data[0][0] < runtime.xmin) {
1575                             // check if the next freeable point is highest
1576                             if (elem.maxYLabel <= elem.chart.series[j].data[0][1]) {
1577                                 elem.chart.series[j].data.splice(0, elem.chart.series[j].data.length - runtime.gridMaxPoints);
1578                                 elem.maxYLabel = getMaxYLabel(elem.chart.series[j].data);
1579                             } else {
1580                                 elem.chart.series[j].data.splice(0, elem.chart.series[j].data.length - runtime.gridMaxPoints);
1581                             }
1582                         }
1583                         if (elem.title === PMA_messages.strSystemMemory
1584                             || elem.title === PMA_messages.strSystemSwap
1585                         ) {
1586                             total += value;
1587                         }
1588                     }
1589                 }
1591                 // update chart options
1592                 // keep ticks number/positioning consistent while refreshrate changes
1593                 var tickInterval = (runtime.xmax - runtime.xmin) / 5;
1594                 elem.chart['axes']['xaxis'].ticks = [(runtime.xmax - tickInterval * 4),
1595                     (runtime.xmax - tickInterval * 3), (runtime.xmax - tickInterval * 2),
1596                     (runtime.xmax - tickInterval), runtime.xmax];
1598                 if (elem.title !== PMA_messages.strSystemCPUUsage
1599                     && elem.title !== PMA_messages.strQueryCacheEfficiency
1600                     && elem.title !== PMA_messages.strSystemMemory
1601                     && elem.title !== PMA_messages.strSystemSwap
1602                 ) {
1603                     elem.chart['axes']['yaxis']['max'] = Math.ceil(elem.maxYLabel * 1.1);
1604                     elem.chart['axes']['yaxis']['tickInterval'] = Math.ceil(elem.maxYLabel * 1.1 / 5);
1605                 } else if (elem.title === PMA_messages.strSystemMemory
1606                     || elem.title === PMA_messages.strSystemSwap
1607                 ) {
1608                     elem.chart['axes']['yaxis']['max'] = Math.ceil(total * 1.1 / 100) * 100;
1609                     elem.chart['axes']['yaxis']['tickInterval'] = Math.ceil(total * 1.1 / 5);
1610                 }
1611                 i++;
1613                 if (runtime.redrawCharts) {
1614                     elem.chart.replot();
1615                 }
1616             });
1618             oldChartData = chartData;
1620             runtime.refreshTimeout = setTimeout(refreshChartGrid, monitorSettings.gridRefresh);
1621         });
1622     }
1624     /* Function to get highest plotted point's y label, to scale the chart,
1625      * TODO: make jqplot's autoscale:true work here
1626      */
1627     function getMaxYLabel(dataValues) {
1628         var maxY = dataValues[0][1];
1629         $.each(dataValues, function (k, v) {
1630             maxY = (v[1] > maxY) ? v[1] : maxY;
1631         });
1632         return maxY;
1633     }
1635     /* Function that supplies special value transform functions for chart values */
1636     function chartValueTransform(name, cur, prev) {
1637         switch (name) {
1638         case 'cpu-linux':
1639             if (prev === null) {
1640                 return undefined;
1641             }
1642             // cur and prev are datapoint arrays, but containing
1643             // only 1 element for cpu-linux
1644             cur = cur[0];
1645             prev = prev[0];
1647             var diff_total = cur.busy + cur.idle - (prev.busy + prev.idle);
1648             var diff_idle = cur.idle - prev.idle;
1649             return 100 * (diff_total - diff_idle) / diff_total;
1651         // Query cache efficiency (%)
1652         case 'qce':
1653             if (prev === null) {
1654                 return undefined;
1655             }
1656             // cur[0].value is Qcache_hits, cur[1].value is Com_select
1657             var diffQHits = cur[0].value - prev[0].value;
1658             // No NaN please :-)
1659             if (cur[1].value - prev[1].value === 0) {
1660                 return 0;
1661             }
1663             return diffQHits / (cur[1].value - prev[1].value + diffQHits) * 100;
1665         // Query cache usage (%)
1666         case 'qcu':
1667             if (cur[1].value === 0) {
1668                 return 0;
1669             }
1670             // cur[0].value is Qcache_free_memory, cur[1].value is query_cache_size
1671             return 100 - cur[0].value / cur[1].value * 100;
1673         }
1674         return undefined;
1675     }
1677     /* Build list of nodes that need to be retrieved from server.
1678      * It creates something like a stripped down version of the runtime.charts object.
1679      */
1680     function buildRequiredDataList() {
1681         runtime.dataList = {};
1682         // Store an own id, because the property name is subject of reordering,
1683         // thus destroying our mapping with runtime.charts <=> runtime.dataList
1684         var chartID = 0;
1685         $.each(runtime.charts, function (key, chart) {
1686             runtime.dataList[chartID] = [];
1687             for (var i = 0, l = chart.nodes.length; i < l; i++) {
1688                 runtime.dataList[chartID][i] = chart.nodes[i].dataPoints;
1689             }
1690             runtime.charts[key].chartID = chartID;
1691             chartID++;
1692         });
1693     }
1695     /* Loads the log table data, generates the table and handles the filters */
1696     function loadLogStatistics(opts) {
1697         var tableStr = '';
1698         var logRequest = null;
1700         if (! opts.removeVariables) {
1701             opts.removeVariables = false;
1702         }
1703         if (! opts.limitTypes) {
1704             opts.limitTypes = false;
1705         }
1707         $('#emptyDialog').dialog({title: PMA_messages.strAnalysingLogsTitle});
1708         $('#emptyDialog').html(PMA_messages.strAnalysingLogs +
1709                                 ' <img class="ajaxIcon" src="' + pmaThemeImage +
1710                                 'ajax_clock_small.gif" alt="">');
1711         var dlgBtns = {};
1713         dlgBtns[PMA_messages.strCancelRequest] = function () {
1714             if (logRequest !== null) {
1715                 logRequest.abort();
1716             }
1718             $(this).dialog("close");
1719         };
1721         $('#emptyDialog').dialog({
1722             width: 'auto',
1723             height: 'auto',
1724             buttons: dlgBtns
1725         });
1728         logRequest = $.get('server_status_monitor.php?' + PMA_commonParams.get('common_query'),
1729             {   ajax_request: true,
1730                 log_data: 1,
1731                 type: opts.src,
1732                 time_start: Math.round(opts.start / 1000),
1733                 time_end: Math.round(opts.end / 1000),
1734                 removeVariables: opts.removeVariables,
1735                 limitTypes: opts.limitTypes
1736             },
1737             function (data) {
1738                 var logData;
1739                 var dlgBtns = {};
1740                 if (data.success === true) {
1741                     logData = data.message;
1742                 } else {
1743                     return serverResponseError();
1744                 }
1746                 if (logData.rows.length !== 0) {
1747                     runtime.logDataCols = buildLogTable(logData);
1749                     /* Show some stats in the dialog */
1750                     $('#emptyDialog').dialog({title: PMA_messages.strLoadingLogs});
1751                     $('#emptyDialog').html('<p>' + PMA_messages.strLogDataLoaded + '</p>');
1752                     $.each(logData.sum, function (key, value) {
1753                         key = key.charAt(0).toUpperCase() + key.slice(1).toLowerCase();
1754                         if (key == 'Total') {
1755                             key = '<b>' + key + '</b>';
1756                         }
1757                         $('#emptyDialog').append(key + ': ' + value + '<br/>');
1758                     });
1760                     /* Add filter options if more than a bunch of rows there to filter */
1761                     if (logData.numRows > 12) {
1762                         $('#logTable').prepend(
1763                             '<fieldset id="logDataFilter">' +
1764                             '    <legend>' + PMA_messages.strFiltersForLogTable + '</legend>' +
1765                             '    <div class="formelement">' +
1766                             '        <label for="filterQueryText">' + PMA_messages.strFilterByWordRegexp + '</label>' +
1767                             '        <input name="filterQueryText" type="text" id="filterQueryText" style="vertical-align: baseline;" />' +
1768                             '    </div>' +
1769                             ((logData.numRows > 250) ? ' <div class="formelement"><button name="startFilterQueryText" id="startFilterQueryText">' + PMA_messages.strFilter + '</button></div>' : '') +
1770                             '    <div class="formelement">' +
1771                             '       <input type="checkbox" id="noWHEREData" name="noWHEREData" value="1" /> ' +
1772                             '       <label for="noWHEREData"> ' + PMA_messages.strIgnoreWhereAndGroup + '</label>' +
1773                             '   </div' +
1774                             '</fieldset>'
1775                         );
1777                         $('#logTable #noWHEREData').change(function () {
1778                             filterQueries(true);
1779                         });
1781                         if (logData.numRows > 250) {
1782                             $('#logTable #startFilterQueryText').click(filterQueries);
1783                         } else {
1784                             $('#logTable #filterQueryText').keyup(filterQueries);
1785                         }
1787                     }
1789                     dlgBtns[PMA_messages.strJumpToTable] = function () {
1790                         $(this).dialog("close");
1791                         $(document).scrollTop($('#logTable').offset().top);
1792                     };
1794                     $('#emptyDialog').dialog("option", "buttons", dlgBtns);
1796                 } else {
1797                     $('#emptyDialog').dialog({title: PMA_messages.strNoDataFoundTitle});
1798                     $('#emptyDialog').html('<p>' + PMA_messages.strNoDataFound + '</p>');
1800                     dlgBtns[PMA_messages.strClose] = function () {
1801                         $(this).dialog("close");
1802                     };
1804                     $('#emptyDialog').dialog("option", "buttons", dlgBtns);
1805                 }
1806             }
1807         );
1809         /* Handles the actions performed when the user uses any of the
1810          * log table filters which are the filter by name and grouping
1811          * with ignoring data in WHERE clauses
1812          *
1813          * @param boolean Should be true when the users enabled or disabled
1814          *                to group queries ignoring data in WHERE clauses
1815         */
1816         function filterQueries(varFilterChange) {
1817             var odd_row = false, cell, textFilter;
1818             var val = $('#logTable #filterQueryText').val();
1820             if (val.length === 0) {
1821                 textFilter = null;
1822             } else {
1823                 textFilter = new RegExp(val, 'i');
1824             }
1826             var rowSum = 0, totalSum = 0, i = 0, q;
1827             var noVars = $('#logTable #noWHEREData').prop('checked');
1828             var equalsFilter = /([^=]+)=(\d+|((\'|"|).*?[^\\])\4((\s+)|$))/gi;
1829             var functionFilter = /([a-z0-9_]+)\(.+?\)/gi;
1830             var filteredQueries = {}, filteredQueriesLines = {};
1831             var hide = false, rowData;
1832             var queryColumnName = runtime.logDataCols[runtime.logDataCols.length - 2];
1833             var sumColumnName = runtime.logDataCols[runtime.logDataCols.length - 1];
1834             var isSlowLog = opts.src == 'slow';
1835             var columnSums = {};
1837             // For the slow log we have to count many columns (query_time, lock_time, rows_examined, rows_sent, etc.)
1838             var countRow = function (query, row) {
1839                 var cells = row.match(/<td>(.*?)<\/td>/gi);
1840                 if (!columnSums[query]) {
1841                     columnSums[query] = [0, 0, 0, 0];
1842                 }
1844                 // lock_time and query_time and displayed in timespan format
1845                 columnSums[query][0] += timeToSec(cells[2].replace(/(<td>|<\/td>)/gi, ''));
1846                 columnSums[query][1] += timeToSec(cells[3].replace(/(<td>|<\/td>)/gi, ''));
1847                 // rows_examind and rows_sent are just numbers
1848                 columnSums[query][2] += parseInt(cells[4].replace(/(<td>|<\/td>)/gi, ''), 10);
1849                 columnSums[query][3] += parseInt(cells[5].replace(/(<td>|<\/td>)/gi, ''), 10);
1850             };
1852             // We just assume the sql text is always in the second last column, and that the total count is right of it
1853             $('#logTable table tbody tr td:nth-child(' + (runtime.logDataCols.length - 1) + ')').each(function () {
1854                 var $t = $(this);
1855                 // If query is a SELECT and user enabled or disabled to group
1856                 // queries ignoring data in where statements, we
1857                 // need to re-calculate the sums of each row
1858                 if (varFilterChange && $t.html().match(/^SELECT/i)) {
1859                     if (noVars) {
1860                         // Group on => Sum up identical columns, and hide all but 1
1862                         q = $t.text().replace(equalsFilter, '$1=...$6').trim();
1863                         q = q.replace(functionFilter, ' $1(...)');
1865                         // Js does not specify a limit on property name length,
1866                         // so we can abuse it as index :-)
1867                         if (filteredQueries[q]) {
1868                             filteredQueries[q] += parseInt($t.next().text(), 10);
1869                             totalSum += parseInt($t.next().text(), 10);
1870                             hide = true;
1871                         } else {
1872                             filteredQueries[q] = parseInt($t.next().text(), 10);
1873                             filteredQueriesLines[q] = i;
1874                             $t.text(q);
1875                         }
1876                         if (isSlowLog) {
1877                             countRow(q, $t.parent().html());
1878                         }
1880                     } else {
1881                         // Group off: Restore original columns
1883                         rowData = $t.parent().data('query');
1884                         // Restore SQL text
1885                         $t.text(rowData[queryColumnName]);
1886                         // Restore total count
1887                         $t.next().text(rowData[sumColumnName]);
1888                         // Restore slow log columns
1889                         if (isSlowLog) {
1890                             $t.parent().children('td:nth-child(3)').text(rowData['query_time']);
1891                             $t.parent().children('td:nth-child(4)').text(rowData['lock_time']);
1892                             $t.parent().children('td:nth-child(5)').text(rowData['rows_sent']);
1893                             $t.parent().children('td:nth-child(6)').text(rowData['rows_examined']);
1894                         }
1895                     }
1896                 }
1898                 // If not required to be hidden, do we need
1899                 // to hide because of a not matching text filter?
1900                 if (! hide && (textFilter !== null && ! textFilter.exec($t.text()))) {
1901                     hide = true;
1902                 }
1904                 // Now display or hide this column
1905                 if (hide) {
1906                     $t.parent().css('display', 'none');
1907                 } else {
1908                     totalSum += parseInt($t.next().text(), 10);
1909                     rowSum++;
1911                     odd_row = ! odd_row;
1912                     $t.parent().css('display', '');
1913                     if (odd_row) {
1914                         $t.parent().addClass('odd');
1915                         $t.parent().removeClass('even');
1916                     } else {
1917                         $t.parent().addClass('even');
1918                         $t.parent().removeClass('odd');
1919                     }
1920                 }
1922                 hide = false;
1923                 i++;
1924             });
1926             // We finished summarizing counts => Update count values of all grouped entries
1927             if (varFilterChange) {
1928                 if (noVars) {
1929                     var numCol, row, $table = $('#logTable table tbody');
1930                     $.each(filteredQueriesLines, function (key, value) {
1931                         if (filteredQueries[key] <= 1) {
1932                             return;
1933                         }
1935                         row =  $table.children('tr:nth-child(' + (value + 1) + ')');
1936                         numCol = row.children(':nth-child(' + (runtime.logDataCols.length) + ')');
1937                         numCol.text(filteredQueries[key]);
1939                         if (isSlowLog) {
1940                             row.children('td:nth-child(3)').text(secToTime(columnSums[key][0]));
1941                             row.children('td:nth-child(4)').text(secToTime(columnSums[key][1]));
1942                             row.children('td:nth-child(5)').text(columnSums[key][2]);
1943                             row.children('td:nth-child(6)').text(columnSums[key][3]);
1944                         }
1945                     });
1946                 }
1948                 $('#logTable table').trigger("update");
1949                 setTimeout(function () {
1950                     $('#logTable table').trigger('sorton', [[[runtime.logDataCols.length - 1, 1]]]);
1951                 }, 0);
1952             }
1954             // Display some stats at the bottom of the table
1955             $('#logTable table tfoot tr')
1956                 .html('<th colspan="' + (runtime.logDataCols.length - 1) + '">' +
1957                       PMA_messages.strSumRows + ' ' + rowSum + '<span style="float:right">' +
1958                       PMA_messages.strTotal + '</span></th><th class="right">' + totalSum + '</th>');
1959         }
1960     }
1962     /* Turns a timespan (12:12:12) into a number */
1963     function timeToSec(timeStr) {
1964         var time = timeStr.split(':');
1965         return (parseInt(time[0], 10) * 3600) + (parseInt(time[1], 10) * 60) + parseInt(time[2], 10);
1966     }
1968     /* Turns a number into a timespan (100 into 00:01:40) */
1969     function secToTime(timeInt) {
1970         var hours = Math.floor(timeInt / 3600);
1971         timeInt -= hours * 3600;
1972         var minutes = Math.floor(timeInt / 60);
1973         timeInt -= minutes * 60;
1975         if (hours < 10) {
1976             hours = '0' + hours;
1977         }
1978         if (minutes < 10) {
1979             minutes = '0' + minutes;
1980         }
1981         if (timeInt < 10) {
1982             timeInt = '0' + timeInt;
1983         }
1985         return hours + ':' + minutes + ':' + timeInt;
1986     }
1988     /* Constructs the log table out of the retrieved server data */
1989     function buildLogTable(data) {
1990         var rows = data.rows;
1991         var cols = [];
1992         var $table = $('<table class="sortable"></table>');
1993         var $tBody, $tRow, $tCell;
1995         $('#logTable').html($table);
1997         var formatValue = function (name, value) {
1998             if (name == 'user_host') {
1999                 return value.replace(/(\[.*?\])+/g, '');
2000             }
2001             return value;
2002         };
2004         for (var i = 0, l = rows.length; i < l; i++) {
2005             if (i === 0) {
2006                 $.each(rows[0], function (key, value) {
2007                     cols.push(key);
2008                 });
2009                 $table.append('<thead>' +
2010                               '<tr><th class="nowrap">' + cols.join('</th><th class="nowrap">') + '</th></tr>' +
2011                               '</thead>'
2012                 );
2014                 $table.append($tBody = $('<tbody></tbody>'));
2015             }
2017             $tBody.append($tRow = $('<tr class="noclick"></tr>'));
2018             var cl = '';
2019             for (var j = 0, ll = cols.length; j < ll; j++) {
2020                 // Assuming the query column is the second last
2021                 if (j == cols.length - 2 && rows[i][cols[j]].match(/^SELECT/i)) {
2022                     $tRow.append($tCell = $('<td class="linkElem">' + formatValue(cols[j], rows[i][cols[j]]) + '</td>'));
2023                     $tCell.click(openQueryAnalyzer);
2024                 } else {
2025                     $tRow.append('<td>' + formatValue(cols[j], rows[i][cols[j]]) + '</td>');
2026                 }
2028                 $tRow.data('query', rows[i]);
2029             }
2030         }
2032         $table.append('<tfoot>' +
2033                     '<tr><th colspan="' + (cols.length - 1) + '">' + PMA_messages.strSumRows +
2034                     ' ' + data.numRows + '<span style="float:right">' + PMA_messages.strTotal +
2035                     '</span></th><th class="right">' + data.sum.TOTAL + '</th></tr></tfoot>');
2037         // Append a tooltip to the count column, if there exist one
2038         if ($('#logTable th:last').html() == '#') {
2039             $('#logTable th:last').append('&nbsp;' + PMA_getImage('b_docs.png', '', {'class': 'qroupedQueryInfoIcon'}));
2041             var tooltipContent = PMA_messages.strCountColumnExplanation;
2042             if (groupInserts) {
2043                 tooltipContent += '<p>' + PMA_messages.strMoreCountColumnExplanation + '</p>';
2044             }
2046             PMA_tooltip(
2047                 $('img.qroupedQueryInfoIcon'),
2048                 'img',
2049                 tooltipContent
2050             );
2051         }
2053         $('#logTable table').tablesorter({
2054             sortList: [[cols.length - 1, 1]],
2055             widgets: ['fast-zebra']
2056         });
2058         $('#logTable table thead th')
2059             .append('<img class="icon sortableIcon" src="themes/dot.gif" alt="">');
2061         return cols;
2062     }
2064     /* Opens the query analyzer dialog */
2065     function openQueryAnalyzer() {
2066         var rowData = $(this).parent().data('query');
2067         var query = rowData.argument || rowData.sql_text;
2069         if (codemirror_editor) {
2070             query = PMA_SQLPrettyPrint(query);
2071             codemirror_editor.setValue(query);
2072             // Codemirror is bugged, it doesn't refresh properly sometimes.
2073             // Following lines seem to fix that
2074             setTimeout(function () {
2075                 codemirror_editor.refresh();
2076             }, 50);
2077         }
2078         else {
2079             $('#sqlquery').val(query);
2080         }
2082         var profilingChart = null;
2083         var dlgBtns = {};
2085         dlgBtns[PMA_messages.strAnalyzeQuery] = function () {
2086             loadQueryAnalysis(rowData);
2087         };
2088         dlgBtns[PMA_messages.strClose] = function () {
2089             $(this).dialog('close');
2090         };
2092         $('#queryAnalyzerDialog').dialog({
2093             width: 'auto',
2094             height: 'auto',
2095             resizable: false,
2096             buttons: dlgBtns,
2097             close: function () {
2098                 if (profilingChart !== null) {
2099                     profilingChart.destroy();
2100                 }
2101                 $('#queryAnalyzerDialog div.placeHolder').html('');
2102                 if (codemirror_editor) {
2103                     codemirror_editor.setValue('');
2104                 } else {
2105                     $('#sqlquery').val('');
2106                 }
2107             }
2108         });
2109     }
2111     /* Loads and displays the analyzed query data */
2112     function loadQueryAnalysis(rowData) {
2113         var db = rowData.db || '';
2115         $('#queryAnalyzerDialog div.placeHolder').html(
2116             PMA_messages.strAnalyzing + ' <img class="ajaxIcon" src="' +
2117             pmaThemeImage + 'ajax_clock_small.gif" alt="">');
2119         $.post('server_status_monitor.php?' + PMA_commonParams.get('common_query'), {
2120             ajax_request: true,
2121             query_analyzer: true,
2122             query: codemirror_editor ? codemirror_editor.getValue() : $('#sqlquery').val(),
2123             database: db
2124         }, function (data) {
2125             var i;
2126             if (data.success === true) {
2127                 data = data.message;
2128             } else {
2129                 $('#queryAnalyzerDialog div.placeHolder').html('<div class="error">' + data.error + '</div>');
2130                 return;
2131             }
2132             var totalTime = 0;
2133             // Float sux, I'll use table :(
2134             $('#queryAnalyzerDialog div.placeHolder')
2135                 .html('<table width="100%" border="0"><tr><td class="explain"></td><td class="chart"></td></tr></table>');
2137             var explain = '<b>' + PMA_messages.strExplainOutput + '</b> ' + $('#explain_docu').html();
2138             if (data.explain.length > 1) {
2139                 explain += ' (';
2140                 for (i = 0; i < data.explain.length; i++) {
2141                     if (i > 0) {
2142                         explain += ', ';
2143                     }
2144                     explain += '<a href="#showExplain-' + i + '">' + i + '</a>';
2145                 }
2146                 explain += ')';
2147             }
2148             explain += '<p></p>';
2149             for (i = 0, l = data.explain.length; i < l; i++) {
2150                 explain += '<div class="explain-' + i + '"' + (i > 0 ?  'style="display:none;"' : '') + '>';
2151                 $.each(data.explain[i], function (key, value) {
2152                     value = (value === null) ? 'null' : value;
2154                     if (key == 'type' && value.toLowerCase() == 'all') {
2155                         value = '<span class="attention">' + value + '</span>';
2156                     }
2157                     if (key == 'Extra') {
2158                         value = value.replace(/(using (temporary|filesort))/gi, '<span class="attention">$1</span>');
2159                     }
2160                     explain += key + ': ' + value + '<br />';
2161                 });
2162                 explain += '</div>';
2163             }
2165             explain += '<p><b>' + PMA_messages.strAffectedRows + '</b> ' + data.affectedRows;
2167             $('#queryAnalyzerDialog div.placeHolder td.explain').append(explain);
2169             $('#queryAnalyzerDialog div.placeHolder a[href*="#showExplain"]').click(function () {
2170                 var id = $(this).attr('href').split('-')[1];
2171                 $(this).parent().find('div[class*="explain"]').hide();
2172                 $(this).parent().find('div[class*="explain-' + id + '"]').show();
2173             });
2175             if (data.profiling) {
2176                 var chartData = [];
2177                 var numberTable = '<table class="queryNums"><thead><tr><th>' + PMA_messages.strStatus + '</th><th>' + PMA_messages.strTime + '</th></tr></thead><tbody>';
2178                 var duration;
2179                 var otherTime = 0;
2181                 for (i = 0, l = data.profiling.length; i < l; i++) {
2182                     duration = parseFloat(data.profiling[i].duration);
2184                     totalTime += duration;
2186                     numberTable += '<tr><td>' + data.profiling[i].state + ' </td><td> ' + PMA_prettyProfilingNum(duration, 2) + '</td></tr>';
2187                 }
2189                 // Only put those values in the pie which are > 2%
2190                 for (i = 0, l = data.profiling.length; i < l; i++) {
2191                     duration = parseFloat(data.profiling[i].duration);
2193                     if (duration / totalTime > 0.02) {
2194                         chartData.push([PMA_prettyProfilingNum(duration, 2) + ' ' + data.profiling[i].state, duration]);
2195                     } else {
2196                         otherTime += duration;
2197                     }
2198                 }
2200                 if (otherTime > 0) {
2201                     chartData.push([PMA_prettyProfilingNum(otherTime, 2) + ' ' + PMA_messages.strOther, otherTime]);
2202                 }
2204                 numberTable += '<tr><td><b>' + PMA_messages.strTotalTime + '</b></td><td>' + PMA_prettyProfilingNum(totalTime, 2) + '</td></tr>';
2205                 numberTable += '</tbody></table>';
2207                 $('#queryAnalyzerDialog div.placeHolder td.chart').append(
2208                     '<b>' + PMA_messages.strProfilingResults + ' ' + $('#profiling_docu').html() + '</b> ' +
2209                     '(<a href="#showNums">' + PMA_messages.strTable + '</a>, <a href="#showChart">' + PMA_messages.strChart + '</a>)<br/>' +
2210                     numberTable + ' <div id="queryProfiling"></div>');
2212                 $('#queryAnalyzerDialog div.placeHolder a[href="#showNums"]').click(function () {
2213                     $('#queryAnalyzerDialog #queryProfiling').hide();
2214                     $('#queryAnalyzerDialog table.queryNums').show();
2215                     return false;
2216                 });
2218                 $('#queryAnalyzerDialog div.placeHolder a[href="#showChart"]').click(function () {
2219                     $('#queryAnalyzerDialog #queryProfiling').show();
2220                     $('#queryAnalyzerDialog table.queryNums').hide();
2221                     return false;
2222                 });
2224                 profilingChart = PMA_createProfilingChartJqplot(
2225                         'queryProfiling',
2226                         chartData
2227                 );
2229                 //$('#queryProfiling').resizable();
2230             }
2231         });
2232     }
2234     /* Saves the monitor to localstorage */
2235     function saveMonitor() {
2236         var gridCopy = {};
2238         $.each(runtime.charts, function (key, elem) {
2239             gridCopy[key] = {};
2240             gridCopy[key].nodes = elem.nodes;
2241             gridCopy[key].settings = elem.settings;
2242             gridCopy[key].title = elem.title;
2243             gridCopy[key].series = elem.series;
2244             gridCopy[key].maxYLabel = elem.maxYLabel;
2245         });
2247         if (window.localStorage) {
2248             window.localStorage['monitorCharts'] = $.toJSON(gridCopy);
2249             window.localStorage['monitorSettings'] = $.toJSON(monitorSettings);
2250             window.localStorage['monitorVersion'] = monitorProtocolVersion;
2251         }
2253         $('a[href="#clearMonitorConfig"]').show();
2254     }
2257 // Run the monitor once loaded
2258 AJAX.registerOnload('server_status_monitor.js', function () {
2259     $('a[href="#pauseCharts"]').trigger('click');
2262 function serverResponseError() {
2263     var btns = {};
2264     btns[PMA_messages.strReloadPage] = function () {
2265         window.location.reload();
2266     };
2267     $('#emptyDialog').dialog({title: PMA_messages.strRefreshFailed});
2268     $('#emptyDialog').html(
2269         PMA_getImage('s_attention.png') +
2270         PMA_messages.strInvalidResponseExplanation
2271     );
2272     $('#emptyDialog').dialog({ buttons: btns });
2275 /* Destroys all monitor related resources */
2276 function destroyGrid() {
2277     if (runtime.charts) {
2278         $.each(runtime.charts, function (key, value) {
2279             try {
2280                 value.chart.destroy();
2281             } catch (err) {}
2282         });
2283     }
2285     try {
2286         runtime.refreshRequest.abort();
2287     } catch (err) {}
2288     try {
2289         clearTimeout(runtime.refreshTimeout);
2290     } catch (err) {}
2291     $('#chartGrid').html('');
2292     runtime.charts = null;
2293     runtime.chartAI = 0;
2294     monitorSettings = null; //TODO:this not global variable