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
});