Translated using Weblate.
[phpmyadmin.git] / js / server_status_monitor.js
blob8679b9f0bcb2de9a2465cbd0fd682a4f852d30c8
1 /* vim: set expandtab sw=4 ts=4 sts=4: */
2 $(function() {
3     // Show tab links
4     $('div#statustabs_charting div.tabLinks').show();
5     $('div#statustabs_charting img#loadingMonitorIcon').remove();
6     // Codemirror is loaded on demand so we might need to initialize it
7     if (! codemirror_editor) {
8         var elm = $('#sqlquery');
9         if (elm.length > 0 && typeof CodeMirror != 'undefined') {
10             codemirror_editor = CodeMirror.fromTextArea(elm[0], { lineNumbers: true, matchBrackets: true, indentUnit: 4, mode: "text/x-mysql" });
11         }
12     }
13     // Timepicker is loaded on demand so we need to initialize datetime fields from the 'load log' dialog
14     $('div#logAnalyseDialog .datetimefield').each(function() {
15         PMA_addDatepicker($(this));
16     });
17     
18     /**** Monitor charting implementation ****/
19     /* Saves the previous ajax response for differential values */
20     var oldChartData = null;
21     // Holds about to created chart
22     var newChart = null;
23     var chartSpacing;
24     
25     // Whenever the monitor object (runtime.charts) or the settings object (monitorSettings) 
26     // changes in a way incompatible to the previous version, increase this number
27     // It will reset the users monitor and settings object in his localStorage to the default configuration
28     var monitorProtocolVersion = '1.0';
30     // Runtime parameter of the monitor, is being fully set in initGrid()
31     var runtime = {
32         // Holds all visible charts in the grid
33         charts: null,
34         // Stores the timeout handler so it can be cleared
35         refreshTimeout: null,
36         // Stores the GET request to refresh the charts
37         refreshRequest: null,
38         // Chart auto increment
39         chartAI: 0,
40         // To play/pause the monitor
41         redrawCharts: false,
42         // Object that contains a list of nodes that need to be retrieved from the server for chart updates
43         dataList: [],
44         // Current max points per chart (needed for auto calculation)
45         gridMaxPoints: 20,
46         // displayed time frame
47         xmin: -1,
48         xmax: -1
49     };
50     
51     var monitorSettings = null;
53     var defaultMonitorSettings = {
54         columns: 3,
55         chartSize: { width: 295, height: 250 },
56         // Max points in each chart. Settings it to 'auto' sets gridMaxPoints to (chartwidth - 40) / 12
57         gridMaxPoints: 'auto',
58         /* Refresh rate of all grid charts in ms */
59         gridRefresh: 5000
60     };
62     // Allows drag and drop rearrange and print/edit icons on charts
63     var editMode = false;
64     
65     /* List of preconfigured charts that the user may select */
66     var presetCharts = {
67         // Query cache efficiency
68         'qce': {
69             title: PMA_messages['strQueryCacheEfficiency'],
70             series: [ {
71                 label: PMA_messages['strQueryCacheEfficiency']
72             } ],
73             nodes: [ {
74                 dataPoints: [{type: 'statusvar', name: 'Qcache_hits'}, {type: 'statusvar', name: 'Com_select'}],
75                 transformFn: 'qce'
76              } ],
77             maxYLabel: []
78         },
79         // Query cache usage
80         'qcu': {
81             title: PMA_messages['strQueryCacheUsage'],
82             series: [ {
83                 label: PMA_messages['strQueryCacheUsed']
84             } ],
85             nodes: [ {
86                 dataPoints: [{type: 'statusvar', name: 'Qcache_free_memory'}, {type: 'servervar', name: 'query_cache_size'}],
87                 transformFn: 'qcu'
88              } ],
89             maxYLabel: []
90         }
91     };
93     // time span selection
94     var selectionTimeDiff = new Array();
95     var selectionStartX, selectionStartY, selectionEndX, selectionEndY;
96     var drawTimeSpan = false;
98     // chart tooltip
99     var tooltipBox;
100     
101     /* Add OS specific system info charts to the preset chart list */
102     switch(server_os) {
103     case 'WINNT': 
104         $.extend(presetCharts, { 
105             'cpu': {
106                 title: PMA_messages['strSystemCPUUsage'],
107                 series: [ { 
108                     label: PMA_messages['strAverageLoad']
109                 } ],
110                 nodes: [ {
111                     dataPoints: [{ type: 'cpu', name: 'loadavg'}]
112                  } ],
113                 maxYLabel: []
114             },
115             
116             'memory': {
117                 title: PMA_messages['strSystemMemory'],
118                 series: [ {
119                     label: PMA_messages['strTotalMemory'],
120                     fill:true,
121                     stackSeries: true
122                 }, {
123                     dataType: 'memory',
124                     label: PMA_messages['strUsedMemory'],
125                     fill:true,
126                     stackSeries: true
127                 } ],
128                 nodes: [{ dataPoints: [{ type: 'memory', name: 'MemTotal' }], valueDivisor: 1024 },
129                         { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 }
130                 ],
131                 maxYLabel: []
132             },
133             
134             'swap': {
135                 title: PMA_messages['strSystemSwap'],
136                 series: [ {
137                     label: PMA_messages['strTotalSwap'],
138                     fill:true,
139                     stackSeries: true
140                 }, {
141                     label: PMA_messages['strUsedSwap'],
142                     fill:true,
143                     stackSeries: true
144                 } ],
145                 nodes: [{ dataPoints: [{ type: 'memory', name: 'SwapTotal' }]},
146                         { dataPoints: [{ type: 'memory', name: 'SwapUsed' }]}
147                 ],
148                 maxYLabel: []
149             }
150         });
151         break;
152         
153     case 'Linux':
154         $.extend(presetCharts, {
155             'cpu': {
156                 title: PMA_messages['strSystemCPUUsage'],
157                 series: [ {
158                     label: PMA_messages['strAverageLoad']
159                 } ],
160                 nodes: [{ dataPoints: [{ type: 'cpu', name: 'irrelevant' }], transformFn: 'cpu-linux'}],
161                 maxYLabel: []
162             },
163             'memory': {
164                 title: PMA_messages['strSystemMemory'],
165                 series: [
166                     { label: PMA_messages['strUsedMemory'], fill:true, stackSeries: true},
167                     { label: PMA_messages['strCachedMemory'], fill:true, stackSeries: true},
168                     { label: PMA_messages['strBufferedMemory'], fill:true, stackSeries: true},
169                     { label: PMA_messages['strFreeMemory'], fill:true, stackSeries: true}
170                 ],
171                 nodes: [
172                     { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 },
173                     { dataPoints: [{ type: 'memory', name: 'Cached' }],  valueDivisor: 1024 },
174                     { dataPoints: [{ type: 'memory', name: 'Buffers' }], valueDivisor: 1024 },
175                     { dataPoints: [{ type: 'memory', name: 'MemFree' }], valueDivisor: 1024 }
176                 ],
177                 maxYLabel: []
178              },
179             'swap': {
180                 title: PMA_messages['strSystemSwap'],
181                 series: [
182                     { label: PMA_messages['strUsedSwap'], fill:true, stackSeries: true},
183                     { label: PMA_messages['strCachedSwap'], fill:true, stackSeries: true},
184                     { label: PMA_messages['strFreeSwap'], fill:true, stackSeries: true}
185                 ],
186                 nodes: [
187                     { dataPoints: [{ type: 'memory', name: 'SwapUsed' }], valueDivisor: 1024 },
188                     { dataPoints: [{ type: 'memory', name: 'SwapCached' }], valueDivisor: 1024 },
189                     { dataPoints: [{ type: 'memory', name: 'SwapFree' }], valueDivisor: 1024 }
190                 ],
191                 maxYLabel: []
192             }
193         });
194         break;
195         
196     case 'SunOS':
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: []
207             },
208             'memory': {
209                 title: PMA_messages['strSystemMemory'],
210                 series: [
211                     { label: PMA_messages['strUsedMemory'], fill:true, stackSeries: true},
212                     { label: PMA_messages['strFreeMemory'], fill:true, stackSeries: true}
213                 ],
214                 nodes: [
215                     { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 },
216                     { dataPoints: [{ type: 'memory', name: 'MemFree' }], valueDivisor: 1024 }
217                 ],
218                 maxYLabel: []
219              },
220             'swap': {
221                 title: PMA_messages['strSystemSwap'],
222                 series: [
223                     { label: PMA_messages['strUsedSwap'], fill:true, stackSeries: true},
224                     { label: PMA_messages['strFreeSwap'], fill:true, stackSeries: true}
225                 ],
226                 nodes: [
227                     { dataPoints: [{ type: 'memory', name: 'SwapUsed' }], valueDivisor: 1024 },
228                     { dataPoints: [{ type: 'memory', name: 'SwapFree' }], valueDivisor: 1024 }
229                 ],
230                 maxYLabel: []
231             }
232         });
233         break;
234     }
236     // Default setting for the chart grid
237     defaultChartGrid = {
238         'c0': {  title: PMA_messages['strQuestions'],
239                  series: [{label: PMA_messages['strQuestions']}],
240                  nodes: [{dataPoints: [{ type: 'statusvar', name: 'Questions' }], display: 'differential' }],
241                 maxYLabel: []
242         },
243         'c1': {
244                  title: PMA_messages['strChartConnectionsTitle'],
245                  series: [ { label: PMA_messages['strConnections']},
246                           { label: PMA_messages['strProcesses']} ],
247                  nodes: [ { dataPoints: [{ type: 'statusvar', name: 'Connections' }], display: 'differential' },
248                           { dataPoints: [{ type: 'proc', name: 'processes' }] }
249                 ],
250                 maxYLabel: []
251         },
252         'c2': {
253                  title: PMA_messages['strTraffic'],
254                  series: [
255                     { label: PMA_messages['strBytesSent']},
256                     { label: PMA_messages['strBytesReceived']}
257                  ],
258                  nodes: [
259                     { dataPoints: [{ type: 'statusvar', name: 'Bytes_sent' }], display: 'differential', valueDivisor: 1024 },
260                     { dataPoints: [{ type: 'statusvar', name: 'Bytes_received' }], display: 'differential', valueDivisor: 1024 }
261                 ],
262                 maxYLabel: []
263          }
264     };
266     // Server is localhost => We can add cpu/memory/swap to the default chart
267     if (server_db_isLocal) {
268         defaultChartGrid['c3'] = presetCharts['cpu'];
269         defaultChartGrid['c4'] = presetCharts['memory'];
270         defaultChartGrid['c5'] = presetCharts['swap'];
271     }
273     /* Buttons that are on the top right corner of each chart */
274     var gridbuttons = {
275         cogButton: {
276             //enabled: true,
277             symbol: 'url(' + pmaThemeImage  + 's_cog.png)',
278             x: -36,
279             symbolFill: '#B5C9DF',
280             hoverSymbolFill: '#779ABF',
281             _titleKey: 'settings',
282             menuName: 'gridsettings',
283             menuItems: [{
284                 textKey: 'editChart',
285                 onclick: function() {
286                     editChart(this);
287                 }
288             }, {
289                 textKey: 'removeChart',
290                 onclick: function() {
291                     removeChart(this);
292                 }
293             }]
294         }
295     };
297     $('a[href="#rearrangeCharts"], a[href="#endChartEditMode"]').click(function() {
298         editMode = !editMode;
299         if ($(this).attr('href') == '#endChartEditMode') {
300             editMode = false;
301         }
303         // Icon graphics have zIndex 19, 20 and 21. Let's just hope nothing else has the same zIndex
304         $('table#chartGrid div svg').find('*[zIndex=20], *[zIndex=21], *[zIndex=19]').toggle(editMode)
306         $('a[href="#endChartEditMode"]').toggle(editMode);
308         if (editMode) {
309             // Close the settings popup
310             $('#statustabs_charting .popupContent').hide().removeClass('openedPopup');
312             $("#chartGrid").sortableTable({
313                 ignoreRect: {
314                     top: 8,
315                     left: chartSize().width - 63,
316                     width: 54,
317                     height: 24
318                 },
319                 events: {
320                     // Drop event. The drag child element is moved into the drop element
321                     // and vice versa. So the parameters are switched.
322                     drop: function(drag, drop, pos) {
323                         var dragKey, dropKey, dropRender;
324                         var dragRender = $(drag).children().first().attr('id');
326                         if ($(drop).children().length > 0) {
327                             dropRender = $(drop).children().first().attr('id');
328                         }
330                         // Find the charts in the array
331                         $.each(runtime.charts, function(key, value) {
332                             if (value.chart.options.chart.renderTo == dragRender) {
333                                 dragKey = key;
334                             }
335                             if (dropRender && value.chart.options.chart.renderTo == dropRender) {
336                                 dropKey = key;
337                             }
338                         });
340                         // Case 1: drag and drop are charts -> Switch keys
341                         if (dropKey) {
342                             if (dragKey) {
343                                 dragChart = runtime.charts[dragKey];
344                                 runtime.charts[dragKey] = runtime.charts[dropKey];
345                                 runtime.charts[dropKey] = dragChart;
346                             } else {
347                                 // Case 2: drop is a empty cell => just completely rebuild the ids
348                                 var keys = [];
349                                 var dropKeyNum = parseInt(dropKey.substr(1));
350                                 var insertBefore = pos.col + pos.row * monitorSettings.columns;
351                                 var values = [];
352                                 var newChartList = {};
353                                 var c = 0;
355                                 $.each(runtime.charts, function(key, value) {
356                                     if (key != dropKey) {
357                                         keys.push(key);
358                                     }
359                                 });
361                                 keys.sort();
363                                 // Rebuilds all ids, with the dragged chart correctly inserted
364                                 for (var i = 0; i<keys.length; i++) {
365                                     if (keys[i] == insertBefore) {
366                                         newChartList['c' + (c++)] = runtime.charts[dropKey];
367                                         insertBefore = -1; // Insert ok
368                                     }
369                                     newChartList['c' + (c++)] = runtime.charts[keys[i]];
370                                 }
372                                 // Not inserted => put at the end
373                                 if (insertBefore != -1) {
374                                     newChartList['c' + (c++)] = runtime.charts[dropKey];
375                                 }
377                                 runtime.charts = newChartList;
378                             }
380                             saveMonitor();
381                         }
382                     }
383                 }
384             });
386         } else {
387             $("#chartGrid").sortableTable('destroy');
388             saveMonitor(); // Save settings
389         }
391         return false;
392     });
394     // global settings
395     $('div#statustabs_charting div.popupContent select[name="chartColumns"]').change(function() {
396         monitorSettings.columns = parseInt(this.value);
398         var newSize = chartSize();
400         // Empty cells should keep their size so you can drop onto them
401         $('table#chartGrid tr td').css('width', newSize.width + 'px');
403         /* Reorder all charts that it fills all column cells */
404         var numColumns;
405         var $tr = $('table#chartGrid tr:first');
406         var row = 0;
407         while($tr.length != 0) {
408             numColumns = 1;
409             // To many cells in one row => put into next row
410             $tr.find('td').each(function() {
411                 if (numColumns > monitorSettings.columns) {
412                     if ($tr.next().length == 0) { 
413                         $tr.after('<tr></tr>');
414                     }
415                     $tr.next().prepend($(this));
416                 }
417                 numColumns++;
418             });
420             // To little cells in one row => for each cell to little, move all cells backwards by 1
421             if ($tr.next().length > 0) {
422                 var cnt = monitorSettings.columns - $tr.find('td').length;
423                 for (var i = 0; i < cnt; i++) {
424                     $tr.append($tr.next().find('td:first'));
425                     $tr.nextAll().each(function() {
426                         if ($(this).next().length != 0) {
427                             $(this).append($(this).next().find('td:first'));
428                         }
429                     });
430                 }
431             }
433             $tr = $tr.next();
434             row++;
435         }
437         /* Apply new chart size to all charts */
438         $.each(runtime.charts, function(key, value) {
439             value.chart.setSize(
440                 newSize.width,
441                 newSize.height,
442                 false
443             );
444         });
446         if (monitorSettings.gridMaxPoints == 'auto') {
447             runtime.gridMaxPoints = Math.round((newSize.width - 40) / 12);
448         }
450         runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
451         runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh;
453         if (editMode) {
454             $("#chartGrid").sortableTable('refresh');
455         }
457         saveMonitor(); // Save settings
458     });
460     $('div#statustabs_charting div.popupContent select[name="gridChartRefresh"]').change(function() {
461         monitorSettings.gridRefresh = parseInt(this.value) * 1000;
462         clearTimeout(runtime.refreshTimeout);
464         if (runtime.refreshRequest) {
465             runtime.refreshRequest.abort();
466         }
468         runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
469         runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh;
471         $.each(runtime.charts, function(key, value) {
472             value.chart['axes']['xaxis']['max'] = runtime.xmax;
473             value.chart['axes']['xaxis']['min'] = runtime.xmin;
474         });
476         runtime.refreshTimeout = setTimeout(refreshChartGrid, monitorSettings.gridRefresh);
478         saveMonitor(); // Save settings
479     });
481     $('a[href="#addNewChart"]').click(function() {
482         var dlgButtons = { };
484         dlgButtons[PMA_messages['strAddChart']] = function() {
485             var type = $('input[name="chartType"]:checked').val();
487             if (type == 'preset') {
488                 newChart = presetCharts[$('div#addChartDialog select[name="presetCharts"]').prop('value')];
489             } else {
490                 // If user builds his own chart, it's being set/updated each time he adds a series
491                 // So here we only warn if he didn't add a series yet
492                 if (! newChart || ! newChart.nodes || newChart.nodes.length == 0) {
493                     alert(PMA_messages['strAddOneSeriesWarning']);
494                     return;
495                 }
496             }
498             newChart.title = $('input[name="chartTitle"]').attr('value');
499             // Add a cloned object to the chart grid
500             addChart($.extend(true, {}, newChart));
502             newChart = null;
504             saveMonitor(); // Save settings
506             $(this).dialog("close");
507         };
508         
509         dlgButtons[PMA_messages['strClose']] = function() {
510             newChart = null;
511             $('span#clearSeriesLink').hide();
512             $('#seriesPreview').html('');
513             $(this).dialog("close");
514         };
515         
516         var $presetList = $('div#addChartDialog select[name="presetCharts"]');
517         if ($presetList.html().length == 0) {
518             $.each(presetCharts, function(key, value) {
519                 $presetList.append('<option value="' + key + '">' + value.title + '</option>');
520             });
521             $presetList.change(function() {
522                 $('input#chartPreset').trigger('click');
523                 $('input[name="chartTitle"]').attr('value', presetCharts[$(this).prop('value')].title);
524             });
525         }
526         
527         $('div#addChartDialog').dialog({
528             width: 'auto',
529             height: 'auto',
530             buttons: dlgButtons
531         });
533         $('div#addChartDialog #seriesPreview').html('<i>' + PMA_messages['strNone'] + '</i>');
535         return false;
536     });
537     
538     $('a[href="#exportMonitorConfig"]').click(function() {
539         var gridCopy = {};
541         $.each(runtime.charts, function(key, elem) {
542             gridCopy[key] = {};
543             gridCopy[key].nodes = elem.nodes;
544             gridCopy[key].settings = elem.settings;
545             gridCopy[key].title = elem.title;
546         });
547         
548         var exportData = {
549             monitorCharts: gridCopy,
550             monitorSettings: monitorSettings
551         };
552         var $form;
553         
554         $('body').append($form = $('<form method="post" action="file_echo.php?' + url_query + '&filename=1" style="display:none;"></form>'));
555         
556         $form.append('<input type="hidden" name="monitorconfig" value="' + encodeURI($.toJSON(exportData)) + '">');
557         $form.submit();
558         $form.remove();
559     });
561     $('a[href="#importMonitorConfig"]').click(function() {
562         $('div#emptyDialog').dialog({title: PMA_messages['strImportDialogTitle']});
563         $('div#emptyDialog').html(PMA_messages['strImportDialogMessage'] + ':<br/><form action="file_echo.php?' + url_query + '&import=1" method="post" enctype="multipart/form-data">' +
564             '<input type="file" name="file"> <input type="hidden" name="import" value="1"> </form>');
565         
566         var dlgBtns = {};
567         
568         dlgBtns[PMA_messages['strImport']] = function() {
569             var $iframe, $form;
570             $('body').append($iframe = $('<iframe id="monitorConfigUpload" style="display:none;"></iframe>'));
571             var d = $iframe[0].contentWindow.document;
572             d.open(); d.close();
573             mew = d;
574             
575             $iframe.load(function() {
576                 var json;
578                 // Try loading config
579                 try {
580                     var data = $('body', $('iframe#monitorConfigUpload')[0].contentWindow.document).html();
581                     // Chrome wraps around '<pre style="word-wrap: break-word; white-space: pre-wrap;">' to any text content -.-
582                     json = $.secureEvalJSON(data.substring(data.indexOf("{"), data.lastIndexOf("}") + 1));
583                 } catch (err) {
584                     alert(PMA_messages['strFailedParsingConfig']);
585                     $('div#emptyDialog').dialog('close');
586                     return;
587                 }
588             
589                 // Basic check, is this a monitor config json?
590                 if (!json || ! json.monitorCharts || ! json.monitorCharts) {
591                     alert(PMA_messages['strFailedParsingConfig']);
592                     $('div#emptyDialog').dialog('close');
593                     return;
594                 }
595                 
596                 // If json ok, try applying config
597                 try {
598                     window.localStorage['monitorCharts'] = $.toJSON(json.monitorCharts);
599                     window.localStorage['monitorSettings'] = $.toJSON(json.monitorSettings);
600                     rebuildGrid();
601                 } catch(err) {
602                     alert(PMA_messages['strFailedBuildingGrid']);
603                     // If an exception is thrown, load default again
604                     window.localStorage.removeItem('monitorCharts');
605                     window.localStorage.removeItem('monitorSettings');
606                     rebuildGrid();
607                 }
608                 
609                 $('div#emptyDialog').dialog('close');
610             });
611             
612             $("body", d).append($form = $('div#emptyDialog').find('form'));
613             $form.submit();
614             $('div#emptyDialog').append('<img class="ajaxIcon" src="' + pmaThemeImage + 'ajax_clock_small.gif" alt="">');
615         };
616         
617         dlgBtns[PMA_messages['strCancel']] = function() {
618             $(this).dialog('close');
619         }
620         
621         
622         $('div#emptyDialog').dialog({
623             width: 'auto',
624             height: 'auto',
625             buttons: dlgBtns
626         });
627     });
629     $('a[href="#clearMonitorConfig"]').click(function() {
630         window.localStorage.removeItem('monitorCharts');
631         window.localStorage.removeItem('monitorSettings');
632         window.localStorage.removeItem('monitorVersion');
633         $(this).hide();
634         rebuildGrid();
635     });
637     $('a[href="#pauseCharts"]').click(function() {
638         runtime.redrawCharts = ! runtime.redrawCharts;
639         if (! runtime.redrawCharts) {
640             $(this).html(PMA_getImage('play.png') + ' ' + PMA_messages['strResumeMonitor']);
641         } else {
642             $(this).html(PMA_getImage('pause.png') + ' ' + PMA_messages['strPauseMonitor']);
643             if (! runtime.charts) {
644                 initGrid();
645                 $('a[href="#settingsPopup"]').show();
646             }
647         }
648         return false;
649     });
650     
651     $('a[href="#monitorInstructionsDialog"]').click(function() {
652         var $dialog = $('div#monitorInstructionsDialog');
654         $dialog.dialog({
655             width: 595,
656             height: 'auto'
657         }).find('img.ajaxIcon').show();
659         var loadLogVars = function(getvars) {
660             vars = { ajax_request: true, logging_vars: true };
661             if (getvars) {
662                 $.extend(vars, getvars);
663             }
665             $.get('server_status.php?' + url_query, vars,
666                 function(data) {
667                     var logVars = $.parseJSON(data),
668                         icon = PMA_getImage('s_success.png'), msg='', str='';
670                     if (logVars['general_log'] == 'ON') {
671                         if (logVars['slow_query_log'] == 'ON') {
672                             msg = PMA_messages['strBothLogOn'];
673                         } else {
674                             msg = PMA_messages['strGenLogOn'];
675                         }
676                     }
678                     if (msg.length == 0 && logVars['slow_query_log'] == 'ON') {
679                         msg = PMA_messages['strSlowLogOn'];
680                     }
682                     if (msg.length == 0) {
683                         icon = PMA_getImage('s_error.png');
684                         msg = PMA_messages['strBothLogOff'];
685                     }
687                     str = '<b>' + PMA_messages['strCurrentSettings'] + '</b><br><div class="smallIndent">';
688                     str += icon + msg + '<br />';
690                     if (logVars['log_output'] != 'TABLE') {
691                         str += PMA_getImage('s_error.png') + ' ' + PMA_messages['strLogOutNotTable'] + '<br />';
692                     } else {
693                         str += PMA_getImage('s_success.png') + ' ' + PMA_messages['strLogOutIsTable'] + '<br />';
694                     }
696                     if (logVars['slow_query_log'] == 'ON') {
697                         if (logVars['long_query_time'] > 2) {
698                             str += PMA_getImage('s_attention.png') + ' '
699                                 + $.sprintf(PMA_messages['strSmallerLongQueryTimeAdvice'], logVars['long_query_time'])
700                                 + '<br />';
701                         }
702                         
703                         if (logVars['long_query_time'] < 2) {
704                             str += PMA_getImage('s_success.png') + ' '
705                                 + $.sprintf(PMA_messages['strLongQueryTimeSet'], logVars['long_query_time'])
706                                 + '<br />';
707                         }
708                     }
710                     str += '</div>';
712                     if (is_superuser) {
713                         str += '<p></p><b>' + PMA_messages['strChangeSettings'] + '</b>';
714                         str += '<div class="smallIndent">';
715                         str += PMA_messages['strSettingsAppliedGlobal'] + '<br/>';
717                         var varValue = 'TABLE';
718                         if (logVars['log_output'] == 'TABLE') {
719                             varValue = 'FILE';
720                         }
721                         
722                         str += '- <a class="set" href="#log_output-' + varValue + '">'
723                             + $.sprintf(PMA_messages['strSetLogOutput'], varValue)
724                             + ' </a><br />';
726                         if (logVars['general_log'] != 'ON') {
727                             str += '- <a class="set" href="#general_log-ON">'
728                                 + $.sprintf(PMA_messages['strEnableVar'], 'general_log')
729                                 + ' </a><br />';
730                         } else {
731                             str += '- <a class="set" href="#general_log-OFF">'
732                                 + $.sprintf(PMA_messages['strDisableVar'], 'general_log')
733                                 + ' </a><br />';
734                         }
736                         if (logVars['slow_query_log'] != 'ON') {
737                             str += '- <a class="set" href="#slow_query_log-ON">'
738                                 +  $.sprintf(PMA_messages['strEnableVar'], 'slow_query_log')
739                                 + ' </a><br />';
740                         } else {
741                             str += '- <a class="set" href="#slow_query_log-OFF">'
742                                 +  $.sprintf(PMA_messages['strDisableVar'], 'slow_query_log')
743                                 + ' </a><br />';
744                         }
746                         varValue = 5;
747                         if (logVars['long_query_time'] > 2) {
748                             varValue = 1;
749                         }
751                         str += '- <a class="set" href="#long_query_time-' + varValue + '">'
752                             + $.sprintf(PMA_messages['setSetLongQueryTime'], varValue)
753                             + ' </a><br />';
755                     } else {
756                         str += PMA_messages['strNoSuperUser'] + '<br/>';
757                     }
759                     str += '</div>';
761                     $dialog.find('div.monitorUse').toggle(
762                         logVars['log_output'] == 'TABLE' && (logVars['slow_query_log'] == 'ON' || logVars['general_log'] == 'ON')
763                     );
765                     $dialog.find('div.ajaxContent').html(str);
766                     $dialog.find('img.ajaxIcon').hide();
767                     $dialog.find('a.set').click(function() {
768                         var nameValue = $(this).attr('href').split('-');
769                         loadLogVars({ varName: nameValue[0].substr(1), varValue: nameValue[1]});
770                         $dialog.find('img.ajaxIcon').show();
771                     });
772                 }
773             );
774         };
775         
776         
777         loadLogVars();
779         return false;
780     });
782     $('input[name="chartType"]').change(function() {
783         $('#chartVariableSettings').toggle(this.checked && this.value == 'variable');
784         var title = $('input[name="chartTitle"]').attr('value');
785         if (title == PMA_messages['strChartTitle'] 
786            || title == $('label[for="' + $('input[name="chartTitle"]').data('lastRadio') + '"]').text()
787         ) {
788             $('input[name="chartTitle"]')
789                 .data('lastRadio', $(this).attr('id'))
790                 .attr('value', $('label[for="' + $(this).attr('id') + '"]').text());
791         }
793     });
795     $('input[name="useDivisor"]').change(function() {
796         $('span.divisorInput').toggle(this.checked);
797     });
798     $('input[name="useUnit"]').change(function() {
799         $('span.unitInput').toggle(this.checked);
800     });
802     $('select[name="varChartList"]').change(function () {
803         if (this.selectedIndex != 0) {
804             $('#variableInput').attr('value', this.value);
805         }
806     });
808     $('a[href="#kibDivisor"]').click(function() {
809         $('input[name="valueDivisor"]').attr('value', 1024);
810         $('input[name="valueUnit"]').attr('value', PMA_messages['strKiB']);
811         $('span.unitInput').toggle(true);
812         $('input[name="useUnit"]').prop('checked', true);
813         return false;
814     });
816     $('a[href="#mibDivisor"]').click(function() {
817         $('input[name="valueDivisor"]').attr('value', 1024*1024);
818         $('input[name="valueUnit"]').attr('value', PMA_messages['strMiB']);
819         $('span.unitInput').toggle(true);
820         $('input[name="useUnit"]').prop('checked', true);
821         return false;
822     });
824     $('a[href="#submitClearSeries"]').click(function() {
825         $('#seriesPreview').html('<i>' + PMA_messages['strNone'] + '</i>');
826         newChart = null;
827         $('span#clearSeriesLink').hide();
828     });
830     $('a[href="#submitAddSeries"]').click(function() {
831         if ($('input#variableInput').attr('value').length == 0) {
832             return false;
833         }
834         
835         if (newChart == null) {
836             $('#seriesPreview').html('');
838             newChart = {
839                 title: $('input[name="chartTitle"]').attr('value'),
840                 nodes: []
841             };
842         }
844         var serie = {
845             dataPoints: [{ type: 'statusvar', name: $('input#variableInput').attr('value') }],
846             name: $('input#variableInput').attr('value'),
847             display: $('input[name="differentialValue"]').attr('checked') ? 'differential' : ''
848         };
850         if (serie.dataPoint == 'Processes') {
851             serie.dataType='proc';
852         }
854         if ($('input[name="useDivisor"]').attr('checked')) {
855             serie.valueDivisor = parseInt($('input[name="valueDivisor"]').attr('value'));
856         }
858         if ($('input[name="useUnit"]').attr('checked')) {
859             serie.unit = $('input[name="valueUnit"]').attr('value');
860         }
862         var str = serie.display == 'differential' ? ', ' + PMA_messages['strDifferential'] : '';
863         str += serie.valueDivisor ? (', ' + $.sprintf(PMA_messages['strDividedBy'], serie.valueDivisor)) : '';
864         str += serie.unit ? (', ' + PMA_messages['strUnit'] + ': ' + serie.unit) : '';
866         $('#seriesPreview').append('- ' + serie.name + str + '<br>');
868         newChart.nodes.push(serie);
870         $('input#variableInput').attr('value', '');
871         $('input[name="differentialValue"]').attr('checked', true);
872         $('input[name="useDivisor"]').attr('checked', false);
873         $('input[name="useUnit"]').attr('checked', false);
874         $('input[name="useDivisor"]').trigger('change');
875         $('input[name="useUnit"]').trigger('change');
876         $('select[name="varChartList"]').get(0).selectedIndex = 0;
878         $('span#clearSeriesLink').show();
880         return false;
881     });
883     $("input#variableInput").autocomplete({
884             source: variableNames
885     });
887     /* Initializes the monitor, called only once */
888     function initGrid() {
890         /* Apply default values & config */
891         if (window.localStorage) {
892             if (window.localStorage['monitorCharts']) {
893                 runtime.charts = $.parseJSON(window.localStorage['monitorCharts']);
894             }
895             if (window.localStorage['monitorSettings']) {
896                 monitorSettings = $.parseJSON(window.localStorage['monitorSettings']);
897             }
899             $('a[href="#clearMonitorConfig"]').toggle(runtime.charts != null);
901             if (runtime.charts != null && monitorProtocolVersion != window.localStorage['monitorVersion']) {
902                 $('div#emptyDialog').dialog({title: PMA_messages['strIncompatibleMonitorConfig']});
903                 $('div#emptyDialog').html(PMA_messages['strIncompatibleMonitorConfigDescription']);
905                 var dlgBtns = {};
906                 dlgBtns[PMA_messages['strClose']] = function() { $(this).dialog('close'); };
908                 $('div#emptyDialog').dialog({ 
909                     width: 400,
910                     buttons: dlgBtns 
911                 });
912             }            
913         }
915         if (runtime.charts == null) {
916             runtime.charts = defaultChartGrid;
917         }
918         if (monitorSettings == null) {
919             monitorSettings = defaultMonitorSettings;
920         }
922         $('select[name="gridChartRefresh"]').attr('value', monitorSettings.gridRefresh / 1000);
923         $('select[name="chartColumns"]').attr('value', monitorSettings.columns);
925         if (monitorSettings.gridMaxPoints == 'auto') {
926             runtime.gridMaxPoints = Math.round((monitorSettings.chartSize.width - 40) / 12);
927         } else {
928             runtime.gridMaxPoints = monitorSettings.gridMaxPoints;
929         }
931         runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
932         runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh;
934         /* Calculate how much spacing there is between each chart */
935         $('table#chartGrid').html('<tr><td></td><td></td></tr><tr><td></td><td></td></tr>');
936         chartSpacing = {
937             width: $('table#chartGrid td:nth-child(2)').offset().left 
938                     - $('table#chartGrid td:nth-child(1)').offset().left,
939             height: $('table#chartGrid tr:nth-child(2) td:nth-child(2)').offset().top 
940                     - $('table#chartGrid tr:nth-child(1) td:nth-child(1)').offset().top
941         }
942         $('table#chartGrid').html('');
943         
944         /* Add all charts - in correct order */
945         var keys = [];
946         $.each(runtime.charts, function(key, value) {
947             keys.push(key);
948         });
949         keys.sort();
950         for (var i = 0; i<keys.length; i++)
951             addChart(runtime.charts[keys[i]], true);
953         /* Fill in missing cells */
954         var numCharts = $('table#chartGrid .monitorChart').length;
955         var numMissingCells = (monitorSettings.columns - numCharts % monitorSettings.columns) % monitorSettings.columns;
956         for (var i = 0; i < numMissingCells; i++) {
957             $('table#chartGrid tr:last').append('<td></td>');
958         }
960         // Empty cells should keep their size so you can drop onto them
961         $('table#chartGrid tr td').css('width', chartSize().width + 'px');
962         
963         buildRequiredDataList();
964         refreshChartGrid();
965     }
966     
967     /* Destroys all monitor related resources */
968     function destroyGrid() {
969         if (runtime.charts) {
970             $.each(runtime.charts, function(key, value) {
971                 try {
972                     value.chart.destroy();
973                 } catch(err) {}
974             });
975         }
976         
977         try {
978             runtime.refreshRequest.abort();
979         } catch(err) {}
980         try {    
981             clearTimeout(runtime.refreshTimeout);
982         } catch(err) {}
983             
984         $('table#chartGrid').html('');
986         runtime.charts = null;
987         runtime.chartAI = 0;
988         monitorSettings = null;
989     }
990     
991     /* Calls destroyGrid() and initGrid(), but before doing so it saves the chart 
992      * data from each chart and restores it after the monitor is initialized again */
993     function rebuildGrid() {
994         var oldData = null;
995         if (runtime.charts) {
996             oldData = {};
997             $.each(runtime.charts, function(key, chartObj) {
998                 for (var i = 0; i < chartObj.nodes.length; i++) {
999                     oldData[chartObj.nodes[i].dataPoint] = [];
1000                     for (var j = 0; j < chartObj.chart.series[i].data.length; j++)
1001                         oldData[chartObj.nodes[i].dataPoint].push([chartObj.chart.series[i].data[j].x, chartObj.chart.series[i].data[j].y]);
1002                 }
1003             });
1004         }
1005         
1006         destroyGrid();
1007         initGrid();
1008         
1009         if (oldData) {
1010             $.each(runtime.charts, function(key, chartObj) {
1011                 for (var j = 0; j < chartObj.nodes.length; j++) {
1012                     if (oldData[chartObj.nodes[j].dataPoint]) {
1013                         chartObj.chart.series[j].setData(oldData[chartObj.nodes[j].dataPoint]);
1014                     }
1015                 }
1016             });
1017         }      
1018     }
1020     /* Calculactes the dynamic chart size that depends on the column width */
1021     function chartSize() {
1022         var wdt = $('div#logTable').innerWidth() / monitorSettings.columns - (monitorSettings.columns - 1) * chartSpacing.width;
1023         return {
1024             width: wdt,
1025             height: 0.75 * wdt
1026         }
1027     }
1029     /* Adds a chart to the chart grid */
1030     function addChart(chartObj, initialize) {
1032         var settings = {
1033             title: chartObj.title,
1034             grid: {
1035                 drawBorder: false,
1036                 shadow: false,
1037                 background: 'rgba(0,0,0,0)'
1038             },
1039             axes: {
1040                 xaxis: {
1041                     renderer: $.jqplot.DateAxisRenderer,
1042                     tickOptions: {
1043                         formatString: '%H:%M:%S',
1044                         showGridline: false
1045                     },
1046                     min: runtime.xmin,
1047                     max: runtime.xmax
1048                 },
1049                 yaxis: {
1050                     min:0,
1051                     max:100,
1052                     tickInterval: 20
1053                 }
1054             },
1055             seriesDefaults: {
1056                 rendererOptions: {
1057                     smooth: true
1058                 }
1059             },
1060             highlighter: {
1061             show: true,
1062             showTooltip: false
1063             }
1064         };
1066         settings.series = chartObj.series;
1068         if ($('#' + 'gridchart' + runtime.chartAI).length == 0) {
1069             var numCharts = $('table#chartGrid .monitorChart').length;
1071             if (numCharts == 0 || !( numCharts % monitorSettings.columns)) {
1072                 $('table#chartGrid').append('<tr></tr>');
1073             }
1075             $('table#chartGrid tr:last').append('<td><div class="ui-state-default monitorChart" id="' + 'gridchart' + runtime.chartAI + '"></div></td>');
1076         }
1078         var series = [];
1079         var emptyArr = new Array(2);
1080         for (i in chartObj.series){
1081             series.push([emptyArr]);
1082         }
1084         chartObj.chart = $.jqplot('gridchart' + runtime.chartAI, series, settings);
1085         chartObj.numPoints = 0;
1087         if (initialize != true) {
1088             runtime.charts['c' + runtime.chartAI] = chartObj;
1089             buildRequiredDataList();
1090         }
1092         // time span selection
1093         $('#gridchart' + runtime.chartAI).bind('jqplotMouseDown', function(ev, gridpos, datapos, neighbor, plot) {
1094             drawTimeSpan = true;
1095                     selectionTimeDiff.push(datapos.xaxis);
1096             if($('#selection_box').length) {
1097                 $('#selection_box').remove();
1098             }
1099             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;">');
1100             $(document.body).append(selectionBox);
1101             selectionStartX = ev.pageX;
1102             selectionStartY = ev.pageY;
1103             selectionBox
1104                 .attr({id: 'selection_box'})
1105                 .css({
1106                     top: selectionStartY-gridpos.y,
1107                     left: selectionStartX
1108                 })
1109                 .fadeIn();
1110             });
1112         $('#gridchart' + runtime.chartAI).bind('jqplotMouseUp', function(ev, gridpos, datapos, neighbor, plot) {
1113             if(! drawTimeSpan)
1114                 return;
1116                     selectionTimeDiff.push(datapos.xaxis);
1118             if(selectionTimeDiff[1] < selectionTimeDiff[0]) {
1119                 selectionTimeDiff = [];
1120                 return;
1121             }
1122             //get date from timestamp
1123             var min = new Date(Math.ceil(selectionTimeDiff[0]));
1124             var max = new Date(Math.ceil(selectionTimeDiff[1]));
1125             PMA_getLogAnalyseDialog(min, max);
1126             selectionTimeDiff = [];
1127             drawTimeSpan = false;
1128             });
1130         $('#gridchart' + runtime.chartAI).bind('jqplotMouseMove', function(ev, gridpos, datapos, neighbor, plot) {
1132             if(neighbor != null) {
1133                 if ($('#tooltip_box').length) {
1134                     $('#tooltip_box')
1135                         .css({
1136                             left: ev.pageX + 15,
1137                             top: ev.pageY + 15,
1138                             padding:'5px'
1139                         })
1140                         .fadeIn();
1141                 }
1142                 var xVal = new Date(Math.ceil(neighbor.data[0]));
1143                 var xValHours = xVal.getHours();
1144                 (xValHours < 10) ? (xValHours = "0" + xValHours) : "";
1146                 var xValMinutes = xVal.getMinutes();
1147                 (xValMinutes < 10) ? (xValMinutes = "0" + xValMinutes) : "";
1149                 var xValSeconds = xVal.getSeconds();
1150                 (xValSeconds < 10) ? (xValSeconds = "0" + xValSeconds) : "";
1152                 xVal = xValHours + ":" + xValMinutes + ":" + xValSeconds;
1153                 var s = '<b>' + xVal + '<br/>' + neighbor.data[1] + '</b>';
1155                 $('#tooltip_box').html(s);
1156             }
1158             if(! drawTimeSpan)
1159                 return;
1161             if (selectionStartX != undefined) {
1162                 $('#selection_box')
1163                     .css({
1164                         width: Math.ceil(ev.pageX - selectionStartX)
1165                     })
1166                     .fadeIn();
1167             }
1168             });
1171         $('#gridchart' + runtime.chartAI).bind('jqplotMouseEnter', function(ev, gridpos, datapos, neighbor, plot) {
1172             if($('#tooltip_box').length) {
1173                 tooltipBox.remove();
1174             }
1175             tooltipBox = $('<div style="z-index:1000;height:40px;position:absolute;background-color:#FFFFFD;opacity:0.8;filter:alpha(opacity=80);">');
1176             $(document.body).append(tooltipBox);
1177             tooltipBox
1178                 .attr({id: 'tooltip_box'})
1179                 .css({
1180                     top: ev.pageY + 15,
1181                     left: ev.pageX + 15
1182                 })
1183                 .fadeIn();
1184             });
1186         $('#gridchart' + runtime.chartAI).bind('jqplotMouseLeave', function(ev, gridpos, datapos, neighbor, plot) {
1187             if($('#tooltip_box').length) {
1188                 tooltipBox.remove();
1189             }
1190             drawTimeSpan = false;
1191             });
1193         $(document.body).mouseup(function() {
1194             if($('#selection_box').length) {
1195                 selectionBox.remove();
1196             }
1197             });
1199         // Edit, Print icon only in edit mode
1200         $('table#chartGrid div svg').find('*[zIndex=20], *[zIndex=21], *[zIndex=19]').toggle(editMode)
1202         runtime.chartAI++;
1203     }
1205     /* Opens a dialog that allows one to edit the title and series labels of the supplied chart */
1206     function editChart(chartObj) {
1207         var htmlnode = chartObj.options.chart.renderTo;
1208         if (! htmlnode ) {
1209             return;
1210         }
1211         
1212         var chart = null;
1213         var chartKey = null;
1214         $.each(runtime.charts, function(key, value) {
1215             if (value.chart.options.chart.renderTo == htmlnode) {
1216                 chart = value;
1217                 chartKey = key;
1218                 return false;
1219             }
1220         });
1221         
1222         if (chart == null) {
1223             return;
1224         }
1226         var htmlStr = '<p><b>' + PMA_messages['strChartTitle'] + ': </b> <br/> <input type="text" size="35" name="chartTitle" value="' + chart.title + '" />';
1227         htmlStr += '</p><p><b>' + PMA_messages['strSeries'] + ':</b> </p><ol>';
1228         for (var i = 0; i<chart.nodes.length; i++) {
1229             htmlStr += '<li><i>' + chart.nodes[i].dataPoints[0].name  + ': </i><br/><input type="text" name="chartSerie-' + i + '" value="' + chart.nodes[i].name + '" /></li>';
1230         }
1231         
1232         dlgBtns = {};
1233         dlgBtns[PMA_messages['strSave']] = function() {
1234             runtime.charts[chartKey].title = $('div#emptyDialog input[name="chartTitle"]').attr('value');
1235             runtime.charts[chartKey].chart.setTitle({ text: runtime.charts[chartKey].title });
1236             
1237             $('div#emptyDialog input[name*="chartSerie"]').each(function() {
1238                 var idx = $(this).attr('name').split('-')[1];
1239                 runtime.charts[chartKey].nodes[idx].name = $(this).attr('value');
1240                 runtime.charts[chartKey].chart.series[idx].name = $(this).attr('value');
1241             });
1242             
1243             $(this).dialog('close');
1244             saveMonitor();
1245         };
1246         dlgBtns[PMA_messages['strCancel']] = function() {
1247             $(this).dialog('close');
1248         };
1250         $('div#emptyDialog').html(htmlStr + '</ol>');
1251         $('div#emptyDialog').dialog({
1252             title: PMA_messages['strChartEdit'],
1253             width: 'auto',
1254             height: 'auto',
1255             buttons: dlgBtns
1256         });
1257     }
1259     function PMA_getLogAnalyseDialog(min, max) {
1260         $('#logAnalyseDialog input[name="dateStart"]')
1261             .attr('value', formatDate(min, 'yyyy-MM-dd HH:mm:ss'));
1262         $('#logAnalyseDialog input[name="dateEnd"]')
1263             .attr('value', formatDate(max, 'yyyy-MM-dd HH:mm:ss'));
1265         var dlgBtns = { };
1267         dlgBtns[PMA_messages['strFromSlowLog']] = function() {
1268             loadLog('slow', min, max);
1269             $(this).dialog("close");
1270         };
1271                         
1272         dlgBtns[PMA_messages['strFromGeneralLog']] = function() {
1273             loadLog('general', min, max);
1274             $(this).dialog("close");
1275         };
1277         $('#logAnalyseDialog').dialog({
1278             width: 'auto',
1279             height: 'auto',
1280             buttons: dlgBtns
1281         });
1282     }
1284     function loadLog(type, min, max) {
1285         var dateStart = Date.parse($('#logAnalyseDialog input[name="dateStart"]').prop('value')) || min;
1286         var dateEnd = Date.parse($('#logAnalyseDialog input[name="dateEnd"]').prop('value')) || max;
1288         loadLogStatistics({
1289             src: type,
1290             start: dateStart,
1291             end: dateEnd,
1292             removeVariables: $('input#removeVariables').prop('checked'),
1293             limitTypes: $('input#limitTypes').prop('checked')
1294         });
1295     }
1296                         
1297     
1298     /* Removes a chart from the grid */
1299     function removeChart(chartObj) {
1300         var htmlnode = chartObj.options.chart.renderTo;
1301         if (! htmlnode ) {
1302             return;
1303         }
1304         
1305         $.each(runtime.charts, function(key, value) {
1306             if (value.chart.options.chart.renderTo == htmlnode) {
1307                 delete runtime.charts[key];
1308                 return false;
1309             }
1310         });
1312         buildRequiredDataList();
1314         // Using settimeout() because clicking the remove link fires an onclick event
1315         // which throws an error when the chart is destroyed
1316         setTimeout(function() {
1317             chartObj.destroy();
1318             $('div#' + htmlnode).remove();
1319         }, 10);
1321         saveMonitor(); // Save settings
1322     }
1324     /* Called in regular intervalls, this function updates the values of each chart in the grid */
1325     function refreshChartGrid() {
1326         /* Send to server */
1327         runtime.refreshRequest = $.post('server_status.php?' + url_query, {
1328             ajax_request: true, 
1329             chart_data: 1, 
1330             type: 'chartgrid', 
1331             requiredData: $.toJSON(runtime.dataList) 
1332         }, function(data) {
1333             var chartData;
1334             try {
1335                 chartData = $.parseJSON(data);
1336             } catch(err) {
1337                 return serverResponseError();
1338             }
1339             var value, i = 0;
1340             var diff;
1341             
1342             /* Update values in each graph */
1343             $.each(runtime.charts, function(orderKey, elem) {
1344                 var key = elem.chartID;
1345                 var maxVal = 1;
1346                 // If newly added chart, we have no data for it yet
1347                 if (! chartData[key]) {
1348                     return;
1349                 }
1350                 // Draw all series
1351                 for (var j = 0; j < elem.nodes.length; j++) {
1352                     // Update x-axis
1353                     if (i == 0 && j == 0) {
1354                         if (oldChartData == null) {
1355                             diff = chartData.x - runtime.xmax;
1356                         } else {
1357                             diff = parseInt(chartData.x - oldChartData.x);
1358                         }
1360                         runtime.xmin += diff;
1361                         runtime.xmax += diff;
1362                     }
1364                     //elem.chart.xAxis[0].setExtremes(runtime.xmin, runtime.xmax, false);
1365                     /* Calculate y value */
1366                     
1367                     // If transform function given, use it
1368                     if (elem.nodes[j].transformFn) {
1369                         value = chartValueTransform(
1370                             elem.nodes[j].transformFn,
1371                             chartData[key][j],
1372                             // Check if first iteration (oldChartData==null), or if newly added chart oldChartData[key]==null
1373                             (oldChartData == null || oldChartData[key] == null ? null : oldChartData[key][j])
1374                         );
1376                     // Otherwise use original value and apply differential and divisor if given,
1377                     // in this case we have only one data point per series - located at chartData[key][j][0]
1378                     } else {
1379                         value = parseFloat(chartData[key][j][0].value);
1381                         if (elem.nodes[j].display == 'differential') {
1382                             if (oldChartData == null || oldChartData[key] == null) { 
1383                                 continue;
1384                             }
1385                             value -= oldChartData[key][j][0].value;
1386                         }
1388                         if (elem.nodes[j].valueDivisor) {
1389                             value = value / elem.nodes[j].valueDivisor;
1390                         }
1391                     }
1392                     
1393                     // Set y value, if defined
1394                     if (value != undefined) {
1395                         elem.chart.series[j].data.push([chartData.x, value]);
1396                         maxVal = (maxVal > value) ? maxVal : value;
1397                     }
1398                 }
1399                 if(elem.maxYLabel.length == 0) {
1400                     elem.maxYLabel.push([runtime.xmax, 1]);
1401                 }
1403                 if(maxVal > elem.maxYLabel[elem.maxYLabel.length - 1][1]) {
1404                     elem.maxYLabel.push([chartData.x, (Math.ceil(maxVal*1.2))]);
1405                 } else if(maxVal > elem.maxYLabel[0][1]) {
1406                     elem.maxYLabel.splice(1,0,[chartData.x, (Math.ceil(maxVal*1.2))]);
1407                 }
1409                 if(elem.maxYLabel.length > 1
1410                     && elem.maxYLabel[elem.maxYLabel.length - 1][0] < runtime.xmin
1411                 ) {
1412                     elem.maxYLabel.pop();
1413                     elem.maxYLabel.sort(function(a,b){return a[1]-b[1]});
1414                 }
1415                 // update chart options
1416                 elem.chart['axes']['xaxis']['max'] = runtime.xmax;
1417                 elem.chart['axes']['xaxis']['min'] = runtime.xmin;
1418                 elem.chart['axes']['yaxis']['max'] = elem.maxYLabel[elem.maxYLabel.length - 1][1];
1419                 elem.chart['axes']['yaxis']['tickInterval'] = elem.maxYLabel[elem.maxYLabel.length - 1][1]/5;
1420                 i++;
1421                 runtime.charts[orderKey].numPoints++;
1422                 if (runtime.redrawCharts) {
1423                     elem.chart.replot();
1424                 }
1425             });
1427             oldChartData = chartData;
1429             runtime.refreshTimeout = setTimeout(refreshChartGrid, monitorSettings.gridRefresh);
1430         });
1431     }
1432     
1433     /* Function that supplies special value transform functions for chart values */
1434     function chartValueTransform(name, cur, prev) {        
1435         switch(name) {
1436         case 'cpu-linux':
1437             if (prev == null) {
1438                 return undefined;
1439             }
1440             // cur and prev are datapoint arrays, but containing only 1 element for cpu-linux
1441             cur = cur[0], prev = prev[0];
1443             var diff_total = cur.busy + cur.idle - (prev.busy + prev.idle);
1444             var diff_idle = cur.idle - prev.idle;
1445             return 100 * (diff_total - diff_idle) / diff_total;
1447         // Query cache efficiency (%)
1448         case 'qce':
1449             if (prev == null) {
1450                 return undefined;
1451             }
1452             // cur[0].value is Qcache_hits, cur[1].value is Com_select
1453             var diffQHits = cur[0].value - prev[0].value;
1454             // No NaN please :-)
1455             if (cur[1].value - prev[1].value == 0) return 0;
1457             return diffQHits / (cur[1].value - prev[1].value + diffQHits) * 100;
1459         // Query cache usage (%)
1460         case 'qcu':
1461             if (cur[1].value == 0) return 0;
1462             // cur[0].value is Qcache_free_memory, cur[1].value is query_cache_size
1463             return 100 - cur[0].value / cur[1].value * 100;
1465         }
1466         return undefined;
1467     }
1469     /* Build list of nodes that need to be retrieved from server.
1470      * It creates something like a stripped down version of the runtime.charts object.
1471      */
1472     function buildRequiredDataList() {
1473         runtime.dataList = {};
1474         // Store an own id, because the property name is subject of reordering, 
1475         // thus destroying our mapping with runtime.charts <=> runtime.dataList
1476         var chartID = 0;
1477         $.each(runtime.charts, function(key, chart) {
1478             runtime.dataList[chartID] = [];
1479             for(var i=0; i < chart.nodes.length; i++) {
1480                 runtime.dataList[chartID][i] = chart.nodes[i].dataPoints;
1481             }
1482             runtime.charts[key].chartID = chartID;
1483             chartID++;
1484         });
1485     }
1487     /* Loads the log table data, generates the table and handles the filters */
1488     function loadLogStatistics(opts) {
1489         var tableStr = '';
1490         var logRequest = null;
1492         if (! opts.removeVariables) {
1493             opts.removeVariables = false;
1494         }
1495         if (! opts.limitTypes) {
1496             opts.limitTypes = false;
1497         }
1498         
1499         $('#emptyDialog').dialog({title: PMA_messages['strAnalysingLogsTitle']});
1500         $('#emptyDialog').html(PMA_messages['strAnalysingLogs'] + 
1501                                 ' <img class="ajaxIcon" src="' + pmaThemeImage + 
1502                                 'ajax_clock_small.gif" alt="">');
1503         var dlgBtns = {};
1505         dlgBtns[PMA_messages['strCancelRequest']] = function() {
1506             if (logRequest != null) {
1507                 logRequest.abort();
1508             }
1510             $(this).dialog("close");
1511         }
1513         $('#emptyDialog').dialog({
1514             width: 'auto',
1515             height: 'auto',
1516             buttons: dlgBtns
1517         });
1520         logRequest = $.get('server_status.php?' + url_query,
1521             {   ajax_request: true,
1522                 log_data: 1,
1523                 type: opts.src,
1524                 time_start: Math.round(opts.start / 1000),
1525                 time_end: Math.round(opts.end / 1000),
1526                 removeVariables: opts.removeVariables,
1527                 limitTypes: opts.limitTypes
1528             },
1529             function(data) { 
1530                 var logData;
1531                 try {
1532                     logData = $.parseJSON(data);
1533                 } catch(err) {
1534                     return serverResponseError();
1535                 }
1536                 
1537                 if (logData.rows.length != 0) {
1538                     runtime.logDataCols = buildLogTable(logData);
1540                     /* Show some stats in the dialog */
1541                     $('#emptyDialog').dialog({title: PMA_messages['strLoadingLogs']});
1542                     $('#emptyDialog').html('<p>' + PMA_messages['strLogDataLoaded'] + '</p>');
1543                     $.each(logData.sum, function(key, value) {
1544                         key = key.charAt(0).toUpperCase() + key.slice(1).toLowerCase();
1545                         if (key == 'Total') {
1546                             key = '<b>' + key + '</b>';
1547                         }
1548                         $('#emptyDialog').append(key + ': ' + value + '<br/>');
1549                     });
1551                     /* Add filter options if more than a bunch of rows there to filter */
1552                     if (logData.numRows > 12) {
1553                         $('div#logTable').prepend(
1554                             '<fieldset id="logDataFilter">' +
1555                             '   <legend>' + PMA_messages['strFiltersForLogTable'] + '</legend>' +
1556                             '   <div class="formelement">' +
1557                             '           <label for="filterQueryText">' + PMA_messages['strFilterByWordRegexp'] + '</label>' +
1558                             '           <input name="filterQueryText" type="text" id="filterQueryText" style="vertical-align: baseline;" />' +
1559                             '   </div>' +
1560                             ((logData.numRows > 250) ? ' <div class="formelement"><button name="startFilterQueryText" id="startFilterQueryText">' + PMA_messages['strFilter'] + '</button></div>' : '') +
1561                             '   <div class="formelement">' +
1562                             '       <input type="checkbox" id="noWHEREData" name="noWHEREData" value="1" /> ' +
1563                             '       <label for="noWHEREData"> ' + PMA_messages['strIgnoreWhereAndGroup'] + '</label>' +
1564                             '   </div' +
1565                             '</fieldset>'
1566                         );
1568                         $('div#logTable input#noWHEREData').change(function() {
1569                             filterQueries(true);
1570                         });
1572                         if (logData.numRows > 250) {
1573                             $('div#logTable button#startFilterQueryText').click(filterQueries);
1574                         } else {
1575                             $('div#logTable input#filterQueryText').keyup(filterQueries);
1576                         }
1578                     }
1580                     var dlgBtns = {};
1581                     dlgBtns[PMA_messages['strJumpToTable']] = function() {
1582                         $(this).dialog("close");
1583                         $(document).scrollTop($('div#logTable').offset().top);
1584                     };
1585                     
1586                     $('#emptyDialog').dialog( "option", "buttons", dlgBtns);
1587                     
1588                 } else {
1589                     $('#emptyDialog').dialog({title: PMA_messages['strNoDataFoundTitle']});
1590                     $('#emptyDialog').html('<p>' + PMA_messages['strNoDataFound'] + '</p>');
1591                     
1592                     var dlgBtns = {};
1593                     dlgBtns[PMA_messages['strClose']] = function() { 
1594                         $(this).dialog("close"); 
1595                     };
1596                     
1597                     $('#emptyDialog').dialog( "option", "buttons", dlgBtns );
1598                 }
1599             }
1600         );
1602         /* Handles the actions performed when the user uses any of the log table filters 
1603          * which are the filter by name and grouping with ignoring data in WHERE clauses
1604          * 
1605          * @param boolean Should be true when the users enabled or disabled to group queries ignoring data in WHERE clauses
1606         */
1607         function filterQueries(varFilterChange) {
1608             var odd_row = false, cell, textFilter;
1609             var val = $('div#logTable input#filterQueryText').val();
1611             if (val.length == 0) {
1612                 textFilter = null;
1613             } else {
1614                 textFilter = new RegExp(val, 'i');
1615             }
1616             
1617             var rowSum = 0, totalSum = 0, i = 0, q;
1618             var noVars = $('div#logTable input#noWHEREData').attr('checked');
1619             var equalsFilter = /([^=]+)=(\d+|((\'|"|).*?[^\\])\4((\s+)|$))/gi;
1620             var functionFilter = /([a-z0-9_]+)\(.+?\)/gi;
1621             var filteredQueries = {}, filteredQueriesLines = {};
1622             var hide = false, rowData;
1623             var queryColumnName = runtime.logDataCols[runtime.logDataCols.length - 2];
1624             var sumColumnName = runtime.logDataCols[runtime.logDataCols.length - 1];
1625             var isSlowLog = opts.src == 'slow';
1626             var columnSums = {};
1627             
1628             // For the slow log we have to count many columns (query_time, lock_time, rows_examined, rows_sent, etc.)
1629             var countRow = function(query, row) {
1630                 var cells = row.match(/<td>(.*?)<\/td>/gi);
1631                 if (!columnSums[query]) {
1632                     columnSums[query] = [0, 0, 0, 0];
1633                 }
1635                 // lock_time and query_time and displayed in timespan format
1636                 columnSums[query][0] += timeToSec(cells[2].replace(/(<td>|<\/td>)/gi, ''));
1637                 columnSums[query][1] += timeToSec(cells[3].replace(/(<td>|<\/td>)/gi, ''));
1638                 // rows_examind and rows_sent are just numbers
1639                 columnSums[query][2] += parseInt(cells[4].replace(/(<td>|<\/td>)/gi, ''));
1640                 columnSums[query][3] += parseInt(cells[5].replace(/(<td>|<\/td>)/gi, ''));
1641             };
1642             
1643             // We just assume the sql text is always in the second last column, and that the total count is right of it
1644             $('div#logTable table tbody tr td:nth-child(' + (runtime.logDataCols.length - 1) + ')').each(function() {
1645                 // If query is a SELECT and user enabled or disabled to group queries ignoring data in where statements, we 
1646                 // need to re-calculate the sums of each row
1647                 if (varFilterChange && $(this).html().match(/^SELECT/i)) {
1648                     if (noVars) {
1649                         // Group on => Sum up identical columns, and hide all but 1
1650                         
1651                         q = $(this).text().replace(equalsFilter, '$1=...$6').trim();
1652                         q = q.replace(functionFilter, ' $1(...)');
1654                         // Js does not specify a limit on property name length, so we can abuse it as index :-)
1655                         if (filteredQueries[q]) {
1656                             filteredQueries[q] += parseInt($(this).next().text());
1657                             totalSum += parseInt($(this).next().text());
1658                             hide = true;
1659                         } else {
1660                             filteredQueries[q] = parseInt($(this).next().text());;
1661                             filteredQueriesLines[q] = i;
1662                             $(this).text(q);
1663                         }
1664                         if (isSlowLog) {
1665                             countRow(q, $(this).parent().html());
1666                         }
1668                     } else {
1669                         // Group off: Restore original columns
1671                         rowData = $(this).parent().data('query');
1672                         // Restore SQL text
1673                         $(this).text(rowData[queryColumnName]);
1674                         // Restore total count
1675                         $(this).next().text(rowData[sumColumnName]);
1676                         // Restore slow log columns
1677                         if (isSlowLog) {
1678                             $(this).parent().children('td:nth-child(3)').text(rowData['query_time']);
1679                             $(this).parent().children('td:nth-child(4)').text(rowData['lock_time']);
1680                             $(this).parent().children('td:nth-child(5)').text(rowData['rows_sent']);
1681                             $(this).parent().children('td:nth-child(6)').text(rowData['rows_examined']);
1682                         }
1683                     }
1684                 }
1686                 // If not required to be hidden, do we need to hide because of a not matching text filter?
1687                 if (! hide && (textFilter != null && ! textFilter.exec($(this).text()))) {
1688                     hide = true;
1689                 }
1691                 // Now display or hide this column
1692                 if (hide) {
1693                     $(this).parent().css('display', 'none');
1694                 } else {
1695                     totalSum += parseInt($(this).next().text());
1696                     rowSum++;
1698                     odd_row = ! odd_row;
1699                     $(this).parent().css('display', '');
1700                     if (odd_row) {
1701                         $(this).parent().addClass('odd');
1702                         $(this).parent().removeClass('even');
1703                     } else {
1704                         $(this).parent().addClass('even');
1705                         $(this).parent().removeClass('odd');
1706                     }
1707                 }
1709                 hide = false;
1710                 i++;
1711             });
1712                        
1713             // We finished summarizing counts => Update count values of all grouped entries
1714             if (varFilterChange) {
1715                 if (noVars) {
1716                     var numCol, row, $table = $('div#logTable table tbody');
1717                     $.each(filteredQueriesLines, function(key, value) {
1718                         if (filteredQueries[key] <= 1) {
1719                             return;
1720                         }
1721                         
1722                         row =  $table.children('tr:nth-child(' + (value + 1) + ')');
1723                         numCol = row.children(':nth-child(' + (runtime.logDataCols.length) + ')');
1724                         numCol.text(filteredQueries[key]);
1725                         
1726                         if (isSlowLog) {
1727                             row.children('td:nth-child(3)').text(secToTime(columnSums[key][0]));
1728                             row.children('td:nth-child(4)').text(secToTime(columnSums[key][1]));
1729                             row.children('td:nth-child(5)').text(columnSums[key][2]);
1730                             row.children('td:nth-child(6)').text(columnSums[key][3]);
1731                         }
1732                     });
1733                 }
1734                 
1735                 $('div#logTable table').trigger("update"); 
1736                 setTimeout(function() {                    
1737                     $('div#logTable table').trigger('sorton', [[[runtime.logDataCols.length - 1, 1]]]);
1738                 }, 0);
1739             }
1741             // Display some stats at the bottom of the table
1742             $('div#logTable table tfoot tr')
1743                 .html('<th colspan="' + (runtime.logDataCols.length - 1) + '">' +
1744                       PMA_messages['strSumRows'] + ' ' + rowSum + '<span style="float:right">' +
1745                       PMA_messages['strTotal'] + '</span></th><th align="right">' + totalSum + '</th>');
1746         }
1747     }
1749     /* Turns a timespan (12:12:12) into a number */
1750     function timeToSec(timeStr) {
1751         var time = timeStr.split(':');
1752         return parseInt(time[0]*3600) + parseInt(time[1]*60) + parseInt(time[2]);
1753     }
1754     
1755     /* Turns a number into a timespan (100 into 00:01:40) */
1756     function secToTime(timeInt) {
1757         hours = Math.floor(timeInt / 3600);
1758         timeInt -= hours*3600;
1759         minutes = Math.floor(timeInt / 60);
1760         timeInt -= minutes*60;
1761         
1762         if (hours < 10) {
1763             hours = '0' + hours;
1764         }
1765         if (minutes < 10) {
1766             minutes = '0' + minutes;
1767         }
1768         if (timeInt < 10) {
1769             timeInt = '0' + timeInt;
1770         }
1771         
1772         return hours + ':' + minutes + ':' + timeInt;
1773     }
1774     
1775     /* Constructs the log table out of the retrieved server data */
1776     function buildLogTable(data) {
1777         var rows = data.rows;
1778         var cols = new Array();
1779         var $table = $('<table border="0" class="sortable"></table>');
1780         var $tBody, $tRow, $tCell;
1782         $('#logTable').html($table);
1784         var formatValue = function(name, value) {
1785             switch(name) {
1786                 case 'user_host':
1787                     return value.replace(/(\[.*?\])+/g, '');
1788             }
1789             return value;
1790         };
1791         
1792         for (var i = 0; i < rows.length; i++) {
1793             if (i == 0) {
1794                 $.each(rows[0], function(key, value) {
1795                     cols.push(key);
1796                 });
1797                 $table.append( '<thead>' +
1798                                '<tr><th class="nowrap">' + cols.join('</th><th class="nowrap">') + '</th></tr>' +
1799                                '</thead>');
1801                 $table.append($tBody = $('<tbody></tbody>'));
1802             }
1804             $tBody.append($tRow = $('<tr class="noclick"></tr>'));
1805             var cl = '';
1806             for (var j = 0; j < cols.length; j++) {
1807                 // Assuming the query column is the second last
1808                 if (j == cols.length - 2 && rows[i][cols[j]].match(/^SELECT/i)) {
1809                     $tRow.append($tCell = $('<td class="linkElem">' + formatValue(cols[j], rows[i][cols[j]]) + '</td>'));
1810                     $tCell.click(openQueryAnalyzer);
1811                 } else
1812                     $tRow.append('<td>' + formatValue(cols[j], rows[i][cols[j]]) + '</td>');
1815                 $tRow.data('query', rows[i]);
1816             }
1817         }
1819         $table.append('<tfoot>' +
1820                     '<tr><th colspan="' + (cols.length - 1) + '">' + PMA_messages['strSumRows'] +
1821                     ' ' + data.numRows + '<span style="float:right">' + PMA_messages['strTotal'] +
1822                     '</span></th><th align="right">' + data.sum.TOTAL + '</th></tr></tfoot>');
1824         // Append a tooltip to the count column, if there exist one
1825         if ($('#logTable th:last').html() == '#') {
1826             $('#logTable th:last').append('&nbsp;' + PMA_getImage('b_docs.png', '', {'class': 'qroupedQueryInfoIcon'}));
1828             var qtipContent = PMA_messages['strCountColumnExplanation'];
1829             if (groupInserts) {
1830                 qtipContent += '<p>' + PMA_messages['strMoreCountColumnExplanation'] + '</p>';
1831             }
1833             $('img.qroupedQueryInfoIcon').qtip({
1834                 content: qtipContent,
1835                 position: {
1836                     corner: {
1837                         target: 'bottomMiddle',
1838                         tooltip: 'topRight'
1839                     }
1841                 },
1842                 hide: { delay: 1000 }
1843             })
1844         }
1846         $('div#logTable table').tablesorter({
1847             sortList: [[cols.length - 1, 1]],
1848             widgets: ['fast-zebra']
1849         });
1851         $('div#logTable table thead th')
1852             .append('<img class="icon sortableIcon" src="themes/dot.gif" alt="">');
1854         return cols;
1855     }
1856         
1857     /* Opens the query analyzer dialog */
1858     function openQueryAnalyzer() {
1859         var rowData = $(this).parent().data('query');
1860         var query = rowData.argument || rowData.sql_text;
1862         query = PMA_SQLPrettyPrint(query);
1863         codemirror_editor.setValue(query);
1864         // Codemirror is bugged, it doesn't refresh properly sometimes. Following lines seem to fix that
1865         setTimeout(function() {
1866             codemirror_editor.refresh()
1867         },50);
1869         var profilingChart = null;
1870         var dlgBtns = {};
1871         
1872         dlgBtns[PMA_messages['strAnalyzeQuery']] = function() { 
1873             loadQueryAnalysis(rowData); 
1874         };
1875         dlgBtns[PMA_messages['strClose']] = function() { 
1876             if (profilingChart != null) {
1877                 profilingChart.destroy();
1878             }
1879             $('div#queryAnalyzerDialog div.placeHolder').html('');
1880             codemirror_editor.setValue('');
1881             $(this).dialog("close");
1882         };
1884         $('div#queryAnalyzerDialog').dialog({
1885             width: 'auto',
1886             height: 'auto',
1887             resizable: false,
1888             buttons: dlgBtns
1889         });
1890     }
1891     
1892     /* Loads and displays the analyzed query data */
1893     function loadQueryAnalysis(rowData) {
1894         var db = rowData.db || '';
1895         
1896         $('div#queryAnalyzerDialog div.placeHolder').html(
1897             PMA_messages['strAnalyzing'] + ' <img class="ajaxIcon" src="' + 
1898             pmaThemeImage + 'ajax_clock_small.gif" alt="">');
1900         $.post('server_status.php?' + url_query, {
1901             ajax_request: true,
1902             query_analyzer: true,
1903             query: codemirror_editor.getValue(),
1904             database: db
1905         }, function(data) {
1906             data = $.parseJSON(data);
1907             var totalTime = 0;
1909             if (data.error) {
1910                 $('div#queryAnalyzerDialog div.placeHolder').html('<div class="error">' + data.error + '</div>');
1911                 return;
1912             }
1914             // Float sux, I'll use table :(
1915             $('div#queryAnalyzerDialog div.placeHolder')
1916                 .html('<table width="100%" border="0"><tr><td class="explain"></td><td class="chart"></td></tr></table>');
1917             
1918             var explain = '<b>' + PMA_messages['strExplainOutput'] + '</b> ' + explain_docu;
1919             if (data.explain.length > 1) {
1920                 explain += ' (';
1921                 for (var i = 0; i < data.explain.length; i++) {
1922                     if (i > 0) {
1923                         explain += ', ';
1924                     }
1925                     explain += '<a href="#showExplain-' + i + '">' + i + '</a>';
1926                 }
1927                 explain += ')';
1928             }
1929             explain += '<p></p>';
1930             for (var i = 0; i < data.explain.length; i++) {
1931                 explain += '<div class="explain-' + i + '"' + (i>0? 'style="display:none;"' : '' ) + '>';
1932                 $.each(data.explain[i], function(key, value) {
1933                     value = (value == null)?'null':value;
1934                     
1935                     if (key == 'type' && value.toLowerCase() == 'all') {
1936                         value = '<span class="attention">' + value + '</span>';
1937                     }
1938                     if (key == 'Extra') {
1939                         value = value.replace(/(using (temporary|filesort))/gi, '<span class="attention">$1</span>');
1940                     }
1941                     explain += key + ': ' + value + '<br />';
1942                 });
1943                 explain += '</div>';
1944             }
1945             
1946             explain += '<p><b>' + PMA_messages['strAffectedRows'] + '</b> ' + data.affectedRows;
1948             $('div#queryAnalyzerDialog div.placeHolder td.explain').append(explain);
1949             
1950             $('div#queryAnalyzerDialog div.placeHolder a[href*="#showExplain"]').click(function() {
1951                 var id = $(this).attr('href').split('-')[1];
1952                 $(this).parent().find('div[class*="explain"]').hide();
1953                 $(this).parent().find('div[class*="explain-' + id + '"]').show();
1954             });
1955             
1956             if (data.profiling) {
1957                 var chartData = [];
1958                 var numberTable = '<table class="queryNums"><thead><tr><th>' + PMA_messages['strStatus'] + '</th><th>' + PMA_messages['strTime'] + '</th></tr></thead><tbody>';
1959                 var duration;
1961                 for (var i = 0; i < data.profiling.length; i++) {
1962                     duration = parseFloat(data.profiling[i].duration);
1964                     chartData.push([data.profiling[i].state, duration]);
1965                     totalTime += duration;
1967                     numberTable += '<tr><td>' + data.profiling[i].state + ' </td><td> ' + PMA_prettyProfilingNum(duration, 2) + '</td></tr>';
1968                 }
1969                 numberTable += '<tr><td><b>' + PMA_messages['strTotalTime'] + '</b></td><td>' + PMA_prettyProfilingNum(totalTime, 2) + '</td></tr>';
1970                 numberTable += '</tbody></table>';
1971                 
1972                 $('div#queryAnalyzerDialog div.placeHolder td.chart').append(
1973                     '<b>' + PMA_messages['strProfilingResults'] + ' ' + profiling_docu + '</b> ' +
1974                     '(<a href="#showNums">' + PMA_messages['strTable'] + '</a>, <a href="#showChart">' + PMA_messages['strChart'] + '</a>)<br/>' +
1975                     numberTable + ' <div id="queryProfiling"></div>');
1976                 
1977                 $('div#queryAnalyzerDialog div.placeHolder a[href="#showNums"]').click(function() {
1978                     $('div#queryAnalyzerDialog div#queryProfiling').hide();
1979                     $('div#queryAnalyzerDialog table.queryNums').show();
1980                     return false;
1981                 });
1983                 $('div#queryAnalyzerDialog div.placeHolder a[href="#showChart"]').click(function() {
1984                     $('div#queryAnalyzerDialog div#queryProfiling').show();
1985                     $('div#queryAnalyzerDialog table.queryNums').hide();
1986                     return false;
1987                 });
1989                 profilingChart = PMA_createProfilingChartJqplot(
1990                         'queryProfiling', 
1991                         chartData
1992                 );
1994                 $('div#queryProfiling').resizable();
1995             }
1996         });
1997     }
1999     /* Saves the monitor to localstorage */
2000     function saveMonitor() {
2001         var gridCopy = {};
2003         $.each(runtime.charts, function(key, elem) {
2004             gridCopy[key] = {};
2005             gridCopy[key].nodes = elem.nodes;
2006             gridCopy[key].settings = elem.settings;
2007             gridCopy[key].title = elem.title;
2008         });
2010         if (window.localStorage) {
2011             window.localStorage['monitorCharts'] = $.toJSON(gridCopy);
2012             window.localStorage['monitorSettings'] = $.toJSON(monitorSettings);
2013             window.localStorage['monitorVersion'] = monitorProtocolVersion;
2014         }
2016         $('a[href="#clearMonitorConfig"]').show();
2017     }
2020 // Run the monitor once loaded
2021 $(function() {
2022     $('a[href="#pauseCharts"]').trigger('click');