1 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 * @fileoverview functions used in server status pages
8 * @requires jQueryCookie
9 * @requires jQueryTablesorter
10 * @requires Highcharts
12 * @requires js/functions.js
16 // Add a tablesorter parser to properly handle thousands seperated numbers and SI prefixes
18 jQuery.tablesorter.addParser({
21 return /^[0-9]?[0-9,\.]*\s?(k|M|G|T|%)?$/.test(s);
24 var num = jQuery.tablesorter.formatFloat(
25 s.replace(PMA_messages['strThousandsSeperator'],'')
26 .replace(PMA_messages['strDecimalSeperator'],'.')
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;
39 return num * Math.pow(10,factor);
46 $('a[rel="popupLink"]').click( function() {
49 $('.' + $link.attr('href').substr(1))
51 .offset({ top: $link.offset().top + $link.height() + 5, left: $link.offset().left })
52 .addClass('openedPopup');
57 $(document).click( function(event) {
58 $('.openedPopup').each(function() {
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');
70 // Filters for status variables
72 var alertFilter = false;
73 var categoryFilter='';
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') {
103 .hide(300,function() {
104 $(this).data('shown',false);
109 $(document).mousemove(function(e) {
110 if($tableSortHint.data('shown') == true)
118 // Tell highcarts not to use UTC dates (global setting)
119 Highcharts.setOptions({
130 $('#serverStatusTabs').tabs({
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(); }
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';
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,
166 chart_activeTimeouts[chart.options.chart.renderTo] = setTimeout(
167 chart.options.realtime.timeoutCallBack,
168 chart.options.realtime.refreshRate
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');
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();
186 tabStatus[tab.attr('id')]='data';
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') {
203 { name: PMA_messages['strChartKBSent'], data: [] },
204 { name: PMA_messages['strChartKBReceived'], data: [] }
206 title: { text: PMA_messages['strChartServerTraffic'] },
207 realtime: { url:'server_status.php?' + url_query,
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 },
214 numLoadedPoints >= chartObj.options.realtime.numMaxPoints
216 chartObj.series[1].addPoint(
217 { x: curVal.x, y: (curVal.y_received - lastVal.y_received) / 1024 },
219 numLoadedPoints >= chartObj.options.realtime.numMaxPoints
222 error: function() { serverResponseError(); }
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';
231 $(this).html(PMA_messages['strLiveTrafficChart']);
232 setupLiveChart($tab,this,null);
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') {
246 { name: PMA_messages['strChartConnections'], data: [] },
247 { name: PMA_messages['strChartProcesses'], data: [] }
249 title: { text: PMA_messages['strChartConnectionsTitle'] },
250 realtime: { url:'server_status.php?'+url_query,
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 },
257 numLoadedPoints >= chartObj.options.realtime.numMaxPoints
259 chartObj.series[1].addPoint(
260 { x: curVal.x, y: curVal.y_proc },
262 numLoadedPoints >= chartObj.options.realtime.numMaxPoints
265 error: function() { serverResponseError(); }
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';
274 $(this).html(PMA_messages['strLiveConnChart']);
275 setupLiveChart($tab,this,null);
281 // Live query statistics
282 $('.buttonlinks a.livequeriesLink').click(function() {
283 var $tab = $(this).parents('div.ui-tabs-panel');
286 if(tabStatus[$tab.attr('id')] == 'static') {
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,
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) },
298 numLoadedPoints >= chartObj.options.realtime.numMaxPoints
301 error: function() { serverResponseError(); }
305 $(this).html(PMA_messages['strLiveQueryChart']);
308 setupLiveChart($tab,this,settings);
309 tabStatus[$tab.attr('id')] = 'livequeries';
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;
324 if(! settings.chart) settings.chart = {};
325 settings.chart.renderTo = $tab.attr('id') + "_chart_cnt";
327 $tab.find('.tabInnerContent')
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();
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();
347 /* 3 Filtering functions */
348 $('#filterAlert').change(function() {
349 alertFilter = this.checked;
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');
364 $('#filterCategory').change(function() {
365 categoryFilter = $(this).val();
369 $('input#dontFormat').change(function() {
370 $('#serverstatusvariables td.value span.original').toggle(this.checked);
371 $('#serverstatusvariables td.value span.formatted').toggle(! this.checked);
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();
381 case 'statustabs_queries':
383 queryPieChart.destroy();
384 tab.find('.tabInnerContent').html(data);
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)]);
393 queryPieChart = PMA_createChart({
395 renderTo: 'serverstatusquerieschart'
403 name: PMA_messages['strChartQueryPie'],
408 allowPointSelect: true,
412 formatter: function() {
413 return '<b>'+ this.point.name +'</b><br/> ' + Highcharts.numberFormat(this.percentage, 2) + ' %';
419 formatter: function() {
420 return '<b>' + this.point.name + '</b><br/>' + Highcharts.numberFormat(this.y, 2) + '<br/>(' + Highcharts.numberFormat(this.percentage, 2) + ' %)';
426 case 'statustabs_allvars':
428 tab.find('.tabInnerContent').html(data);
434 initTableSorter(tab.attr('id'));
437 function initTableSorter(tabid) {
439 case 'statustabs_queries':
440 $('#serverstatusqueriesdetails').tablesorter({
444 1: { sorter: 'fancyNumber' },
445 2: { sorter: 'fancyNumber' }
449 $('#serverstatusqueriesdetails tr:first th')
450 .append('<img class="icon sortableIcon" src="themes/dot.gif" alt="">');
454 case 'statustabs_allvars':
455 $('#serverstatusvariables').tablesorter({
459 1: { sorter: 'fancyNumber' }
463 $('#serverstatusvariables tr:first th')
464 .append('<img class="icon sortableIcon" src="themes/dot.gif" alt="">');
470 /* Filters the status variables by name/category/alert in the variables tab */
471 function filterVariables() {
472 var useful_links = 0;
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) {
481 $(this).css('display','');
483 $(this).css('display','none');
491 $('#linkSuggestions').css('display','');
492 else $('#linkSuggestions').css('display','none');
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))) {
500 $(this).parent().css('display','');
502 $(this).parent().addClass('odd');
503 $(this).parent().removeClass('even');
505 $(this).parent().addClass('even');
506 $(this).parent().removeClass('odd');
509 $(this).parent().css('display','none');
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();
522 // Separate keys and values, then sort them
523 $.each(queries.pointInfo, function(key,value) {
524 if(value-lastQueries.pointInfo[key] > 0) {
526 queryValues.push(value-lastQueries.pointInfo[key]);
527 sumTotal += value-lastQueries.pointInfo[key];
530 var numQueries = queryKeys.length;
531 var pointInfo = '<b>' + PMA_messages['strTotal'] + ': ' + sumTotal + '</b><br>';
533 while(queryKeys.length > 0) {
535 for(var i=0; i < queryKeys.length; i++) {
536 if(queryValues[i] > max) {
537 max = queryValues[i];
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);
551 pointInfo += PMA_messages['strOther'] + ': ' + sumOther;
556 /**** Server config advisor ****/
558 $('a[href="#openAdvisorInstructions"]').click(function() {
559 $('#advisorInstructionsDialog').dialog();
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="">');
566 $.get('server_status.php?'+url_query, { ajax_request: true, advisor: true },function(data) {
567 var $tbody, $tr, str, even = true;
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>'));
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>'
590 $('div#emptyDialog').dialog({
593 'Close' : function() {
594 $(this).dialog('close');
607 /**** Monitor charting implementation ****/
608 /* Saves the previous ajax response for differential values */
609 var oldChartData = null;
610 // Holds about to created chart
614 // Runtime parameter of the monitor, is being fully set in initGrid()
616 // Holds all visible charts in the grid
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
624 // To play/pause the monitor
626 // Object that contains a list of nodes that need to be retrieved from the server for chart updates
628 // Current max points per chart (needed for auto calculation)
630 // displayed time frame
635 var monitorSettings = null;
637 var defaultMonitorSettings = {
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 */
646 // Allows drag and drop rearrange and print/edit icons on charts
647 var editMode = false;
651 title: PMA_messages['strSystemCPUUsage'],
652 nodes: [{ dataType: 'cpu', name: PMA_messages['strAverageLoad'], dataPoint: 'loadavg', unit: '%'}]
655 title: PMA_messages['strSystemMemory'],
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'] },
662 title: PMA_messages['strSystemSwap'],
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'] },
669 title: PMA_messages['strSystemCPUUsage'],
672 name: PMA_messages['strAverageLoad'],
674 transformFn: 'cpu-linux'
679 title: PMA_messages['strSystemMemory'],
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'] },
699 title: PMA_messages['strSystemSwap'],
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'] },
721 'c0': { title: PMA_messages['strQuestions'],
722 nodes: [{ dataType: 'statusvar', name: PMA_messages['strQuestions'], dataPoint: 'Questions', display: 'differential' }]
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'} ]
730 title: PMA_messages['strTraffic'],
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'] }
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];
748 symbol: 'url(' + pmaThemeImage + 's_cog.png)',
750 symbolFill: '#B5C9DF',
751 hoverSymbolFill: '#779ABF',
752 _titleKey: 'settings',
753 menuName: 'gridsettings',
755 textKey: 'editChart',
756 onclick: function() {
760 textKey: 'removeChart',
761 onclick: function() {
768 Highcharts.setOptions({
770 settings: PMA_messages['strSettings'],
771 removeChart: PMA_messages['strRemoveChart'],
772 editChart: PMA_messages['strEditChart']
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);
786 // Close the settings popup
787 $('#statustabs_charting .popupContent').hide().removeClass('openedPopup');
789 $("#chartGrid").sortableTable({
792 left: chartSize().width - 63,
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)
810 if(dropRender && value.chart.options.chart.renderTo == dropRender)
814 // Case 1: drag and drop are charts -> Switch keys
817 dragChart = runtime.charts[dragKey];
818 runtime.charts[dragKey] = runtime.charts[dropKey];
819 runtime.charts[dropKey] = dragChart;
821 // Case 2: drop is a empty cell => just completely rebuild the ids
823 var dropKeyNum = parseInt(dropKey.substr(1));
824 var insertBefore = pos.col + pos.row * monitorSettings.columns;
826 var newChartList = {};
829 $.each(runtime.charts, function(key, value) {
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
842 newChartList['c' + (c++)] = runtime.charts[keys[i]];
845 // Not inserted => put at the end
846 if(insertBefore != -1)
847 newChartList['c' + (c++)] = runtime.charts[dropKey];
849 runtime.charts = newChartList;
859 $("#chartGrid").sortableTable('destroy');
860 saveMonitor(); // Save 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 */
877 var $tr = $('table#chartGrid tr:first');
879 while($tr.length != 0) {
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));
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'));
906 /* Apply new chart size to all charts */
907 $.each(runtime.charts, function(key, value) {
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;
922 $("#chartGrid").sortableTable('refresh');
924 saveMonitor(); // Save settings
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);
941 runtime.refreshTimeout = setTimeout(refreshChartGrid, monitorSettings.gridRefresh);
943 saveMonitor(); // Save settings
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];
955 if(! newChart || ! newChart.nodes || newChart.nodes.length == 0) {
956 alert(PMA_messages['strAddOneSeriesWarning']);
961 newChart.title = $('input[name="chartTitle"]').attr('value');
962 // Add a cloned object to the chart grid
963 addChart($.extend(true, {}, newChart));
967 saveMonitor(); // Save settings
969 $(this).dialog("close");
972 dlgButtons[PMA_messages['strClose']] = function() {
974 $('span#clearSeriesLink').hide();
975 $('#seriesPreview').html('');
976 $(this).dialog("close");
979 $('div#addChartDialog').dialog({
985 $('div#addChartDialog #seriesPreview').html('<i>' + PMA_messages['strNone'] + '</i>');
990 $('a[href="#exportMonitorConfig"]').click(function() {
993 $.each(runtime.charts, function(key, elem) {
995 gridCopy[key].nodes = elem.nodes;
996 gridCopy[key].settings = elem.settings;
997 gridCopy[key].title = elem.title;
1001 monitorCharts: gridCopy,
1002 monitorSettings: monitorSettings
1006 $('body').append($form = $('<form method="post" action="file_echo.php?'+url_query+'&filename=1" style="display:none;"></form>'));
1008 $form.append('<input type="hidden" name="monitorconfig" value="' + encodeURI($.toJSON(exportData)) + '">');
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>');
1020 dlgBtns[PMA_messages['strImport']] = function() {
1022 $('body').append($iframe = $('<iframe id="monitorConfigUpload" style="display:none;"></iframe>'));
1023 var d = $iframe[0].contentWindow.document;
1024 d.open(); d.close();
1027 $iframe.load(function() {
1030 // Try loading config
1032 json = $.secureEvalJSON($('body',$('iframe#monitorConfigUpload')[0].contentWindow.document).html());
1034 alert(PMA_messages['strFailedParsingConfig']);
1035 $('div#emptyDialog').dialog('close');
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');
1046 // If json ok, try applying config
1048 window.localStorage['monitorCharts'] = $.toJSON(json.monitorCharts);
1049 window.localStorage['monitorSettings'] = $.toJSON(json.monitorSettings);
1052 alert(PMA_messages['strFailedBuildingGrid']);
1053 // If an exception is thrown, load default again
1054 window.localStorage.removeItem('monitorCharts');
1055 window.localStorage.removeItem('monitorSettings');
1059 $('div#emptyDialog').dialog('close');
1062 $("body", d).append($form=$('div#emptyDialog').find('form'));
1064 $('div#emptyDialog').append('<img class="ajaxIcon" src="' + pmaThemeImage + 'ajax_clock_small.gif" alt="">');
1067 dlgBtns[PMA_messages['strCancel']] = function() {
1068 $(this).dialog('close');
1072 $('div#emptyDialog').dialog({
1079 $('a[href="#clearMonitorConfig"]').click(function() {
1080 window.localStorage.removeItem('monitorCharts');
1081 window.localStorage.removeItem('monitorSettings');
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']);
1091 $(this).html('<img src="themes/dot.gif" class="icon ic_pause" alt="" /> ' + PMA_messages['strPauseMonitor']);
1092 if(! runtime.charts) {
1094 $('a[href="#settingsPopup"]').show();
1100 $('a[href="#monitorInstructionsDialog"]').click(function() {
1101 var $dialog = $('div#monitorInstructionsDialog');
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,
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'];
1121 msg = PMA_messages['strGenLogOn'];
1124 if(msg.length == 0 && logVars['slow_query_log'] == 'ON') {
1125 msg = PMA_messages['strSlowLogOn'];
1128 if(msg.length == 0) {
1129 icon = 'ic_s_error';
1130 msg = PMA_messages['strBothLogOff'];
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 />';
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'])
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'])
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)
1167 if(logVars['general_log'] != 'ON')
1168 str += '- <a class="set" href="#general_log-ON">'
1169 + $.sprintf(PMA_messages['strEnableVar'], 'general_log')
1172 str += '- <a class="set" href="#general_log-OFF">'
1173 + $.sprintf(PMA_messages['strDisableVar'], 'general_log')
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')
1181 str += '- <a class="set" href="#slow_query_log-OFF">'
1182 + $.sprintf(PMA_messages['strDisableVar'], 'slow_query_log')
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)
1194 str += PMA_messages['strNoSuperUser'] + '<br/>';
1198 $dialog.find('div.monitorUse').toggle(
1199 logVars['log_output'] == 'TABLE' && (logVars['slow_query_log'] == 'ON' || logVars['general_log'] == 'ON')
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();
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());
1229 $('input[name="useDivisor"]').change(function() {
1230 $('span.divisorInput').toggle(this.checked);
1232 $('input[name="useUnit"]').change(function() {
1233 $('span.unitInput').toggle(this.checked);
1236 $('select[name="varChartList"]').change(function () {
1237 if(this.selectedIndex!=0)
1238 $('#variableInput').attr('value',this.value);
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);
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);
1257 $('a[href="#submitClearSeries"]').click(function() {
1258 $('#seriesPreview').html('<i>' + PMA_messages['strNone'] + '</i>');
1260 $('span#clearSeriesLink').hide();
1263 $('a[href="#submitAddSeries"]').click(function() {
1264 if($('input#variableInput').attr('value').length == 0) return false;
1266 if(newChart == null) {
1267 $('#seriesPreview').html('');
1270 title: $('input[name="chartTitle"]').attr('value'),
1276 dataType:'statusvar',
1277 dataPoint: $('input#variableInput').attr('value'),
1278 name: $('input#variableInput').attr('value'),
1279 display: $('input[name="differentialValue"]').attr('checked') ? 'differential' : ''
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();
1312 $("input#variableInput").autocomplete({
1313 source: variableNames
1317 function initGrid() {
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);
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);
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>');
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
1353 $('table#chartGrid').html('');
1355 /* Add all charts - in correct order */
1357 $.each(runtime.charts, function(key, value) {
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>');
1371 // Empty cells should keep their size so you can drop onto them
1372 $('table#chartGrid tr td').css('width',chartSize().width + 'px');
1374 buildRequiredDataList();
1378 function destroyGrid() {
1380 $.each(runtime.charts, function(key, value) {
1382 value.chart.destroy();
1386 runtime.refreshRequest.abort();
1389 clearTimeout(runtime.refreshTimeout);
1392 $('table#chartGrid').html('');
1394 runtime.charts = null;
1395 runtime.chartAI = 0;
1396 monitorSettings = null;
1399 function rebuildGrid() {
1401 if(runtime.charts) {
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]);
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]);
1425 function chartSize() {
1426 var wdt = $('div#logTable').innerWidth() / monitorSettings.columns - (monitorSettings.columns - 1) * chartSpacing.width;
1433 function addChart(chartObj, initialize) {
1435 for(var j=0; j<chartObj.nodes.length; j++)
1436 series.push(chartObj.nodes[j]);
1440 renderTo: 'gridchart' + runtime.chartAI,
1441 width: chartSize().width,
1442 height: chartSize().height,
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)));
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;
1468 removeVariables: $('input#removeVariables').prop('checked'),
1469 limitTypes: $('input#limitTypes').prop('checked')
1472 $('#logAnalyseDialog').find('dateStart,dateEnd').datepicker('destroy');
1474 $(this).dialog("close");
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;
1485 removeVariables: $('input#removeVariables').prop('checked'),
1486 limitTypes: $('input#limitTypes').prop('checked')
1489 $('#logAnalyseDialog').find('dateStart,dateEnd').datepicker('destroy');
1491 $(this).dialog("close");
1494 $('#logAnalyseDialog').dialog({
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 || '');
1531 buttons: gridbuttons,
1532 title: { text: chartObj.title }
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>');
1547 chartObj.chart = PMA_createChart(settings);
1548 chartObj.numPoints = 0;
1550 if(initialize != true) {
1551 runtime.charts['c'+runtime.chartAI] = chartObj;
1552 buildRequiredDataList();
1555 // Edit,Print icon only in edit mode
1556 $('table#chartGrid div svg').find('*[zIndex=20], *[zIndex=21], *[zIndex=19]').toggle(editMode)
1561 function editChart(chartObj) {
1562 var htmlnode = chartObj.options.chart.renderTo;
1563 if(! htmlnode ) return;
1567 $.each(runtime.charts, function(key, value) {
1568 if(value.chart.options.chart.renderTo == htmlnode) {
1575 if(chart == null) return;
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>';
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 });
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');
1594 $(this).dialog('close');
1597 dlgBtns['Cancel'] = function() {
1598 $(this).dialog('close');
1601 $('div#emptyDialog').attr('title','Edit chart');
1602 $('div#emptyDialog').html(htmlStr+'</ol>');
1603 $('div#emptyDialog').dialog({
1610 function removeChart(chartObj) {
1611 var htmlnode = chartObj.options.chart.renderTo;
1612 if(! htmlnode ) return;
1614 $.each(runtime.charts, function(key, value) {
1615 if(value.chart.options.chart.renderTo == htmlnode) {
1616 delete runtime.charts[key];
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() {
1627 $('div#' + htmlnode).remove();
1630 saveMonitor(); // Save settings
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) {
1638 chartData = $.parseJSON(data);
1640 return serverResponseError();
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;
1651 for(var j=0; j < elem.nodes.length; j++) {
1652 value = chartData[key][j].y;
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;
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;
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,
1676 (oldChartData == null ? null : oldChartData[key][j])
1680 if(value != undefined)
1681 elem.chart.series[j].addPoint(
1682 { x: chartData.x, y: value },
1684 elem.numPoints >= runtime.gridMaxPoints
1690 runtime.charts[orderKey].numPoints++;
1691 if(runtime.redrawCharts)
1692 elem.chart.redraw();
1695 oldChartData = chartData;
1697 runtime.refreshTimeout = setTimeout(refreshChartGrid, monitorSettings.gridRefresh);
1701 function chartValueTransform(name,cur,prev) {
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;
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
1717 $.each(runtime.charts, function(key, chart) {
1718 runtime.dataList[chartID] = chart.nodes;
1719 runtime.charts[key].chartID = chartID;
1724 function loadLogStatistics(opts) {
1726 var logRequest = null;
1728 if(! opts.removeVariables)
1729 opts.removeVariables = false;
1730 if(! opts.limitTypes)
1731 opts.limitTypes = false;
1733 $('#emptyDialog').html(PMA_messages['strAnalysingLogs'] + ' <img class="ajaxIcon" src="' + pmaThemeImage + 'ajax_clock_small.gif" alt="">');
1735 $('#emptyDialog').dialog({
1739 'Cancel request': function() {
1740 if(logRequest != null)
1743 $(this).dialog("close");
1749 logRequest = $.get('server_status.php?'+url_query,
1750 { ajax_request: true,
1753 time_start: Math.round(opts.start / 1000),
1754 time_end: Math.round(opts.end / 1000),
1755 removeVariables: opts.removeVariables,
1756 limitTypes: opts.limitTypes
1761 logData = $.parseJSON(data);
1763 return serverResponseError();
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/>');
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;" />' +
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>' +
1795 $('div#logTable input#noWHEREData').change(function() {
1796 filterQueries(true);
1799 //preg_replace('/\s+([^=]+)=(\d+|((\'|"|)(?U)(.+)(?<!\\\)\4(\s+|$)))/i',' $1={} ',$str);
1801 if(logData.numRows > 250) {
1802 $('div#logTable button#startFilterQueryText').click(filterQueries);
1804 $('div#logTable input#filterQueryText').keyup(filterQueries);
1810 dlgBtns[PMA_messages['strJumpToTable']] = function() {
1811 $(this).dialog("close");
1812 $(document).scrollTop($('div#logTable').offset().top);
1815 $('#emptyDialog').dialog( "option", "buttons", dlgBtns);
1818 $('#emptyDialog').html('<p>' + PMA_messages['strNoDataFound'] + '</p>');
1821 dlgBtns[PMA_messages['strClose']] = function() {
1822 $(this).dialog("close");
1825 $('#emptyDialog').dialog( "option", "buttons", dlgBtns );
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');
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];
1847 var isSlowLog = opts.src == 'slow';
1848 var columnSums = {};
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,''));
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)) {
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());
1873 filteredQueries[q] = parseInt($(this).next().text());;
1874 filteredQueriesLines[q] = i;
1877 if(isSlowLog) countRow(q, $(this).parent().html());
1879 // Restore original columns
1881 rowData = $(this).parent().data('query');
1884 $(this).text(rowData[queryColumnName]);
1886 $(this).next().text(rowData[sumColumnName]);
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']);
1897 if(! hide && (textFilter != null && ! textFilter.exec($(this).text()))) hide = true;
1900 $(this).parent().css('display','none');
1902 totalSum += parseInt($(this).next().text());
1905 odd_row = ! odd_row;
1906 $(this).parent().css('display','');
1908 $(this).parent().addClass('odd');
1909 $(this).parent().removeClass('even');
1911 $(this).parent().addClass('even');
1912 $(this).parent().removeClass('odd');
1920 // Update count values of grouped entries
1921 if(varFilterChange) {
1923 var numCol, row, $table = $('div#logTable table tbody');
1924 $.each(filteredQueriesLines, function(key,value) {
1925 if(filteredQueries[key] <= 1) return;
1927 row = $table.children('tr:nth-child(' + (value+1) + ')');
1928 numCol = row.children(':nth-child(' + (runtime.logDataCols.length) + ')');
1929 numCol.text(filteredQueries[key]);
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]);
1940 $('div#logTable table').trigger("update");
1941 setTimeout(function() {
1942 $('div#logTable table').trigger('sorton',[[[runtime.logDataCols.length - 1,1]]]);
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>');
1953 /*loadLogStatistics({
1955 start:1311076210*1000,
1956 end:1311162689*1000,
1957 removeVariables: true,
1961 function timeToSec(timeStr) {
1962 var time = timeStr.split(':');
1963 return parseInt(time[0]*3600) + parseInt(time[1]*60) + parseInt(time[2]);
1966 function secToTime(timeInt) {
1967 hours = Math.floor(timeInt / 3600);
1968 timeInt -= hours*3600;
1969 minutes = Math.floor(timeInt / 60);
1970 timeInt -= minutes*60;
1972 if(hours < 10) hours = '0' + hours;
1973 if(minutes < 10) minutes = '0' + minutes;
1974 if(timeInt < 10) timeInt = '0' + timeInt;
1976 return hours + ':' + minutes + ':' + timeInt;
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) {
1990 return value.replace(/(\[.*?\])+/g,'');
1995 for(var i=0; i < rows.length; i++) {
1997 $.each(rows[0],function(key, value) {
2000 $table.append( '<thead>' +
2001 '<tr><th class="nowrap">' + cols.join('</th><th class="nowrap">') + '</th></tr>' +
2004 $table.append($tBody = $('<tbody></tbody>'));
2007 $tBody.append($tRow = $('<tr class="noclick"></tr>'));
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);
2015 $tRow.append('<td>' + formatValue(cols[j], rows[i][cols[j]]) + '</td>');
2018 $tRow.data('query',rows[i]);
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
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);
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'));
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')
2052 codemirror_editor.setValue(query);
2054 var profilingChart = null;
2056 $('div#queryAnalyzerDialog').dialog({
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, {
2066 query_analyzer: true,
2067 query: codemirror_editor.getValue(),
2070 data = $.parseJSON(data);
2074 $('div#queryAnalyzerDialog div.placeHolder').html('<div class="error">' + data.error + '</div>');
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>');
2082 var explain = '<b>Explain output</b> '+explain_docu;
2083 if(data.explain.length > 1) {
2085 for(var i=0; i < data.explain.length; i++) {
2086 if(i > 0) explain += ', ';
2087 explain += '<a href="#showExplain-' + i + '">' + i + '</a>';
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;
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 />';
2101 explain += '</div>';
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);
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();
2115 if(data.profiling) {
2117 var numberTable = '<table class="queryNums"><thead><tr><th>Status</th><th>Time</th></tr></thead><tbody>';
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>';
2128 numberTable += '<tr><td><b>Total time:</b></td><td>' + PMA_prettyProfilingNum(totalTime,2) + '</td></tr>';
2129 numberTable += '</tbody></table>';
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>');
2133 $('div#queryAnalyzerDialog div.placeHolder a[href="#showNums"]').click(function() {
2134 $('div#queryAnalyzerDialog div#queryProfiling').hide();
2135 $('div#queryAnalyzerDialog table.queryNums').show();
2139 $('div#queryAnalyzerDialog div.placeHolder a[href="#showChart"]').click(function() {
2140 $('div#queryAnalyzerDialog div#queryProfiling').show();
2141 $('div#queryAnalyzerDialog table.queryNums').hide();
2145 profilingChart = PMA_createProfilingChart(chartData, {
2147 renderTo: 'queryProfiling'
2157 $('div#queryProfiling').resizable();
2162 'Close' : function() {
2163 if(profilingChart != null) {
2164 profilingChart.destroy();
2166 $('div#queryAnalyzerDialog div.placeHolder').html('');
2167 codemirror_editor.setValue('');
2169 $(this).dialog("close");
2175 // Append a tooltip to the count column, if there exist one
2176 if($('#logTable th:last').html() == '#') {
2177 $('#logTable th:last').append(' <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,
2186 target: 'bottomMiddle',
2191 hide: { delay: 1000 }
2195 $('div#logTable table').tablesorter({
2196 sortList: [[cols.length - 1,1]],
2200 $('div#logTable table thead th')
2201 .append('<img class="icon sortableIcon" src="themes/dot.gif" alt="">');
2206 function saveMonitor() {
2209 $.each(runtime.charts, function(key, elem) {
2211 gridCopy[key].nodes = elem.nodes;
2212 gridCopy[key].settings = elem.settings;
2213 gridCopy[key].title = elem.title;
2216 if(window.localStorage) {
2217 window.localStorage['monitorCharts'] = $.toJSON(gridCopy);
2218 window.localStorage['monitorSettings'] = $.toJSON(monitorSettings);
2221 $('a[href="#clearMonitorConfig"]').show();
2224 function serverResponseError() {
2226 btns[PMA_messages['strReloadPage']] = function() {
2227 window.location.reload();
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 });