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
64 || event.pageY < pos.top
65 || event.pageX > pos.left + $cnt.outerWidth()
66 || event.pageY > pos.top + $cnt.outerHeight()
68 $cnt.hide().removeClass('openedPopup');
75 // Filters for status variables
76 var textFilter = null;
77 var alertFilter = false;
78 var categoryFilter = '';
80 var text = ''; // Holds filter text
81 var queryPieChart = null;
82 var monitorLoaded = false;
84 /* Chart configuration */
85 // Defines what the tabs are currently displaying (realtime or data)
86 var tabStatus = new Object();
87 // Holds the current chart instances for each tab
88 var tabChart = new Object();
90 /*** Table sort tooltip ***/
91 PMA_createqTip($('table.sortable thead th'), PMA_messages['strSortHint']);
93 // Tell highcarts not to use UTC dates (global setting)
94 Highcharts.setOptions({
105 $('#serverStatusTabs').tabs({
107 cookie: { name: 'pma_serverStatusTabs', expires: 1 },
108 show: function(event, ui) {
109 // Fixes line break in the menu bar when the page overflows and scrollbar appears
111 // Load Server status monitor
112 if (ui.tab.hash == '#statustabs_charting' && ! monitorLoaded) {
113 $('div#statustabs_charting').append(
114 '<img class="ajaxIcon" id="loadingMonitorIcon" src="' +
115 pmaThemeImage + 'ajax_clock_small.gif" alt="">'
117 // Delay loading a bit so the tab loads and the user gets to see a ajax loading icon
118 setTimeout(function() {
119 loadJavascript(['js/jquery/timepicker.js', 'js/jquery/jquery.json-2.2.js',
120 'js/jquery/jquery.sprintf.js', 'js/jquery/jquery.sortableTable.js',
121 'js/codemirror/lib/codemirror.js', 'js/codemirror/mode/mysql/mysql.js',
122 'js/server_status_monitor.js']);
125 monitorLoaded = true;
128 // Run the advisor immediately when the user clicks the tab, but only when this is the first time
129 if (ui.tab.hash == '#statustabs_advisor' && $('table#rulesFired').length == 0) {
130 // Start with a small delay because the click event hasn't been setup yet
131 setTimeout(function() {
132 $('a[href="#startAnalyzer"]').trigger('click');
138 // Fixes wrong tab height with floated elements. See also http://bugs.jqueryui.com/ticket/5601
139 $(".ui-widget-content:not(.ui-tabs):not(.ui-helper-clearfix)").addClass("ui-helper-clearfix");
141 // Initialize each tab
142 $('div.ui-tabs-panel').each(function() {
143 initTab($(this), null);
144 tabStatus[$(this).attr('id')] = 'static';
147 // Display button links
148 $('div.buttonlinks').show();
150 // Handles refresh rate changing
151 $('.buttonlinks select').change(function() {
152 var chart = tabChart[$(this).parents('div.ui-tabs-panel').attr('id')];
154 // Clear current timeout and set timeout with the new refresh rate
155 clearTimeout(chart_activeTimeouts[chart.options.chart.renderTo]);
156 if (chart.options.realtime.postRequest) {
157 chart.options.realtime.postRequest.abort();
160 chart.options.realtime.refreshRate = 1000*parseInt(this.value);
162 chart.xAxis[0].setExtremes(
163 new Date().getTime() - server_time_diff - chart.options.realtime.numMaxPoints * chart.options.realtime.refreshRate,
164 new Date().getTime() - server_time_diff,
168 chart_activeTimeouts[chart.options.chart.renderTo] = setTimeout(
169 chart.options.realtime.timeoutCallBack,
170 chart.options.realtime.refreshRate
174 // Ajax refresh of variables (always the first element in each tab)
175 $('.buttonlinks a.tabRefresh').click(function() {
176 // ui-tabs-panel class is added by the jquery tabs feature
177 var tab = $(this).parents('div.ui-tabs-panel');
180 // Show ajax load icon
181 $(this).find('img').show();
183 $.get($(this).attr('href'), { ajax_request: 1 }, function(data) {
184 $(that).find('img').hide();
188 tabStatus[tab.attr('id')] = 'data';
194 /** Realtime charting of variables **/
196 // Live traffic charting
197 $('.buttonlinks a.livetrafficLink').click(function() {
198 // ui-tabs-panel class is added by the jquery tabs feature
199 var $tab = $(this).parents('div.ui-tabs-panel');
200 var tabstat = tabStatus[$tab.attr('id')];
202 if (tabstat == 'static' || tabstat == 'liveconnections') {
205 { name: PMA_messages['strChartKBSent'], data: [] },
206 { name: PMA_messages['strChartKBReceived'], data: [] }
208 title: { text: PMA_messages['strChartServerTraffic'] },
209 realtime: { url: 'server_status.php?' + url_query,
211 callback: function(chartObj, curVal, lastVal, numLoadedPoints) {
212 if (lastVal == null) {
215 chartObj.series[0].addPoint(
216 { x: curVal.x, y: (curVal.y_sent - lastVal.y_sent) / 1024 },
218 numLoadedPoints >= chartObj.options.realtime.numMaxPoints
220 chartObj.series[1].addPoint(
221 { x: curVal.x, y: (curVal.y_received - lastVal.y_received) / 1024 },
223 numLoadedPoints >= chartObj.options.realtime.numMaxPoints
226 error: function() { serverResponseError(); }
230 setupLiveChart($tab, this, settings);
231 if (tabstat == 'liveconnections') {
232 $tab.find('.buttonlinks a.liveconnectionsLink').html(PMA_messages['strLiveConnChart']);
234 tabStatus[$tab.attr('id')] = 'livetraffic';
236 $(this).html(PMA_messages['strLiveTrafficChart']);
237 setupLiveChart($tab, this, null);
243 // Live connection/process charting
244 $('.buttonlinks a.liveconnectionsLink').click(function() {
245 var $tab = $(this).parents('div.ui-tabs-panel');
246 var tabstat = tabStatus[$tab.attr('id')];
248 if (tabstat == 'static' || tabstat == 'livetraffic') {
251 { name: PMA_messages['strChartConnections'], data: [] },
252 { name: PMA_messages['strChartProcesses'], data: [] }
254 title: { text: PMA_messages['strChartConnectionsTitle'] },
255 realtime: { url: 'server_status.php?' + url_query,
257 callback: function(chartObj, curVal, lastVal, numLoadedPoints) {
258 if (lastVal == null) {
261 chartObj.series[0].addPoint(
262 { x: curVal.x, y: curVal.y_conn - lastVal.y_conn },
264 numLoadedPoints >= chartObj.options.realtime.numMaxPoints
266 chartObj.series[1].addPoint(
267 { x: curVal.x, y: curVal.y_proc },
269 numLoadedPoints >= chartObj.options.realtime.numMaxPoints
272 error: function() { serverResponseError(); }
276 setupLiveChart($tab, this, settings);
277 if (tabstat == 'livetraffic') {
278 $tab.find('.buttonlinks a.livetrafficLink').html(PMA_messages['strLiveTrafficChart']);
280 tabStatus[$tab.attr('id')] = 'liveconnections';
282 $(this).html(PMA_messages['strLiveConnChart']);
283 setupLiveChart($tab, this, null);
289 // Live query statistics
290 $('.buttonlinks a.livequeriesLink').click(function() {
291 var $tab = $(this).parents('div.ui-tabs-panel');
294 if (tabStatus[$tab.attr('id')] == 'static') {
296 series: [ { name: PMA_messages['strChartIssuedQueries'], data: [] } ],
297 title: { text: PMA_messages['strChartIssuedQueriesTitle'] },
298 tooltip: { formatter: function() { return this.point.name; } },
299 realtime: { url: 'server_status.php?' + url_query,
301 callback: function(chartObj, curVal, lastVal, numLoadedPoints) {
302 if (lastVal == null) { return; }
303 chartObj.series[0].addPoint({
305 y: curVal.y - lastVal.y,
306 name: sortedQueriesPointInfo(curVal, lastVal)
309 numLoadedPoints >= chartObj.options.realtime.numMaxPoints
312 error: function() { serverResponseError(); }
316 $(this).html(PMA_messages['strLiveQueryChart']);
319 setupLiveChart($tab, this, settings);
320 tabStatus[$tab.attr('id')] = 'livequeries';
324 function setupLiveChart($tab, link, settings) {
325 if (settings != null) {
326 // Loading a chart with existing chart => remove old chart first
327 if (tabStatus[$tab.attr('id')] != 'static') {
328 clearTimeout(chart_activeTimeouts[$tab.attr('id') + "_chart_cnt"]);
329 chart_activeTimeouts[$tab.attr('id') + "_chart_cnt"] = null;
330 tabChart[$tab.attr('id')].destroy();
331 // Also reset the select list
332 $tab.find('.buttonlinks select').get(0).selectedIndex = 2;
335 if (! settings.chart) settings.chart = {};
336 settings.chart.renderTo = $tab.attr('id') + "_chart_cnt";
338 $tab.find('.tabInnerContent')
340 .after('<div class="liveChart" id="' + $tab.attr('id') + '_chart_cnt"></div>');
341 tabChart[$tab.attr('id')] = PMA_createChart(settings);
342 $(link).html(PMA_messages['strStaticData']);
343 $tab.find('.buttonlinks a.tabRefresh').hide();
344 $tab.find('.buttonlinks .refreshList').show();
346 clearTimeout(chart_activeTimeouts[$tab.attr('id') + "_chart_cnt"]);
347 chart_activeTimeouts[$tab.attr('id') + "_chart_cnt"] = null;
348 $tab.find('.tabInnerContent').show();
349 $tab.find('div#' + $tab.attr('id') + '_chart_cnt').remove();
350 tabStatus[$tab.attr('id')] = 'static';
351 tabChart[$tab.attr('id')].destroy();
352 $tab.find('.buttonlinks a.tabRefresh').show();
353 $tab.find('.buttonlinks select').get(0).selectedIndex = 2;
354 $tab.find('.buttonlinks .refreshList').hide();
358 /* 3 Filtering functions */
359 $('#filterAlert').change(function() {
360 alertFilter = this.checked;
364 $('#filterText').keyup(function(e) {
365 word = $(this).val().replace(/_/g, ' ');
367 if (word.length == 0) {
370 else textFilter = new RegExp("(^|_)" + word, 'i');
377 $('#filterCategory').change(function() {
378 categoryFilter = $(this).val();
382 $('input#dontFormat').change(function() {
383 // Hiding the table while changing values speeds up the process a lot
384 $('#serverstatusvariables').hide();
385 $('#serverstatusvariables td.value span.original').toggle(this.checked);
386 $('#serverstatusvariables td.value span.formatted').toggle(! this.checked);
387 $('#serverstatusvariables').show();
390 /* Adjust DOM / Add handlers to the tabs */
391 function initTab(tab, data) {
392 switch(tab.attr('id')) {
393 case 'statustabs_traffic':
395 tab.find('.tabInnerContent').html(data);
397 PMA_convertFootnotesToTooltips();
399 case 'statustabs_queries':
401 queryPieChart.destroy();
402 tab.find('.tabInnerContent').html(data);
405 // Build query statistics chart
406 var cdata = new Array();
407 $.each(jQuery.parseJSON($('#serverstatusquerieschart span').html()), function(key, value) {
408 cdata.push([key, parseInt(value)]);
411 queryPieChart = PMA_createChart({
413 renderTo: 'serverstatusquerieschart'
421 name: PMA_messages['strChartQueryPie'],
426 allowPointSelect: true,
430 formatter: function() {
431 return '<b>' + this.point.name +'</b><br/> ' +
432 Highcharts.numberFormat(this.percentage, 2) + ' %';
438 formatter: function() {
439 return '<b>' + this.point.name + '</b><br/>' +
440 Highcharts.numberFormat(this.y, 2) + '<br/>(' +
441 Highcharts.numberFormat(this.percentage, 2) + ' %)';
447 case 'statustabs_allvars':
449 tab.find('.tabInnerContent').html(data);
455 initTableSorter(tab.attr('id'));
458 function initTableSorter(tabid) {
460 case 'statustabs_queries':
461 $('#serverstatusqueriesdetails').tablesorter({
465 1: { sorter: 'fancyNumber' },
466 2: { sorter: 'fancyNumber' }
470 $('#serverstatusqueriesdetails tr:first th')
471 .append('<img class="icon sortableIcon" src="themes/dot.gif" alt="">');
475 case 'statustabs_allvars':
476 $('#serverstatusvariables').tablesorter({
480 1: { sorter: 'fancyNumber' }
484 $('#serverstatusvariables tr:first th')
485 .append('<img class="icon sortableIcon" src="themes/dot.gif" alt="">');
491 /* Filters the status variables by name/category/alert in the variables tab */
492 function filterVariables() {
493 var useful_links = 0;
496 if (categoryFilter.length > 0) {
497 section = categoryFilter;
500 if (section.length > 1) {
501 $('#linkSuggestions span').each(function() {
502 if ($(this).attr('class').indexOf('status_' + section) != -1) {
504 $(this).css('display', '');
506 $(this).css('display', 'none');
513 if (useful_links > 0) {
514 $('#linkSuggestions').css('display', '');
516 $('#linkSuggestions').css('display', 'none');
520 $('#serverstatusvariables th.name').each(function() {
521 if ((textFilter == null || textFilter.exec($(this).text()))
522 && (! alertFilter || $(this).next().find('span.attention').length>0)
523 && (categoryFilter.length == 0 || $(this).parent().hasClass('s_' + categoryFilter))
526 $(this).parent().css('display', '');
528 $(this).parent().addClass('odd');
529 $(this).parent().removeClass('even');
531 $(this).parent().addClass('even');
532 $(this).parent().removeClass('odd');
535 $(this).parent().css('display', 'none');
540 // Provides a nicely formatted and sorted tooltip of each datapoint of the query statistics
541 function sortedQueriesPointInfo(queries, lastQueries){
542 var max, maxIdx, num = 0;
543 var queryKeys = new Array();
544 var queryValues = new Array();
548 // Separate keys and values, then sort them
549 $.each(queries.pointInfo, function(key, value) {
550 if (value-lastQueries.pointInfo[key] > 0) {
552 queryValues.push(value-lastQueries.pointInfo[key]);
553 sumTotal += value-lastQueries.pointInfo[key];
556 var numQueries = queryKeys.length;
557 var pointInfo = '<b>' + PMA_messages['strTotal'] + ': ' + sumTotal + '</b><br>';
559 while(queryKeys.length > 0) {
561 for (var i = 0; i < queryKeys.length; i++) {
562 if (queryValues[i] > max) {
563 max = queryValues[i];
567 if (numQueries > 8 && num >= 6) {
568 sumOther += queryValues[maxIdx];
570 pointInfo += queryKeys[maxIdx].substr(4).replace('_', ' ') + ': ' + queryValues[maxIdx] + '<br>';
573 queryKeys.splice(maxIdx, 1);
574 queryValues.splice(maxIdx, 1);
579 pointInfo += PMA_messages['strOther'] + ': ' + sumOther;
585 /**** Server config advisor ****/
587 $('a[href="#openAdvisorInstructions"]').click(function() {
590 dlgBtns[PMA_messages['strClose']] = function() {
591 $(this).dialog('close');
594 $('#advisorInstructionsDialog').attr('title', PMA_messages['strAdvisorSystem']);
595 $('#advisorInstructionsDialog').dialog({
601 $('a[href="#startAnalyzer"]').click(function() {
602 var $cnt = $('#statustabs_advisor .tabInnerContent');
603 $cnt.html('<img class="ajaxIcon" src="' + pmaThemeImage + 'ajax_clock_small.gif" alt="">');
605 $.get('server_status.php?' + url_query, { ajax_request: true, advisor: true }, function(data) {
606 var $tbody, $tr, str, even = true;
608 data = $.parseJSON(data);
612 if (data.parse.errors.length > 0) {
613 $cnt.append('<b>Rules file not well formed, following errors were found:</b><br />- ');
614 $cnt.append(data.parse.errors.join('<br/>- '));
615 $cnt.append('<p></p>');
618 if (data.run.errors.length > 0) {
619 $cnt.append('<b>Errors occured while executing rule expressions:</b><br />- ');
620 $cnt.append(data.run.errors.join('<br/>- '));
621 $cnt.append('<p></p>');
624 if (data.run.fired.length > 0) {
625 $cnt.append('<p><b>' + PMA_messages['strPerformanceIssues'] + '</b></p>');
626 $cnt.append('<table class="data" id="rulesFired" border="0"><thead><tr>' +
627 '<th>' + PMA_messages['strIssuse'] + '</th><th>' + PMA_messages['strRecommendation'] +
628 '</th></tr></thead><tbody></tbody></table>');
629 $tbody = $cnt.find('table#rulesFired');
633 $.each(data.run.fired, function(key, value) {
634 // recommendation may contain links, don't show those in overview table (clicking on them redirects the user)
635 rc_stripped = $.trim($('<div>').html(value.recommendation).text())
636 $tbody.append($tr = $('<tr class="linkElem noclick ' + (even ? 'even' : 'odd') + '"><td>' +
637 value.issue + '</td><td>' + rc_stripped + ' </td></tr>'));
639 $tr.data('rule', value);
641 $tr.click(function() {
642 var rule = $(this).data('rule');
643 $('div#emptyDialog').attr('title', PMA_messages['strRuleDetails']);
644 $('div#emptyDialog').html(
645 '<p><b>' + PMA_messages['strIssuse'] + ':</b><br />' + rule.issue + '</p>' +
646 '<p><b>' + PMA_messages['strRecommendation'] + ':</b><br />' + rule.recommendation + '</p>' +
647 '<p><b>' + PMA_messages['strJustification'] + ':</b><br />' + rule.justification + '</p>' +
648 '<p><b>' + PMA_messages['strFormula'] + ':</b><br />' + rule.formula + '</p>' +
649 '<p><b>' + PMA_messages['strTest'] + ':</b><br />' + rule.test + '</p>'
653 dlgBtns[PMA_messages['strClose']] = function() {
654 $(this).dialog('close');
657 $('div#emptyDialog').dialog({ width: 600, buttons: dlgBtns });
668 // Needs to be global as server_status_monitor.js uses it too
669 function serverResponseError() {
671 btns[PMA_messages['strReloadPage']] = function() {
672 window.location.reload();
674 $('#emptyDialog').attr('title', PMA_messages['strRefreshFailed']);
675 $('#emptyDialog').html(
676 '<img class="icon ic_s_attention" src="themes/dot.gif" alt=""> ' +
677 PMA_messages['strInvalidResponseExplanation']
679 $('#emptyDialog').dialog({ buttons: btns });