Translation update done using Pootle.
[phpmyadmin/ammaryasirr.git] / js / server_status.js
blob60a3490a9aca1e07de2a1b71d9d5d3de7259b954
1 /* vim: set expandtab sw=4 ts=4 sts=4: */
2 /**
3  * @fileoverview    functions used in server status pages
4  * @name            Server Status
5  *
6  * @requires    jQuery
7  * @requires    jQueryUI
8  * @requires    jQueryCookie
9  * @requires    jQueryTablesorter
10  * @requires    Highcharts
11  * @requires    canvg
12  * @requires    js/functions.js
13  *
14  */
16 // Add a tablesorter parser to properly handle thousands seperated numbers and SI prefixes
17 $(function() {
18     jQuery.tablesorter.addParser({
19         id: "fancyNumber",
20         is: function(s) {
21             return /^[0-9]?[0-9,\.]*\s?(k|M|G|T|%)?$/.test(s);
22         },
23         format: function(s) {
24             var num = jQuery.tablesorter.formatFloat(
25                 s.replace(PMA_messages['strThousandsSeperator'],'')
26                  .replace(PMA_messages['strDecimalSeperator'],'.')
27             );
29             var factor = 1;
30             switch (s.charAt(s.length - 1)) {
31                 case '%': factor = -2; break;
32                 // Todo: Complete this list (as well as in the regexp a few lines up)
33                 case 'k': factor = 3; break;
34                 case 'M': factor = 6; break;
35                 case 'G': factor = 9; break;
36                 case 'T': factor = 12; break;
37             }
39             return num * Math.pow(10,factor);
40         },
41         type: "numeric"
42     });
45     // Popup behaviour
46     $('a[rel="popupLink"]').click( function() {
47         var $link = $(this);
49         $('.' + $link.attr('href').substr(1))
50             .show()
51             .offset({ top: $link.offset().top + $link.height() + 5, left: $link.offset().left })
52             .addClass('openedPopup');
54         return false;
55     });
57     $(document).click( function(event) {
58         $('.openedPopup').each(function() {
59             var $cnt = $(this);
60             var pos = $(this).offset();
62             // Hide if the mouseclick is outside the popupcontent
63             if(event.pageX < pos.left || event.pageY < pos.top || event.pageX > pos.left + $cnt.outerWidth() || event.pageY > pos.top + $cnt.outerHeight())
64                 $cnt.hide().removeClass('openedPopup');
65         });
66     });
67 });
69 $(function() {
70     // Filters for status variables
71     var textFilter=null;
72     var alertFilter = false;
73     var categoryFilter='';
74     var odd_row=false;
75     var text=''; // Holds filter text
76     var queryPieChart = null;
78     /* Chart configuration */
79     // Defines what the tabs are currently displaying (realtime or data)
80     var tabStatus = new Object();
81     // Holds the current chart instances for each tab
82     var tabChart = new Object();
85     /*** Table sort tooltip ***/
87     var $tableSortHint = $('<div class="dHint" style="display:none;">' + 'Click to sort' + '</div>');
88     $('body').append($tableSortHint);
90     $('table.sortable thead th').live('mouseover mouseout',function(e) {
91         if(e.type == 'mouseover') {
92             $tableSortHint
93                 .stop(true, true)
94                 .css({
95                     top: e.clientY + 15,
96                     left: e.clientX + 15
97                 })
98                 .show('fast')
99                 .data('shown',true);
100         } else {
101             $tableSortHint
102                 .stop(true, true)
103                 .hide(300,function() {
104                     $(this).data('shown',false);
105                 });
106         }
107     });
109     $(document).mousemove(function(e) {
110         if($tableSortHint.data('shown') == true)
111             $tableSortHint.css({
112                 top: e.clientY + 15,
113                 left: e.clientX + 15
114             })
115     });
118     // Tell highcarts not to use UTC dates (global setting)
119     Highcharts.setOptions({
120         global: {
121             useUTC: false
122         }
123     });
125     $.ajaxSetup({
126         cache:false
127     });
129     // Add tabs
130     $('#serverStatusTabs').tabs({
131         // Tab persistence
132         cookie: { name: 'pma_serverStatusTabs', expires: 1 },
133         // Fixes line break in the menu bar when the page overflows and scrollbar appears
134         show: function() { menuResize(); }
135     });
137     // Fixes wrong tab height with floated elements. See also http://bugs.jqueryui.com/ticket/5601
138     $(".ui-widget-content:not(.ui-tabs):not(.ui-helper-clearfix)").addClass("ui-helper-clearfix");
140     // Initialize each tab
141     $('div.ui-tabs-panel').each(function() {
142         initTab($(this),null);
143         tabStatus[$(this).attr('id')] = 'static';
144     });
146     // Display button links
147     $('div.buttonlinks').show();
149     // Handles refresh rate changing
150     $('.buttonlinks select').change(function() {
151         var chart=tabChart[$(this).parents('div.ui-tabs-panel').attr('id')];
153         // Clear current timeout and set timeout with the new refresh rate
154         clearTimeout(chart_activeTimeouts[chart.options.chart.renderTo]);
155         if(chart.options.realtime.postRequest)
156             chart.options.realtime.postRequest.abort();
158         chart.options.realtime.refreshRate = 1000*parseInt(this.value);
160         chart.xAxis[0].setExtremes(
161             new Date().getTime() - server_time_diff - chart.options.realtime.numMaxPoints * chart.options.realtime.refreshRate,
162             new Date().getTime() - server_time_diff,
163             true
164         );
166         chart_activeTimeouts[chart.options.chart.renderTo] = setTimeout(
167             chart.options.realtime.timeoutCallBack,
168             chart.options.realtime.refreshRate
169         );
170     });
172     // Ajax refresh of variables (always the first element in each tab)
173     $('.buttonlinks a.tabRefresh').click(function() {
174         // ui-tabs-panel class is added by the jquery tabs feature
175         var tab=$(this).parents('div.ui-tabs-panel');
176         var that = this;
178         // Show ajax load icon
179         $(this).find('img').show();
181         $.get($(this).attr('href'),{ajax_request:1},function(data) {
182             $(that).find('img').hide();
183             initTab(tab,data);
184         });
186         tabStatus[tab.attr('id')]='data';
188         return false;
189     });
192     /** Realtime charting of variables **/
194     // Live traffic charting
195     $('.buttonlinks a.livetrafficLink').click(function() {
196         // ui-tabs-panel class is added by the jquery tabs feature
197         var $tab=$(this).parents('div.ui-tabs-panel');
198         var tabstat = tabStatus[$tab.attr('id')];
200         if(tabstat=='static' || tabstat=='liveconnections') {
201             var settings = {
202                 series: [
203                     { name: PMA_messages['strChartKBSent'], data: [] },
204                     { name: PMA_messages['strChartKBReceived'], data: [] }
205                 ],
206                 title: { text: PMA_messages['strChartServerTraffic'] },
207                 realtime: { url:'server_status.php?' + url_query,
208                            type: 'traffic',
209                            callback: function(chartObj, curVal, lastVal, numLoadedPoints) {
210                                if(lastVal==null) return;
211                                 chartObj.series[0].addPoint(
212                                     { x: curVal.x, y: (curVal.y_sent - lastVal.y_sent) / 1024 },
213                                     false,
214                                     numLoadedPoints >= chartObj.options.realtime.numMaxPoints
215                                 );
216                                 chartObj.series[1].addPoint(
217                                     { x: curVal.x, y: (curVal.y_received - lastVal.y_received) / 1024 },
218                                     true,
219                                     numLoadedPoints >= chartObj.options.realtime.numMaxPoints
220                                 );                                            
221                             },
222                             error: function() { serverResponseError(); }
223                          }
224             }
226             setupLiveChart($tab,this,settings);
227             if(tabstat == 'liveconnections')
228                 $tab.find('.buttonlinks a.liveconnectionsLink').html(PMA_messages['strLiveConnChart']);
229             tabStatus[$tab.attr('id')]='livetraffic';
230         } else {
231             $(this).html(PMA_messages['strLiveTrafficChart']);
232             setupLiveChart($tab,this,null);
233         }
235         return false;
236     });
238     // Live connection/process charting
239     $('.buttonlinks a.liveconnectionsLink').click(function() {
240         var $tab=$(this).parents('div.ui-tabs-panel');
241         var tabstat = tabStatus[$tab.attr('id')];
243         if(tabstat == 'static' || tabstat == 'livetraffic') {
244             var settings = {
245                 series: [
246                     { name: PMA_messages['strChartConnections'], data: [] },
247                     { name: PMA_messages['strChartProcesses'], data: [] }
248                 ],
249                 title: { text: PMA_messages['strChartConnectionsTitle'] },
250                 realtime: { url:'server_status.php?'+url_query,
251                            type: 'proc',
252                            callback: function(chartObj, curVal, lastVal,numLoadedPoints) {
253                                if(lastVal==null) return;
254                                 chartObj.series[0].addPoint(
255                                     { x: curVal.x, y: curVal.y_conn - lastVal.y_conn },
256                                     false,
257                                     numLoadedPoints >= chartObj.options.realtime.numMaxPoints
258                                 );
259                                 chartObj.series[1].addPoint(
260                                     { x: curVal.x, y: curVal.y_proc },
261                                     true,
262                                     numLoadedPoints >= chartObj.options.realtime.numMaxPoints
263                                 );                                            
264                             },
265                             error: function() { serverResponseError(); }
266                          }
267             };
269             setupLiveChart($tab,this,settings);
270             if(tabstat == 'livetraffic')
271                 $tab.find('.buttonlinks a.livetrafficLink').html(PMA_messages['strLiveTrafficChart']);
272             tabStatus[$tab.attr('id')]='liveconnections';
273         } else {
274             $(this).html(PMA_messages['strLiveConnChart']);
275             setupLiveChart($tab,this,null);
276         }
278         return false;
279     });
281     // Live query statistics
282     $('.buttonlinks a.livequeriesLink').click(function() {
283         var $tab = $(this).parents('div.ui-tabs-panel');
284         var settings = null;
286         if(tabStatus[$tab.attr('id')] == 'static') {
287             settings = {
288                 series: [ { name: PMA_messages['strChartIssuedQueries'], data: [] } ],
289                 title: { text: PMA_messages['strChartIssuedQueriesTitle'] },
290                 tooltip: { formatter:function() { return this.point.name; } },
291                 realtime: { url:'server_status.php?'+url_query,
292                           type: 'queries',
293                           callback: function(chartObj, curVal, lastVal,numLoadedPoints) {
294                                 if(lastVal == null) return;
295                                 chartObj.series[0].addPoint(
296                                     { x: curVal.x,  y: curVal.y - lastVal.y, name: sortedQueriesPointInfo(curVal,lastVal) },
297                                     true,
298                                     numLoadedPoints >= chartObj.options.realtime.numMaxPoints
299                                 );
300                             },
301                             error: function() { serverResponseError(); }
302                          }
303             };
304         } else {
305             $(this).html(PMA_messages['strLiveQueryChart']);
306         }
308         setupLiveChart($tab,this,settings);
309         tabStatus[$tab.attr('id')] = 'livequeries';
310         return false;
311     });
313     function setupLiveChart($tab,link,settings) {
314         if(settings != null) {
315             // Loading a chart with existing chart => remove old chart first
316             if(tabStatus[$tab.attr('id')] != 'static') {
317                 clearTimeout(chart_activeTimeouts[$tab.attr('id') + "_chart_cnt"]);
318                 chart_activeTimeouts[$tab.attr('id')+"_chart_cnt"] = null;
319                 tabChart[$tab.attr('id')].destroy();
320                 // Also reset the select list
321                 $tab.find('.buttonlinks select').get(0).selectedIndex = 2;
322             }
324             if(! settings.chart) settings.chart = {};
325             settings.chart.renderTo = $tab.attr('id') + "_chart_cnt";
327             $tab.find('.tabInnerContent')
328                 .hide()
329                 .after('<div class="liveChart" id="' + $tab.attr('id') + '_chart_cnt"></div>');
330             tabChart[$tab.attr('id')] = PMA_createChart(settings);
331             $(link).html(PMA_messages['strStaticData']);
332             $tab.find('.buttonlinks a.tabRefresh').hide();
333             $tab.find('.buttonlinks .refreshList').show();
334         } else {
335             clearTimeout(chart_activeTimeouts[$tab.attr('id') + "_chart_cnt"]);
336             chart_activeTimeouts[$tab.attr('id') + "_chart_cnt"]=null;
337             $tab.find('.tabInnerContent').show();
338             $tab.find('div#'+$tab.attr('id') + '_chart_cnt').remove();
339             tabStatus[$tab.attr('id')]='static';
340             tabChart[$tab.attr('id')].destroy();
341             $tab.find('.buttonlinks a.tabRefresh').show();
342             $tab.find('.buttonlinks select').get(0).selectedIndex=2;
343             $tab.find('.buttonlinks .refreshList').hide();
344         }
345     }
347     /* 3 Filtering functions */
348     $('#filterAlert').change(function() {
349         alertFilter = this.checked;
350         filterVariables();
351     });
353     $('#filterText').keyup(function(e) {
354         word = $(this).val().replace(/_/g,' ');
356         if(word.length == 0) textFilter = null;
357         else textFilter = new RegExp("(^|_)" + word,'i');
359         text = word;
361         filterVariables();
362     });
364     $('#filterCategory').change(function() {
365         categoryFilter = $(this).val();
366         filterVariables();
367     });
368     
369     $('input#dontFormat').change(function() {
370         $('#serverstatusvariables td.value span.original').toggle(this.checked);
371         $('#serverstatusvariables td.value span.formatted').toggle(! this.checked);
372     });
374     /* Adjust DOM / Add handlers to the tabs */
375     function initTab(tab,data) {
376         switch(tab.attr('id')) {
377             case 'statustabs_traffic':
378                 if(data != null) tab.find('.tabInnerContent').html(data);
379                 PMA_convertFootnotesToTooltips();
380                 break;
381             case 'statustabs_queries':
382                 if(data != null) {
383                     queryPieChart.destroy();
384                     tab.find('.tabInnerContent').html(data);
385                 }
387                 // Build query statistics chart
388                 var cdata = new Array();
389                 $.each(jQuery.parseJSON($('#serverstatusquerieschart span').html()),function(key,value) {
390                     cdata.push([key,parseInt(value)]);
391                 });
393                 queryPieChart = PMA_createChart({
394                     chart: {
395                         renderTo: 'serverstatusquerieschart'
396                     },
397                     title: {
398                         text:'',
399                         margin:0
400                     },
401                     series: [{
402                         type:'pie',
403                         name: PMA_messages['strChartQueryPie'],
404                         data: cdata
405                     }],
406                     plotOptions: {
407                         pie: {
408                             allowPointSelect: true,
409                             cursor: 'pointer',
410                             dataLabels: {
411                                 enabled: true,
412                                 formatter: function() {
413                                     return '<b>'+ this.point.name +'</b><br/> ' + Highcharts.numberFormat(this.percentage, 2) + ' %';
414                                }
415                             }
416                         }
417                     },
418                     tooltip: {
419                         formatter: function() {
420                             return '<b>' + this.point.name + '</b><br/>' + Highcharts.numberFormat(this.y, 2) + '<br/>(' + Highcharts.numberFormat(this.percentage, 2) + ' %)';
421                         }
422                     }
423                 });
424                 break;
426             case 'statustabs_allvars':
427                 if(data != null) {
428                     tab.find('.tabInnerContent').html(data);
429                     filterVariables();
430                 }
431                 break;
432         }
434         initTableSorter(tab.attr('id'));
435     }
437     function initTableSorter(tabid) {
438         switch(tabid) {
439             case 'statustabs_queries':
440                 $('#serverstatusqueriesdetails').tablesorter({
441                         sortList: [[3,1]],
442                         widgets: ['zebra'],
443                         headers: {
444                             1: { sorter: 'fancyNumber' },
445                             2: { sorter: 'fancyNumber' }
446                         }
447                     });
449                 $('#serverstatusqueriesdetails tr:first th')
450                     .append('<img class="icon sortableIcon" src="themes/dot.gif" alt="">');
452                 break;
454             case 'statustabs_allvars':
455                 $('#serverstatusvariables').tablesorter({
456                         sortList: [[0,0]],
457                         widgets: ['zebra'],
458                         headers: {
459                             1: { sorter: 'fancyNumber' }
460                         }
461                     });
463                 $('#serverstatusvariables tr:first th')
464                     .append('<img class="icon sortableIcon" src="themes/dot.gif" alt="">');
466                 break;
467         }
468     }
470     /* Filters the status variables by name/category/alert in the variables tab */
471     function filterVariables() {
472         var useful_links = 0;
473         var section = text;
475         if(categoryFilter.length > 0) section = categoryFilter;
477         if(section.length > 1) {
478             $('#linkSuggestions span').each(function() {
479                 if($(this).attr('class').indexOf('status_'+section) != -1) {
480                     useful_links++;
481                     $(this).css('display','');
482                 } else {
483                     $(this).css('display','none');
484                 }
487             });
488         }
490         if(useful_links > 0)
491             $('#linkSuggestions').css('display','');
492         else $('#linkSuggestions').css('display','none');
494         odd_row=false;
495         $('#serverstatusvariables th.name').each(function() {
496             if((textFilter == null || textFilter.exec($(this).text()))
497                 && (! alertFilter || $(this).next().find('span.attention').length>0)
498                 && (categoryFilter.length == 0 || $(this).parent().hasClass('s_'+categoryFilter))) {
499                 odd_row = ! odd_row;
500                 $(this).parent().css('display','');
501                 if(odd_row) {
502                     $(this).parent().addClass('odd');
503                     $(this).parent().removeClass('even');
504                 } else {
505                     $(this).parent().addClass('even');
506                     $(this).parent().removeClass('odd');
507                 }
508             } else {
509                 $(this).parent().css('display','none');
510             }
511         });
512     }
514     // Provides a nicely formatted and sorted tooltip of each datapoint of the query statistics
515     function sortedQueriesPointInfo(queries, lastQueries){
516         var max, maxIdx, num=0;
517         var queryKeys = new Array();
518         var queryValues = new Array();
519         var sumOther=0;
520         var sumTotal=0;
522         // Separate keys and values, then  sort them
523         $.each(queries.pointInfo, function(key,value) {
524             if(value-lastQueries.pointInfo[key] > 0) {
525                 queryKeys.push(key);
526                 queryValues.push(value-lastQueries.pointInfo[key]);
527                 sumTotal += value-lastQueries.pointInfo[key];
528             }
529         });
530         var numQueries = queryKeys.length;
531         var pointInfo = '<b>' + PMA_messages['strTotal'] + ': ' + sumTotal + '</b><br>';
533         while(queryKeys.length > 0) {
534             max = 0;
535             for(var i=0; i < queryKeys.length; i++) {
536                 if(queryValues[i] > max) {
537                     max = queryValues[i];
538                     maxIdx = i;
539                 }
540             }
541             if(numQueries > 8 && num >= 6)
542                 sumOther += queryValues[maxIdx];
543             else pointInfo += queryKeys[maxIdx].substr(4).replace('_',' ') + ': ' + queryValues[maxIdx] + '<br>';
545             queryKeys.splice(maxIdx,1);
546             queryValues.splice(maxIdx,1);
547             num++;
548         }
550         if(sumOther>0)
551             pointInfo += PMA_messages['strOther'] + ': ' + sumOther;
553         return pointInfo;
554     }
556     /**** Server config advisor ****/
558     $('a[href="#openAdvisorInstructions"]').click(function() {
559         $('#advisorInstructionsDialog').dialog();
560     });
561     
562     $('a[href="#startAnalyzer"]').click(function() {
563         var $cnt = $('#statustabs_advisor .tabInnerContent');
564         $cnt.html('<img class="ajaxIcon" src="' + pmaThemeImage + 'ajax_clock_small.gif" alt="">');
565         
566         $.get('server_status.php?'+url_query, { ajax_request: true, advisor: true },function(data) {
567             var $tbody, $tr, str, even = true;
568             
569             data = $.parseJSON(data);
570             $cnt.html('<p><b>Possible performance issues</b></p>');
571             if(data.fired.length > 0) {
572                 $cnt.append('<table class="data" id="rulesFired" border="0"><thead><tr><th>Issue</th><th>Recommendation</th></tr></thead><tbody></tbody></table>'); 
573                 $tbody = $cnt.find('table#rulesFired');
574                 $.each(data.fired, function(key,value) {
575                     $tbody.append($tr = $('<tr class="linkElem noclick ' + (even ? 'even' : 'odd') + '"><td>' + value.issue + '</td>' +
576                                            '<td>' + value.recommendation + ' </td></tr>')); 
577                     even = !even;
578                     
579                     $tr.data('rule',value);
580                     $tr.click(function() {
581                         var rule = $(this).data('rule');
582                         $('div#emptyDialog').attr('title','Rule details');
583                         $('div#emptyDialog').html(
584                             '<p><b>Issue:</b><br />' + rule.issue + '</p>' +
585                             '<p><b>Recommendation:</b><br />' + rule.recommendation + '</p>' +
586                             '<p><b>Justification:</b><br />' + rule.justification + '</p>' +
587                             '<p><b>Used variable / formula:</b><br />' + rule.formula + '</p>' +
588                             '<p><b>Test:</b><br />' + rule.test + '</p>'
589                         );
590                         $('div#emptyDialog').dialog({
591                             width: 600,
592                             buttons: {
593                                 'Close' : function() {
594                                     $(this).dialog('close');
595                                 }
596                             }
597                         });
598                     });
599                 });
600             }
601         });
602                 
603         return false;
604     });
607     /**** Monitor charting implementation ****/
608     /* Saves the previous ajax response for differential values */
609     var oldChartData = null;
610     // Holds about to created chart
611     var newChart = null;
612     var chartSpacing;
614     // Runtime parameter of the monitor, is being fully set in initGrid()
615     var runtime = {
616         // Holds all visible charts in the grid
617         charts: null,
618         // Stores the timeout handler so it can be cleared
619         refreshTimeout: null,
620         // Stores the GET request to refresh the charts
621         refreshRequest: null,
622         // Chart auto increment
623         chartAI: 0,
624         // To play/pause the monitor
625         redrawCharts: false,
626         // Object that contains a list of nodes that need to be retrieved from the server for chart updates
627         dataList: [],
628         // Current max points per chart (needed for auto calculation)
629         gridMaxPoints: 20,
630         // displayed time frame
631         xmin: -1,
632         xmax: -1
633     };
634     
635     var monitorSettings = null;
637     var defaultMonitorSettings = {
638         columns: 3,
639         chartSize: { width: 295, height: 250 },
640         // Max points in each chart. Settings it to 'auto' sets gridMaxPoints to (chartwidth - 40) / 12
641         gridMaxPoints: 'auto',
642         /* Refresh rate of all grid charts in ms */
643         gridRefresh: 5000
644     };
646     // Allows drag and drop rearrange and print/edit icons on charts
647     var editMode = false;
649     var presetCharts = {
650         'cpu-WINNT': {
651             title: PMA_messages['strSystemCPUUsage'],
652             nodes: [{ dataType: 'cpu', name: PMA_messages['strAverageLoad'], dataPoint: 'loadavg', unit: '%'}]
653         },
654         'memory-WINNT': {
655             title: PMA_messages['strSystemMemory'],
656             nodes: [
657                 { dataType: 'memory', name: PMA_messages['strTotalMemory'], dataPoint: 'MemTotal', valueDivisor: 1024, unit: PMA_messages['strMiB'] },
658                 { dataType: 'memory', name: PMA_messages['strUsedMemory'], dataPoint: 'MemUsed', valueDivisor: 1024, unit: PMA_messages['strMiB']  },
659             ]
660         },
661         'swap-WINNT': {
662             title: PMA_messages['strSystemSwap'],
663             nodes: [
664                 { dataType: 'memory', name: PMA_messages['strTotalSwap'], dataPoint: 'SwapTotal', valueDivisor: 1024, unit: PMA_messages['strMiB'] },
665                 { dataType: 'memory', name: PMA_messages['strUsedSwap'], dataPoint: 'SwapUsed', valueDivisor: 1024, unit: PMA_messages['strMiB'] },
666             ]
667         },
668         'cpu-Linux': {
669             title: PMA_messages['strSystemCPUUsage'],
670             nodes: [
671                 { dataType: 'cpu',
672                   name: PMA_messages['strAverageLoad'],
673                   unit: '%',
674                   transformFn: 'cpu-linux'
675                 }
676             ]
677         },
678         'memory-Linux': {
679             title: PMA_messages['strSystemMemory'],
680             nodes: [
681                 { dataType: 'memory', name: PMA_messages['strUsedMemory'], dataPoint: 'MemUsed', valueDivisor: 1024, unit: PMA_messages['strMiB'] },
682                 { dataType: 'memory', name: PMA_messages['strCachedMemory'], dataPoint: 'Cached',  valueDivisor: 1024, unit: PMA_messages['strMiB'] },
683                 { dataType: 'memory', name: PMA_messages['strBufferedMemory'], dataPoint: 'Buffers', valueDivisor: 1024, unit: PMA_messages['strMiB'] },
684                 { dataType: 'memory', name: PMA_messages['strFreeMemory'], dataPoint:'MemFree', valueDivisor: 1024, unit: PMA_messages['strMiB'] },
685             ],
686             settings: {
687                 chart: {
688                     type: 'area',
689                     animation: false
690                 },
691                 plotOptions: {
692                     area: {
693                         stacking: 'percent'
694                     }
695                 }
696             }
697         },
698         'swap-Linux': {
699             title: PMA_messages['strSystemSwap'],
700             nodes: [
701                 { dataType: 'memory', name: PMA_messages['strTotalSwap'], dataPoint: 'SwapUsed',   valueDivisor: 1024, unit: PMA_messages['strMiB'] },
702                 { dataType: 'memory', name: PMA_messages['strCachedSwap'], dataPoint: 'SwapCached', valueDivisor: 1024, unit: PMA_messages['strMiB'] },
703                 { dataType: 'memory', name: PMA_messages['strFreeSwap'], dataPoint: 'SwapFree',   valueDivisor: 1024, unit: PMA_messages['strMiB'] },
704             ],
705             settings: {
706                 chart: {
707                     type: 'area',
708                     animation: false
709                 },
710                 plotOptions: {
711                     area: {
712                         stacking: 'percent'
713                     }
714                 }
715             }
716         }
717     };
719     // Default setting
720     defaultChartGrid = {
721         'c0': {  title: PMA_messages['strQuestions'],
722                  nodes: [{ dataType: 'statusvar', name: PMA_messages['strQuestions'], dataPoint: 'Questions', display: 'differential' }]
723         },
724         'c1': {
725                  title: PMA_messages['strChartConnectionsTitle'],
726                  nodes: [ { dataType: 'statusvar', name: PMA_messages['strConnections'], dataPoint: 'Connections', display: 'differential' },
727                           { dataType: 'proc', name: PMA_messages['strProcesses'], dataPoint: 'processes'} ]
728         },
729         'c2': {
730                  title: PMA_messages['strTraffic'],
731                  nodes: [
732                     { dataType: 'statusvar', name: PMA_messages['strBytesSent'], dataPoint: 'Bytes_sent', display: 'differential', valueDivisor: 1024, unit: PMA_messages['strKiB'] },
733                     { dataType: 'statusvar', name: PMA_messages['strBytesReceived'], dataPoint: 'Bytes_received', display: 'differential', valueDivisor: 1024, unit: PMA_messages['strKiB'] }
734                  ]
735          }
736     };
738     // Server is localhost => We can add cpu/memory/swap
739     if(server_db_isLocal) {
740         defaultChartGrid['c3'] = presetCharts['cpu-' + server_os];
741         defaultChartGrid['c4'] = presetCharts['memory-' + server_os];
742         defaultChartGrid['c5'] = presetCharts['swap-' + server_os];
743     }
745     var gridbuttons = {
746         cogButton: {
747             //enabled: true,
748             symbol: 'url(' + pmaThemeImage  + 's_cog.png)',
749             x: -36,
750             symbolFill: '#B5C9DF',
751             hoverSymbolFill: '#779ABF',
752             _titleKey: 'settings',
753             menuName: 'gridsettings',
754             menuItems: [{
755                 textKey: 'editChart',
756                 onclick: function() {
757                     editChart(this);
758                 }
759             }, {
760                 textKey: 'removeChart',
761                 onclick: function() {
762                     removeChart(this);
763                 }
764             }]
765         }
766     };
768     Highcharts.setOptions({
769         lang: {
770             settings:    PMA_messages['strSettings'],
771             removeChart: PMA_messages['strRemoveChart'],
772             editChart:   PMA_messages['strEditChart']
773         }
774     });
776     $('a[href="#rearrangeCharts"], a[href="#endChartEditMode"]').click(function() {
777         editMode = !editMode;
778         if($(this).attr('href') == '#endChartEditMode') editMode = false;
780         // Icon graphics have zIndex 19,20 and 21. Let's just hope nothing else has the same zIndex
781         $('table#chartGrid div svg').find('*[zIndex=20], *[zIndex=21], *[zIndex=19]').toggle(editMode)
783         $('a[href="#endChartEditMode"]').toggle(editMode);
785         if(editMode) {
786             // Close the settings popup
787             $('#statustabs_charting .popupContent').hide().removeClass('openedPopup');
789             $("#chartGrid").sortableTable({
790                 ignoreRect: {
791                     top: 8,
792                     left: chartSize().width - 63,
793                     width: 54,
794                     height: 24
795                 },
796                 events: {
797                     // Drop event. The drag child element is moved into the drop element
798                     // and vice versa. So the parameters are switched.
799                     drop: function(drag, drop, pos) {
800                         var dragKey, dropKey, dropRender;
801                         var dragRender = $(drag).children().first().attr('id');
803                         if($(drop).children().length > 0)
804                             dropRender = $(drop).children().first().attr('id');
806                         // Find the charts in the array
807                         $.each(runtime.charts, function(key, value) {
808                             if(value.chart.options.chart.renderTo == dragRender)
809                                 dragKey = key;
810                             if(dropRender && value.chart.options.chart.renderTo == dropRender)
811                                 dropKey = key;
812                         });
814                         // Case 1: drag and drop are charts -> Switch keys
815                         if(dropKey) {
816                             if(dragKey) {
817                                 dragChart = runtime.charts[dragKey];
818                                 runtime.charts[dragKey] = runtime.charts[dropKey];
819                                 runtime.charts[dropKey] = dragChart;
820                             } else {
821                                 // Case 2: drop is a empty cell => just completely rebuild the ids
822                                 var keys = [];
823                                 var dropKeyNum = parseInt(dropKey.substr(1));
824                                 var insertBefore = pos.col + pos.row * monitorSettings.columns;
825                                 var values = [];
826                                 var newChartList = {};
827                                 var c = 0;
829                                 $.each(runtime.charts, function(key, value) {
830                                     if(key != dropKey)
831                                         keys.push(key);
832                                 });
834                                 keys.sort();
836                                 // Rebuilds all ids, with the dragged chart correctly inserted
837                                 for(var i=0; i<keys.length; i++) {
838                                     if(keys[i] == insertBefore) {
839                                         newChartList['c' + (c++)] = runtime.charts[dropKey];
840                                         insertBefore = -1; // Insert ok
841                                     }
842                                     newChartList['c' + (c++)] = runtime.charts[keys[i]];
843                                 }
845                                 // Not inserted => put at the end
846                                 if(insertBefore != -1)
847                                     newChartList['c' + (c++)] = runtime.charts[dropKey];
849                                 runtime.charts = newChartList;
850                             }
852                             saveMonitor();
853                         }
854                     }
855                 }
856             });
858         } else {
859             $("#chartGrid").sortableTable('destroy');
860             saveMonitor(); // Save settings
861         }
863         return false;
864     });
866     // global settings
867     $('div#statustabs_charting div.popupContent select[name="chartColumns"]').change(function() {
868         monitorSettings.columns = parseInt(this.value);
870         var newSize = chartSize();
872         // Empty cells should keep their size so you can drop onto them
873         $('table#chartGrid tr td').css('width',newSize.width + 'px');
875         /* Reorder all charts that it fills all column cells */
876         var numColumns;
877         var $tr = $('table#chartGrid tr:first');
878         var row=0;
879         while($tr.length != 0) {
880             numColumns = 1;
881             // To many cells in one row => put into next row
882             $tr.find('td').each(function() {
883                 if(numColumns > monitorSettings.columns) {
884                     if($tr.next().length == 0) $tr.after('<tr></tr>');
885                     $tr.next().prepend($(this));
886                 }
887                 numColumns++;
888             });
890             // To little cells in one row => for each cell to little, move all cells backwards by 1
891             if($tr.next().length > 0) {
892                 var cnt = monitorSettings.columns - $tr.find('td').length;
893                 for(var i=0; i < cnt; i++) {
894                     $tr.append($tr.next().find('td:first'));
895                     $tr.nextAll().each(function() {
896                         if($(this).next().length != 0)
897                             $(this).append($(this).next().find('td:first'));
898                     });
899                 }
900             }
902             $tr = $tr.next();
903             row++;
904         }
906         /* Apply new chart size to all charts */
907         $.each(runtime.charts, function(key, value) {
908             value.chart.setSize(
909                 newSize.width,
910                 newSize.height,
911                 false
912             );
913         });
915         if(monitorSettings.gridMaxPoints == 'auto')
916             runtime.gridMaxPoints = Math.round((newSize.width - 40) / 12);
918         runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
919         runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh;
921         if(editMode)
922             $("#chartGrid").sortableTable('refresh');
924         saveMonitor(); // Save settings
925     });
927     $('div#statustabs_charting div.popupContent select[name="gridChartRefresh"]').change(function() {
928         monitorSettings.gridRefresh = parseInt(this.value) * 1000;
929         clearTimeout(runtime.refreshTimeout);
931         if(runtime.refreshRequest)
932             runtime.refreshRequest.abort();
934         runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
935         runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh;
937         $.each(runtime.charts, function(key, value) {
938             value.chart.xAxis[0].setExtremes(runtime.xmin, runtime.xmax, false);
939         });
941         runtime.refreshTimeout = setTimeout(refreshChartGrid, monitorSettings.gridRefresh);
943         saveMonitor(); // Save settings
944     });
946     $('a[href="#addNewChart"]').click(function() {
947         var dlgButtons = { };
949         dlgButtons[PMA_messages['strAddChart']] = function() {
950             var type = $('input[name="chartType"]:checked').val();
952             if(type == 'cpu' || type == 'memory' || type=='swap')
953                 newChart = presetCharts[type + '-' + server_os];
954             else {
955                 if(! newChart || ! newChart.nodes || newChart.nodes.length == 0) {
956                     alert(PMA_messages['strAddOneSeriesWarning']);
957                     return;
958                 }
959             }
961             newChart.title = $('input[name="chartTitle"]').attr('value');
962             // Add a cloned object to the chart grid
963             addChart($.extend(true, {}, newChart));
965             newChart = null;
967             saveMonitor(); // Save settings
969             $(this).dialog("close");
970         };
971         
972         dlgButtons[PMA_messages['strClose']] = function() {
973             newChart = null;
974             $('span#clearSeriesLink').hide();
975             $('#seriesPreview').html('');
976             $(this).dialog("close");
977         };
978         
979         $('div#addChartDialog').dialog({
980             width:'auto',
981             height:'auto',
982             buttons: dlgButtons
983         });
985         $('div#addChartDialog #seriesPreview').html('<i>' + PMA_messages['strNone'] + '</i>');
987         return false;
988     });
989     
990     $('a[href="#exportMonitorConfig"]').click(function() {
991         var gridCopy = {};
993         $.each(runtime.charts, function(key, elem) {
994             gridCopy[key] = {};
995             gridCopy[key].nodes = elem.nodes;
996             gridCopy[key].settings = elem.settings;
997             gridCopy[key].title = elem.title;
998         });
999         
1000         var exportData = {
1001             monitorCharts: gridCopy,
1002             monitorSettings: monitorSettings
1003         };
1004         var $form;
1005         
1006         $('body').append($form = $('<form method="post" action="file_echo.php?'+url_query+'&filename=1" style="display:none;"></form>'));
1007         
1008         $form.append('<input type="hidden" name="monitorconfig" value="' + encodeURI($.toJSON(exportData)) + '">');
1009         $form.submit();
1010         $form.remove();
1011     });
1013     $('a[href="#importMonitorConfig"]').click(function() {
1014         $('div#emptyDialog').attr('title','Import monitor configuration');
1015         $('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">'+
1016             '<input type="file" name="file"> <input type="hidden" name="import" value="1"> </form>');
1017         
1018         var dlgBtns = {};
1019         
1020         dlgBtns[PMA_messages['strImport']] = function() {
1021             var $iframe, $form;
1022             $('body').append($iframe = $('<iframe id="monitorConfigUpload" style="display:none;"></iframe>'));
1023             var d = $iframe[0].contentWindow.document;
1024             d.open(); d.close();
1025             mew = d;
1026             
1027             $iframe.load(function() {
1028                 var json;
1030                 // Try loading config
1031                 try {
1032                     json = $.secureEvalJSON($('body',$('iframe#monitorConfigUpload')[0].contentWindow.document).html());
1033                 } catch (err) {
1034                     alert(PMA_messages['strFailedParsingConfig']);
1035                     $('div#emptyDialog').dialog('close');
1036                     return;
1037                 }
1038             
1039                 // Basic check, is this a monitor config json?
1040                 if(!json || ! json.monitorCharts || ! json.monitorCharts) {
1041                     alert(PMA_messages['strFailedParsingConfig']);
1042                     $('div#emptyDialog').dialog('close');
1043                     return;
1044                 }
1045                 
1046                 // If json ok, try applying config
1047                 try {
1048                     window.localStorage['monitorCharts'] = $.toJSON(json.monitorCharts);
1049                     window.localStorage['monitorSettings'] = $.toJSON(json.monitorSettings);
1050                     rebuildGrid();
1051                 } catch(err) {
1052                     alert(PMA_messages['strFailedBuildingGrid']);
1053                     // If an exception is thrown, load default again
1054                     window.localStorage.removeItem('monitorCharts');
1055                     window.localStorage.removeItem('monitorSettings');
1056                     rebuildGrid();
1057                 }
1058                 
1059                 $('div#emptyDialog').dialog('close');
1060             });
1061             
1062             $("body", d).append($form=$('div#emptyDialog').find('form'));
1063             $form.submit();
1064             $('div#emptyDialog').append('<img class="ajaxIcon" src="' + pmaThemeImage + 'ajax_clock_small.gif" alt="">');
1065         };
1066         
1067         dlgBtns[PMA_messages['strCancel']] = function() {
1068             $(this).dialog('close');
1069         }
1070         
1071         
1072         $('div#emptyDialog').dialog({
1073             width: 'auto',
1074             height: 'auto',
1075             buttons: dlgBtns
1076         });
1077     });
1079     $('a[href="#clearMonitorConfig"]').click(function() {
1080         window.localStorage.removeItem('monitorCharts');
1081         window.localStorage.removeItem('monitorSettings');
1082         $(this).hide();
1083         rebuildGrid();
1084     });
1086     $('a[href="#pauseCharts"]').click(function() {
1087         runtime.redrawCharts = ! runtime.redrawCharts;
1088         if(! runtime.redrawCharts)
1089             $(this).html('<img src="themes/dot.gif" class="icon ic_play" alt="" /> ' + PMA_messages['strResumeMonitor']);
1090         else {
1091             $(this).html('<img src="themes/dot.gif" class="icon ic_pause" alt="" /> ' + PMA_messages['strPauseMonitor']);
1092             if(! runtime.charts) {
1093                 initGrid();
1094                 $('a[href="#settingsPopup"]').show();
1095             }
1096         }
1097         return false;
1098     });
1100     $('a[href="#monitorInstructionsDialog"]').click(function() {
1101         var $dialog = $('div#monitorInstructionsDialog');
1103         $dialog.dialog({
1104             width: 595,
1105             height: 'auto'
1106         }).find('img.ajaxIcon').show();
1108         var loadLogVars = function(getvars) {
1109             vars = { ajax_request: true, logging_vars: true };
1110             if(getvars) $.extend(vars,getvars);
1112             $.get('server_status.php?' + url_query, vars,
1113                 function(data) {
1114                     var logVars = $.parseJSON(data),
1115                         icon = 'ic_s_success', msg='', str='';
1117                     if(logVars['general_log'] == 'ON') {
1118                         if(logVars['slow_query_log'] == 'ON')
1119                             msg = PMA_messages['strBothLogOn'];
1120                         else
1121                             msg = PMA_messages['strGenLogOn'];
1122                     }
1124                     if(msg.length == 0 && logVars['slow_query_log'] == 'ON') {
1125                         msg = PMA_messages['strSlowLogOn'];
1126                     }
1128                     if(msg.length == 0) {
1129                         icon = 'ic_s_error';
1130                         msg = PMA_messages['strBothLogOff'];
1131                     }
1133                     str = '<b>' + PMA_messages['strCurrentSettings'] + '</b><br><div class="smallIndent">';
1134                     str += '<img src="themes/dot.gif" class="icon ' + icon + '" alt=""/> ' + msg + '<br />';
1136                     if(logVars['log_output'] != 'TABLE')
1137                         str += '<img src="themes/dot.gif" class="icon ic_s_error" alt=""/> ' + PMA_messages['strLogOutNotTable'] + '<br />';
1138                     else
1139                         str += '<img src="themes/dot.gif" class="icon ic_s_success" alt=""/> ' + PMA_messages['strLogOutIsTable'] + '<br />';
1141                     if(logVars['slow_query_log'] == 'ON') {
1142                         if(logVars['long_query_time'] > 2)
1143                             str += '<img src="themes/dot.gif" class="icon ic_s_attention" alt=""/> '
1144                                 + $.sprintf(PMA_messages['strSmallerLongQueryTimeAdvice'], logVars['long_query_time'])
1145                                 + '<br />';
1147                         if(logVars['long_query_time'] < 2)
1148                             str += '<img src="themes/dot.gif" class="icon ic_s_success" alt=""/> '
1149                                 + $.sprintf(PMA_messages['strLongQueryTimeSet'], logVars['long_query_time'])
1150                                 + '<br />';
1151                     }
1153                     str += '</div>';
1155                     if(is_superuser) {
1156                         str += '<p></p><b>Change settings</b>';
1157                         str += '<div class="smallIndent">';
1158                         str += PMA_messages['strSettingsAppliedGlobal'] + '<br/>';
1160                         var varValue = 'TABLE';
1161                         if(logVars['log_output'] == 'TABLE') varValue = 'FILE';
1163                         str += '- <a class="set" href="#log_output-' + varValue + '">'
1164                             + $.sprintf(PMA_messages['strSetLogOutput'], varValue)
1165                             + ' </a><br />';
1167                         if(logVars['general_log'] != 'ON')
1168                             str += '- <a class="set" href="#general_log-ON">'
1169                                 + $.sprintf(PMA_messages['strEnableVar'], 'general_log')
1170                                 + ' </a><br />';
1171                         else
1172                             str += '- <a class="set" href="#general_log-OFF">'
1173                                 + $.sprintf(PMA_messages['strDisableVar'], 'general_log')
1174                                 + ' </a><br />';
1176                         if(logVars['slow_query_log'] != 'ON')
1177                             str += '- <a class="set" href="#slow_query_log-ON">'
1178                                 +  $.sprintf(PMA_messages['strEnableVar'], 'slow_query_log')
1179                                 + ' </a><br />';
1180                         else
1181                             str += '- <a class="set" href="#slow_query_log-OFF">'
1182                                 +  $.sprintf(PMA_messages['strDisableVar'], 'slow_query_log')
1183                                 + ' </a><br />';
1186                         varValue = 5;
1187                         if(logVars['long_query_time'] > 2) varValue = 1;
1189                         str += '- <a class="set" href="#long_query_time-' + varValue + '">'
1190                             + $.sprintf(PMA_messages['setSetLongQueryTime'], varValue)
1191                             + ' </a><br />';
1193                     } else
1194                         str += PMA_messages['strNoSuperUser'] + '<br/>';
1196                     str += '</div>';
1198                     $dialog.find('div.monitorUse').toggle(
1199                         logVars['log_output'] == 'TABLE' && (logVars['slow_query_log'] == 'ON' || logVars['general_log'] == 'ON')
1200                     );
1202                     $dialog.find('div.ajaxContent').html(str);
1203                     $dialog.find('img.ajaxIcon').hide();
1204                     $dialog.find('a.set').click(function() {
1205                         var nameValue = $(this).attr('href').split('-');
1206                         loadLogVars({ varName: nameValue[0].substr(1), varValue: nameValue[1]});
1207                         $dialog.find('img.ajaxIcon').show();
1208                     });
1209                 }
1210             );
1211         };
1212         
1213         
1214         loadLogVars();
1216         return false;
1217     });
1219     $('input[name="chartType"]').change(function() {
1220         $('#chartVariableSettings').toggle(this.checked && this.value == 'variable');
1221         var title = $('input[name="chartTitle"]').attr('value');
1222         if(title == PMA_messages['strChartTitle'] || title == $('label[for="'+$('input[name="chartTitle"]').data('lastRadio')+'"]').text()) {
1223             $('input[name="chartTitle"]').data('lastRadio',$(this).attr('id'));
1224             $('input[name="chartTitle"]').attr('value',$('label[for="'+$(this).attr('id')+'"]').text());
1225         }
1227     });
1229     $('input[name="useDivisor"]').change(function() {
1230         $('span.divisorInput').toggle(this.checked);
1231     });
1232     $('input[name="useUnit"]').change(function() {
1233         $('span.unitInput').toggle(this.checked);
1234     });
1236     $('select[name="varChartList"]').change(function () {
1237         if(this.selectedIndex!=0)
1238             $('#variableInput').attr('value',this.value);
1239     });
1241     $('a[href="#kibDivisor"]').click(function() {
1242         $('input[name="valueDivisor"]').attr('value',1024);
1243         $('input[name="valueUnit"]').attr('value',PMA_messages['strKiB']);
1244         $('span.unitInput').toggle(true);
1245         $('input[name="useUnit"]').prop('checked',true);
1246         return false;
1247     });
1249     $('a[href="#mibDivisor"]').click(function() {
1250         $('input[name="valueDivisor"]').attr('value',1024*1024);
1251         $('input[name="valueUnit"]').attr('value',PMA_messages['strMiB']);
1252         $('span.unitInput').toggle(true);
1253         $('input[name="useUnit"]').prop('checked',true);
1254         return false;
1255     });
1257     $('a[href="#submitClearSeries"]').click(function() {
1258         $('#seriesPreview').html('<i>' + PMA_messages['strNone'] + '</i>');
1259         newChart = null;
1260         $('span#clearSeriesLink').hide();
1261     });
1263     $('a[href="#submitAddSeries"]').click(function() {
1264         if($('input#variableInput').attr('value').length == 0) return false;
1266         if(newChart == null) {
1267             $('#seriesPreview').html('');
1269             newChart = {
1270                 title: $('input[name="chartTitle"]').attr('value'),
1271                 nodes: []
1272             }
1273         }
1275         var serie = {
1276             dataType:'statusvar',
1277             dataPoint: $('input#variableInput').attr('value'),
1278             name: $('input#variableInput').attr('value'),
1279             display: $('input[name="differentialValue"]').attr('checked') ? 'differential' : ''
1280         };
1282         if(serie.dataPoint == 'Processes') serie.dataType='proc';
1284         if($('input[name="useDivisor"]').attr('checked'))
1285             serie.valueDivisor = parseInt($('input[name="valueDivisor"]').attr('value'));
1287         if($('input[name="useUnit"]').attr('checked'))
1288             serie.unit = $('input[name="valueUnit"]').attr('value');
1292         var str = serie.display == 'differential' ? ', ' + PMA_messages['strDifferential'] : '';
1293         str += serie.valueDivisor ? (', ' + $.sprintf(PMA_messages['strDividedBy'], serie.valueDivisor)) : '';
1295         $('#seriesPreview').append('- ' + serie.dataPoint + str + '<br>');
1297         newChart.nodes.push(serie);
1299         $('input#variableInput').attr('value','');
1300         $('input[name="differentialValue"]').attr('checked',true);
1301         $('input[name="useDivisor"]').attr('checked',false);
1302         $('input[name="useUnit"]').attr('checked',false);
1303         $('input[name="useDivisor"]').trigger('change');
1304         $('input[name="useUnit"]').trigger('change');
1305         $('select[name="varChartList"]').get(0).selectedIndex=0;
1307         $('span#clearSeriesLink').show();
1309         return false;
1310     });
1312     $("input#variableInput").autocomplete({
1313             source: variableNames
1314     });
1317     function initGrid() {
1318         var settings;
1319         var series;
1321         /* Apply default values & config */
1322         if(window.localStorage) {
1323             if(window.localStorage['monitorCharts'])
1324                 runtime.charts = $.parseJSON(window.localStorage['monitorCharts']);
1325             if(window.localStorage['monitorSettings'])
1326                 monitorSettings = $.parseJSON(window.localStorage['monitorSettings']);
1328             $('a[href="#clearMonitorConfig"]').toggle(runtime.charts != null);
1329         }
1331         if(runtime.charts == null)
1332             runtime.charts = defaultChartGrid;
1333         if(monitorSettings == null)
1334             monitorSettings = defaultMonitorSettings;
1336         $('select[name="gridChartRefresh"]').attr('value',monitorSettings.gridRefresh / 1000);
1337         $('select[name="chartColumns"]').attr('value',monitorSettings.columns);
1339         if(monitorSettings.gridMaxPoints == 'auto')
1340             runtime.gridMaxPoints = Math.round((monitorSettings.chartSize.width - 40) / 12);
1341         else
1342             runtime.gridMaxPoints = monitorSettings.gridMaxPoints;
1344         runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
1345         runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh;
1347         /* Calculate how much spacing there is between each chart */
1348         $('table#chartGrid').html('<tr><td></td><td></td></tr><tr><td></td><td></td></tr>');
1349         chartSpacing = {
1350             width: $('table#chartGrid td:nth-child(2)').offset().left - $('table#chartGrid td:nth-child(1)').offset().left,
1351             height: $('table#chartGrid tr:nth-child(2) td:nth-child(2)').offset().top - $('table#chartGrid tr:nth-child(1) td:nth-child(1)').offset().top
1352         }
1353         $('table#chartGrid').html('');
1354         
1355         /* Add all charts - in correct order */
1356         var keys = [];
1357         $.each(runtime.charts, function(key, value) {
1358             keys.push(key);
1359         });
1360         keys.sort();
1361         for(var i=0; i<keys.length; i++)
1362             addChart(runtime.charts[keys[i]],true);
1364         /* Fill in missing cells */
1365         var numCharts = $('table#chartGrid .monitorChart').length;
1366         var numMissingCells = (monitorSettings.columns - numCharts % monitorSettings.columns) % monitorSettings.columns;
1367         for(var i=0; i < numMissingCells; i++) {
1368             $('table#chartGrid tr:last').append('<td></td>');
1369         }
1371         // Empty cells should keep their size so you can drop onto them
1372         $('table#chartGrid tr td').css('width',chartSize().width + 'px');
1373         
1374         buildRequiredDataList();
1375         refreshChartGrid();
1376     }
1377     
1378     function destroyGrid() {
1379         if(runtime.charts)
1380             $.each(runtime.charts, function(key, value) {
1381                 try {
1382                     value.chart.destroy();
1383                 } catch(err) {}
1384             });
1385         try {
1386             runtime.refreshRequest.abort();
1387         } catch(err) {}
1388         try {    
1389             clearTimeout(runtime.refreshTimeout);
1390         } catch(err) {}
1391             
1392         $('table#chartGrid').html('');
1394         runtime.charts = null;
1395         runtime.chartAI = 0;
1396         monitorSettings = null;
1397     }
1398     
1399     function rebuildGrid() {
1400         var oldData = null;
1401         if(runtime.charts) {
1402             oldData = {};
1403             $.each(runtime.charts, function(key, chartObj) {
1404                 for(var i=0; i < chartObj.nodes.length; i++) {
1405                     oldData[chartObj.nodes[i].dataPoint] = [];
1406                     for(var j=0; j < chartObj.chart.series[i].data.length; j++)
1407                         oldData[chartObj.nodes[i].dataPoint].push([chartObj.chart.series[i].data[j].x, chartObj.chart.series[i].data[j].y]);
1408                 }
1409             });
1410         }
1411         
1412         destroyGrid();
1413         initGrid();
1414         
1415         if(oldData) {
1416             $.each(runtime.charts, function(key, chartObj) {
1417                 for(var j=0; j < chartObj.nodes.length; j++) {
1418                     if(oldData[chartObj.nodes[j].dataPoint])
1419                         chartObj.chart.series[j].setData(oldData[chartObj.nodes[j].dataPoint]);
1420                 }
1421             });
1422         }      
1423     }
1425     function chartSize() {
1426         var wdt = $('div#logTable').innerWidth() / monitorSettings.columns - (monitorSettings.columns - 1) * chartSpacing.width;
1427         return {
1428             width: wdt,
1429             height: 0.75 * wdt
1430         }
1431     }
1433     function addChart(chartObj, initialize) {
1434         series = [];
1435         for(var j=0; j<chartObj.nodes.length; j++)
1436             series.push(chartObj.nodes[j]);
1438         settings = {
1439             chart: {
1440                 renderTo: 'gridchart' + runtime.chartAI,
1441                 width: chartSize().width,
1442                 height: chartSize().height,
1443                 marginRight: 5,
1444                 zoomType: 'x',
1445                 events: {
1446                     selection: function(event) {
1447                         if(editMode) return false;
1449                         var extremesObject = event.xAxis[0],
1450                             min = extremesObject.min,
1451                             max = extremesObject.max;
1453                         $('#logAnalyseDialog input[name="dateStart"]')
1454                             .attr('value', Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', new Date(min)));
1455                         $('#logAnalyseDialog input[name="dateEnd"]')
1456                             .attr('value', Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', new Date(max)));
1458                         var dlgBtns = { };
1460                         dlgBtns[PMA_messages['strFromSlowLog']] = function() {
1461                             var dateStart = Date.parse($('#logAnalyseDialog input[name="dateStart"]').attr('value')) || min;
1462                             var dateEnd = Date.parse($('#logAnalyseDialog input[name="dateEnd"]').attr('value')) || max;
1464                             loadLogStatistics({
1465                                 src: 'slow',
1466                                 start: dateStart,
1467                                 end: dateEnd,
1468                                 removeVariables: $('input#removeVariables').prop('checked'),
1469                                 limitTypes: $('input#limitTypes').prop('checked')
1470                             });
1472                             $('#logAnalyseDialog').find('dateStart,dateEnd').datepicker('destroy');
1474                             $(this).dialog("close");
1475                         };
1476                         
1477                         dlgBtns[PMA_messages['strFromGeneralLog']] = function() {
1478                             var dateStart = Date.parse($('#logAnalyseDialog input[name="dateStart"]').attr('value')) || min;
1479                             var dateEnd = Date.parse($('#logAnalyseDialog input[name="dateEnd"]').attr('value')) || max;
1481                             loadLogStatistics({
1482                                 src: 'general',
1483                                 start: dateStart,
1484                                 end: dateEnd,
1485                                 removeVariables: $('input#removeVariables').prop('checked'),
1486                                 limitTypes: $('input#limitTypes').prop('checked')
1487                             });
1489                             $('#logAnalyseDialog').find('dateStart,dateEnd').datepicker('destroy');
1491                             $(this).dialog("close");
1492                         };
1493                         
1494                         $('#logAnalyseDialog').dialog({
1495                             width: 'auto',
1496                             height: 'auto',
1497                             buttons: dlgBtns
1498                         });
1500                         return false;
1501                     }
1502                 }
1503             },
1504             xAxis: {
1505                 min: runtime.xmin,
1506                 max: runtime.xmax
1507             },
1509             yAxis: {
1510                 title: {
1511                     text: ''
1512                 }
1513             },
1514             tooltip: {
1515                 formatter: function() {
1516                         var s = '<b>'+Highcharts.dateFormat('%H:%M:%S', this.x)+'</b>';
1518                         $.each(this.points, function(i, point) {
1519                             s += '<br/><span style="color:'+point.series.color+'">'+ point.series.name +':</span> '+
1520                                 ((parseInt(point.y) == point.y) ? point.y : Highcharts.numberFormat(this.y, 2)) + ' ' + (point.series.options.unit || '');
1521                         });
1523                         return s;
1524                 },
1525                 shared: true
1526             },
1527             legend: {
1528                 enabled: false
1529             },
1530             series: series,
1531             buttons: gridbuttons,
1532             title: { text: chartObj.title }
1533         };
1535         if(chartObj.settings)
1536             $.extend(true,settings,chartObj.settings);
1538         if($('#'+settings.chart.renderTo).length==0) {
1539             var numCharts = $('table#chartGrid .monitorChart').length;
1541             if(numCharts == 0 || !( numCharts % monitorSettings.columns))
1542                 $('table#chartGrid').append('<tr></tr>');
1544             $('table#chartGrid tr:last').append('<td><div class="ui-state-default monitorChart" id="'+settings.chart.renderTo+'"></div></td>');
1545         }
1547         chartObj.chart = PMA_createChart(settings);
1548         chartObj.numPoints = 0;
1549         
1550         if(initialize != true) {
1551             runtime.charts['c'+runtime.chartAI] = chartObj;
1552             buildRequiredDataList();
1553         }
1555         // Edit,Print icon only in edit mode
1556         $('table#chartGrid div svg').find('*[zIndex=20], *[zIndex=21], *[zIndex=19]').toggle(editMode)
1558         runtime.chartAI++;
1559     }
1561     function editChart(chartObj) {
1562         var htmlnode = chartObj.options.chart.renderTo;
1563         if(! htmlnode ) return;
1564         
1565         var chart=null;
1566         var chartKey=null;
1567         $.each(runtime.charts, function(key, value) {
1568             if(value.chart.options.chart.renderTo == htmlnode) {
1569                 chart = value;
1570                 chartKey = key;
1571                 return false;
1572             }
1573         });
1574         
1575         if(chart == null) return;
1576         
1577         var htmlStr = '<p><b>Chart title: </b> <br/> <input type="text" size="35" name="chartTitle" value="' + chart.title + '" />';       
1578         htmlStr += '</p><p><b>Series:</b> </p><ol>';
1579         for(var i=0; i<chart.nodes.length; i++) {
1580             htmlStr += '<li><i>' + chart.nodes[i].dataPoint  +': </i><br/><input type="text" name="chartSerie-' + i + '" value=" ' + chart.nodes[i].name + '" /></li>';
1581         }
1582         
1583         dlgBtns = {};
1584         dlgBtns['Save'] = function() {
1585             runtime.charts[chartKey].title = $('div#emptyDialog input[name="chartTitle"]').attr('value');
1586             runtime.charts[chartKey].chart.setTitle({ text: runtime.charts[chartKey].title });
1587             
1588             $('div#emptyDialog input[name*="chartSerie"]').each(function() {
1589                 var idx = $(this).attr('name').split('-')[1];
1590                 runtime.charts[chartKey].nodes[idx].name = $(this).attr('value');
1591                 runtime.charts[chartKey].chart.series[idx].name = $(this).attr('value');
1592             });
1593             
1594             $(this).dialog('close');
1595             saveMonitor();
1596         };
1597         dlgBtns['Cancel'] = function() {
1598             $(this).dialog('close');
1599         };
1600         
1601         $('div#emptyDialog').attr('title','Edit chart');
1602         $('div#emptyDialog').html(htmlStr+'</ol>');
1603         $('div#emptyDialog').dialog({
1604             width: 'auto',
1605             height: 'auto',
1606             buttons: dlgBtns
1607         });
1608     }
1609     
1610     function removeChart(chartObj) {
1611         var htmlnode = chartObj.options.chart.renderTo;
1612         if(! htmlnode ) return;
1613         
1614         $.each(runtime.charts, function(key, value) {
1615             if(value.chart.options.chart.renderTo == htmlnode) {
1616                 delete runtime.charts[key];
1617                 return false;
1618             }
1619         });
1621         buildRequiredDataList();
1623         // Using settimeout() because clicking the remove link fires an onclick event
1624         // which throws an error when the chart is destroyed
1625         setTimeout(function() {
1626             chartObj.destroy();
1627             $('div#' + htmlnode).remove();
1628         },10);
1630         saveMonitor(); // Save settings
1631     }
1633     function refreshChartGrid() {
1634         /* Send to server */
1635         runtime.refreshRequest = $.post('server_status.php?'+url_query, { ajax_request: true, chart_data: 1, type: 'chartgrid', requiredData: $.toJSON(runtime.dataList) },function(data) {
1636             var chartData;
1637             try {
1638                 chartData = $.parseJSON(data);
1639             } catch(err) {
1640                 return serverResponseError();
1641             }
1642             var value, i=0;
1643             var diff;
1644             
1645             /* Update values in each graph */
1646             $.each(runtime.charts, function(orderKey, elem) {
1647                 var key = elem.chartID;
1648                 // If newly added chart, we have no data for it yet
1649                 if(! chartData[key]) return;
1650                 // Draw all points
1651                 for(var j=0; j < elem.nodes.length; j++) {
1652                     value = chartData[key][j].y;
1654                     if(i==0 && j==0) {
1655                         if(oldChartData==null) diff = chartData.x - runtime.xmax;
1656                         else diff = parseInt(chartData.x - oldChartData.x);
1658                         runtime.xmin+= diff;
1659                         runtime.xmax+= diff;
1660                     }
1662                     elem.chart.xAxis[0].setExtremes(runtime.xmin, runtime.xmax, false);
1664                     if(elem.nodes[j].display == 'differential') {
1665                         if(oldChartData == null || oldChartData[key] == null) continue;
1666                         value -= oldChartData[key][j].y;
1667                     }
1669                     if(elem.nodes[j].valueDivisor)
1670                         value = value / elem.nodes[j].valueDivisor;
1672                     if(elem.nodes[j].transformFn) {
1673                         value = chartValueTransform(
1674                             elem.nodes[j].transformFn, 
1675                             chartData[key][j], 
1676                             (oldChartData == null ? null : oldChartData[key][j])
1677                         );
1678                     }
1679                     
1680                     if(value != undefined)
1681                         elem.chart.series[j].addPoint(
1682                             {  x: chartData.x, y: value },
1683                             false,
1684                             elem.numPoints >= runtime.gridMaxPoints
1685                         );
1686                 }
1688                 i++;
1690                 runtime.charts[orderKey].numPoints++;
1691                 if(runtime.redrawCharts)
1692                     elem.chart.redraw();
1693             });
1695             oldChartData = chartData;
1697             runtime.refreshTimeout = setTimeout(refreshChartGrid, monitorSettings.gridRefresh);
1698         });
1699     }
1700     
1701     function chartValueTransform(name,cur,prev) {
1702         switch(name) {
1703             case 'cpu-linux':
1704                 if(prev == null) return undefined;
1705                 var diff_total = cur.busy + cur.idle - (prev.busy + prev.idle);
1706                 var diff_idle = cur.idle - prev.idle;
1707                 return 100*(diff_total - diff_idle) / diff_total;
1708         }
1709         return undefined;
1710     }
1712     /* Build list of nodes that need to be retrieved */
1713     function buildRequiredDataList() {
1714         runtime.dataList = {};
1715         // Store an own id, because the property name is subject of reordering, thus destroying our mapping with runtime.charts <=> runtime.dataList
1716         var chartID = 0;
1717         $.each(runtime.charts, function(key, chart) {
1718             runtime.dataList[chartID] = chart.nodes;
1719             runtime.charts[key].chartID = chartID;
1720             chartID++;
1721         });
1722     }
1724     function loadLogStatistics(opts) {
1725         var tableStr = '';
1726         var logRequest = null;
1728         if(! opts.removeVariables)
1729             opts.removeVariables = false;
1730         if(! opts.limitTypes)
1731             opts.limitTypes = false;
1732         
1733         $('#emptyDialog').html(PMA_messages['strAnalysingLogs'] + ' <img class="ajaxIcon" src="' + pmaThemeImage + 'ajax_clock_small.gif" alt="">');
1735         $('#emptyDialog').dialog({
1736             width: 'auto',
1737             height: 'auto',
1738             buttons: {
1739                 'Cancel request': function() {
1740                     if(logRequest != null)
1741                         logRequest.abort();
1743                     $(this).dialog("close");
1744                 }
1745             }
1746         });
1749         logRequest = $.get('server_status.php?'+url_query,
1750             {   ajax_request: true,
1751                 log_data: 1,
1752                 type: opts.src,
1753                 time_start: Math.round(opts.start / 1000),
1754                 time_end: Math.round(opts.end / 1000),
1755                 removeVariables: opts.removeVariables,
1756                 limitTypes: opts.limitTypes
1757             },
1758             function(data) { 
1759                 var logData;
1760                 try {
1761                     logData = $.parseJSON(data);
1762                 } catch(err) {
1763                     return serverResponseError();
1764                 }
1765                 
1766                 if(logData.rows.length != 0) {
1767                     runtime.logDataCols = buildLogTable(logData);
1769                     /* Show some stats in the dialog */
1770                     $('#emptyDialog').attr('title', PMA_messages['strLoadingLogs']);
1771                     $('#emptyDialog').html('<p>' + PMA_messages['strLogDataLoaded'] + '</p>');
1772                     $.each(logData.sum, function(key, value) {
1773                         key = key.charAt(0).toUpperCase() + key.slice(1).toLowerCase();
1774                         if(key == 'Total') key = '<b>' + key + '</b>';
1775                         $('#emptyDialog').append(key + ': ' + value + '<br/>');
1776                     });
1778                     /* Add filter options if more than a bunch of rows there to filter */
1779                     if(logData.numRows > 12) {
1780                         $('div#logTable').prepend(
1781                             '<fieldset id="logDataFilter">' +
1782                             '   <legend>' + PMA_messages['strFilters'] + '</legend>' +
1783                             '   <div class="formelement">' +
1784                             '           <label for="filterQueryText">' + PMA_messages['strFilterByWordRegexp'] + '</label>' +
1785                             '           <input name="filterQueryText" type="text" id="filterQueryText" style="vertical-align: baseline;" />' +
1786                             '   </div>' +
1787                             ((logData.numRows > 250) ? ' <div class="formelement"><button name="startFilterQueryText" id="startFilterQueryText">' + PMA_messages['strFilter'] + '</button></div>' : '') +
1788                             '   <div class="formelement">' +
1789                             '       <input type="checkbox" id="noWHEREData" name="noWHEREData" value="1" /> ' +
1790                             '       <label for="noWHEREData"> ' + PMA_messages['strIgnoreWhereAndGroup'] + '</label>' +
1791                             '   </div' +
1792                             '</fieldset>'
1793                         );
1795                         $('div#logTable input#noWHEREData').change(function() {
1796                             filterQueries(true);
1797                         });
1799                         //preg_replace('/\s+([^=]+)=(\d+|((\'|"|)(?U)(.+)(?<!\\\)\4(\s+|$)))/i',' $1={} ',$str);
1801                         if(logData.numRows > 250) {
1802                             $('div#logTable button#startFilterQueryText').click(filterQueries);
1803                         } else {
1804                             $('div#logTable input#filterQueryText').keyup(filterQueries);
1805                         }
1807                     }
1809                     var dlgBtns = {};
1810                     dlgBtns[PMA_messages['strJumpToTable']] = function() {
1811                         $(this).dialog("close");
1812                         $(document).scrollTop($('div#logTable').offset().top);
1813                     };
1814                     
1815                     $('#emptyDialog').dialog( "option", "buttons", dlgBtns);
1816                     
1817                 } else {
1818                     $('#emptyDialog').html('<p>' + PMA_messages['strNoDataFound'] + '</p>');
1819                     
1820                     var dlgBtns = {};
1821                     dlgBtns[PMA_messages['strClose']] = function() { 
1822                         $(this).dialog("close"); 
1823                     };
1824                     
1825                     $('#emptyDialog').dialog( "option", "buttons", dlgBtns );
1826                 }
1827             }
1828         );
1830         function filterQueries(varFilterChange) {
1831             var odd_row=false, cell, textFilter;
1832             var val = $('div#logTable input#filterQueryText').val();
1834             if(val.length == 0) textFilter = null;
1835             else textFilter = new RegExp(val, 'i');
1836             
1837             var rowSum = 0, totalSum = 0, i=0, q;
1838             var noVars = $('div#logTable input#noWHEREData').attr('checked');
1839             var equalsFilter = /([^=]+)=(\d+|((\'|"|).*?[^\\])\4((\s+)|$))/gi;
1840             var functionFilter = /([a-z0-9_]+)\(.+?\)/gi;
1841             var filteredQueries = {};
1842             var filteredQueriesLines = {};
1843             var hide = false, rowData;
1844             var queryColumnName = runtime.logDataCols[runtime.logDataCols.length - 2];
1845             var sumColumnName = runtime.logDataCols[runtime.logDataCols.length - 1];
1846                 
1847             var isSlowLog = opts.src == 'slow';
1848             var columnSums = {};
1849                 
1850             var countRow = function(query, row) {
1851                 var cells = row.match(/<td>(.*?)<\/td>/gi);
1852                 if(!columnSums[query]) columnSums[query] = [0,0,0,0];
1854                 columnSums[query][0] += timeToSec(cells[2].replace(/(<td>|<\/td>)/gi,''));
1855                 columnSums[query][1] += timeToSec(cells[3].replace(/(<td>|<\/td>)/gi,''));
1856                 columnSums[query][2] += parseInt(cells[4].replace(/(<td>|<\/td>)/gi,''));
1857                 columnSums[query][3] += parseInt(cells[5].replace(/(<td>|<\/td>)/gi,''));
1858             };
1859             
1860             // We just assume the sql text is always in the second last column, and that the total count is right of it
1861             $('div#logTable table tbody tr td:nth-child(' + (runtime.logDataCols.length - 1) + ')').each(function() {
1862                 if(varFilterChange && $(this).html().match(/^SELECT/i)) {
1863                     if(noVars) {
1864                         q = $(this).text().replace(equalsFilter, '$1=...$6').trim();
1865                         q = q.replace(functionFilter, ' $1(...)');
1867                         // Js does not specify a limit on property name length, so we can abuse it as index :-)
1868                         if(filteredQueries[q]) {
1869                             filteredQueries[q] += parseInt($(this).next().text());
1870                             totalSum += parseInt($(this).next().text());
1871                             hide = true;
1872                         } else {
1873                             filteredQueries[q] = parseInt($(this).next().text());;
1874                             filteredQueriesLines[q] = i;
1875                             $(this).text(q);
1876                         }
1877                         if(isSlowLog) countRow(q, $(this).parent().html());
1878                         
1879                     // Restore original columns
1880                     } else {
1881                         rowData = $(this).parent().data('query');
1882                         
1883                         // SQL Text
1884                         $(this).text(rowData[queryColumnName]);
1885                         // #
1886                         $(this).next().text(rowData[sumColumnName]);
1887                         // Slow log columns
1888                         if(isSlowLog) {
1889                             $(this).parent().children('td:nth-child(3)').text(rowData['query_time']);
1890                             $(this).parent().children('td:nth-child(4)').text(rowData['lock_time']);
1891                             $(this).parent().children('td:nth-child(5)').text(rowData['rows_sent']);
1892                             $(this).parent().children('td:nth-child(6)').text(rowData['rows_examined']);
1893                         }
1894                     }
1895                 }
1897                 if(! hide && (textFilter != null && ! textFilter.exec($(this).text()))) hide = true;
1899                 if(hide) {
1900                     $(this).parent().css('display','none');
1901                 } else {
1902                     totalSum += parseInt($(this).next().text());
1903                     rowSum ++;
1905                     odd_row = ! odd_row;
1906                     $(this).parent().css('display','');
1907                     if(odd_row) {
1908                         $(this).parent().addClass('odd');
1909                         $(this).parent().removeClass('even');
1910                     } else {
1911                         $(this).parent().addClass('even');
1912                         $(this).parent().removeClass('odd');
1913                     }
1914                 }
1916                 hide = false;
1917                 i++;
1918             });
1919                        
1920             // Update count values of grouped entries
1921             if(varFilterChange) {
1922                 if(noVars) {
1923                     var numCol, row, $table = $('div#logTable table tbody');
1924                     $.each(filteredQueriesLines, function(key,value) {
1925                         if(filteredQueries[key] <= 1) return;
1926                         
1927                         row =  $table.children('tr:nth-child(' + (value+1) + ')');
1928                         numCol = row.children(':nth-child(' + (runtime.logDataCols.length) + ')');
1929                         numCol.text(filteredQueries[key]);
1930                         
1931                         if(isSlowLog) {
1932                             row.children('td:nth-child(3)').text(secToTime(columnSums[key][0]));
1933                             row.children('td:nth-child(4)').text(secToTime(columnSums[key][1]));
1934                             row.children('td:nth-child(5)').text(columnSums[key][2]);
1935                             row.children('td:nth-child(6)').text(columnSums[key][3]);
1936                         }
1937                     });
1938                 }
1939                 
1940                 $('div#logTable table').trigger("update"); 
1941                 setTimeout(function() {                    
1942                     $('div#logTable table').trigger('sorton',[[[runtime.logDataCols.length - 1,1]]]);
1943                 }, 0);
1944             }
1946             $('div#logTable table tfoot tr')
1947                 .html('<th colspan="' + (runtime.logDataCols.length - 1) + '">' +
1948                       PMA_messages['strSumRows'] + ' '+ rowSum +'<span style="float:right">' +
1949                       PMA_messages['strTotal'] + '</span></th><th align="right">' + totalSum + '</th>');
1950         }
1951     }
1953     /*loadLogStatistics({
1954         src: 'general',
1955         start:1311076210*1000,
1956         end:1311162689*1000,
1957         removeVariables: true,
1958         limitTypes: true
1959     });*/
1960     
1961     function timeToSec(timeStr) {
1962         var time = timeStr.split(':');
1963         return parseInt(time[0]*3600) + parseInt(time[1]*60) + parseInt(time[2]);
1964     }
1965     
1966     function secToTime(timeInt) {
1967         hours = Math.floor(timeInt / 3600);
1968         timeInt -= hours*3600;
1969         minutes = Math.floor(timeInt / 60);
1970         timeInt -= minutes*60;
1971         
1972         if(hours < 10) hours = '0' + hours;
1973         if(minutes < 10) minutes = '0' + minutes;
1974         if(timeInt < 10) timeInt = '0' + timeInt;
1975         
1976         return hours + ':' + minutes + ':' + timeInt;
1977     }
1978     
1979     function buildLogTable(data) {
1980         var rows = data.rows;
1981         var cols = new Array();
1982         var $table = $('<table border="0" class="sortable"></table>');
1983         var $tBody, $tRow, $tCell;
1985         $('#logTable').html($table);
1987         var formatValue = function(name, value) {
1988             switch(name) {
1989                 case 'user_host':
1990                     return value.replace(/(\[.*?\])+/g,'');
1991             }
1992             return value;
1993         };
1994         
1995         for(var i=0; i < rows.length; i++) {
1996             if(i == 0) {
1997                 $.each(rows[0],function(key, value) {
1998                     cols.push(key);
1999                 });
2000                 $table.append( '<thead>' +
2001                                '<tr><th class="nowrap">' + cols.join('</th><th class="nowrap">') + '</th></tr>' +
2002                                '</thead>');
2004                 $table.append($tBody = $('<tbody></tbody>'));
2005             }
2007             $tBody.append($tRow = $('<tr class="noclick"></tr>'));
2008             var cl=''
2009             for(var j=0; j < cols.length; j++) {
2010                 // Assuming the query column is the second last
2011                 if(j == cols.length - 2 && rows[i][cols[j]].match(/^SELECT/i)) {
2012                     $tRow.append($tCell=$('<td class="linkElem">' + formatValue(cols[j], rows[i][cols[j]]) + '</td>'));
2013                     $tCell.click(queryAnalyzer);
2014                 } else
2015                     $tRow.append('<td>' + formatValue(cols[j], rows[i][cols[j]]) + '</td>');
2018                 $tRow.data('query',rows[i]);
2019             }
2020         }
2022         $table.append('<tfoot>' +
2023                     '<tr><th colspan="' + (cols.length - 1) + '">' + PMA_messages['strSumRows'] +
2024                     ' '+ data.numRows +'<span style="float:right">' + PMA_messages['strTotal'] +
2025                     '</span></th><th align="right">' + data.sum.TOTAL + '</th></tr></tfoot>');
2028         function queryAnalyzer() {
2029             var query = $(this).parent().data('query').argument || $(this).parent().data('query').sql_text;
2030             var db = $(this).parent().data('query').db || '';
2032             /* A very basic SQL Formatter. Totally fails in the cases of
2033                - Any string appearance containing a MySQL Keyword, surrounded by whitespaces, e.g. WHERE bar = "This where the formatter fails"
2034                - Subqueries too probably
2035             */
2036             
2037             // Matches the columns to be selected
2038             // .* selector doesn't include whitespace and we have no PCRE_DOTALL modifier, (.|\s)+ crashes Chrome (reported and confirmed), 
2039             // [^]+ results in JS error in IE8, thus we use [^\0]+ for matching each column since the zero-byte char (hopefully) doesn't appear in column names ;)
2040             var sLists = query.match(/SELECT\s+[^\0]+\s+FROM\s+/gi);
2041             if(sLists) {
2042                 for(var i=0; i < sLists.length; i++) {
2043                     query = query.replace(sLists[i],sLists[i].replace(/\s*((`|'|"|).*?\1,)\s*/gi,'$1\n\t'));
2044                 }
2045                 query = query
2046                   .replace(/(\s+|^)(SELECT|FROM|WHERE|GROUP BY|HAVING|ORDER BY|LIMIT)(\s+|$)/gi,'\n$2\n\t')
2047                   .replace(/\s+UNION\s+/gi,'\n\nUNION\n\n')
2048                   .replace(/\s+(AND)\s+/gi,' $1\n\t')
2049                   .trim();
2050             }
2052             codemirror_editor.setValue(query);
2054             var profilingChart = null;
2056             $('div#queryAnalyzerDialog').dialog({
2057                 width: 'auto',
2058                 height: 'auto',
2059                 resizable: false,
2060                 buttons: {
2061                     'Analyse Query' : function() {
2062                         $('div#queryAnalyzerDialog div.placeHolder').html('Analyzing... ' + '<img class="ajaxIcon" src="' + pmaThemeImage + 'ajax_clock_small.gif" alt="">');
2064                         $.post('server_status.php?'+url_query, {
2065                             ajax_request: true,
2066                             query_analyzer: true,
2067                             query: codemirror_editor.getValue(),
2068                             database: db
2069                         }, function(data) {
2070                             data = $.parseJSON(data);
2071                             var totalTime = 0;
2073                             if(data.error) {
2074                                 $('div#queryAnalyzerDialog div.placeHolder').html('<div class="error">' + data.error + '</div>');
2075                                 return;
2076                             }
2078                             // Float sux, I'll use table :(
2079                             $('div#queryAnalyzerDialog div.placeHolder')
2080                                 .html('<table width="100%" border="0"><tr><td class="explain"></td><td class="chart"></td></tr></table>');
2081                             
2082                             var explain = '<b>Explain output</b> '+explain_docu;
2083                             if(data.explain.length > 1) {
2084                                 explain += ' (';
2085                                 for(var i=0; i < data.explain.length; i++) {
2086                                     if(i > 0) explain += ', ';
2087                                     explain += '<a href="#showExplain-' + i + '">' + i + '</a>';
2088                                 }
2089                                 explain += ')';
2090                             }
2091                             explain +='<p></p>';
2092                             for(var i=0; i < data.explain.length; i++) {
2093                                 explain += '<div class="explain-' + i + '"' + (i>0? 'style="display:none;"' : '' ) + '>';
2094                                 $.each(data.explain[i], function(key,value) {
2095                                     value = (value==null)?'null':value;
2096                                     
2097                                     if(key == 'type' && value.toLowerCase() == 'all') value = '<span class="attention">' + value +'</span>';
2098                                     if(key == 'Extra') value = value.replace(/(using (temporary|filesort))/gi,'<span class="attention">$1</span>');
2099                                     explain += key+': ' + value + '<br />';
2100                                 });
2101                                 explain += '</div>';
2102                             }
2103                             
2104                             // Since there is such a nice free space below the explain, lets put it here for now
2105                             explain += '<p><b>' + PMA_messages['strAffectedRows'] + '</b> ' + data.affectedRows;
2107                             $('div#queryAnalyzerDialog div.placeHolder td.explain').append(explain);
2108                             
2109                             $('div#queryAnalyzerDialog div.placeHolder a[href*="#showExplain"]').click(function() {
2110                                 var id = $(this).attr('href').split('-')[1];
2111                                 $(this).parent().find('div[class*="explain"]').hide();
2112                                 $(this).parent().find('div[class*="explain-' + id + '"]').show();
2113                             });
2114                             
2115                             if(data.profiling) {
2116                                 var chartData = [];
2117                                 var numberTable = '<table class="queryNums"><thead><tr><th>Status</th><th>Time</th></tr></thead><tbody>';
2118                                 var duration;
2120                                 for(var i=0; i < data.profiling.length; i++) {
2121                                     duration = parseFloat(data.profiling[i].duration);
2123                                     chartData.push([data.profiling[i].state, duration]);
2124                                     totalTime+=duration;
2126                                     numberTable += '<tr><td>' + data.profiling[i].state + ' </td><td> ' + PMA_prettyProfilingNum(duration,2) + '</td></tr>';
2127                                 }
2128                                 numberTable += '<tr><td><b>Total time:</b></td><td>' + PMA_prettyProfilingNum(totalTime,2) + '</td></tr>';
2129                                 numberTable += '</tbody></table>';
2130                                 
2131                                 $('div#queryAnalyzerDialog div.placeHolder td.chart').append('<b>Profiling results ' + profiling_docu + '</b> (<a href="#showNums">Table</a>, <a href="#showChart">Chart</a>)<br/>' + numberTable + ' <div id="queryProfiling"></div>');
2132                                 
2133                                 $('div#queryAnalyzerDialog div.placeHolder a[href="#showNums"]').click(function() {
2134                                     $('div#queryAnalyzerDialog div#queryProfiling').hide();
2135                                     $('div#queryAnalyzerDialog table.queryNums').show();
2136                                     return false;
2137                                 });
2139                                 $('div#queryAnalyzerDialog div.placeHolder a[href="#showChart"]').click(function() {
2140                                     $('div#queryAnalyzerDialog div#queryProfiling').show();
2141                                     $('div#queryAnalyzerDialog table.queryNums').hide();
2142                                     return false;
2143                                 });
2145                                 profilingChart = PMA_createProfilingChart(chartData, {
2146                                     chart: {
2147                                         renderTo: 'queryProfiling'
2148                                     },
2149                                     plotOptions: {
2150                                         pie: {
2151                                             size: '50%'
2152                                         }
2153                                     }
2154                                 });
2157                                 $('div#queryProfiling').resizable();
2158                             }
2160                         });
2161                     },
2162                     'Close' : function() {
2163                         if(profilingChart != null) {
2164                             profilingChart.destroy();
2165                         }
2166                         $('div#queryAnalyzerDialog div.placeHolder').html('');
2167                         codemirror_editor.setValue('');
2169                         $(this).dialog("close");
2170                     }
2171                 }
2172             });
2173         }
2175         // Append a tooltip to the count column, if there exist one
2176         if($('#logTable th:last').html() == '#') {
2177             $('#logTable th:last').append('&nbsp;<img class="qroupedQueryInfoIcon icon ic_b_docs" src="themes/dot.gif" alt="" />');
2179             var qtipContent = PMA_messages['strCountColumnExplanation'];
2180             if(groupInserts) qtipContent += '<p>' + PMA_messages['strMoreCountColumnExplanation'] + '</p>';
2182             $('img.qroupedQueryInfoIcon').qtip({
2183                 content: qtipContent,
2184                 position: {
2185                     corner: {
2186                         target: 'bottomMiddle',
2187                         tooltip: 'topRight'
2188                     }
2190                 },
2191                 hide: { delay: 1000 }
2192             })
2193         }
2195         $('div#logTable table').tablesorter({
2196             sortList: [[cols.length - 1,1]],
2197             widgets: ['zebra']
2198         });
2200         $('div#logTable table thead th')
2201             .append('<img class="icon sortableIcon" src="themes/dot.gif" alt="">');
2203         return cols;
2204     }
2206     function saveMonitor() {
2207         var gridCopy = {};
2209         $.each(runtime.charts, function(key, elem) {
2210             gridCopy[key] = {};
2211             gridCopy[key].nodes = elem.nodes;
2212             gridCopy[key].settings = elem.settings;
2213             gridCopy[key].title = elem.title;
2214         });
2216         if(window.localStorage) {
2217             window.localStorage['monitorCharts'] = $.toJSON(gridCopy);
2218             window.localStorage['monitorSettings'] = $.toJSON(monitorSettings);
2219         }
2221         $('a[href="#clearMonitorConfig"]').show();
2222     }
2224     function serverResponseError() {
2225         var btns = {};
2226         btns[PMA_messages['strReloadPage']] = function() {
2227             window.location.reload();
2228         };
2229         $('#emptyDialog').attr('title',PMA_messages['strRefreshFailed']);
2230         $('#emptyDialog').html('<img class="icon ic_s_attention" src="themes/dot.gif" alt=""> ' + PMA_messages['strInvalidResponseExplanation'])
2231         $('#emptyDialog').dialog({ buttons: btns });       
2232     }
2233