Translation update done using Pootle.
[phpmyadmin.git] / js / server_status_monitor.js
blob84e82c993e823f9aebb46a6e61ae15c63aa95cd5
1 $(function() {
2     // Show tab links
3     $('div#statustabs_charting div.tabLinks').show();
4     $('div#statustabs_charting img#loadingMonitorIcon').remove();
5     // Codemirror is loaded on demand so we might need to initialize it
6     if (! codemirror_editor) {
7         var elm = $('#sqlquery');
8         if (elm.length > 0 && typeof CodeMirror != 'undefined') {
9             codemirror_editor = CodeMirror.fromTextArea(elm[0], { lineNumbers: true, matchBrackets: true, indentUnit: 4, mode: "text/x-mysql" });
10         }
11     }
12     // Timepicker is loaded on demand so we need to initialize datetime fields from the 'load log' dialog
13     $('div#logAnalyseDialog .datetimefield').each(function() {
14         PMA_addDatepicker($(this));
15     });
16     
17     /**** Monitor charting implementation ****/
18     /* Saves the previous ajax response for differential values */
19     var oldChartData = null;
20     // Holds about to created chart
21     var newChart = null;
22     var chartSpacing;
23     
24     // Whenever the monitor object (runtime.charts) or the settings object (monitorSettings) 
25     // changes in a way incompatible to the previous version, increase this number
26     // It will reset the users monitor and settings object in his localStorage to the default configuration
27     var monitorProtocolVersion = '1.0';
29     // Runtime parameter of the monitor, is being fully set in initGrid()
30     var runtime = {
31         // Holds all visible charts in the grid
32         charts: null,
33         // Stores the timeout handler so it can be cleared
34         refreshTimeout: null,
35         // Stores the GET request to refresh the charts
36         refreshRequest: null,
37         // Chart auto increment
38         chartAI: 0,
39         // To play/pause the monitor
40         redrawCharts: false,
41         // Object that contains a list of nodes that need to be retrieved from the server for chart updates
42         dataList: [],
43         // Current max points per chart (needed for auto calculation)
44         gridMaxPoints: 20,
45         // displayed time frame
46         xmin: -1,
47         xmax: -1
48     };
49     
50     var monitorSettings = null;
52     var defaultMonitorSettings = {
53         columns: 3,
54         chartSize: { width: 295, height: 250 },
55         // Max points in each chart. Settings it to 'auto' sets gridMaxPoints to (chartwidth - 40) / 12
56         gridMaxPoints: 'auto',
57         /* Refresh rate of all grid charts in ms */
58         gridRefresh: 5000
59     };
61     // Allows drag and drop rearrange and print/edit icons on charts
62     var editMode = false;
63     
64     /* List of preconfigured charts that the user may select */
65     var presetCharts = {
66         // Query cache efficiency
67         'qce': {
68             title: PMA_messages['strQueryCacheEfficiency'],
69             nodes: [ {
70                 name: PMA_messages['strQueryCacheEfficiency'],
71                 dataPoints: [{type: 'statusvar', name: 'Qcache_hits'}, {type: 'statusvar', name: 'Com_select'}],
72                 unit: '%',
73                 transformFn: 'qce'
74             } ]
75         },
76         // Query cache usage
77         'qcu': {
78             title: PMA_messages['strQueryCacheUsage'],
79             nodes: [ {
80                 name: PMA_messages['strQueryCacheUsed'],
81                 dataPoints: [{type: 'statusvar', name: 'Qcache_free_memory'}, {type: 'servervar', name: 'query_cache_size'}],
82                 unit: '%',
83                 transformFn: 'qcu'
84             } ]
85         }
86     };
87     
88     /* Add OS specific system info charts to the preset chart list */
89     switch(server_os) {
90     case 'WINNT': 
91         $.extend(presetCharts, { 
92             'cpu': {
93                 title: PMA_messages['strSystemCPUUsage'],
94                 nodes: [ { 
95                     name: PMA_messages['strAverageLoad'], 
96                     dataPoints: [{ type: 'cpu', name: 'loadavg'}], 
97                     unit: '%'
98                 } ]
99             },
100             
101             'memory': {
102                 title: PMA_messages['strSystemMemory'],
103                 nodes: [ {
104                     name: PMA_messages['strTotalMemory'],
105                     dataPoints: [{ type: 'memory', name: 'MemTotal' }],
106                     valueDivisor: 1024,
107                     unit: PMA_messages['strMiB']
108                 }, {
109                     dataType: 'memory',
110                       name: PMA_messages['strUsedMemory'],
111                       dataPoints: [{ type: 'memory', name: 'MemUsed' }],
112                       valueDivisor: 1024,
113                       unit: PMA_messages['strMiB']
114                 } ]
115             },
116             
117             'swap': {
118                 title: PMA_messages['strSystemSwap'],
119                 nodes: [ {
120                     name: PMA_messages['strTotalSwap'],
121                     dataPoints: [{ type: 'memory', name: 'SwapTotal' }],
122                     valueDivisor: 1024,
123                     unit: PMA_messages['strMiB']
124                 }, {
125                     name: PMA_messages['strUsedSwap'],
126                     dataPoints: [{ type: 'memory', name: 'SwapUsed' }],
127                     valueDivisor: 1024,
128                     unit: PMA_messages['strMiB']
129                 } ]
130             }
131         });
132         break;
133         
134     case 'Linux':
135         $.extend(presetCharts, {
136             'cpu': {
137                 title: PMA_messages['strSystemCPUUsage'],
138                 nodes: [ {
139                     name: PMA_messages['strAverageLoad'],
140                     dataPoints: [{ type: 'cpu', name: 'irrelevant' }],
141                     unit: '%',
142                     transformFn: 'cpu-linux'
143                 } ]
144             },
145             'memory': {
146                 title: PMA_messages['strSystemMemory'],
147                 nodes: [
148                     { name: PMA_messages['strUsedMemory'], dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024, unit: PMA_messages['strMiB'] },
149                     { name: PMA_messages['strCachedMemory'], dataPoints: [{ type: 'memory', name: 'Cached' }],  valueDivisor: 1024, unit: PMA_messages['strMiB'] },
150                     { name: PMA_messages['strBufferedMemory'], dataPoints: [{ type: 'memory', name: 'Buffers' }], valueDivisor: 1024, unit: PMA_messages['strMiB'] },
151                     { name: PMA_messages['strFreeMemory'], dataPoints: [{ type: 'memory', name: 'MemFree' }], valueDivisor: 1024, unit: PMA_messages['strMiB'] }
152                 ],
153                 settings: {
154                     chart: {
155                         type: 'area',
156                         animation: false
157                     },
158                     plotOptions: {
159                         area: {
160                             stacking: 'percent'
161                         }
162                     }
163                 }
164             },
165             'swap': {
166                 title: PMA_messages['strSystemSwap'],
167                 nodes: [
168                     { name: PMA_messages['strUsedSwap'], dataPoints: [{ type: 'memory', name: 'SwapUsed' }], valueDivisor: 1024, unit: PMA_messages['strMiB'] },
169                     { name: PMA_messages['strCachedSwap'], dataPoints: [{ type: 'memory', name: 'SwapCached' }], valueDivisor: 1024, unit: PMA_messages['strMiB'] },
170                     { name: PMA_messages['strFreeSwap'], dataPoints: [{ type: 'memory', name: 'SwapFree' }], valueDivisor: 1024, unit: PMA_messages['strMiB'] }
171                 ],
172                 settings: {
173                     chart: {
174                         type: 'area',
175                         animation: false
176                     },
177                     plotOptions: {
178                         area: {
179                             stacking: 'percent'
180                         }
181                     }
182                 }
183             }
184         });
185         break;
186     }
188     // Default setting for the chart grid
189     defaultChartGrid = {
190         'c0': {  title: PMA_messages['strQuestions'],
191                  nodes: [{name: PMA_messages['strQuestions'], dataPoints: [{ type: 'statusvar', name: 'Questions' }], display: 'differential' }]
192         },
193         'c1': {
194                  title: PMA_messages['strChartConnectionsTitle'],
195                  nodes: [ { name: PMA_messages['strConnections'], dataPoints: [{ type: 'statusvar', name: 'Connections' }], display: 'differential' },
196                           { name: PMA_messages['strProcesses'], dataPoints: [{ type: 'proc', name: 'processes' }] } ]
197         },
198         'c2': {
199                  title: PMA_messages['strTraffic'],
200                  nodes: [
201                     { name: PMA_messages['strBytesSent'], dataPoints: [{ type: 'statusvar', name: 'Bytes_sent' }], display: 'differential', valueDivisor: 1024, unit: PMA_messages['strKiB'] },
202                     { name: PMA_messages['strBytesReceived'], dataPoints: [{ type: 'statusvar', name: 'Bytes_received' }], display: 'differential', valueDivisor: 1024, unit: PMA_messages['strKiB'] }
203                  ]
204          }
205     };
207     // Server is localhost => We can add cpu/memory/swap to the default chart
208     if (server_db_isLocal) {
209         defaultChartGrid['c3'] = presetCharts['cpu'];
210         defaultChartGrid['c4'] = presetCharts['memory'];
211         defaultChartGrid['c5'] = presetCharts['swap'];
212     }
214     /* Buttons that are on the top right corner of each chart */
215     var gridbuttons = {
216         cogButton: {
217             //enabled: true,
218             symbol: 'url(' + pmaThemeImage  + 's_cog.png)',
219             x: -36,
220             symbolFill: '#B5C9DF',
221             hoverSymbolFill: '#779ABF',
222             _titleKey: 'settings',
223             menuName: 'gridsettings',
224             menuItems: [{
225                 textKey: 'editChart',
226                 onclick: function() {
227                     editChart(this);
228                 }
229             }, {
230                 textKey: 'removeChart',
231                 onclick: function() {
232                     removeChart(this);
233                 }
234             }]
235         }
236     };
238     Highcharts.setOptions({
239         lang: {
240             settings:    PMA_messages['strSettings'],
241             removeChart: PMA_messages['strRemoveChart'],
242             editChart:   PMA_messages['strEditChart']
243         }
244     });
246     $('a[href="#rearrangeCharts"], a[href="#endChartEditMode"]').click(function() {
247         editMode = !editMode;
248         if ($(this).attr('href') == '#endChartEditMode') {
249             editMode = false;
250         }
252         // Icon graphics have zIndex 19, 20 and 21. Let's just hope nothing else has the same zIndex
253         $('table#chartGrid div svg').find('*[zIndex=20], *[zIndex=21], *[zIndex=19]').toggle(editMode)
255         $('a[href="#endChartEditMode"]').toggle(editMode);
257         if (editMode) {
258             // Close the settings popup
259             $('#statustabs_charting .popupContent').hide().removeClass('openedPopup');
261             $("#chartGrid").sortableTable({
262                 ignoreRect: {
263                     top: 8,
264                     left: chartSize().width - 63,
265                     width: 54,
266                     height: 24
267                 },
268                 events: {
269                     // Drop event. The drag child element is moved into the drop element
270                     // and vice versa. So the parameters are switched.
271                     drop: function(drag, drop, pos) {
272                         var dragKey, dropKey, dropRender;
273                         var dragRender = $(drag).children().first().attr('id');
275                         if ($(drop).children().length > 0) {
276                             dropRender = $(drop).children().first().attr('id');
277                         }
279                         // Find the charts in the array
280                         $.each(runtime.charts, function(key, value) {
281                             if (value.chart.options.chart.renderTo == dragRender) {
282                                 dragKey = key;
283                             }
284                             if (dropRender && value.chart.options.chart.renderTo == dropRender) {
285                                 dropKey = key;
286                             }
287                         });
289                         // Case 1: drag and drop are charts -> Switch keys
290                         if (dropKey) {
291                             if (dragKey) {
292                                 dragChart = runtime.charts[dragKey];
293                                 runtime.charts[dragKey] = runtime.charts[dropKey];
294                                 runtime.charts[dropKey] = dragChart;
295                             } else {
296                                 // Case 2: drop is a empty cell => just completely rebuild the ids
297                                 var keys = [];
298                                 var dropKeyNum = parseInt(dropKey.substr(1));
299                                 var insertBefore = pos.col + pos.row * monitorSettings.columns;
300                                 var values = [];
301                                 var newChartList = {};
302                                 var c = 0;
304                                 $.each(runtime.charts, function(key, value) {
305                                     if (key != dropKey) {
306                                         keys.push(key);
307                                     }
308                                 });
310                                 keys.sort();
312                                 // Rebuilds all ids, with the dragged chart correctly inserted
313                                 for (var i = 0; i<keys.length; i++) {
314                                     if (keys[i] == insertBefore) {
315                                         newChartList['c' + (c++)] = runtime.charts[dropKey];
316                                         insertBefore = -1; // Insert ok
317                                     }
318                                     newChartList['c' + (c++)] = runtime.charts[keys[i]];
319                                 }
321                                 // Not inserted => put at the end
322                                 if (insertBefore != -1) {
323                                     newChartList['c' + (c++)] = runtime.charts[dropKey];
324                                 }
326                                 runtime.charts = newChartList;
327                             }
329                             saveMonitor();
330                         }
331                     }
332                 }
333             });
335         } else {
336             $("#chartGrid").sortableTable('destroy');
337             saveMonitor(); // Save settings
338         }
340         return false;
341     });
343     // global settings
344     $('div#statustabs_charting div.popupContent select[name="chartColumns"]').change(function() {
345         monitorSettings.columns = parseInt(this.value);
347         var newSize = chartSize();
349         // Empty cells should keep their size so you can drop onto them
350         $('table#chartGrid tr td').css('width', newSize.width + 'px');
352         /* Reorder all charts that it fills all column cells */
353         var numColumns;
354         var $tr = $('table#chartGrid tr:first');
355         var row = 0;
356         while($tr.length != 0) {
357             numColumns = 1;
358             // To many cells in one row => put into next row
359             $tr.find('td').each(function() {
360                 if (numColumns > monitorSettings.columns) {
361                     if ($tr.next().length == 0) { 
362                         $tr.after('<tr></tr>');
363                     }
364                     $tr.next().prepend($(this));
365                 }
366                 numColumns++;
367             });
369             // To little cells in one row => for each cell to little, move all cells backwards by 1
370             if ($tr.next().length > 0) {
371                 var cnt = monitorSettings.columns - $tr.find('td').length;
372                 for (var i = 0; i < cnt; i++) {
373                     $tr.append($tr.next().find('td:first'));
374                     $tr.nextAll().each(function() {
375                         if ($(this).next().length != 0) {
376                             $(this).append($(this).next().find('td:first'));
377                         }
378                     });
379                 }
380             }
382             $tr = $tr.next();
383             row++;
384         }
386         /* Apply new chart size to all charts */
387         $.each(runtime.charts, function(key, value) {
388             value.chart.setSize(
389                 newSize.width,
390                 newSize.height,
391                 false
392             );
393         });
395         if (monitorSettings.gridMaxPoints == 'auto') {
396             runtime.gridMaxPoints = Math.round((newSize.width - 40) / 12);
397         }
399         runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
400         runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh;
402         if (editMode) {
403             $("#chartGrid").sortableTable('refresh');
404         }
406         saveMonitor(); // Save settings
407     });
409     $('div#statustabs_charting div.popupContent select[name="gridChartRefresh"]').change(function() {
410         monitorSettings.gridRefresh = parseInt(this.value) * 1000;
411         clearTimeout(runtime.refreshTimeout);
413         if (runtime.refreshRequest) {
414             runtime.refreshRequest.abort();
415         }
417         runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
418         runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh;
420         $.each(runtime.charts, function(key, value) {
421             value.chart.xAxis[0].setExtremes(runtime.xmin, runtime.xmax, false);
422         });
424         runtime.refreshTimeout = setTimeout(refreshChartGrid, monitorSettings.gridRefresh);
426         saveMonitor(); // Save settings
427     });
429     $('a[href="#addNewChart"]').click(function() {
430         var dlgButtons = { };
432         dlgButtons[PMA_messages['strAddChart']] = function() {
433             var type = $('input[name="chartType"]:checked').val();
435             if (type == 'preset') {
436                 newChart = presetCharts[$('div#addChartDialog select[name="presetCharts"]').prop('value')];
437             } else {
438                 // If user builds his own chart, it's being set/updated each time he adds a series
439                 // So here we only warn if he didn't add a series yet
440                 if (! newChart || ! newChart.nodes || newChart.nodes.length == 0) {
441                     alert(PMA_messages['strAddOneSeriesWarning']);
442                     return;
443                 }
444             }
446             newChart.title = $('input[name="chartTitle"]').attr('value');
447             // Add a cloned object to the chart grid
448             addChart($.extend(true, {}, newChart));
450             newChart = null;
452             saveMonitor(); // Save settings
454             $(this).dialog("close");
455         };
456         
457         dlgButtons[PMA_messages['strClose']] = function() {
458             newChart = null;
459             $('span#clearSeriesLink').hide();
460             $('#seriesPreview').html('');
461             $(this).dialog("close");
462         };
463         
464         var $presetList = $('div#addChartDialog select[name="presetCharts"]');
465         if ($presetList.html().length == 0) {
466             $.each(presetCharts, function(key, value) {
467                 $presetList.append('<option value="' + key + '">' + value.title + '</option>');
468             });
469             $presetList.change(function() {
470                 $('input#chartPreset').trigger('click');
471                 $('input[name="chartTitle"]').attr('value', presetCharts[$(this).prop('value')].title);
472             });
473         }
474         
475         $('div#addChartDialog').dialog({
476             width: 'auto',
477             height: 'auto',
478             buttons: dlgButtons
479         });
481         $('div#addChartDialog #seriesPreview').html('<i>' + PMA_messages['strNone'] + '</i>');
483         return false;
484     });
485     
486     $('a[href="#exportMonitorConfig"]').click(function() {
487         var gridCopy = {};
489         $.each(runtime.charts, function(key, elem) {
490             gridCopy[key] = {};
491             gridCopy[key].nodes = elem.nodes;
492             gridCopy[key].settings = elem.settings;
493             gridCopy[key].title = elem.title;
494         });
495         
496         var exportData = {
497             monitorCharts: gridCopy,
498             monitorSettings: monitorSettings
499         };
500         var $form;
501         
502         $('body').append($form = $('<form method="post" action="file_echo.php?' + url_query + '&filename=1" style="display:none;"></form>'));
503         
504         $form.append('<input type="hidden" name="monitorconfig" value="' + encodeURI($.toJSON(exportData)) + '">');
505         $form.submit();
506         $form.remove();
507     });
509     $('a[href="#importMonitorConfig"]').click(function() {
510         $('div#emptyDialog').attr('title', 'Import monitor configuration');
511         $('div#emptyDialog').html('Please select the file you want to import:<br/><form action="file_echo.php?' + url_query + '&import=1" method="post" enctype="multipart/form-data">' +
512             '<input type="file" name="file"> <input type="hidden" name="import" value="1"> </form>');
513         
514         var dlgBtns = {};
515         
516         dlgBtns[PMA_messages['strImport']] = function() {
517             var $iframe, $form;
518             $('body').append($iframe = $('<iframe id="monitorConfigUpload" style="display:none;"></iframe>'));
519             var d = $iframe[0].contentWindow.document;
520             d.open(); d.close();
521             mew = d;
522             
523             $iframe.load(function() {
524                 var json;
526                 // Try loading config
527                 try {
528                     var data = $('body', $('iframe#monitorConfigUpload')[0].contentWindow.document).html();
529                     // Chrome wraps around '<pre style="word-wrap: break-word; white-space: pre-wrap;">' to any text content -.-
530                     json = $.secureEvalJSON(data.substring(data.indexOf("{"), data.lastIndexOf("}") + 1));
531                 } catch (err) {
532                     alert(PMA_messages['strFailedParsingConfig']);
533                     $('div#emptyDialog').dialog('close');
534                     return;
535                 }
536             
537                 // Basic check, is this a monitor config json?
538                 if (!json || ! json.monitorCharts || ! json.monitorCharts) {
539                     alert(PMA_messages['strFailedParsingConfig']);
540                     $('div#emptyDialog').dialog('close');
541                     return;
542                 }
543                 
544                 // If json ok, try applying config
545                 try {
546                     window.localStorage['monitorCharts'] = $.toJSON(json.monitorCharts);
547                     window.localStorage['monitorSettings'] = $.toJSON(json.monitorSettings);
548                     rebuildGrid();
549                 } catch(err) {
550                     alert(PMA_messages['strFailedBuildingGrid']);
551                     // If an exception is thrown, load default again
552                     window.localStorage.removeItem('monitorCharts');
553                     window.localStorage.removeItem('monitorSettings');
554                     rebuildGrid();
555                 }
556                 
557                 $('div#emptyDialog').dialog('close');
558             });
559             
560             $("body", d).append($form = $('div#emptyDialog').find('form'));
561             $form.submit();
562             $('div#emptyDialog').append('<img class="ajaxIcon" src="' + pmaThemeImage + 'ajax_clock_small.gif" alt="">');
563         };
564         
565         dlgBtns[PMA_messages['strCancel']] = function() {
566             $(this).dialog('close');
567         }
568         
569         
570         $('div#emptyDialog').dialog({
571             width: 'auto',
572             height: 'auto',
573             buttons: dlgBtns
574         });
575     });
577     $('a[href="#clearMonitorConfig"]').click(function() {
578         window.localStorage.removeItem('monitorCharts');
579         window.localStorage.removeItem('monitorSettings');
580         window.localStorage.removeItem('monitorVersion');
581         $(this).hide();
582         rebuildGrid();
583     });
585     $('a[href="#pauseCharts"]').click(function() {
586         runtime.redrawCharts = ! runtime.redrawCharts;
587         if (! runtime.redrawCharts) {
588             $(this).html(PMA_getImage('play.png') + ' ' + PMA_messages['strResumeMonitor']);
589         } else {
590             $(this).html(PMA_getImage('pause.png') + ' ' + PMA_messages['strPauseMonitor']);
591             if (! runtime.charts) {
592                 initGrid();
593                 $('a[href="#settingsPopup"]').show();
594             }
595         }
596         return false;
597     });
598     
599     $('a[href="#monitorInstructionsDialog"]').click(function() {
600         var $dialog = $('div#monitorInstructionsDialog');
602         $dialog.dialog({
603             width: 595,
604             height: 'auto'
605         }).find('img.ajaxIcon').show();
607         var loadLogVars = function(getvars) {
608             vars = { ajax_request: true, logging_vars: true };
609             if (getvars) {
610                 $.extend(vars, getvars);
611             }
613             $.get('server_status.php?' + url_query, vars,
614                 function(data) {
615                     var logVars = $.parseJSON(data),
616                         icon = PMA_getImage('s_success.png'), msg='', str='';
618                     if (logVars['general_log'] == 'ON') {
619                         if (logVars['slow_query_log'] == 'ON') {
620                             msg = PMA_messages['strBothLogOn'];
621                         } else {
622                             msg = PMA_messages['strGenLogOn'];
623                         }
624                     }
626                     if (msg.length == 0 && logVars['slow_query_log'] == 'ON') {
627                         msg = PMA_messages['strSlowLogOn'];
628                     }
630                     if (msg.length == 0) {
631                         icon = PMA_getImage('s_error.png');
632                         msg = PMA_messages['strBothLogOff'];
633                     }
635                     str = '<b>' + PMA_messages['strCurrentSettings'] + '</b><br><div class="smallIndent">';
636                     str += icon + msg + '<br />';
638                     if (logVars['log_output'] != 'TABLE') {
639                         str += PMA_getImage('s_error.png') + ' ' + PMA_messages['strLogOutNotTable'] + '<br />';
640                     } else {
641                         str += PMA_getImage('s_success.png') + ' ' + PMA_messages['strLogOutIsTable'] + '<br />';
642                     }
644                     if (logVars['slow_query_log'] == 'ON') {
645                         if (logVars['long_query_time'] > 2) {
646                             str += PMA_getImage('s_attention.png') + ' '
647                                 + $.sprintf(PMA_messages['strSmallerLongQueryTimeAdvice'], logVars['long_query_time'])
648                                 + '<br />';
649                         }
650                         
651                         if (logVars['long_query_time'] < 2) {
652                             str += PMA_getImage('s_success.png') + ' '
653                                 + $.sprintf(PMA_messages['strLongQueryTimeSet'], logVars['long_query_time'])
654                                 + '<br />';
655                         }
656                     }
658                     str += '</div>';
660                     if (is_superuser) {
661                         str += '<p></p><b>' + PMA_messages['strChangeSettings'] + '</b>';
662                         str += '<div class="smallIndent">';
663                         str += PMA_messages['strSettingsAppliedGlobal'] + '<br/>';
665                         var varValue = 'TABLE';
666                         if (logVars['log_output'] == 'TABLE') {
667                             varValue = 'FILE';
668                         }
669                         
670                         str += '- <a class="set" href="#log_output-' + varValue + '">'
671                             + $.sprintf(PMA_messages['strSetLogOutput'], varValue)
672                             + ' </a><br />';
674                         if (logVars['general_log'] != 'ON') {
675                             str += '- <a class="set" href="#general_log-ON">'
676                                 + $.sprintf(PMA_messages['strEnableVar'], 'general_log')
677                                 + ' </a><br />';
678                         } else {
679                             str += '- <a class="set" href="#general_log-OFF">'
680                                 + $.sprintf(PMA_messages['strDisableVar'], 'general_log')
681                                 + ' </a><br />';
682                         }
684                         if (logVars['slow_query_log'] != 'ON') {
685                             str += '- <a class="set" href="#slow_query_log-ON">'
686                                 +  $.sprintf(PMA_messages['strEnableVar'], 'slow_query_log')
687                                 + ' </a><br />';
688                         } else {
689                             str += '- <a class="set" href="#slow_query_log-OFF">'
690                                 +  $.sprintf(PMA_messages['strDisableVar'], 'slow_query_log')
691                                 + ' </a><br />';
692                         }
694                         varValue = 5;
695                         if (logVars['long_query_time'] > 2) {
696                             varValue = 1;
697                         }
699                         str += '- <a class="set" href="#long_query_time-' + varValue + '">'
700                             + $.sprintf(PMA_messages['setSetLongQueryTime'], varValue)
701                             + ' </a><br />';
703                     } else {
704                         str += PMA_messages['strNoSuperUser'] + '<br/>';
705                     }
707                     str += '</div>';
709                     $dialog.find('div.monitorUse').toggle(
710                         logVars['log_output'] == 'TABLE' && (logVars['slow_query_log'] == 'ON' || logVars['general_log'] == 'ON')
711                     );
713                     $dialog.find('div.ajaxContent').html(str);
714                     $dialog.find('img.ajaxIcon').hide();
715                     $dialog.find('a.set').click(function() {
716                         var nameValue = $(this).attr('href').split('-');
717                         loadLogVars({ varName: nameValue[0].substr(1), varValue: nameValue[1]});
718                         $dialog.find('img.ajaxIcon').show();
719                     });
720                 }
721             );
722         };
723         
724         
725         loadLogVars();
727         return false;
728     });
730     $('input[name="chartType"]').change(function() {
731         $('#chartVariableSettings').toggle(this.checked && this.value == 'variable');
732         var title = $('input[name="chartTitle"]').attr('value');
733         if (title == PMA_messages['strChartTitle'] 
734            || title == $('label[for="' + $('input[name="chartTitle"]').data('lastRadio') + '"]').text()
735         ) {
736             $('input[name="chartTitle"]')
737                 .data('lastRadio', $(this).attr('id'))
738                 .attr('value', $('label[for="' + $(this).attr('id') + '"]').text());
739         }
741     });
743     $('input[name="useDivisor"]').change(function() {
744         $('span.divisorInput').toggle(this.checked);
745     });
746     $('input[name="useUnit"]').change(function() {
747         $('span.unitInput').toggle(this.checked);
748     });
750     $('select[name="varChartList"]').change(function () {
751         if (this.selectedIndex != 0) {
752             $('#variableInput').attr('value', this.value);
753         }
754     });
756     $('a[href="#kibDivisor"]').click(function() {
757         $('input[name="valueDivisor"]').attr('value', 1024);
758         $('input[name="valueUnit"]').attr('value', PMA_messages['strKiB']);
759         $('span.unitInput').toggle(true);
760         $('input[name="useUnit"]').prop('checked', true);
761         return false;
762     });
764     $('a[href="#mibDivisor"]').click(function() {
765         $('input[name="valueDivisor"]').attr('value', 1024*1024);
766         $('input[name="valueUnit"]').attr('value', PMA_messages['strMiB']);
767         $('span.unitInput').toggle(true);
768         $('input[name="useUnit"]').prop('checked', true);
769         return false;
770     });
772     $('a[href="#submitClearSeries"]').click(function() {
773         $('#seriesPreview').html('<i>' + PMA_messages['strNone'] + '</i>');
774         newChart = null;
775         $('span#clearSeriesLink').hide();
776     });
778     $('a[href="#submitAddSeries"]').click(function() {
779         if ($('input#variableInput').attr('value').length == 0) {
780             return false;
781         }
782         
783         if (newChart == null) {
784             $('#seriesPreview').html('');
786             newChart = {
787                 title: $('input[name="chartTitle"]').attr('value'),
788                 nodes: []
789             };
790         }
792         var serie = {
793             dataPoints: [{ type: 'statusvar', name: $('input#variableInput').attr('value') }],
794             name: $('input#variableInput').attr('value'),
795             display: $('input[name="differentialValue"]').attr('checked') ? 'differential' : ''
796         };
798         if (serie.dataPoint == 'Processes') {
799             serie.dataType='proc';
800         }
802         if ($('input[name="useDivisor"]').attr('checked')) {
803             serie.valueDivisor = parseInt($('input[name="valueDivisor"]').attr('value'));
804         }
806         if ($('input[name="useUnit"]').attr('checked')) {
807             serie.unit = $('input[name="valueUnit"]').attr('value');
808         }
810         var str = serie.display == 'differential' ? ', ' + PMA_messages['strDifferential'] : '';
811         str += serie.valueDivisor ? (', ' + $.sprintf(PMA_messages['strDividedBy'], serie.valueDivisor)) : '';
812         str += serie.unit ? (', ' + PMA_messages['strUnit'] + ': ' + serie.unit) : '';
814         $('#seriesPreview').append('- ' + serie.name + str + '<br>');
816         newChart.nodes.push(serie);
818         $('input#variableInput').attr('value', '');
819         $('input[name="differentialValue"]').attr('checked', true);
820         $('input[name="useDivisor"]').attr('checked', false);
821         $('input[name="useUnit"]').attr('checked', false);
822         $('input[name="useDivisor"]').trigger('change');
823         $('input[name="useUnit"]').trigger('change');
824         $('select[name="varChartList"]').get(0).selectedIndex = 0;
826         $('span#clearSeriesLink').show();
828         return false;
829     });
831     $("input#variableInput").autocomplete({
832             source: variableNames
833     });
835     /* Initializes the monitor, called only once */
836     function initGrid() {
837         var settings;
838         var series;
840         /* Apply default values & config */
841         if (window.localStorage) {
842             if (window.localStorage['monitorCharts']) {
843                 runtime.charts = $.parseJSON(window.localStorage['monitorCharts']);
844             }
845             if (window.localStorage['monitorSettings']) {
846                 monitorSettings = $.parseJSON(window.localStorage['monitorSettings']);
847             }
849             $('a[href="#clearMonitorConfig"]').toggle(runtime.charts != null);
851             if (runtime.charts != null && monitorProtocolVersion != window.localStorage['monitorVersion']) {
852                 $('div#emptyDialog').attr('title',PMA_messages['strIncompatibleMonitorConfig']);
853                 $('div#emptyDialog').html(PMA_messages['strIncompatibleMonitorConfigDescription']);
855                 var dlgBtns = {};
856                 dlgBtns[PMA_messages['strClose']] = function() { $(this).dialog('close'); };
858                 $('div#emptyDialog').dialog({ 
859                     width: 400,
860                     buttons: dlgBtns 
861                 });
862             }            
863         }
865         if (runtime.charts == null) {
866             runtime.charts = defaultChartGrid;
867         }
868         if (monitorSettings == null) {
869             monitorSettings = defaultMonitorSettings;
870         }
872         $('select[name="gridChartRefresh"]').attr('value', monitorSettings.gridRefresh / 1000);
873         $('select[name="chartColumns"]').attr('value', monitorSettings.columns);
875         if (monitorSettings.gridMaxPoints == 'auto') {
876             runtime.gridMaxPoints = Math.round((monitorSettings.chartSize.width - 40) / 12);
877         } else {
878             runtime.gridMaxPoints = monitorSettings.gridMaxPoints;
879         }
881         runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
882         runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh;
884         /* Calculate how much spacing there is between each chart */
885         $('table#chartGrid').html('<tr><td></td><td></td></tr><tr><td></td><td></td></tr>');
886         chartSpacing = {
887             width: $('table#chartGrid td:nth-child(2)').offset().left 
888                     - $('table#chartGrid td:nth-child(1)').offset().left,
889             height: $('table#chartGrid tr:nth-child(2) td:nth-child(2)').offset().top 
890                     - $('table#chartGrid tr:nth-child(1) td:nth-child(1)').offset().top
891         }
892         $('table#chartGrid').html('');
893         
894         /* Add all charts - in correct order */
895         var keys = [];
896         $.each(runtime.charts, function(key, value) {
897             keys.push(key);
898         });
899         keys.sort();
900         for (var i = 0; i<keys.length; i++)
901             addChart(runtime.charts[keys[i]], true);
903         /* Fill in missing cells */
904         var numCharts = $('table#chartGrid .monitorChart').length;
905         var numMissingCells = (monitorSettings.columns - numCharts % monitorSettings.columns) % monitorSettings.columns;
906         for (var i = 0; i < numMissingCells; i++) {
907             $('table#chartGrid tr:last').append('<td></td>');
908         }
910         // Empty cells should keep their size so you can drop onto them
911         $('table#chartGrid tr td').css('width', chartSize().width + 'px');
912         
913         buildRequiredDataList();
914         refreshChartGrid();
915     }
916     
917     /* Destroys all monitor related resources */
918     function destroyGrid() {
919         if (runtime.charts) {
920             $.each(runtime.charts, function(key, value) {
921                 try {
922                     value.chart.destroy();
923                 } catch(err) {}
924             });
925         }
926         
927         try {
928             runtime.refreshRequest.abort();
929         } catch(err) {}
930         try {    
931             clearTimeout(runtime.refreshTimeout);
932         } catch(err) {}
933             
934         $('table#chartGrid').html('');
936         runtime.charts = null;
937         runtime.chartAI = 0;
938         monitorSettings = null;
939     }
940     
941     /* Calls destroyGrid() and initGrid(), but before doing so it saves the chart 
942      * data from each chart and restores it after the monitor is initialized again */
943     function rebuildGrid() {
944         var oldData = null;
945         if (runtime.charts) {
946             oldData = {};
947             $.each(runtime.charts, function(key, chartObj) {
948                 for (var i = 0; i < chartObj.nodes.length; i++) {
949                     oldData[chartObj.nodes[i].dataPoint] = [];
950                     for (var j = 0; j < chartObj.chart.series[i].data.length; j++)
951                         oldData[chartObj.nodes[i].dataPoint].push([chartObj.chart.series[i].data[j].x, chartObj.chart.series[i].data[j].y]);
952                 }
953             });
954         }
955         
956         destroyGrid();
957         initGrid();
958         
959         if (oldData) {
960             $.each(runtime.charts, function(key, chartObj) {
961                 for (var j = 0; j < chartObj.nodes.length; j++) {
962                     if (oldData[chartObj.nodes[j].dataPoint]) {
963                         chartObj.chart.series[j].setData(oldData[chartObj.nodes[j].dataPoint]);
964                     }
965                 }
966             });
967         }      
968     }
970     /* Calculactes the dynamic chart size that depends on the column width */
971     function chartSize() {
972         var wdt = $('div#logTable').innerWidth() / monitorSettings.columns - (monitorSettings.columns - 1) * chartSpacing.width;
973         return {
974             width: wdt,
975             height: 0.75 * wdt
976         }
977     }
979     /* Adds a chart to the chart grid */
980     function addChart(chartObj, initialize) {
981         series = [];
982         for (var j = 0; j<chartObj.nodes.length; j++)
983             series.push(chartObj.nodes[j]);
985         settings = {
986             chart: {
987                 renderTo: 'gridchart' + runtime.chartAI,
988                 width: chartSize().width,
989                 height: chartSize().height,
990                 marginRight: 5,
991                 zoomType: 'x',
992                 events: {
993                     selection: function(event) {
994                         if (editMode || $('#logAnalyseDialog').length == 0) {
995                             return false;
996                         }
998                         var extremesObject = event.xAxis[0],
999                             min = extremesObject.min,
1000                             max = extremesObject.max;
1002                         $('#logAnalyseDialog input[name="dateStart"]')
1003                             .attr('value', Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', new Date(min)));
1004                         $('#logAnalyseDialog input[name="dateEnd"]')
1005                             .attr('value', Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', new Date(max)));
1007                         var dlgBtns = { };
1009                         dlgBtns[PMA_messages['strFromSlowLog']] = function() {
1010                             loadLog('slow');
1011                             $(this).dialog("close");
1012                         };
1013                         
1014                         dlgBtns[PMA_messages['strFromGeneralLog']] = function() {
1015                             loadLog('general');
1016                             $(this).dialog("close");
1017                         };
1018                         
1019                         function loadLog(type) {
1020                             var dateStart = Date.parse($('#logAnalyseDialog input[name="dateStart"]').prop('value')) || min;
1021                             var dateEnd = Date.parse($('#logAnalyseDialog input[name="dateEnd"]').prop('value')) || max;
1023                             loadLogStatistics({
1024                                 src: type,
1025                                 start: dateStart,
1026                                 end: dateEnd,
1027                                 removeVariables: $('input#removeVariables').prop('checked'),
1028                                 limitTypes: $('input#limitTypes').prop('checked')
1029                             });
1030                         }
1031                         
1032                         $('#logAnalyseDialog').dialog({
1033                             width: 'auto',
1034                             height: 'auto',
1035                             buttons: dlgBtns
1036                         });
1038                         return false;
1039                     }
1040                 }
1041             },
1042             xAxis: {
1043                 min: runtime.xmin,
1044                 max: runtime.xmax
1045             },
1047             yAxis: {
1048                 title: {
1049                     text: ''
1050                 }
1051             },
1052             tooltip: {
1053                 formatter: function() {
1054                         var s = '<b>' + Highcharts.dateFormat('%H:%M:%S', this.x) + '</b>';
1056                         $.each(this.points, function(i, point) {
1057                             s += '<br/><span style="color:' + point.series.color + '">' + point.series.name + ':</span> ' +
1058                                 ((parseInt(point.y) == point.y) ? point.y : Highcharts.numberFormat(this.y, 2)) + ' ' + (point.series.options.unit || '');
1059                         });
1061                         return s;
1062                 },
1063                 shared: true
1064             },
1065             legend: {
1066                 enabled: false
1067             },
1068             series: series,
1069             buttons: gridbuttons,
1070             title: { text: chartObj.title }
1071         };
1073         if (chartObj.settings) {
1074             $.extend(true, settings, chartObj.settings);
1075         }
1077         if ($('#' + settings.chart.renderTo).length == 0) {
1078             var numCharts = $('table#chartGrid .monitorChart').length;
1080             if (numCharts == 0 || !( numCharts % monitorSettings.columns)) {
1081                 $('table#chartGrid').append('<tr></tr>');
1082             }
1084             $('table#chartGrid tr:last').append('<td><div class="ui-state-default monitorChart" id="' + settings.chart.renderTo + '"></div></td>');
1085         }
1087         chartObj.chart = PMA_createChart(settings);
1088         chartObj.numPoints = 0;
1089         
1090         if (initialize != true) {
1091             runtime.charts['c' + runtime.chartAI] = chartObj;
1092             buildRequiredDataList();
1093         }
1095         // Edit, Print icon only in edit mode
1096         $('table#chartGrid div svg').find('*[zIndex=20], *[zIndex=21], *[zIndex=19]').toggle(editMode)
1098         runtime.chartAI++;
1099     }
1101     /* Opens a dialog that allows one to edit the title and series labels of the supplied chart */
1102     function editChart(chartObj) {
1103         var htmlnode = chartObj.options.chart.renderTo;
1104         if (! htmlnode ) {
1105             return;
1106         }
1107         
1108         var chart = null;
1109         var chartKey = null;
1110         $.each(runtime.charts, function(key, value) {
1111             if (value.chart.options.chart.renderTo == htmlnode) {
1112                 chart = value;
1113                 chartKey = key;
1114                 return false;
1115             }
1116         });
1117         
1118         if (chart == null) {
1119             return;
1120         }
1122         var htmlStr = '<p><b>Chart title: </b> <br/> <input type="text" size="35" name="chartTitle" value="' + chart.title + '" />';
1123         htmlStr += '</p><p><b>Series:</b> </p><ol>';
1124         for (var i = 0; i<chart.nodes.length; i++) {
1125             htmlStr += '<li><i>' + chart.nodes[i].dataPoints[0].name  + ': </i><br/><input type="text" name="chartSerie-' + i + '" value="' + chart.nodes[i].name + '" /></li>';
1126         }
1127         
1128         dlgBtns = {};
1129         dlgBtns['Save'] = function() {
1130             runtime.charts[chartKey].title = $('div#emptyDialog input[name="chartTitle"]').attr('value');
1131             runtime.charts[chartKey].chart.setTitle({ text: runtime.charts[chartKey].title });
1132             
1133             $('div#emptyDialog input[name*="chartSerie"]').each(function() {
1134                 var idx = $(this).attr('name').split('-')[1];
1135                 runtime.charts[chartKey].nodes[idx].name = $(this).attr('value');
1136                 runtime.charts[chartKey].chart.series[idx].name = $(this).attr('value');
1137             });
1138             
1139             $(this).dialog('close');
1140             saveMonitor();
1141         };
1142         dlgBtns['Cancel'] = function() {
1143             $(this).dialog('close');
1144         };
1145         
1146         $('div#emptyDialog').attr('title', 'Edit chart');
1147         $('div#emptyDialog').html(htmlStr + '</ol>');
1148         $('div#emptyDialog').dialog({
1149             width: 'auto',
1150             height: 'auto',
1151             buttons: dlgBtns
1152         });
1153     }
1154     
1155     /* Removes a chart from the grid */
1156     function removeChart(chartObj) {
1157         var htmlnode = chartObj.options.chart.renderTo;
1158         if (! htmlnode ) {
1159             return;
1160         }
1161         
1162         $.each(runtime.charts, function(key, value) {
1163             if (value.chart.options.chart.renderTo == htmlnode) {
1164                 delete runtime.charts[key];
1165                 return false;
1166             }
1167         });
1169         buildRequiredDataList();
1171         // Using settimeout() because clicking the remove link fires an onclick event
1172         // which throws an error when the chart is destroyed
1173         setTimeout(function() {
1174             chartObj.destroy();
1175             $('div#' + htmlnode).remove();
1176         }, 10);
1178         saveMonitor(); // Save settings
1179     }
1181     /* Called in regular intervalls, this function updates the values of each chart in the grid */
1182     function refreshChartGrid() {
1183         /* Send to server */
1184         runtime.refreshRequest = $.post('server_status.php?' + url_query, {
1185             ajax_request: true, 
1186             chart_data: 1, 
1187             type: 'chartgrid', 
1188             requiredData: $.toJSON(runtime.dataList) 
1189         }, function(data) {
1190             var chartData;
1191             try {
1192                 chartData = $.parseJSON(data);
1193             } catch(err) {
1194                 return serverResponseError();
1195             }
1196             var value, i = 0;
1197             var diff;
1198             
1199             /* Update values in each graph */
1200             $.each(runtime.charts, function(orderKey, elem) {
1201                 var key = elem.chartID;
1202                 // If newly added chart, we have no data for it yet
1203                 if (! chartData[key]) {
1204                     return;
1205                 }
1206                 // Draw all series
1207                 for (var j = 0; j < elem.nodes.length; j++) {
1208                     // Update x-axis
1209                     if (i == 0 && j == 0) {
1210                         if (oldChartData == null) {
1211                             diff = chartData.x - runtime.xmax;
1212                         } else {
1213                             diff = parseInt(chartData.x - oldChartData.x);
1214                         }
1216                         runtime.xmin += diff;
1217                         runtime.xmax += diff;
1218                     }
1220                     elem.chart.xAxis[0].setExtremes(runtime.xmin, runtime.xmax, false);
1222                     /* Calculate y value */
1223                     
1224                     // If transform function given, use it
1225                     if (elem.nodes[j].transformFn) {
1226                         value = chartValueTransform(
1227                             elem.nodes[j].transformFn,
1228                             chartData[key][j],
1229                             // Check if first iteration (oldChartData==null), or if newly added chart oldChartData[key]==null
1230                             (oldChartData == null || oldChartData[key] == null ? null : oldChartData[key][j])
1231                         );
1233                     // Otherwise use original value and apply differential and divisor if given,
1234                     // in this case we have only one data point per series - located at chartData[key][j][0]
1235                     } else {
1236                         value = parseFloat(chartData[key][j][0].value);
1238                         if (elem.nodes[j].display == 'differential') {
1239                             if (oldChartData == null || oldChartData[key] == null) { 
1240                                 continue;
1241                             }
1242                             value -= oldChartData[key][j][0].value;
1243                         }
1245                         if (elem.nodes[j].valueDivisor) {
1246                             value = value / elem.nodes[j].valueDivisor;
1247                         }
1248                     }
1249                     
1250                     // Set y value, if defined
1251                     if (value != undefined) {
1252                         elem.chart.series[j].addPoint(
1253                             { x: chartData.x, y: value },
1254                             false,
1255                             elem.numPoints >= runtime.gridMaxPoints
1256                         );
1257                     }
1258                 }
1260                 i++;
1262                 runtime.charts[orderKey].numPoints++;
1263                 if (runtime.redrawCharts) {
1264                     elem.chart.redraw();
1265                 }
1266             });
1268             oldChartData = chartData;
1270             runtime.refreshTimeout = setTimeout(refreshChartGrid, monitorSettings.gridRefresh);
1271         });
1272     }
1273     
1274     /* Function that supplies special value transform functions for chart values */
1275     function chartValueTransform(name, cur, prev) {        
1276         switch(name) {
1277         case 'cpu-linux':
1278             if (prev == null) {
1279                 return undefined;
1280             }
1281             // cur and prev are datapoint arrays, but containing only 1 element for cpu-linux
1282             cur = cur[0], prev = prev[0];
1284             var diff_total = cur.busy + cur.idle - (prev.busy + prev.idle);
1285             var diff_idle = cur.idle - prev.idle;
1286             return 100 * (diff_total - diff_idle) / diff_total;
1288         // Query cache efficiency (%)
1289         case 'qce':
1290             if (prev == null) {
1291                 return undefined;
1292             }
1293             // cur[0].value is Qcache_hits, cur[1].value is Com_select
1294             var diffQHits = cur[0].value - prev[0].value;
1295             // No NaN please :-)
1296             if (cur[1].value - prev[1].value == 0) return 0;
1298             return diffQHits / (cur[1].value - prev[1].value + diffQHits) * 100;
1300         // Query cache usage (%)
1301         case 'qcu':
1302             if (cur[1].value == 0) return 0;
1303             // cur[0].value is Qcache_free_memory, cur[1].value is query_cache_size
1304             return 100 - cur[0].value / cur[1].value * 100;
1306         }
1307         return undefined;
1308     }
1310     /* Build list of nodes that need to be retrieved from server.
1311      * It creates something like a stripped down version of the runtime.charts object.
1312      */
1313     function buildRequiredDataList() {
1314         runtime.dataList = {};
1315         // Store an own id, because the property name is subject of reordering, 
1316         // thus destroying our mapping with runtime.charts <=> runtime.dataList
1317         var chartID = 0;
1318         $.each(runtime.charts, function(key, chart) {
1319             runtime.dataList[chartID] = [];
1320             for(var i=0; i < chart.nodes.length; i++) {
1321                 runtime.dataList[chartID][i] = chart.nodes[i].dataPoints;
1322             }
1323             runtime.charts[key].chartID = chartID;
1324             chartID++;
1325         });
1326     }
1328     /* Loads the log table data, generates the table and handles the filters */
1329     function loadLogStatistics(opts) {
1330         var tableStr = '';
1331         var logRequest = null;
1333         if (! opts.removeVariables) {
1334             opts.removeVariables = false;
1335         }
1336         if (! opts.limitTypes) {
1337             opts.limitTypes = false;
1338         }
1339         
1340         $('#emptyDialog').html(PMA_messages['strAnalysingLogs'] + 
1341                                 ' <img class="ajaxIcon" src="' + pmaThemeImage + 
1342                                 'ajax_clock_small.gif" alt="">');
1343         var dlgBtns = {};
1345         dlgBtns[PMA_messages['strCancelRequest']] = function() {
1346             if (logRequest != null) {
1347                 logRequest.abort();
1348             }
1350             $(this).dialog("close");
1351         }
1353         $('#emptyDialog').dialog({
1354             width: 'auto',
1355             height: 'auto',
1356             buttons: dlgBtns
1357         });
1360         logRequest = $.get('server_status.php?' + url_query,
1361             {   ajax_request: true,
1362                 log_data: 1,
1363                 type: opts.src,
1364                 time_start: Math.round(opts.start / 1000),
1365                 time_end: Math.round(opts.end / 1000),
1366                 removeVariables: opts.removeVariables,
1367                 limitTypes: opts.limitTypes
1368             },
1369             function(data) { 
1370                 var logData;
1371                 try {
1372                     logData = $.parseJSON(data);
1373                 } catch(err) {
1374                     return serverResponseError();
1375                 }
1376                 
1377                 if (logData.rows.length != 0) {
1378                     runtime.logDataCols = buildLogTable(logData);
1380                     /* Show some stats in the dialog */
1381                     $('#emptyDialog').attr('title', PMA_messages['strLoadingLogs']);
1382                     $('#emptyDialog').html('<p>' + PMA_messages['strLogDataLoaded'] + '</p>');
1383                     $.each(logData.sum, function(key, value) {
1384                         key = key.charAt(0).toUpperCase() + key.slice(1).toLowerCase();
1385                         if (key == 'Total') {
1386                             key = '<b>' + key + '</b>';
1387                         }
1388                         $('#emptyDialog').append(key + ': ' + value + '<br/>');
1389                     });
1391                     /* Add filter options if more than a bunch of rows there to filter */
1392                     if (logData.numRows > 12) {
1393                         $('div#logTable').prepend(
1394                             '<fieldset id="logDataFilter">' +
1395                             '   <legend>' + PMA_messages['strFiltersForLogTable'] + '</legend>' +
1396                             '   <div class="formelement">' +
1397                             '           <label for="filterQueryText">' + PMA_messages['strFilterByWordRegexp'] + '</label>' +
1398                             '           <input name="filterQueryText" type="text" id="filterQueryText" style="vertical-align: baseline;" />' +
1399                             '   </div>' +
1400                             ((logData.numRows > 250) ? ' <div class="formelement"><button name="startFilterQueryText" id="startFilterQueryText">' + PMA_messages['strFilter'] + '</button></div>' : '') +
1401                             '   <div class="formelement">' +
1402                             '       <input type="checkbox" id="noWHEREData" name="noWHEREData" value="1" /> ' +
1403                             '       <label for="noWHEREData"> ' + PMA_messages['strIgnoreWhereAndGroup'] + '</label>' +
1404                             '   </div' +
1405                             '</fieldset>'
1406                         );
1408                         $('div#logTable input#noWHEREData').change(function() {
1409                             filterQueries(true);
1410                         });
1412                         if (logData.numRows > 250) {
1413                             $('div#logTable button#startFilterQueryText').click(filterQueries);
1414                         } else {
1415                             $('div#logTable input#filterQueryText').keyup(filterQueries);
1416                         }
1418                     }
1420                     var dlgBtns = {};
1421                     dlgBtns[PMA_messages['strJumpToTable']] = function() {
1422                         $(this).dialog("close");
1423                         $(document).scrollTop($('div#logTable').offset().top);
1424                     };
1425                     
1426                     $('#emptyDialog').dialog( "option", "buttons", dlgBtns);
1427                     
1428                 } else {
1429                     $('#emptyDialog').html('<p>' + PMA_messages['strNoDataFound'] + '</p>');
1430                     
1431                     var dlgBtns = {};
1432                     dlgBtns[PMA_messages['strClose']] = function() { 
1433                         $(this).dialog("close"); 
1434                     };
1435                     
1436                     $('#emptyDialog').dialog( "option", "buttons", dlgBtns );
1437                 }
1438             }
1439         );
1441         /* Handles the actions performed when the user uses any of the log table filters 
1442          * which are the filter by name and grouping with ignoring data in WHERE clauses
1443          * 
1444          * @param boolean Should be true when the users enabled or disabled to group queries ignoring data in WHERE clauses
1445         */
1446         function filterQueries(varFilterChange) {
1447             var odd_row = false, cell, textFilter;
1448             var val = $('div#logTable input#filterQueryText').val();
1450             if (val.length == 0) {
1451                 textFilter = null;
1452             } else {
1453                 textFilter = new RegExp(val, 'i');
1454             }
1455             
1456             var rowSum = 0, totalSum = 0, i = 0, q;
1457             var noVars = $('div#logTable input#noWHEREData').attr('checked');
1458             var equalsFilter = /([^=]+)=(\d+|((\'|"|).*?[^\\])\4((\s+)|$))/gi;
1459             var functionFilter = /([a-z0-9_]+)\(.+?\)/gi;
1460             var filteredQueries = {}, filteredQueriesLines = {};
1461             var hide = false, rowData;
1462             var queryColumnName = runtime.logDataCols[runtime.logDataCols.length - 2];
1463             var sumColumnName = runtime.logDataCols[runtime.logDataCols.length - 1];
1464             var isSlowLog = opts.src == 'slow';
1465             var columnSums = {};
1466             
1467             // For the slow log we have to count many columns (query_time, lock_time, rows_examined, rows_sent, etc.)
1468             var countRow = function(query, row) {
1469                 var cells = row.match(/<td>(.*?)<\/td>/gi);
1470                 if (!columnSums[query]) {
1471                     columnSums[query] = [0, 0, 0, 0];
1472                 }
1474                 // lock_time and query_time and displayed in timespan format
1475                 columnSums[query][0] += timeToSec(cells[2].replace(/(<td>|<\/td>)/gi, ''));
1476                 columnSums[query][1] += timeToSec(cells[3].replace(/(<td>|<\/td>)/gi, ''));
1477                 // rows_examind and rows_sent are just numbers
1478                 columnSums[query][2] += parseInt(cells[4].replace(/(<td>|<\/td>)/gi, ''));
1479                 columnSums[query][3] += parseInt(cells[5].replace(/(<td>|<\/td>)/gi, ''));
1480             };
1481             
1482             // We just assume the sql text is always in the second last column, and that the total count is right of it
1483             $('div#logTable table tbody tr td:nth-child(' + (runtime.logDataCols.length - 1) + ')').each(function() {
1484                 // If query is a SELECT and user enabled or disabled to group queries ignoring data in where statements, we 
1485                 // need to re-calculate the sums of each row
1486                 if (varFilterChange && $(this).html().match(/^SELECT/i)) {
1487                     if (noVars) {
1488                         // Group on => Sum up identical columns, and hide all but 1
1489                         
1490                         q = $(this).text().replace(equalsFilter, '$1=...$6').trim();
1491                         q = q.replace(functionFilter, ' $1(...)');
1493                         // Js does not specify a limit on property name length, so we can abuse it as index :-)
1494                         if (filteredQueries[q]) {
1495                             filteredQueries[q] += parseInt($(this).next().text());
1496                             totalSum += parseInt($(this).next().text());
1497                             hide = true;
1498                         } else {
1499                             filteredQueries[q] = parseInt($(this).next().text());;
1500                             filteredQueriesLines[q] = i;
1501                             $(this).text(q);
1502                         }
1503                         if (isSlowLog) {
1504                             countRow(q, $(this).parent().html());
1505                         }
1507                     } else {
1508                         // Group off: Restore original columns
1510                         rowData = $(this).parent().data('query');
1511                         // Restore SQL text
1512                         $(this).text(rowData[queryColumnName]);
1513                         // Restore total count
1514                         $(this).next().text(rowData[sumColumnName]);
1515                         // Restore slow log columns
1516                         if (isSlowLog) {
1517                             $(this).parent().children('td:nth-child(3)').text(rowData['query_time']);
1518                             $(this).parent().children('td:nth-child(4)').text(rowData['lock_time']);
1519                             $(this).parent().children('td:nth-child(5)').text(rowData['rows_sent']);
1520                             $(this).parent().children('td:nth-child(6)').text(rowData['rows_examined']);
1521                         }
1522                     }
1523                 }
1525                 // If not required to be hidden, do we need to hide because of a not matching text filter?
1526                 if (! hide && (textFilter != null && ! textFilter.exec($(this).text()))) {
1527                     hide = true;
1528                 }
1530                 // Now display or hide this column
1531                 if (hide) {
1532                     $(this).parent().css('display', 'none');
1533                 } else {
1534                     totalSum += parseInt($(this).next().text());
1535                     rowSum++;
1537                     odd_row = ! odd_row;
1538                     $(this).parent().css('display', '');
1539                     if (odd_row) {
1540                         $(this).parent().addClass('odd');
1541                         $(this).parent().removeClass('even');
1542                     } else {
1543                         $(this).parent().addClass('even');
1544                         $(this).parent().removeClass('odd');
1545                     }
1546                 }
1548                 hide = false;
1549                 i++;
1550             });
1551                        
1552             // We finished summarizing counts => Update count values of all grouped entries
1553             if (varFilterChange) {
1554                 if (noVars) {
1555                     var numCol, row, $table = $('div#logTable table tbody');
1556                     $.each(filteredQueriesLines, function(key, value) {
1557                         if (filteredQueries[key] <= 1) {
1558                             return;
1559                         }
1560                         
1561                         row =  $table.children('tr:nth-child(' + (value + 1) + ')');
1562                         numCol = row.children(':nth-child(' + (runtime.logDataCols.length) + ')');
1563                         numCol.text(filteredQueries[key]);
1564                         
1565                         if (isSlowLog) {
1566                             row.children('td:nth-child(3)').text(secToTime(columnSums[key][0]));
1567                             row.children('td:nth-child(4)').text(secToTime(columnSums[key][1]));
1568                             row.children('td:nth-child(5)').text(columnSums[key][2]);
1569                             row.children('td:nth-child(6)').text(columnSums[key][3]);
1570                         }
1571                     });
1572                 }
1573                 
1574                 $('div#logTable table').trigger("update"); 
1575                 setTimeout(function() {                    
1576                     $('div#logTable table').trigger('sorton', [[[runtime.logDataCols.length - 1, 1]]]);
1577                 }, 0);
1578             }
1580             // Display some stats at the bottom of the table
1581             $('div#logTable table tfoot tr')
1582                 .html('<th colspan="' + (runtime.logDataCols.length - 1) + '">' +
1583                       PMA_messages['strSumRows'] + ' ' + rowSum + '<span style="float:right">' +
1584                       PMA_messages['strTotal'] + '</span></th><th align="right">' + totalSum + '</th>');
1585         }
1586     }
1588     /* Turns a timespan (12:12:12) into a number */
1589     function timeToSec(timeStr) {
1590         var time = timeStr.split(':');
1591         return parseInt(time[0]*3600) + parseInt(time[1]*60) + parseInt(time[2]);
1592     }
1593     
1594     /* Turns a number into a timespan (100 into 00:01:40) */
1595     function secToTime(timeInt) {
1596         hours = Math.floor(timeInt / 3600);
1597         timeInt -= hours*3600;
1598         minutes = Math.floor(timeInt / 60);
1599         timeInt -= minutes*60;
1600         
1601         if (hours < 10) {
1602             hours = '0' + hours;
1603         }
1604         if (minutes < 10) {
1605             minutes = '0' + minutes;
1606         }
1607         if (timeInt < 10) {
1608             timeInt = '0' + timeInt;
1609         }
1610         
1611         return hours + ':' + minutes + ':' + timeInt;
1612     }
1613     
1614     /* Constructs the log table out of the retrieved server data */
1615     function buildLogTable(data) {
1616         var rows = data.rows;
1617         var cols = new Array();
1618         var $table = $('<table border="0" class="sortable"></table>');
1619         var $tBody, $tRow, $tCell;
1621         $('#logTable').html($table);
1623         var formatValue = function(name, value) {
1624             switch(name) {
1625                 case 'user_host':
1626                     return value.replace(/(\[.*?\])+/g, '');
1627             }
1628             return value;
1629         };
1630         
1631         for (var i = 0; i < rows.length; i++) {
1632             if (i == 0) {
1633                 $.each(rows[0], function(key, value) {
1634                     cols.push(key);
1635                 });
1636                 $table.append( '<thead>' +
1637                                '<tr><th class="nowrap">' + cols.join('</th><th class="nowrap">') + '</th></tr>' +
1638                                '</thead>');
1640                 $table.append($tBody = $('<tbody></tbody>'));
1641             }
1643             $tBody.append($tRow = $('<tr class="noclick"></tr>'));
1644             var cl = '';
1645             for (var j = 0; j < cols.length; j++) {
1646                 // Assuming the query column is the second last
1647                 if (j == cols.length - 2 && rows[i][cols[j]].match(/^SELECT/i)) {
1648                     $tRow.append($tCell = $('<td class="linkElem">' + formatValue(cols[j], rows[i][cols[j]]) + '</td>'));
1649                     $tCell.click(openQueryAnalyzer);
1650                 } else
1651                     $tRow.append('<td>' + formatValue(cols[j], rows[i][cols[j]]) + '</td>');
1654                 $tRow.data('query', rows[i]);
1655             }
1656         }
1658         $table.append('<tfoot>' +
1659                     '<tr><th colspan="' + (cols.length - 1) + '">' + PMA_messages['strSumRows'] +
1660                     ' ' + data.numRows + '<span style="float:right">' + PMA_messages['strTotal'] +
1661                     '</span></th><th align="right">' + data.sum.TOTAL + '</th></tr></tfoot>');
1663         // Append a tooltip to the count column, if there exist one
1664         if ($('#logTable th:last').html() == '#') {
1665             $('#logTable th:last').append('&nbsp;' + PMA_getImage('b_docs.png', '', {'class': 'qroupedQueryInfoIcon'}));
1667             var qtipContent = PMA_messages['strCountColumnExplanation'];
1668             if (groupInserts) {
1669                 qtipContent += '<p>' + PMA_messages['strMoreCountColumnExplanation'] + '</p>';
1670             }
1672             $('img.qroupedQueryInfoIcon').qtip({
1673                 content: qtipContent,
1674                 position: {
1675                     corner: {
1676                         target: 'bottomMiddle',
1677                         tooltip: 'topRight'
1678                     }
1680                 },
1681                 hide: { delay: 1000 }
1682             })
1683         }
1685         $('div#logTable table').tablesorter({
1686             sortList: [[cols.length - 1, 1]],
1687             widgets: ['fast-zebra']
1688         });
1690         $('div#logTable table thead th')
1691             .append('<img class="icon sortableIcon" src="themes/dot.gif" alt="">');
1693         return cols;
1694     }
1695         
1696     /* Opens the query analyzer dialog */
1697     function openQueryAnalyzer() {
1698         var rowData = $(this).parent().data('query');
1699         var query = rowData.argument || rowData.sql_text;
1701         query = PMA_SQLPrettyPrint(query);
1702         codemirror_editor.setValue(query);
1703         // Codemirror is bugged, it doesn't refresh properly sometimes. Following lines seem to fix that
1704         setTimeout(function() {
1705             codemirror_editor.refresh()
1706         },50);
1708         var profilingChart = null;
1709         var dlgBtns = {};
1710         
1711         dlgBtns[PMA_messages['strAnalyzeQuery']] = function() { 
1712             loadQueryAnalysis(rowData); 
1713         };
1714         dlgBtns[PMA_messages['strClose']] = function() { 
1715             if (profilingChart != null) {
1716                 profilingChart.destroy();
1717             }
1718             $('div#queryAnalyzerDialog div.placeHolder').html('');
1719             codemirror_editor.setValue('');
1720             $(this).dialog("close");
1721         };
1723         $('div#queryAnalyzerDialog').dialog({
1724             width: 'auto',
1725             height: 'auto',
1726             resizable: false,
1727             buttons: dlgBtns
1728         });
1729     }
1730     
1731     /* Loads and displays the analyzed query data */
1732     function loadQueryAnalysis(rowData) {
1733         var db = rowData.db || '';
1734         
1735         $('div#queryAnalyzerDialog div.placeHolder').html(
1736             PMA_messages['strAnalyzing'] + ' <img class="ajaxIcon" src="' + 
1737             pmaThemeImage + 'ajax_clock_small.gif" alt="">');
1739         $.post('server_status.php?' + url_query, {
1740             ajax_request: true,
1741             query_analyzer: true,
1742             query: codemirror_editor.getValue(),
1743             database: db
1744         }, function(data) {
1745             data = $.parseJSON(data);
1746             var totalTime = 0;
1748             if (data.error) {
1749                 $('div#queryAnalyzerDialog div.placeHolder').html('<div class="error">' + data.error + '</div>');
1750                 return;
1751             }
1753             // Float sux, I'll use table :(
1754             $('div#queryAnalyzerDialog div.placeHolder')
1755                 .html('<table width="100%" border="0"><tr><td class="explain"></td><td class="chart"></td></tr></table>');
1756             
1757             var explain = '<b>' + PMA_messages['strExplainOutput'] + '</b> ' + explain_docu;
1758             if (data.explain.length > 1) {
1759                 explain += ' (';
1760                 for (var i = 0; i < data.explain.length; i++) {
1761                     if (i > 0) {
1762                         explain += ', ';
1763                     }
1764                     explain += '<a href="#showExplain-' + i + '">' + i + '</a>';
1765                 }
1766                 explain += ')';
1767             }
1768             explain += '<p></p>';
1769             for (var i = 0; i < data.explain.length; i++) {
1770                 explain += '<div class="explain-' + i + '"' + (i>0? 'style="display:none;"' : '' ) + '>';
1771                 $.each(data.explain[i], function(key, value) {
1772                     value = (value == null)?'null':value;
1773                     
1774                     if (key == 'type' && value.toLowerCase() == 'all') {
1775                         value = '<span class="attention">' + value + '</span>';
1776                     }
1777                     if (key == 'Extra') {
1778                         value = value.replace(/(using (temporary|filesort))/gi, '<span class="attention">$1</span>');
1779                     }
1780                     explain += key + ': ' + value + '<br />';
1781                 });
1782                 explain += '</div>';
1783             }
1784             
1785             explain += '<p><b>' + PMA_messages['strAffectedRows'] + '</b> ' + data.affectedRows;
1787             $('div#queryAnalyzerDialog div.placeHolder td.explain').append(explain);
1788             
1789             $('div#queryAnalyzerDialog div.placeHolder a[href*="#showExplain"]').click(function() {
1790                 var id = $(this).attr('href').split('-')[1];
1791                 $(this).parent().find('div[class*="explain"]').hide();
1792                 $(this).parent().find('div[class*="explain-' + id + '"]').show();
1793             });
1794             
1795             if (data.profiling) {
1796                 var chartData = [];
1797                 var numberTable = '<table class="queryNums"><thead><tr><th>' + PMA_messages['strStatus'] + '</th><th>' + PMA_messages['strTime'] + '</th></tr></thead><tbody>';
1798                 var duration;
1800                 for (var i = 0; i < data.profiling.length; i++) {
1801                     duration = parseFloat(data.profiling[i].duration);
1803                     chartData.push([data.profiling[i].state, duration]);
1804                     totalTime += duration;
1806                     numberTable += '<tr><td>' + data.profiling[i].state + ' </td><td> ' + PMA_prettyProfilingNum(duration, 2) + '</td></tr>';
1807                 }
1808                 numberTable += '<tr><td><b>' + PMA_messages['strTotalTime'] + '</b></td><td>' + PMA_prettyProfilingNum(totalTime, 2) + '</td></tr>';
1809                 numberTable += '</tbody></table>';
1810                 
1811                 $('div#queryAnalyzerDialog div.placeHolder td.chart').append(
1812                     '<b>' + PMA_messages['strProfilingResults'] + ' ' + profiling_docu + '</b> ' +
1813                     '(<a href="#showNums">' + PMA_messages['strTable'] + '</a>, <a href="#showChart">' + PMA_messages['strChart'] + '</a>)<br/>' +
1814                     numberTable + ' <div id="queryProfiling"></div>');
1815                 
1816                 $('div#queryAnalyzerDialog div.placeHolder a[href="#showNums"]').click(function() {
1817                     $('div#queryAnalyzerDialog div#queryProfiling').hide();
1818                     $('div#queryAnalyzerDialog table.queryNums').show();
1819                     return false;
1820                 });
1822                 $('div#queryAnalyzerDialog div.placeHolder a[href="#showChart"]').click(function() {
1823                     $('div#queryAnalyzerDialog div#queryProfiling').show();
1824                     $('div#queryAnalyzerDialog table.queryNums').hide();
1825                     return false;
1826                 });
1828                 profilingChart = PMA_createProfilingChart(chartData, {
1829                     chart: {
1830                         renderTo: 'queryProfiling'
1831                     },
1832                     plotOptions: {
1833                         pie: {
1834                             size: '50%'
1835                         }
1836                     }
1837                 });
1840                 $('div#queryProfiling').resizable();
1841             }
1842         });
1843     }
1845     /* Saves the monitor to localstorage */
1846     function saveMonitor() {
1847         var gridCopy = {};
1849         $.each(runtime.charts, function(key, elem) {
1850             gridCopy[key] = {};
1851             gridCopy[key].nodes = elem.nodes;
1852             gridCopy[key].settings = elem.settings;
1853             gridCopy[key].title = elem.title;
1854         });
1856         if (window.localStorage) {
1857             window.localStorage['monitorCharts'] = $.toJSON(gridCopy);
1858             window.localStorage['monitorSettings'] = $.toJSON(monitorSettings);
1859             window.localStorage['monitorVersion'] = monitorProtocolVersion;
1860         }
1862         $('a[href="#clearMonitorConfig"]').show();
1863     }
1866 // Run the monitor once loaded
1867 $(function() {
1868     $('a[href="#pauseCharts"]').trigger('click');