]> arthur.barton.de Git - netdata.git/blobdiff - web/index.html
fix nginx entry in web dashboard
[netdata.git] / web / index.html
index 3dcef9087bde078cd2fe66f701731b3328897a7b..f0a3b75de60cf57ce562377d81771e474c02a471 100644 (file)
@@ -1,7 +1,8 @@
-<!DOCTYPE html>
+<!DOCTYPE html>
 <html lang="en">
 <head>
        <title>netdata dashboard</title>
+       <meta name="application-name" content="netdata">
 
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <meta charset="utf-8">
        <link rel="icon" type="image/png" sizes="16x16" href="images/seo-performance-16.png">
 
        <meta property="og:locale" content="en_US" />
-       <meta property="og:image" content="https://my-netdata.io/images/seo-performance-512.png"/>
+       <meta property="og:image" content="http://my-netdata.io/images/post.png"/>
        <meta property="og:url" content="http://my-netdata.io/"/>
        <meta property="og:type" content="website"/>
        <meta property="og:site_name" content="netdata"/>
        <meta property="og:title" content="netdata - real-time performance monitoring, done right!"/>
-       <meta property="og:description" content="netdata is an embeddable real-time performance monitoring solution, for Linux systems, micro services, applications, APIs, SNMP devices. Stunning dashboards, blazzingly fast and extremely interactive." />
+       <meta property="og:description" content="Stunning real-time dashboards, blazingly fast and extremely interactive. Zero configuration, zero dependencies, zero maintenance." />
 
        <style>
 
                        return saveLocalStorage('netdataTheme', theme);
                }
 
-               var netdataRegistryCallback = function(urls_array) {
+               var netdataRegistryCallback = function(machines_array) {
                        var el = '';
                        var a1 = '';
                        var found = 0;
 
-                       if(urls_array) {
+                       if(machines_array) {
                                function name_comparator_desc(a, b) {
                                        if (a.name > b.name) return -1;
                                        if (a.name < b.name) return 1;
                                        return 0;
                                }
 
-                               var urls = urls_array.sort(name_comparator_desc);
-                               var len = urls.length;
+                               var machines = machines_array.sort(name_comparator_desc);
+                               var len = machines.length;
                                while(len--) {
-                                       var u = urls[len];
-
-                                       var status = "enabled";
+                                       var u = machines[len];
                                        found++;
-
-                                       if(u.guid === NETDATA.registry.machine_guid)
-                                               status = "disabled"
-
-                                       el += '<li id="registry_server_' + u.guid + '" class="' + status + '"><a href="' + u.url + '">' + u.name + '</a></li>';
+                                       el += '<li id="registry_server_' + u.guid + '"><a class="registry_link" href="' + u.url + '" onClick="return gotoServerModalHandler(\'' + u.guid + '\');">' + u.name + '</a></li>';
                                        a1 += '<li id="registry_action_' + u.guid + '"><a href="#" onclick="deleteRegistryModalHandler(\'' + u.guid + '\',\'' + u.name + '\',\'' + u.url + '\'); return false;"><i class="fa fa-trash-o" aria-hidden="true" style="color: #999;"></i></a></li>';
                                }
                        }
 
                        if(!found) {
-                               if(urls)
+                               if(machines)
                                        el += '<li><a href="https://github.com/firehol/netdata/wiki/mynetdata-menu-item" style="color: #666;" target="_blank">your netdata server list is empty...</a></li>';
                                else
                                        el += '<li><a href="https://github.com/firehol/netdata/wiki/mynetdata-menu-item" style="color: #666;" target="_blank">failed to contact the registry...</a></li>';
 
-                               a1 += '<li><a href="#">&nbsp;</a></li>';
+                               a1 += '<li><a href="#" onClick="return false;">&nbsp;</a></li>';
 
                                el += '<li role="separator" class="divider"></li>' +
                                                '<li><a href="//london.netdata.rocks/default.html">EU - London (DigitalOcean.com)</a></li>' +
                        document.getElementById('mynetdata_servers').innerHTML = el;
                        document.getElementById('mynetdata_servers2').innerHTML = el;
                        document.getElementById('mynetdata_actions1').innerHTML = a1;
+
+                       gotoServerInit();
                };
 
        </script>
 
        <!-- load the dashboard manager - it will do the rest -->
-       <script type="text/javascript" src="dashboard.js?v37"></script>
+       <script type="text/javascript" src="dashboard.js?v40"></script>
 </head>
 
 <body data-spy="scroll" data-target="#sidebar">
                                                        <div class="row">
                                                                <div class="col-sm-6" style="width: 85%; padding-right: 0;">
                                                                        <ul id="mynetdata_servers" class="multi-column-dropdown">
-                                                                               <li><a href="#" onclck="return false;" style="color: #999;">loading...</a></li>
+                                                                               <li><a href="#" onclick="return false;" style="color: #999;">loading...</a></li>
                                                                        </ul>
                                                                </div>
                                                                <div class="col-sm-3 hidden-xs" style="width: 15%; padding-left: 0;">
                                        <li class="dropdown hidden-sm hidden-md hidden-lg">
                                                <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="current_view">my-netdata <strong class="caret"></strong></a>
                                                <ul id="mynetdata_servers2" class="dropdown-menu scrollable-menu inpagemenu" role="menu">
-                                                       <li><a href="#" onclck="return false;" style="color: #999;">loading...</a></li>
+                                                       <li><a href="#" onclick="return false;" style="color: #999;">loading...</a></li>
                                                </ul>
                                        </li>
                                </ul>
                                        <div id="versionCheckLog">Not checked yet. Please press the Check Now button.</div>
                                </div>
                                <div class="modal-footer">
-                                       <a href="#" onclick="notifyForUpdate(true);" type="button" class="btn btn-default">Check Now</a>
+                                       <a href="#" onclick="notifyForUpdate(true); return false;" type="button" class="btn btn-default">Check Now</a>
                                        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
                                </div>
                        </div>
                </div>
        </div>
 
+       <div class="modal fade" id="gotoServerModal" tabindex="-1" role="dialog" aria-labelledby="gotoServerModalLabel">
+               <div class="modal-dialog" role="document">
+                       <div class="modal-content">
+                               <div class="modal-header">
+                                       <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+                                       <h4 class="modal-title" id="gotoServerModalLabel"><span id="gotoServerName"></span></h4>
+                               </div>
+                               <div class="modal-body">
+                                       Checking known URLs for this server...
+                                       <div  style="padding-top: 20px;">
+                                               <table id="gotoServerList">
+                                               </table>
+                                       </div>
+                                       <p style="padding-top: 10px;"><small>
+                                               Checks may fail if you are viewing an HTTPS page and the server to be checked is HTTP only.
+                                       </small></p>
+                                       <div id="gotoServerResponse" style="display: block; width: 100%; text-align: center; padding-top: 20px;"></div>
+                               </div>
+                               <div class="modal-footer">
+                                       <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
+                               </div>
+                       </div>
+               </div>
+       </div>
+
 <script>
 var this_is_demo = null;
 function isdemo() {
@@ -999,6 +1021,80 @@ function isdemo() {
 if(isdemo()) {
        document.getElementById('masthead').style.display = 'block';
 }
+var gotoServerValidateRemaining = 0;
+var gotoServerMiddleClick = false;
+var gotoServerStop = false;
+function gotoServerValidateUrl(id, guid, url) {
+       var penaldy = 0;
+       if(document.location.toString().startsWith('http://') && url.toString().startsWith('https://'))
+                       // we penalize https only if the current url is http
+                       // to allow the user walk through all its servers.
+                       penaldy = 500;
+
+       setTimeout(function() {
+               document.getElementById('gotoServerList').innerHTML += '<tr><td style="padding-left: 20px;"><a href="' + url + '" target="_blank">' + url + '</a></td><td style="padding-left: 30px;"><code id="' + guid + '-' + id + '-status">checking...</code></td></tr>';
+
+               NETDATA.registry.hello(url, function(data) {
+                       if (data) {
+                               // console.log('OK ' + id + ' URL: ' + url);
+                               document.getElementById(guid + '-' + id + '-status').innerHTML = "OK";
+                               var hash = document.location.hash || '';
+
+                               if(!gotoServerStop) {
+                                       gotoServerStop = true;
+
+                                       if(gotoServerMiddleClick) {
+                                               window.open(url + hash, '_blank');
+                                               gotoServerMiddleClick = false;
+                                               document.getElementById('gotoServerResponse').innerHTML = '<b>Opening new window to ' + NETDATA.registry.machines[guid].name + '<br/><a href="' + url + hash + '">' + url + hash + '</a></b><br/>(check your pop-up blocker if it fails)';
+                                       }
+                                       else
+                                               document.location = url + hash;
+                               }
+                       }
+                       else {
+                               document.getElementById(guid + '-' + id + '-status').innerHTML = "failed!";
+                               gotoServerValidateRemaining--;
+                               if(gotoServerValidateRemaining <= 0) {
+                                       gotoServerMiddleClick = false;
+                                       document.getElementById('gotoServerResponse').innerHTML = '<b>Sorry! I cannot find any operational URL for this server</b>';
+                               }
+                       }
+               });
+       }, (id * 50) + penaldy);
+}
+
+function gotoServerModalHandler(guid) {
+       // console.log('goto server: ' + guid);
+
+       gotoServerStop = false;
+       var len = NETDATA.registry.machines[guid].alternate_urls.length;
+
+       document.getElementById('gotoServerResponse').innerHTML = '';
+       document.getElementById('gotoServerList').innerHTML = '';
+       document.getElementById('gotoServerName').innerHTML = NETDATA.registry.machines[guid].name;
+       $('#gotoServerModal').modal('show');
+
+       gotoServerValidateRemaining = len;
+       while(len--)
+               gotoServerValidateUrl(len, guid, NETDATA.registry.machines[guid].alternate_urls[len]);
+
+       return false;
+}
+
+function gotoServerInit() {
+       $(".registry_link").on('click', function(e) {
+               if(e.which === 2) {
+                       e.preventDefault();
+                       gotoServerMiddleClick = true;
+               }
+               else {
+                       gotoServerMiddleClick = false;
+               }
+
+               return true;
+       });
+}
 
 function switchRegistryModalHandler() {
        document.getElementById('switchRegistryPersonGUID').value = NETDATA.registry.person_guid;
@@ -1063,7 +1159,7 @@ var options = {
        chartsPerRow: 0,
        chartsMinWidth: 1450,
        chartsHeight: 180,
-       sparklinesHeight: 60
+       sparklinesHeight: 60,
 };
 
 // generate a sparkline
@@ -1123,6 +1219,21 @@ function sortObjectByPriority(object) {
        return sorted;
 }
 
+
+// ----------------------------------------------------------------------------
+// scroll to a section, without changing the browser history
+
+function scrollToId(hash) {
+       if(hash && hash != '') {
+               var offset = $('#' + hash).offset();
+               if(typeof offset !== 'undefined')
+                       $('html, body').animate({ scrollTop: offset.top }, 0);
+       }
+
+       // we must return false to prevent the default action
+       return false;
+}
+
 // ----------------------------------------------------------------------------
 
 function gaugeChart(title, width, dimensions, colors) {
@@ -1215,19 +1326,19 @@ var menuData = {
 
        'apps': {
                title: 'Applications',
-               info: 'Per application statistics are collected using netdata\'s <code>apps.plugin</code>. This plugin walks through the entire <code>/proc</code> filesystem and aggregates statistics for applications of interest, defined in <code>/etc/netdata/apps_groups.conf</code> (the default is <a href="https://github.com/firehol/netdata/blob/master/conf.d/apps_groups.conf" target="_blank">here</a>). The plugin internally builds a process tree (much like <code>ps fax</code> does), and groups processes together (evaluating both child and parent processes) so that the result is always a chart with a predefined set of dimensions (of course, only application groups found running are reported).<br/><b>IMPORTANT</b>: The values shown here are not 100% accurate. They only include values for the processes running. If an application is spawning children continuously, which are terminated in just a few milliseconds (like shell scripts do), the values reported will be inaccurate. Linux does report the values for the exited children of a process. However, these values are reported to the parent process <b>only when the child exits</b>. If these values, of the exited child processes, were also aggregated in the charts below, the charts would have been full of spikes, presenting unrealistic utilization for each process group. So, we decided to ignore these values and present only the utilization of <b>the currently running processes</b>.',
+               info: 'Per application statistics are collected using netdata\'s <code>apps.plugin</code>. This plugin walks through the entire <code>/proc</code> filesystem and aggregates statistics for applications of interest, defined in <code>/etc/netdata/apps_groups.conf</code> (the default is <a href="https://github.com/firehol/netdata/blob/master/conf.d/apps_groups.conf" target="_blank">here</a>). The plugin internally builds a process tree (much like <code>ps fax</code> does), and groups processes together (evaluating both child and parent processes) so that the result is always a chart with a predefined set of dimensions (of course, only application groups found running are reported). The reported values are compatible with <code>top</code>, although the netdata plugin counts also the resources of exited children (unlike <code>top</code> which shows only the resources of the currently running processes). So for processes like shell scripts, the reported values include the resources used by the commands these scripts run within each timeframe.',
                height: 1.5
        },
 
        'users': {
                title: 'Users',
-               info: 'Per user statistics are collected using netdata\'s <code>apps.plugin</code>. This plugin walks through the entire <code>/proc</code> filesystem and aggregates statistics per user.<br/><b>IMPORTANT</b>: The values shown here are not 100% accurate. They only include values for the processes running. If an application is spawning children continuously, which are terminated in just a few milliseconds (like shell scripts do), the values reported will be inaccurate. Linux does report the values for the exited children of a process. However, these values are reported to the parent process <b>only when the child exits</b>. If these values, of the exited child processes, were also aggregated in the charts below, the charts would have been full of spikes, presenting unrealistic utilization for each process group. So, we decided to ignore these values and present only the utilization of <b>the currently running processes</b>.',
+               info: 'Per user statistics are collected using netdata\'s <code>apps.plugin</code>. This plugin walks through the entire <code>/proc</code> filesystem and aggregates statistics per user. The reported values are compatible with <code>top</code>, although the netdata plugin counts also the resources of exited children (unlike <code>top</code> which shows only the resources of the currently running processes). So for processes like shell scripts, the reported values include the resources used by the commands these scripts run within each timeframe.',
                height: 1.5
        },
 
        'groups': {
                title: 'User Groups',
-               info: 'Per user group statistics are collected using netdata\'s <code>apps.plugin</code>. This plugin walks through the entire <code>/proc</code> filesystem and aggregates statistics per user group.<br/><b>IMPORTANT</b>: The values shown here are not 100% accurate. They only include values for the processes running. If an application is spawning children continuously, which are terminated in just a few milliseconds (like shell scripts do), the values reported will be inaccurate. Linux does report the values for the exited children of a process. However, these values are reported to the parent process <b>only when the child exits</b>. If these values, of the exited child processes, were also aggregated in the charts below, the charts would have been full of spikes, presenting unrealistic utilization for each process group. So, we decided to ignore these values and present only the utilization of <b>the currently running processes</b>.',
+               info: 'Per user group statistics are collected using netdata\'s <code>apps.plugin</code>. This plugin walks through the entire <code>/proc</code> filesystem and aggregates statistics per user group. The reported values are compatible with <code>top</code>, although the netdata plugin counts also the resources of exited children (unlike <code>top</code> which shows only the resources of the currently running processes). So for processes like shell scripts, the reported values include the resources used by the commands these scripts run within each timeframe.',
                height: 1.5
        },
 
@@ -1251,10 +1362,25 @@ var menuData = {
                info: undefined
        },
 
+       'phpfpm': {
+               title: 'PHP-FPM',
+               info: undefined,
+       },
+
+       'nginx': {
+               title: 'nginx',
+               info: undefined,
+       },
+/*
+       'apache': {
+               title: 'Apache',
+               info: undefined,
+       },*/
+
        'named': {
                title: 'named',
                info: undefined
-       },
+       }
 };
 
 var submenuData = {
@@ -1280,6 +1406,46 @@ var submenuData = {
 };
 
 var chartData = {
+       'mysql.net': {
+               info: 'The amount of data sent to mysql clients (<strong>out</strong>) and received from mysql clients (<strong>in</strong>).'
+       },
+
+       'mysql.queries': {
+               info: 'The number of statements executed by the server.<ul>' +
+               '<li><strong>queries</strong> counts the statements executed within stored SQL programs.</li>' +
+               '<li><strong>questions</strong> counts the statements sent to the mysql server by mysql clients.</li>' +
+               '<li><strong>slow queries</strong> counts the number of statements that took more than <a href="http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_long_query_time" target="_blank">long_query_time</a> seconds to be executed.' +
+               ' For more information about slow queries check the mysql <a href="http://dev.mysql.com/doc/refman/5.7/en/slow-query-log.html" target="_blank">slow query log</a>.</li>' +
+               '</ul>'
+       },
+
+       'mysql.handlers': {
+               info: 'Usage of the internal handlers of mysql. This chart provides very good insights of what the mysql server is actually doing.' +
+               ' (if the chart is not showing all these dimensions it is because they are zero - set <strong>Which dimensions to show?</strong> to <strong>All</strong> from the dashboard settings, to render even the zero values)<ul>' +
+               '<li><strong>commit</strong>, the number of internal <a href="http://dev.mysql.com/doc/refman/5.7/en/commit.html" target="_blank">COMMIT</a> statements.</li>' +
+               '<li><strong>delete</strong>, the number of times that rows have been deleted from tables.</li>' +
+               '<li><strong>prepare</strong>, a counter for the prepare phase of two-phase commit operations.</li>' +
+               '<li><strong>read first</strong>, the number of times the first entry in an index was read. A high value suggests that the server is doing a lot of full index scans; e.g. <strong>SELECT col1 FROM foo</strong>, with col1 indexed.</li>' +
+               '<li><strong>read key</strong>, the number of requests to read a row based on a key. If this value is high, it is a good indication that your tables are properly indexed for your queries.</li>' +
+               '<li><strong>read next</strong>, the number of requests to read the next row in key order. This value is incremented if you are querying an index column with a range constraint or if you are doing an index scan.</li>' +
+               '<li><strong>read prev</strong>, the number of requests to read the previous row in key order. This read method is mainly used to optimize <strong>ORDER BY ... DESC</strong>.</li>' +
+               '<li><strong>read rnd</strong>, the number of requests to read a row based on a fixed position. A high value indicates you are doing a lot of queries that require sorting of the result. You probably have a lot of queries that require MySQL to scan entire tables or you have joins that do not use keys properly.</li>' +
+               '<li><strong>read rnd next</strong>, the number of requests to read the next row in the data file. This value is high if you are doing a lot of table scans. Generally this suggests that your tables are not properly indexed or that your queries are not written to take advantage of the indexes you have.</li>' +
+               '<li><strong>rollback</strong>, the number of requests for a storage engine to perform a rollback operation.</li>' +
+               '<li><strong>savepoint</strong>, the number of requests for a storage engine to place a savepoint.</li>' +
+               '<li><strong>savepoint rollback</strong>, the number of requests for a storage engine to roll back to a savepoint.</li>' +
+               '<li><strong>update</strong>, the number of requests to update a row in a table.</li>' +
+               '<li><strong>write</strong>, the number of requests to insert a row in a table.</li>' +
+               '</ul>'
+       },
+
+       'mysql.table_locks': {
+               info: 'MySQL table locks counters: <ul>' +
+               '<li><strong>immediate</strong>, the number of times that a request for a table lock could be granted immediately.</li>' +
+               '<li><strong>waited</strong>, the number of times that a request for a table lock could not be granted immediately and a wait was needed. If this is high and you have performance problems, you should first optimize your queries, and then either split your table or tables or use replication.</li>' +
+               '</ul>'
+       },
+
        'system.cpu': {
                info: 'Total CPU utilization (all cores). 100% here means there is no CPU idle time at all. You can get per core usage at the <a href="#cpu">CPUs</a> section and per application usage at the <a href="#apps">Applications Monitoring</a> section.<br/>Keep an eye on <b>iowait</b> ' + sparkline('system.cpu', 'iowait', '%') + '. If it is constantly high, your disks are a bottleneck and they slow your system down.<br/>Another important metric worth monitoring, is <b>softirq</b> ' + sparkline('system.cpu', 'softirq', '%') + '. A constantly high percentage of softirq may indicate network drivers issues.'
        },
@@ -1650,6 +1816,9 @@ function enrichChartData(chart) {
                        break;
 
                case 'mysql':
+               case 'phpfpm':
+/*             case 'nginx':
+               case 'apache':*/
                case 'named':
                case 'cgroup':
                        chart.menu = chart.type;
@@ -1852,7 +2021,7 @@ function renderPage(menus, data) {
                // generate an entry at the main menu
 
                var menuid = name2id(menu);
-               sidebar += '<li class=""><a href="#' + menuid + '">' + menus[menu].title + '</a><ul class="nav">';
+               sidebar += '<li class=""><a href="#' + menuid + '" onClick="return scrollToId(\'' + menuid + '\');">' + menus[menu].title + '</a><ul class="nav">';
                html += '<div role="section"><div role="sectionhead"><h1 id="' + menuid + '" role="heading">' + menus[menu].title + '</h1></div><div id="menu_' + menuid + '" role="document">';
 
                if(menus[menu].info !== null)
@@ -1872,7 +2041,7 @@ function renderPage(menus, data) {
 
                        // generate an entry at the submenu
                        var submenuid = name2id(menu + '_' + submenu);
-                       sidebar += '<li class><a href="#' + submenuid + '">' + menus[menu].submenus[submenu].title + '</a></li>';
+                       sidebar += '<li class><a href="#' + submenuid + '" onClick="return scrollToId(\'' + submenuid + '\');">' + menus[menu].submenus[submenu].title + '</a></li>';
                        shtml += '<div class="netdata-group-container" id="submenu_' + submenuid + '" style="display: inline-block; width: ' + pcent_width.toString() + '%"><h2 id="' + submenuid + '" class="netdata-chart-alignment" role="heading">' + menus[menu].submenus[submenu].title + '</h2>';
 
                        if(menus[menu].submenus[submenu].info !== null)
@@ -1990,7 +2159,7 @@ function downloadAllCharts(netdata_url) {
                        options.hostname = data.hostname;
                        document.getElementById('hostname').innerHTML = options.hostname;
                        document.getElementById('hostname').href = NETDATA.serverDefault;
-                       document.title = options.hostname + ' dashboard';
+                       document.title = options.hostname + ' netdata dashboard';
 
                        renderChartsAndMenu(data);
 
@@ -2157,18 +2326,146 @@ function finalizePage() {
                NETDATA.globalPanAndZoom.setMaster(NETDATA.options.targets[0], after, before);
        }
 
+       // ------------------------------------------------------------------------
+       // https://github.com/viralpatel/jquery.shorten/blob/master/src/jquery.shorten.js
+       $.fn.shorten = function(settings) {
+               "use strict";
+
+               var config = {
+                       showChars: 750,
+                       minHideChars: 10,
+                       ellipsesText: "...",
+                       moreText: '<i class="fa fa-expand" aria-hidden="true"></i> show more information',
+                       lessText: '<i class="fa fa-compress" aria-hidden="true"></i> show less information',
+                       onLess: function() { NETDATA.onscroll(); },
+                       onMore: function() { NETDATA.onscroll(); },
+                       errMsg: null,
+                       force: false
+               };
+
+               if (settings) {
+                       $.extend(config, settings);
+               }
+
+               if ($(this).data('jquery.shorten') && !config.force) {
+                       return false;
+               }
+               $(this).data('jquery.shorten', true);
+
+               $(document).off("click", '.morelink');
+
+               $(document).on({
+                       click: function() {
+
+                               var $this = $(this);
+                               if ($this.hasClass('less')) {
+                                       $this.removeClass('less');
+                                       $this.html(config.moreText);
+                                       $this.parent().prev().animate({'height':'0'+'%'}, 0, function () { $this.parent().prev().prev().show(); }).hide(0, function() {
+                                               config.onLess();
+                                       });
+
+                               } else {
+                                       $this.addClass('less');
+                                       $this.html(config.lessText);
+                                       $this.parent().prev().animate({'height':'100'+'%'}, 0, function () { $this.parent().prev().prev().hide(); }).show(0, function() {
+                                               config.onMore();
+                                       });
+                               }
+                               return false;
+                       }
+               }, '.morelink');
+
+               return this.each(function() {
+                       var $this = $(this);
+
+                       var content = $this.html();
+                       var contentlen = $this.text().length;
+                       if (contentlen > config.showChars + config.minHideChars) {
+                               var c = content.substr(0, config.showChars);
+                               if (c.indexOf('<') >= 0) // If there's HTML don't want to cut it
+                               {
+                                       var inTag = false; // I'm in a tag?
+                                       var bag = ''; // Put the characters to be shown here
+                                       var countChars = 0; // Current bag size
+                                       var openTags = []; // Stack for opened tags, so I can close them later
+                                       var tagName = null;
+
+                                       for (var i = 0, r = 0; r <= config.showChars; i++) {
+                                               if (content[i] == '<' && !inTag) {
+                                                       inTag = true;
+
+                                                       // This could be "tag" or "/tag"
+                                                       tagName = content.substring(i + 1, content.indexOf('>', i));
+
+                                                       // If its a closing tag
+                                                       if (tagName[0] == '/') {
+
+
+                                                               if (tagName != '/' + openTags[0]) {
+                                                                       config.errMsg = 'ERROR en HTML: the top of the stack should be the tag that closes';
+                                                               } else {
+                                                                       openTags.shift(); // Pops the last tag from the open tag stack (the tag is closed in the retult HTML!)
+                                                               }
+
+                                                       } else {
+                                                               // There are some nasty tags that don't have a close tag like <br/>
+                                                               if (tagName.toLowerCase() != 'br') {
+                                                                       openTags.unshift(tagName); // Add to start the name of the tag that opens
+                                                               }
+                                                       }
+                                               }
+                                               if (inTag && content[i] == '>') {
+                                                       inTag = false;
+                                               }
+
+                                               if (inTag) { bag += content.charAt(i); } // Add tag name chars to the result
+                                               else {
+                                                       r++;
+                                                       if (countChars <= config.showChars) {
+                                                               bag += content.charAt(i); // Fix to ie 7 not allowing you to reference string characters using the []
+                                                               countChars++;
+                                                       } else // Now I have the characters needed
+                                                       {
+                                                               if (openTags.length > 0) // I have unclosed tags
+                                                               {
+                                                                       //console.log('They were open tags');
+                                                                       //console.log(openTags);
+                                                                       for (j = 0; j < openTags.length; j++) {
+                                                                               //console.log('Cierro tag ' + openTags[j]);
+                                                                               bag += '</' + openTags[j] + '>'; // Close all tags that were opened
+
+                                                                               // You could shift the tag from the stack to check if you end with an empty stack, that means you have closed all open tags
+                                                                       }
+                                                                       break;
+                                                               }
+                                                       }
+                                               }
+                                       }
+                                       c = $('<div/>').html(bag + '<span class="ellip">' + config.ellipsesText + '</span>').html();
+                               }else{
+                                       c+=config.ellipsesText;
+                               }
+
+                               var html = '<div class="shortcontent">' + c +
+                                               '</div><div class="allcontent">' + content +
+                                               '</div><span><a href="javascript://nop/" class="morelink">' + config.moreText + '</a></span>';
+
+                               $this.html(html);
+                               $this.find(".allcontent").hide(); // Hide all text
+                               $('.shortcontent p:last', $this).css('margin-bottom', 0); //Remove bottom margin on last paragraph as it's likely shortened
+                       }
+               });
+
+       };
+       $(".chart-message").shorten();
+       // ------------------------------------------------------------------------
+
        // let it run (update the charts)
        NETDATA.unpause();
 
        // check if we have to jump to a specific section
-       var hash = location.hash.replace('#','');
-       if(hash != '') {
-               // Clear the hash in the URL
-               // location.hash = '';   // delete front "//" if you want to change the address bar
-               var offset = $(location.hash).offset();
-               if(typeof offset !== 'undefined')
-                       $('html, body').animate({ scrollTop: offset.top }, 0);
-       }
+       scrollToId(location.hash.replace('#',''));
 
        /* activate bootstrap sidebar (affix) */
        $('#sidebar').affix({
@@ -2191,8 +2488,14 @@ function finalizePage() {
                offset: $(window).height() / 3 // controls the diff of the <hX> element to the top, to select it
        });
 
-       document.getElementById('footer').style.display = 'block';
+       // change the URL based on the current position of the screen
+       $('#sidebar').on('activate.bs.scrollspy', function (e) {
+               var el = $(e.target);
+               if(el.find('ul').size() == 0)
+                       history.replaceState(null, document.title, el.find('a').attr('href'));
+       });
 
+       document.getElementById('footer').style.display = 'block';
 
        var update_options_modal = function() {
                // console.log('update_options_modal');