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 // Show all javascript related parts of the page
19 $('.jsfeature').show();
21 jQuery.tablesorter.addParser({
24 return /^[0-9]?[0-9,\.]*\s?(k|M|G|T|%)?$/.test(s);
27 var num = jQuery.tablesorter.formatFloat(
28 s.replace(PMA_messages['strThousandsSeperator'], '')
29 .replace(PMA_messages['strDecimalSeperator'], '.')
33 switch (s.charAt(s.length - 1)) {
34 case '%': factor = -2; break;
35 // Todo: Complete this list (as well as in the regexp a few lines up)
36 case 'k': factor = 3; break;
37 case 'M': factor = 6; break;
38 case 'G': factor = 9; break;
39 case 'T': factor = 12; break;
42 return num * Math.pow(10, factor);
47 jQuery.tablesorter.addParser({
48 id: "withinSpanNumber",
50 return /<span class="original"/.test(s);
52 format: function(s, table, html) {
53 var res = html.innerHTML.match(/<span(\s*style="display:none;"\s*)?\s*class="original">(.*)?<\/span>/);
54 return (res && res.length >= 3) ? res[2] : 0;
59 // faster zebra widget: no row visibility check, faster css class switching, no cssChildRow check
60 jQuery.tablesorter.addWidget({
62 format: function (table) {
63 if (table.config.debug) {
64 var time = new Date();
66 $("tr:even", table.tBodies[0])
67 .removeClass(table.config.widgetZebra.css[0])
68 .addClass(table.config.widgetZebra.css[1]);
69 $("tr:odd", table.tBodies[0])
70 .removeClass(table.config.widgetZebra.css[1])
71 .addClass(table.config.widgetZebra.css[0]);
72 if (table.config.debug) {
73 $.tablesorter.benchmark("Applying Fast-Zebra widget", time);
79 $('a[rel="popupLink"]').click( function() {
82 $('.' + $link.attr('href').substr(1))
84 .offset({ top: $link.offset().top + $link.height() + 5, left: $link.offset().left })
85 .addClass('openedPopup');
90 $(document).click( function(event) {
91 $('.openedPopup').each(function() {
93 var pos = $(this).offset();
95 // Hide if the mouseclick is outside the popupcontent
96 if (event.pageX < pos.left
97 || event.pageY < pos.top
98 || event.pageX > pos.left + $cnt.outerWidth()
99 || event.pageY > pos.top + $cnt.outerHeight()
101 $cnt.hide().removeClass('openedPopup');
108 // Filters for status variables
109 var textFilter = null;
110 var alertFilter = false;
111 var categoryFilter = '';
113 var text = ''; // Holds filter text
114 var queryPieChart = null;
115 var monitorLoaded = false;
117 /* Chart configuration */
118 // Defines what the tabs are currently displaying (realtime or data)
119 var tabStatus = new Object();
120 // Holds the current chart instances for each tab
121 var tabChart = new Object();
123 /*** Table sort tooltip ***/
124 PMA_createqTip($('table.sortable thead th'), PMA_messages['strSortHint']);
126 // Tell highcarts not to use UTC dates (global setting)
127 Highcharts.setOptions({
138 $('#serverStatusTabs').tabs({
140 cookie: { name: 'pma_serverStatusTabs', expires: 1 },
141 show: function(event, ui) {
142 // Fixes line break in the menu bar when the page overflows and scrollbar appears
145 // Initialize selected tab
146 if (!$(ui.tab.hash).data('init-done')) {
147 initTab($(ui.tab.hash), null);
150 // Load Server status monitor
151 if (ui.tab.hash == '#statustabs_charting' && ! monitorLoaded) {
152 $('div#statustabs_charting').append( //PMA_messages['strLoadingMonitor'] + ' ' +
153 '<img class="ajaxIcon" id="loadingMonitorIcon" src="' +
154 pmaThemeImage + 'ajax_clock_small.gif" alt="">'
156 // Delay loading a bit so the tab loads and the user gets to see a ajax loading icon
157 setTimeout(function() {
158 loadJavascript(['js/jquery/timepicker.js', 'js/jquery/jquery.json-2.2.js',
159 'js/jquery/jquery.sprintf.js', 'js/jquery/jquery.sortableTable.js',
160 'js/codemirror/lib/codemirror.js', 'js/codemirror/mode/mysql/mysql.js',
161 'js/server_status_monitor.js']);
164 monitorLoaded = true;
167 // Run the advisor immediately when the user clicks the tab, but only when this is the first time
168 if (ui.tab.hash == '#statustabs_advisor' && $('table#rulesFired').length == 0) {
169 // Start with a small delay because the click event hasn't been setup yet
170 setTimeout(function() {
171 $('a[href="#startAnalyzer"]').trigger('click');
177 // Fixes wrong tab height with floated elements. See also http://bugs.jqueryui.com/ticket/5601
178 $(".ui-widget-content:not(.ui-tabs):not(.ui-helper-clearfix)").addClass("ui-helper-clearfix");
180 // Initialize each tab
181 $('div.ui-tabs-panel').each(function() {
183 tabStatus[$tab.attr('id')] = 'static';
184 // Initialize tabs after browser catches up with previous changes and displays tabs
185 setTimeout(function() {
190 // Handles refresh rate changing
191 $('.buttonlinks select').change(function() {
192 var chart = tabChart[$(this).parents('div.ui-tabs-panel').attr('id')];
194 // Clear current timeout and set timeout with the new refresh rate
195 clearTimeout(chart_activeTimeouts[chart.options.chart.renderTo]);
196 if (chart.options.realtime.postRequest) {
197 chart.options.realtime.postRequest.abort();
200 chart.options.realtime.refreshRate = 1000*parseInt(this.value);
202 chart.xAxis[0].setExtremes(
203 new Date().getTime() - server_time_diff - chart.options.realtime.numMaxPoints * chart.options.realtime.refreshRate,
204 new Date().getTime() - server_time_diff,
208 chart_activeTimeouts[chart.options.chart.renderTo] = setTimeout(
209 chart.options.realtime.timeoutCallBack,
210 chart.options.realtime.refreshRate
214 // Ajax refresh of variables (always the first element in each tab)
215 $('.buttonlinks a.tabRefresh').click(function() {
216 // ui-tabs-panel class is added by the jquery tabs feature
217 var tab = $(this).parents('div.ui-tabs-panel');
220 // Show ajax load icon
221 $(this).find('img').show();
223 $.get($(this).attr('href'), { ajax_request: 1 }, function(data) {
224 $(that).find('img').hide();
228 tabStatus[tab.attr('id')] = 'data';
234 /** Realtime charting of variables **/
236 // Live traffic charting
237 $('.buttonlinks a.livetrafficLink').click(function() {
238 // ui-tabs-panel class is added by the jquery tabs feature
239 var $tab = $(this).parents('div.ui-tabs-panel');
240 var tabstat = tabStatus[$tab.attr('id')];
242 if (tabstat == 'static' || tabstat == 'liveconnections') {
245 { name: PMA_messages['strChartKBSent'], data: [] },
246 { name: PMA_messages['strChartKBReceived'], data: [] }
248 title: { text: PMA_messages['strChartServerTraffic'] },
249 realtime: { url: 'server_status.php?' + url_query,
251 callback: function(chartObj, curVal, lastVal, numLoadedPoints) {
252 if (lastVal == null) {
255 chartObj.series[0].addPoint(
256 { x: curVal.x, y: (curVal.y_sent - lastVal.y_sent) / 1024 },
258 numLoadedPoints >= chartObj.options.realtime.numMaxPoints
260 chartObj.series[1].addPoint(
261 { x: curVal.x, y: (curVal.y_received - lastVal.y_received) / 1024 },
263 numLoadedPoints >= chartObj.options.realtime.numMaxPoints
266 error: function() { serverResponseError(); }
270 setupLiveChart($tab, this, settings);
271 if (tabstat == 'liveconnections') {
272 $tab.find('.buttonlinks a.liveconnectionsLink').html(PMA_messages['strLiveConnChart']);
274 tabStatus[$tab.attr('id')] = 'livetraffic';
276 $(this).html(PMA_messages['strLiveTrafficChart']);
277 setupLiveChart($tab, this, null);
283 // Live connection/process charting
284 $('.buttonlinks a.liveconnectionsLink').click(function() {
285 var $tab = $(this).parents('div.ui-tabs-panel');
286 var tabstat = tabStatus[$tab.attr('id')];
288 if (tabstat == 'static' || tabstat == 'livetraffic') {
291 { name: PMA_messages['strChartConnections'], data: [] },
292 { name: PMA_messages['strChartProcesses'], data: [] }
294 title: { text: PMA_messages['strChartConnectionsTitle'] },
295 realtime: { url: 'server_status.php?' + url_query,
297 callback: function(chartObj, curVal, lastVal, numLoadedPoints) {
298 if (lastVal == null) {
301 chartObj.series[0].addPoint(
302 { x: curVal.x, y: curVal.y_conn - lastVal.y_conn },
304 numLoadedPoints >= chartObj.options.realtime.numMaxPoints
306 chartObj.series[1].addPoint(
307 { x: curVal.x, y: curVal.y_proc },
309 numLoadedPoints >= chartObj.options.realtime.numMaxPoints
312 error: function() { serverResponseError(); }
316 setupLiveChart($tab, this, settings);
317 if (tabstat == 'livetraffic') {
318 $tab.find('.buttonlinks a.livetrafficLink').html(PMA_messages['strLiveTrafficChart']);
320 tabStatus[$tab.attr('id')] = 'liveconnections';
322 $(this).html(PMA_messages['strLiveConnChart']);
323 setupLiveChart($tab, this, null);
329 // Live query statistics
330 $('.buttonlinks a.livequeriesLink').click(function() {
331 var $tab = $(this).parents('div.ui-tabs-panel');
334 if (tabStatus[$tab.attr('id')] == 'static') {
336 series: [ { name: PMA_messages['strChartIssuedQueries'], data: [] } ],
337 title: { text: PMA_messages['strChartIssuedQueriesTitle'] },
338 tooltip: { formatter: function() { return this.point.name; } },
339 realtime: { url: 'server_status.php?' + url_query,
341 callback: function(chartObj, curVal, lastVal, numLoadedPoints) {
342 if (lastVal == null) { return; }
343 chartObj.series[0].addPoint({
345 y: curVal.y - lastVal.y,
346 name: sortedQueriesPointInfo(curVal, lastVal)
349 numLoadedPoints >= chartObj.options.realtime.numMaxPoints
352 error: function() { serverResponseError(); }
356 $(this).html(PMA_messages['strLiveQueryChart']);
359 setupLiveChart($tab, this, settings);
360 tabStatus[$tab.attr('id')] = 'livequeries';
364 function setupLiveChart($tab, link, settings) {
365 if (settings != null) {
366 // Loading a chart with existing chart => remove old chart first
367 if (tabStatus[$tab.attr('id')] != 'static') {
368 clearTimeout(chart_activeTimeouts[$tab.attr('id') + "_chart_cnt"]);
369 chart_activeTimeouts[$tab.attr('id') + "_chart_cnt"] = null;
370 tabChart[$tab.attr('id')].destroy();
371 // Also reset the select list
372 $tab.find('.buttonlinks select').get(0).selectedIndex = 2;
375 if (! settings.chart) settings.chart = {};
376 settings.chart.renderTo = $tab.attr('id') + "_chart_cnt";
378 $tab.find('.tabInnerContent')
380 .after('<div class="liveChart" id="' + $tab.attr('id') + '_chart_cnt"></div>');
381 tabChart[$tab.attr('id')] = PMA_createChart(settings);
382 $(link).html(PMA_messages['strStaticData']);
383 $tab.find('.buttonlinks a.tabRefresh').hide();
384 $tab.find('.buttonlinks .refreshList').show();
386 clearTimeout(chart_activeTimeouts[$tab.attr('id') + "_chart_cnt"]);
387 chart_activeTimeouts[$tab.attr('id') + "_chart_cnt"] = null;
388 $tab.find('.tabInnerContent').show();
389 $tab.find('div#' + $tab.attr('id') + '_chart_cnt').remove();
390 tabStatus[$tab.attr('id')] = 'static';
391 tabChart[$tab.attr('id')].destroy();
392 $tab.find('.buttonlinks a.tabRefresh').show();
393 $tab.find('.buttonlinks select').get(0).selectedIndex = 2;
394 $tab.find('.buttonlinks .refreshList').hide();
398 /* 3 Filtering functions */
399 $('#filterAlert').change(function() {
400 alertFilter = this.checked;
404 $('#filterText').keyup(function(e) {
405 var word = $(this).val().replace(/_/g, ' ');
407 if (word.length == 0) {
410 else textFilter = new RegExp("(^| )" + word, 'i');
417 $('#filterCategory').change(function() {
418 categoryFilter = $(this).val();
422 $('input#dontFormat').change(function() {
423 // Hiding the table while changing values speeds up the process a lot
424 $('#serverstatusvariables').hide();
425 $('#serverstatusvariables td.value span.original').toggle(this.checked);
426 $('#serverstatusvariables td.value span.formatted').toggle(! this.checked);
427 $('#serverstatusvariables').show();
430 /* Adjust DOM / Add handlers to the tabs */
431 function initTab(tab, data) {
432 if ($(tab).data('init-done') && !data) {
435 $(tab).data('init-done', true);
436 switch(tab.attr('id')) {
437 case 'statustabs_traffic':
439 tab.find('.tabInnerContent').html(data);
441 PMA_convertFootnotesToTooltips();
443 case 'statustabs_queries':
445 queryPieChart.destroy();
446 tab.find('.tabInnerContent').html(data);
449 // Build query statistics chart
450 var cdata = new Array();
451 $.each(jQuery.parseJSON($('#serverstatusquerieschart span').html()), function(key, value) {
452 cdata.push([key, parseInt(value)]);
455 queryPieChart = PMA_createChart({
457 renderTo: 'serverstatusquerieschart'
465 name: PMA_messages['strChartQueryPie'],
470 allowPointSelect: true,
474 formatter: function() {
475 return '<b>' + this.point.name +'</b><br/> ' +
476 Highcharts.numberFormat(this.percentage, 2) + ' %';
482 formatter: function() {
483 return '<b>' + this.point.name + '</b><br/>' +
484 Highcharts.numberFormat(this.y, 2) + '<br/>(' +
485 Highcharts.numberFormat(this.percentage, 2) + ' %)';
489 initTableSorter(tab.attr('id'));
492 case 'statustabs_allvars':
494 tab.find('.tabInnerContent').html(data);
497 initTableSorter(tab.attr('id'));
502 // TODO: tablesorter shouldn't sort already sorted columns
503 function initTableSorter(tabid) {
506 case 'statustabs_queries':
507 $table = $('#serverstatusqueriesdetails');
510 widgets: ['fast-zebra'],
512 1: { sorter: 'fancyNumber' },
513 2: { sorter: 'fancyNumber' }
517 case 'statustabs_allvars':
518 $table = $('#serverstatusvariables');
521 widgets: ['fast-zebra'],
523 1: { sorter: 'withinSpanNumber' }
528 $table.tablesorter(opts);
529 $table.find('tr:first th')
530 .append('<img class="icon sortableIcon" src="themes/dot.gif" alt="">');
533 /* Filters the status variables by name/category/alert in the variables tab */
534 function filterVariables() {
535 var useful_links = 0;
538 if (categoryFilter.length > 0) {
539 section = categoryFilter;
542 if (section.length > 1) {
543 $('#linkSuggestions span').each(function() {
544 if ($(this).attr('class').indexOf('status_' + section) != -1) {
546 $(this).css('display', '');
548 $(this).css('display', 'none');
555 if (useful_links > 0) {
556 $('#linkSuggestions').css('display', '');
558 $('#linkSuggestions').css('display', 'none');
562 $('#serverstatusvariables th.name').each(function() {
563 if ((textFilter == null || textFilter.exec($(this).text()))
564 && (! alertFilter || $(this).next().find('span.attention').length>0)
565 && (categoryFilter.length == 0 || $(this).parent().hasClass('s_' + categoryFilter))
568 $(this).parent().css('display', '');
570 $(this).parent().addClass('odd');
571 $(this).parent().removeClass('even');
573 $(this).parent().addClass('even');
574 $(this).parent().removeClass('odd');
577 $(this).parent().css('display', 'none');
582 // Provides a nicely formatted and sorted tooltip of each datapoint of the query statistics
583 function sortedQueriesPointInfo(queries, lastQueries){
584 var max, maxIdx, num = 0;
585 var queryKeys = new Array();
586 var queryValues = new Array();
590 // Separate keys and values, then sort them
591 $.each(queries.pointInfo, function(key, value) {
592 if (value-lastQueries.pointInfo[key] > 0) {
594 queryValues.push(value-lastQueries.pointInfo[key]);
595 sumTotal += value-lastQueries.pointInfo[key];
598 var numQueries = queryKeys.length;
599 var pointInfo = '<b>' + PMA_messages['strTotal'] + ': ' + sumTotal + '</b><br>';
601 while(queryKeys.length > 0) {
603 for (var i = 0; i < queryKeys.length; i++) {
604 if (queryValues[i] > max) {
605 max = queryValues[i];
609 if (numQueries > 8 && num >= 6) {
610 sumOther += queryValues[maxIdx];
612 pointInfo += queryKeys[maxIdx].substr(4).replace('_', ' ') + ': ' + queryValues[maxIdx] + '<br>';
615 queryKeys.splice(maxIdx, 1);
616 queryValues.splice(maxIdx, 1);
621 pointInfo += PMA_messages['strOther'] + ': ' + sumOther;
627 /**** Server config advisor ****/
629 $('a[href="#openAdvisorInstructions"]').click(function() {
632 dlgBtns[PMA_messages['strClose']] = function() {
633 $(this).dialog('close');
636 $('#advisorInstructionsDialog').attr('title', PMA_messages['strAdvisorSystem']);
637 $('#advisorInstructionsDialog').dialog({
643 $('a[href="#startAnalyzer"]').click(function() {
644 var $cnt = $('#statustabs_advisor .tabInnerContent');
645 $cnt.html('<img class="ajaxIcon" src="' + pmaThemeImage + 'ajax_clock_small.gif" alt="">');
647 $.get('server_status.php?' + url_query, { ajax_request: true, advisor: true }, function(data) {
648 var $tbody, $tr, str, even = true;
650 data = $.parseJSON(data);
654 if (data.parse.errors.length > 0) {
655 $cnt.append('<b>Rules file not well formed, following errors were found:</b><br />- ');
656 $cnt.append(data.parse.errors.join('<br/>- '));
657 $cnt.append('<p></p>');
660 if (data.run.errors.length > 0) {
661 $cnt.append('<b>Errors occured while executing rule expressions:</b><br />- ');
662 $cnt.append(data.run.errors.join('<br/>- '));
663 $cnt.append('<p></p>');
666 if (data.run.fired.length > 0) {
667 $cnt.append('<p><b>' + PMA_messages['strPerformanceIssues'] + '</b></p>');
668 $cnt.append('<table class="data" id="rulesFired" border="0"><thead><tr>' +
669 '<th>' + PMA_messages['strIssuse'] + '</th><th>' + PMA_messages['strRecommendation'] +
670 '</th></tr></thead><tbody></tbody></table>');
671 $tbody = $cnt.find('table#rulesFired');
675 $.each(data.run.fired, function(key, value) {
676 // recommendation may contain links, don't show those in overview table (clicking on them redirects the user)
677 rc_stripped = $.trim($('<div>').html(value.recommendation).text())
678 $tbody.append($tr = $('<tr class="linkElem noclick ' + (even ? 'even' : 'odd') + '"><td>' +
679 value.issue + '</td><td>' + rc_stripped + ' </td></tr>'));
681 $tr.data('rule', value);
683 $tr.click(function() {
684 var rule = $(this).data('rule');
685 $('div#emptyDialog').attr('title', PMA_messages['strRuleDetails']);
686 $('div#emptyDialog').html(
687 '<p><b>' + PMA_messages['strIssuse'] + ':</b><br />' + rule.issue + '</p>' +
688 '<p><b>' + PMA_messages['strRecommendation'] + ':</b><br />' + rule.recommendation + '</p>' +
689 '<p><b>' + PMA_messages['strJustification'] + ':</b><br />' + rule.justification + '</p>' +
690 '<p><b>' + PMA_messages['strFormula'] + ':</b><br />' + rule.formula + '</p>' +
691 '<p><b>' + PMA_messages['strTest'] + ':</b><br />' + rule.test + '</p>'
695 dlgBtns[PMA_messages['strClose']] = function() {
696 $(this).dialog('close');
699 $('div#emptyDialog').dialog({ width: 600, buttons: dlgBtns });
710 // Needs to be global as server_status_monitor.js uses it too
711 function serverResponseError() {
713 btns[PMA_messages['strReloadPage']] = function() {
714 window.location.reload();
716 $('#emptyDialog').attr('title', PMA_messages['strRefreshFailed']);
717 $('#emptyDialog').html(
718 '<img class="icon ic_s_attention" src="themes/dot.gif" alt=""> ' +
719 PMA_messages['strInvalidResponseExplanation']
721 $('#emptyDialog').dialog({ buttons: btns });