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