]> arthur.barton.de Git - netdata.git/blobdiff - web/index.html
Merge branch 'master' into ab-debian
[netdata.git] / web / index.html
index 55a605aa786767155de45bc8051e2125b1d3c9dc..3283b16e89cf53e215ca0a6758a30758ff6a783b 100644 (file)
@@ -35,7 +35,7 @@
     <meta property="og:site_name"          content="netdata"/>
     <meta property="og:title"              content="Get control of your Linux Servers. Simple. Effective. Awesome." />
     <meta property="og:description"        content="Unparalleled insights, in real-time, of everything happening on your Linux systems and applications, with stunning, interactive web dashboards and powerful performance and health alarms." />
-    <meta property="og:image"              content="https://cloud.githubusercontent.com/assets/2662304/20910305/65d10354-bb69-11e6-8128-c44b547517b4.png" />
+    <meta property="og:image"              content="https://cloud.githubusercontent.com/assets/2662304/22945737/e98cd0c6-f2fd-11e6-96f1-5501934b0955.png" />
     <meta property="og:image:type"         content="image/png" />
     <meta property="fb:app_id"             content="1200089276712916" />
 
         // --------------------------------------------------------------------
         // check options that should be processed before loading netdata.js
 
+        var localStorageTested = -1;
+        function localStorageTest() {
+            if(localStorageTested !== -1)
+                return localStorageTested;
+
+            if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
+                var test = 'test';
+                try {
+                    localStorage.setItem(test, test);
+                    localStorage.removeItem(test);
+                    localStorageTested = true;
+                }
+                catch (e) {
+                    localStorageTested = false;
+                }
+            }
+            else
+                localStorageTested = false;
+
+            return localStorageTested;
+        }
+
         function loadLocalStorage(name) {
             var ret = null;
 
             try {
-                if(typeof Storage !== "undefined" && typeof localStorage === 'object')
+                if(localStorageTest() === true)
                     ret = localStorage.getItem(name);
             }
-            catch(error) {
-                ;
-            }
+            catch(error) {}
 
             if(typeof ret === 'undefined' || ret === null)
                 return null;
         function saveLocalStorage(name, value) {
             // console.log('saving: ' + name.toString() + ' = ' + value.toString());
             try {
-                if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
+                if(localStorageTest() === true) {
                     localStorage.setItem(name, value.toString());
                     return true;
                 }
             }
-            catch(error) {
-                ;
-            }
+            catch(error) {}
 
             return false;
         }
         var netdataRegistryCallback = function(machines_array) {
             var el = '';
             var a1 = '';
-            var found = 0;
+            var found = 0, hosted = 0;
+            var len, i, url, hostname, icon;
+
+            if(options.hosts.length > 1) {
+                // there are mirrored hosts here
+
+                el += '<li><a href="#" onClick="return false;" style="color: #666;" target="_blank">databases available on this host</a></li>';
+                a1 += '<li><a href="#" onClick="return false;"><i class="fa fa-info-circle" aria-hidden="true" style="color: #666;"></i></a></li>';
+
+                var base = document.location.origin.toString() + document.location.pathname.toString();
+                if(base.endsWith("/host/" + options.hostname + "/"))
+                    base = base.substring(0, base.length - ("/host/" + options.hostname + "/").toString().length);
+
+                if(base.endsWith("/"))
+                    base = base.substring(0, base.length - 1);
+
+                var master = options.hosts[0].hostname;
+                var sorted = options.hosts.sort(function(a, b) {
+                    if(a.hostname === master) return -1;
+                    if(a.hostname === b.hostname) return 0;
+                    else if(a.hostname > b.hostname) return 1;
+                    return -1;
+                });
+
+                i = 0;
+                len = sorted.length;
+                while(len--) {
+                    hostname = sorted[i].hostname;
+                    if(hostname == master) {
+                        url = base + "/";
+                        icon = "home";
+                    }
+                    else {
+                        url = base + "/host/" + hostname + "/";
+                        icon = "window-restore";
+                    }
+
+                    el += '<li id="registry_server_hosted_' + len.toString() + '"><a class="registry_link" href="' + url + '" onClick="return gotoHostedModalHandler(\'' + url + '\');">' + hostname + '</a></li>';
+                    a1 += '<li id="registry_action_hosted_' + len.toString() + '"><a class="registry_link" href="' + url + '" onClick="return gotoHostedModalHandler(\'' + url + '\');"><i class="fa fa-' + icon + '" aria-hidden="true" style="color: #999;"></i></a></li>';
+                    hosted++;
+                    i++;
+                }
+
+                el += '<li role="separator" class="divider"></li>';
+                a1 += '<li role="separator" class="divider"></li>';
+            }
 
             if(machines_array === null) {
                 var ret = loadLocalStorage("registryCallback");
                     return 0;
                 });
 
-                var len = machines.length;
+                len = machines.length;
                 while(len--) {
                     var u = machines[len];
                     found++;
                             this_is_demo = true;
                 }
             }
-            catch(error) {
-                ;
-            }
-
+            catch(error) {}
             return this_is_demo;
         }
 
         }
 
         function netdataReload(url) {
-            var t = netdataURL(url);
-            // console.log('netdataReload: ' + t);
-            document.location = t;
+            document.location = netdataURL(url);
 
             // since we play with hash
             // this is needed to reload the page
             location.reload();
         }
 
+        function gotoHostedModalHandler(url) {
+            document.location = url + urlOptions.genHash();
+            return false;
+        }
+
         var gotoServerValidateRemaining = 0;
         var gotoServerMiddleClick = false;
         var gotoServerStop = false;
 
         var deleteRegistryUrl = null;
         function deleteRegistryModalHandler(guid, name, url) {
+            void(guid);
+
             deleteRegistryUrl = url;
             document.getElementById('deleteRegistryServerName').innerHTML = name;
             document.getElementById('deleteRegistryServerName2').innerHTML = name;
         }
 
         var options = {
-            sparklines_registry: {},
             menus: {},
             submenu_names: {},
             data: null,
             hostname: 'netdata_server', // will be overwritten by the netdata server
-            categories: new Array(),
+            version: 'unknown',
+            categories: [],
             categories_idx: {},
-            families: new Array(),
+            families: [],
             families_idx: {},
+            hosts: [],
 
             chartsPerRow: 0,
-            chartsMinWidth: 1450,
-            chartsHeight: 180,
-            sparklinesHeight: 60,
+            // chartsMinWidth: 1450,
+            chartsHeight: 180
         };
 
-        // generate a sparkline
-        // used in the documentation
-        function sparkline(chart, dimension, units) {
-            var key = chart + '.' + dimension;
-
-            if(typeof units === 'undefined')
-                units = '';
-
-            if(typeof options.sparklines_registry[key] === 'undefined')
-                options.sparklines_registry[key] = { count: 1 };
-            else
-                options.sparklines_registry[key].count++;
-
-            key = key + '.' + options.sparklines_registry[key].count;
-
-            var h = '<div data-netdata="' + chart + '" data-after="-120" data-width="25%" data-height="15px" data-chart-library="dygraph" data-dygraph-theme="sparkline" data-dimensions="' + dimension + '" data-show-value-of-' + dimension + '-at="' + key + '"></div> (<span id="' + key + '" style="display: inline-block; min-width: 50px; text-align: right;">X</span>' + units + ')';
-
-            return h;
-        }
-
         function chartsPerRow(total) {
             if(options.chartsPerRow === 0) {
-                width = Math.floor(total / options.chartsMinWidth);
-                if(width === 0) width = 1;
-                return width;
+                return 1;
+                //var width = Math.floor(total / options.chartsMinWidth);
+                //if(width === 0) width = 1;
+                //return width;
             }
             else return options.chartsPerRow;
         }
 
         function sortObjectByPriority(object) {
             var idx = {};
-            var sorted = new Array();
+            var sorted = [];
 
             for(var i in object) {
+                if(!object.hasOwnProperty(i)) continue;
+
                 if(typeof idx[i] === 'undefined') {
                     idx[i] = object[i];
                     sorted.push(i);
 
         // ----------------------------------------------------------------------------
 
+        // user editable information
+        var customDashboard = {
+            menu: {},
+            submenu: {},
+            context: {}
+        };
+
+        // netdata standard information
         var netdataDashboard = {
+            sparklines_registry: {},
+            os: 'unknown',
+
             menu: {},
             submenu: {},
             context: {},
 
+            // generate a sparkline
+            // used in the documentation
+            sparkline: function (prefix, chart, dimension, units, suffix) {
+                if(options.data === null || typeof options.data.charts === 'undefined')
+                    return '';
+
+                if(typeof options.data.charts[chart] === 'undefined')
+                    return '';
+
+                if(typeof options.data.charts[chart].dimensions === 'undefined')
+                    return '';
+
+                if(typeof options.data.charts[chart].dimensions[dimension] === 'undefined')
+                    return '';
+
+                var key = chart + '.' + dimension;
+
+                if(typeof units === 'undefined')
+                    units = '';
+
+                if(typeof this.sparklines_registry[key] === 'undefined')
+                    this.sparklines_registry[key] = { count: 1 };
+                else
+                    this.sparklines_registry[key].count++;
+
+                key = key + '.' + this.sparklines_registry[key].count;
+
+                return prefix + '<div data-netdata="' + chart + '" data-after="-120" data-width="25%" data-height="15px" data-chart-library="dygraph" data-dygraph-theme="sparkline" data-dimensions="' + dimension + '" data-show-value-of-' + dimension + '-at="' + key + '"></div> (<span id="' + key + '" style="display: inline-block; min-width: 50px; text-align: right;">X</span>' + units + ')' + suffix;
+            },
+
             gaugeChart: function(title, width, dimensions, colors) {
                 if(typeof colors === 'undefined')
                     colors = '';
                     dimensions = '';
 
                 return '<div data-netdata="CHART_UNIQUE_ID"'
-                                        + ' data-dimensions="' + dimensions + '"'
-                                        + ' data-chart-library="gauge"'
-                                        + ' data-gauge-adjust="width"'
-                                        + ' data-title="' + title + '"'
-                                        + ' data-width="' + width + '"'
-                                        + ' data-before="0"'
-                                        + ' data-after="-CHART_DURATION"'
-                                        + ' data-points="CHART_DURATION"'
-                                        + ' data-colors="' + colors + '"'
-                                        + ' role="application"></div>';
+                            + ' data-dimensions="' + dimensions + '"'
+                            + ' data-chart-library="gauge"'
+                            + ' data-gauge-adjust="width"'
+                            + ' data-title="' + title + '"'
+                            + ' data-width="' + width + '"'
+                            + ' data-before="0"'
+                            + ' data-after="-CHART_DURATION"'
+                            + ' data-points="CHART_DURATION"'
+                            + ' data-colors="' + colors + '"'
+                            + ' role="application"></div>';
             },
 
             anyAttribute: function(obj, attr, key, def) {
-                if(typeof obj[key] !== 'undefined') {
-                    if(typeof obj[key][attr] !== 'undefined')
-                        return obj[key][attr];
+                if(typeof(obj[key]) !== 'undefined') {
+                    var x = obj[key][attr];
+
+                    if(typeof(x) === 'undefined')
+                        return def;
+
+                    if(typeof(x) === 'function') {
+                        return x(netdataDashboard.os);
+                    }
+
+                    return x;
                 }
+
                 return def;
             },
 
             menuTitle: function(chart) {
                 if(typeof chart.menu_pattern !== 'undefined') {
-                    return (netdataDashboard.anyAttribute(netdataDashboard.menu, 'title', chart.menu_pattern, chart.menu_pattern).toString()
+                    return (this.anyAttribute(this.menu, 'title', chart.menu_pattern, chart.menu_pattern).toString()
                             + '&nbsp;' + chart.type.slice(-(chart.type.length - chart.menu_pattern.length - 1)).toString()).replace(/_/g, ' ');
                 }
 
-                return (netdataDashboard.anyAttribute(netdataDashboard.menu, 'title', chart.menu, chart.menu)).toString().replace(/_/g, ' ');
+                return (this.anyAttribute(this.menu, 'title', chart.menu, chart.menu)).toString().replace(/_/g, ' ');
             },
 
             menuIcon: function(chart) {
                 if(typeof chart.menu_pattern !== 'undefined')
-                    return netdataDashboard.anyAttribute(netdataDashboard.menu, 'icon', chart.menu_pattern, '<i class="fa fa-puzzle-piece" aria-hidden="true"></i>').toString();
+                    return this.anyAttribute(this.menu, 'icon', chart.menu_pattern, '<i class="fa fa-puzzle-piece" aria-hidden="true"></i>').toString();
 
-                return netdataDashboard.anyAttribute(netdataDashboard.menu, 'icon', chart.menu, '<i class="fa fa-puzzle-piece" aria-hidden="true"></i>');
+                return this.anyAttribute(this.menu, 'icon', chart.menu, '<i class="fa fa-puzzle-piece" aria-hidden="true"></i>');
             },
 
-            menuInfo: function(menu) {
-                return netdataDashboard.anyAttribute(netdataDashboard.menu, 'info', menu, null);
+            menuInfo: function(chart) {
+                if(typeof chart.menu_pattern !== 'undefined')
+                    return this.anyAttribute(this.menu, 'info', chart.menu_pattern, null);
+
+                return this.anyAttribute(this.menu, 'info', chart.menu, null);
             },
 
-            menuHeight: function(menu, relative) {
-                return netdataDashboard.anyAttribute(netdataDashboard.menu, 'height', menu, 1.0) * relative;
+            menuHeight: function(chart) {
+                if(typeof chart.menu_pattern !== 'undefined')
+                    return this.anyAttribute(this.menu, 'height', chart.menu_pattern, 1.0);
+
+                return this.anyAttribute(this.menu, 'height', chart.menu, 1.0);
             },
 
             submenuTitle: function(menu, submenu) {
                 var key = menu + '.' + submenu;
-                var title = netdataDashboard.anyAttribute(netdataDashboard.submenu, 'title', key, submenu).toString().replace(/_/g, ' ');;
+                // console.log(key);
+                var title = this.anyAttribute(this.submenu, 'title', key, submenu).toString().replace(/_/g, ' ');
                 if(title.length > 28) {
                     var a = title.substring(0, 13);
                     var b = title.substring(title.length - 12, title.length);
 
             submenuInfo: function(menu, submenu) {
                 var key = menu + '.' + submenu;
-                return netdataDashboard.anyAttribute(netdataDashboard.submenu, 'info', key, null);
+                return this.anyAttribute(this.submenu, 'info', key, null);
             },
 
             submenuHeight: function(menu, submenu, relative) {
                 var key = menu + '.' + submenu;
-                return netdataDashboard.anyAttribute(netdataDashboard.submenu, 'height', key, 1.0) * relative;
+                return this.anyAttribute(this.submenu, 'height', key, 1.0) * relative;
             },
 
             contextInfo: function(id) {
-                if(typeof netdataDashboard.context[id] !== 'undefined' && typeof netdataDashboard.context[id].info !== 'undefined')
-                    return '<div class="chart-message netdata-chart-alignment" role="document">' + netdataDashboard.context[id].info + '</div>';
+                var x = this.anyAttribute(this.context, 'info', id, null);
+
+                if(x !== null)
+                    return '<div class="chart-message netdata-chart-alignment" role="document">' + x + '</div>';
                 else
                     return '';
             },
 
             contextValueRange: function(id) {
-                if(typeof netdataDashboard.context[id] !== 'undefined' && typeof netdataDashboard.context[id].valueRange !== 'undefined')
-                    return netdataDashboard.context[id].valueRange;
+                if(typeof this.context[id] !== 'undefined' && typeof this.context[id].valueRange !== 'undefined')
+                    return this.context[id].valueRange;
                 else
                     return '[null, null]';
             },
 
             contextHeight: function(id, def) {
-                if(typeof netdataDashboard.context[id] !== 'undefined' && typeof netdataDashboard.context[id].height !== 'undefined')
-                    return def * netdataDashboard.context[id].height;
+                if(typeof this.context[id] !== 'undefined' && typeof this.context[id].height !== 'undefined')
+                    return def * this.context[id].height;
                 else
                     return def;
             }
 
         // enrich the data structure returned by netdata
         // to reflect our menu system and content
+        // FIXME: this is a shame - we should fix charts naming (issue #807)
         function enrichChartData(chart) {
-            var tmp = chart.type.split('_')[0];
+            var parts = chart.type.split('_');
+            var tmp = parts[0];
 
             switch(tmp) {
                 case 'ap':
                     chart.menu = tmp;
                     break;
 
+                case 'apache':
+                    chart.menu = chart.type;
+                    if(parts.length > 2 && parts[1] === 'cache')
+                        chart.menu_pattern = tmp + '_' + parts[1];
+                    else if(parts.length > 1)
+                        chart.menu_pattern = tmp;
+                    break;
+
+                case 'bind':
+                    chart.menu = chart.type;
+                    if(parts.length > 2 && parts[1] === 'rndc')
+                        chart.menu_pattern = tmp + '_' + parts[1];
+                    else if(parts.length > 1)
+                        chart.menu_pattern = tmp;
+                    break;
+
                 case 'cgroup':
                     chart.menu = chart.type;
                     if(chart.id.match(/.*[\._\/-:]qemu[\._\/-:]*/) || chart.id.match(/.*[\._\/-:]kvm[\._\/-:]*/))
                         chart.menu_pattern = 'cgroup';
                     break;
 
-                case 'apache':
-                case 'exim':
-                case 'dovecot':
-                case 'hddtemp':
-                case 'ipfs':
-                case 'memcached':
-                case 'mysql':
-                case 'named':
-                case 'nginx':
-                case 'nut':
-                case 'phpfpm':
-                case 'postfix':
-                case 'postgres':
-                case 'redis':
-                case 'retroshare':
-                case 'smawebbox':
-                case 'snmp':
-                case 'squid':
-                case 'tomcat':
+                case 'isc':
                     chart.menu = chart.type;
-                    chart.menu_pattern = tmp;
+                    if(parts.length > 2 && parts[1] === 'dhcpd')
+                        chart.menu_pattern = tmp + '_' + parts[1];
+                    else if(parts.length > 1)
+                        chart.menu_pattern = tmp;
+                    break;
+
+                case 'ovpn':
+                    chart.menu = chart.type;
+                    if(parts.length > 3 && parts[1] === 'status' && parts[2] === 'log')
+                        chart.menu_pattern = tmp + '_' + parts[1];
+                    else if(parts.length > 1)
+                        chart.menu_pattern = tmp;
+                    break;
+
+                case 'smartd':
+                case 'web':
+                    chart.menu = chart.type;
+                    if(parts.length > 2 && parts[1] === 'log')
+                        chart.menu_pattern = tmp + '_' + parts[1];
+                    else if(parts.length > 1)
+                        chart.menu_pattern = tmp;
                     break;
 
                 case 'tc':
 
                 default:
                     chart.menu = chart.type;
+                    if(parts.length > 1)
+                        chart.menu_pattern = tmp;
                     break;
             }
 
 
         // ----------------------------------------------------------------------------
 
-        function headMain(charts, duration) {
+        function headMain(os, charts, duration) {
+            void(os);
+
             var head = '';
 
             if(typeof charts['system.swap'] !== 'undefined')
                 var hi = 0, hlen = hcharts.length;
                 while(hi < hlen) {
                     if(typeof hcharts[hi] === 'function')
-                        head += hcharts[hi](chart.id).replace('CHART_DURATION', duration.toString()).replace('CHART_UNIQUE_ID', chart.id);
+                        head += hcharts[hi](netdataDashboard.os, chart.id).replace('CHART_DURATION', duration.toString()).replace('CHART_UNIQUE_ID', chart.id);
                     else
                         head += hcharts[hi].replace('CHART_DURATION', duration.toString()).replace('CHART_UNIQUE_ID', chart.id);
                     hi++;
             var duration = Math.round(($(div).width() * pcent_width / 100 * data.update_every / 3) / 60) * 60;
             var html = '';
             var sidebar = '<ul class="nav dashboard-sidenav" data-spy="affix" id="sidebar_ul">';
-            var mainhead = headMain(data.charts, duration);
+            var mainhead = headMain(netdataDashboard.os, data.charts, duration);
 
             // sort the menus
             var main = sortObjectByPriority(menus);
 
             sidebar += '<li class="" style="padding-top:15px;"><a href="https://github.com/firehol/netdata/wiki/Add-more-charts-to-netdata" target="_blank"><i class="fa fa-plus" aria-hidden="true"></i> add more charts</a></li>';
             sidebar += '<li class=""><a href="https://github.com/firehol/netdata/wiki/Add-more-alarms-to-netdata" target="_blank"><i class="fa fa-plus" aria-hidden="true"></i> add more alarms</a></li>';
-            sidebar += '<li class="" style="margin:20px;color:#666;"><small>netdata on <b>' + data.hostname.toString() + '</b>, collects every ' + ((data.update_every == 1)?'second':data.update_every.toString() + ' seconds') + ' <b>' + data.dimensions_count.toLocaleString() + '</b> metrics, presented as <b>' + data.charts_count.toLocaleString() + '</b> charts and monitored by <b>' + data.alarms_count.toLocaleString() + '</b> alarms, using ' + Math.round(data.rrd_memory_bytes / 1024 / 1024).toLocaleString() + ' MB of memory for ' + Math.round(data.history / (3600/data.update_every)).toLocaleString() + ' ' + ((data.history == (3600/data.update_every))?'hour':'hours').toString() + ' of real-time history.</small></li>';
+            sidebar += '<li class="" style="margin:20px;color:#666;"><small>netdata on <b>' + data.hostname.toString() + '</b>, collects every ' + ((data.update_every == 1)?'second':data.update_every.toString() + ' seconds') + ' <b>' + data.dimensions_count.toLocaleString() + '</b> metrics, presented as <b>' + data.charts_count.toLocaleString() + '</b> charts and monitored by <b>' + data.alarms_count.toLocaleString() + '</b> alarms, using ' + Math.round(data.rrd_memory_bytes / 1024 / 1024).toLocaleString() + ' MB of memory for ' + seconds4human(data.update_every * data.history) + ' of real-time history.<br/>&nbsp;<br/><b>netdata</b><br/>v' +  data.version.toString() +'</small></li>';
             sidebar += '</ul>';
             div.innerHTML = html;
             document.getElementById('sidebar').innerHTML = sidebar;
         function renderChartsAndMenu(data) {
             var menus = options.menus;
             var charts = data.charts;
+            var m, menu_key;
 
             for(var c in charts) {
-                enrichChartData(charts[c]);
+                if(!charts.hasOwnProperty(c)) continue;
+
+                var chart = charts[c];
+                enrichChartData(chart);
+                m = chart.menu;
 
                 // create the menu
-                if(typeof menus[charts[c].menu] === 'undefined') {
-                    menus[charts[c].menu] = {
-                        priority: charts[c].priority,
+                if(typeof menus[m] === 'undefined') {
+                    menus[m] = {
+                        menu_pattern: chart.menu_pattern,
+                        priority: chart.priority,
                         submenus: {},
-                        title: netdataDashboard.menuTitle(charts[c]),
-                        icon: netdataDashboard.menuIcon(charts[c]),
-                        info: netdataDashboard.menuInfo(charts[c].menu),
-                        height: netdataDashboard.menuHeight(charts[c].menu, options.chartsHeight)
+                        title: netdataDashboard.menuTitle(chart),
+                        icon: netdataDashboard.menuIcon(chart),
+                        info: netdataDashboard.menuInfo(chart),
+                        height: netdataDashboard.menuHeight(chart) * options.chartsHeight
                     };
                 }
+                else {
+                    if(typeof(menus[m].menu_pattern) === 'undefined')
+                        menus[m].menu_pattern = chart.menu_pattern;
 
-                if(charts[c].priority < menus[charts[c].menu].priority)
-                    menus[charts[c].menu].priority = charts[c].priority;
+                    if(chart.priority < menus[m].priority)
+                        menus[m].priority = chart.priority;
+                }
+
+                menu_key = (typeof(menus[m].menu_pattern) !== 'undefined')?menus[m].menu_pattern:m;
 
                 // create the submenu
-                if(typeof menus[charts[c].menu].submenus[charts[c].submenu] === 'undefined') {
-                    menus[charts[c].menu].submenus[charts[c].submenu] = {
-                        priority: charts[c].priority,
-                        charts: new Array(),
+                if(typeof menus[m].submenus[chart.submenu] === 'undefined') {
+                    menus[m].submenus[chart.submenu] = {
+                        priority: chart.priority,
+                        charts: [],
                         title: null,
-                        info: netdataDashboard.submenuInfo(charts[c].menu, charts[c].submenu),
-                        height: netdataDashboard.submenuHeight(charts[c].menu, charts[c].submenu, menus[charts[c].menu].height)
+                        info: netdataDashboard.submenuInfo(menu_key, chart.submenu),
+                        height: netdataDashboard.submenuHeight(menu_key, chart.submenu, menus[m].height)
                     };
                 }
-
-                if(charts[c].priority < menus[charts[c].menu].submenus[charts[c].submenu].priority)
-                    menus[charts[c].menu].submenus[charts[c].submenu].priority = charts[c].priority;
+                else {
+                    if (chart.priority < menus[m].submenus[chart.submenu].priority)
+                        menus[m].submenus[chart.submenu].priority = chart.priority;
+                }
 
                 // index the chart in the menu/submenu
-                menus[charts[c].menu].submenus[charts[c].submenu].charts.push(charts[c]);
+                menus[m].submenus[chart.submenu].charts.push(chart);
             }
 
             // propagate the descriptive subname given to QoS
             // to all the other submenus with the same name
-            for(var m in menus) {
+            for(m in menus) {
+                if(!menus.hasOwnProperty(m)) continue;
+
                 for(var s in menus[m].submenus) {
+                    if(!menus[m].submenus.hasOwnProperty(s)) continue;
+
                     // set the family using a name
                     if(typeof options.submenu_names[s] !== 'undefined') {
                         menus[m].submenus[s].title = s + ' (' + options.submenu_names[s] + ')';
                     }
                     else {
-                        menus[m].submenus[s].title = netdataDashboard.submenuTitle(m, s);
+                        menu_key = (typeof(menus[m].menu_pattern) !== 'undefined')?menus[m].menu_pattern:m;
+                        menus[m].submenus[s].title = netdataDashboard.submenuTitle(menu_key, s);
                     }
                 }
             }
         var bootstrapTableLoaded = false;
         function loadBootstrapTable(callback) {
             if(bootstrapTableLoaded === false) {
-                bootstrapTableLoaded === true;
+                bootstrapTableLoaded = true;
                 loadJs(NETDATA.serverDefault + 'lib/bootstrap-table-1.11.0.min.js', function() {
                     loadJs(NETDATA.serverDefault + 'lib/bootstrap-table-export-1.11.0.min.js', function() {
                         loadJs(NETDATA.serverDefault + 'lib/tableExport-1.6.0.min.js', callback);
             var footer = '<hr/><a href="https://github.com/firehol/netdata/wiki/Generating-Badges" target="_blank">netdata badges</a> refresh automatically. Their color indicates the state of the alarm: <span style="color: #e05d44"><b>&nbsp;red&nbsp;</b></span> is critical, <span style="color:#fe7d37"><b>&nbsp;orange&nbsp;</b></span> is warning, <span style="color: #4c1"><b>&nbsp;bright green&nbsp;</b></span> is ok, <span style="color: #9f9f9f"><b>&nbsp;light grey&nbsp;</b></span> is undefined (i.e. no data or no status), <span style="color: #000"><b>&nbsp;black&nbsp;</b></span> is not initialized. You can copy and paste their URLs to embed them in any web page.<br/>netdata can send notifications for these alarms. Check <a href="https://github.com/firehol/netdata/blob/master/conf.d/health_alarm_notify.conf">this configuration file</a> for more information.';
 
             NETDATA.alarms.get('all', function(data) {
-                options.alarm_families = new Array();
+                options.alarm_families = [];
 
                 alarmsCallback(data);
 
                     return t.toLocaleDateString() + space + t.toLocaleTimeString();
                 }
 
-                function seconds4human(seconds, options) {
-                    var default_options = {
-                        now: 'now',
-                        space: '&nbsp;',
-                        negative_suffix: 'ago',
-                        hour: 'hour',
-                        hours: 'hours',
-                        minute: 'minute',
-                        minutes: 'minutes',
-                        second: 'second',
-                        seconds: 'seconds',
-                        and: 'and'
-                    };
-
-                    if(typeof options !== 'object')
-                        options = default_options;
-                    else {
-                        var x;
-                        for(x in default_options) {
-                            if(typeof options[x] !== 'string')
-                                options[x] = default_options[x];
-                        }
-                    }
-
-                    if(typeof seconds === 'string')
-                        seconds = parseInt(seconds);
-
-                    if(seconds === 0)
-                        return options.now;
-
-                    var suffix = '';
-                    if(seconds < 0) {
-                        seconds = -seconds;
-                        if(options.negative_suffix !== '') suffix = options.space + options.negative_suffix;
-                    }
-
-                    var hours = Math.floor(seconds / 3600);
-                    seconds -= (hours * 3600);
-
-                    var minutes = Math.floor(seconds / 60);
-                    seconds -= (minutes * 60);
-
-                    var txt = '';
-
-                    if(hours > 1) txt += hours.toString() + options.space + options.hours;
-                    else if(hours === 1) txt += hours.toString() + options.space + options.hour;
-
-                    if(hours > 0 && minutes > 0 && seconds == 0)
-                        txt += options.space + options.and + options.space;
-                    else if(hours > 0 && minutes > 0 && seconds > 0)
-                        txt += ',' + options.space;
-
-                    if(minutes > 1) txt += minutes.toString() + options.space + options.minutes;
-                    else if(minutes === 1) txt += minutes.toString() + options.space + options.minute;
-
-                    if((minutes > 0 || minutes > 0) && seconds > 0)
-                        txt += options.space + options.and + options.space;
-
-                    if(seconds > 1) txt += Math.floor(seconds).toString() + options.space + options.seconds;
-                    else if(seconds === 1) txt += Math.floor(seconds).toString() + options.space + options.second;
-
-                    return txt + suffix;
-                }
-
                 function alarm_lookup_explain(alarm, chart) {
                     var dimensions = ' of all values ';
 
                         return '';
                     }
 
-                    var has_alarm = ((typeof alarm.warn !== 'undefined' || typeof alarm.crit !== 'undefined')?true:false);
+                    var has_alarm = (typeof alarm.warn !== 'undefined' || typeof alarm.crit !== 'undefined');
 
                     var role_href = ((has_alarm === true)?('<br/>&nbsp;<br/>role: <b>' + alarm.recipient + '</b><br/>&nbsp;<br/><b><i class="fa fa-line-chart" aria-hidden="true"></i></b><small>&nbsp;&nbsp;<a href="#" onClick="NETDATA.alarms.scrollToChart(\'' + alarm.chart + '\'); $(\'#alarmsModal\').modal(\'hide\'); return false;">jump to chart</a></small>'):('&nbsp;'));
 
 
                 // find the proper family of each alarm
                 var now = Date.now();
-                var x;
+                var x, family, alarm;
                 var count_active = 0;
                 var count_all = 0;
                 var families = {};
-                var families_sort = new Array();
+                var families_sort = [];
                 for(x in data.alarms) {
-                    var alarm = data.alarms[x];
-                    var family = alarm.family;
+                    if(!data.alarms.hasOwnProperty(x)) continue;
+
+                    alarm = data.alarms[x];
+                    family = alarm.family;
 
                     // find the chart
                     var chart = options.data.charts[alarm.chart];
                     if(typeof families[family] === 'undefined') {
                         families[family] = {
                             name: family,
-                            arr: new Array(),
+                            arr: [],
                             priority: chart.priority
                         };
 
                 var fc = 0;
                 var len = families_sorted.length;
                 while(len--) {
-                    var family = families_sorted[len].name;
+                    family = families_sorted[len].name;
                     var active_family_added = false;
                     var expanded = 'true';
                     var collapsed = '';
                     if(fc !== 0) {
                         all += "</table></div></div></div>";
                         expanded = 'false';
-                        collapsed = 'class="collapsed"'
+                        collapsed = 'class="collapsed"';
                         cin = '';
                     }
 
                     var arr = families[family].arr;
                     var c = arr.length;
                     while(c--) {
-                        var alarm = arr[c];
+                        alarm = arr[c];
                         if(alarm.status === 'WARNING' || alarm.status === 'CRITICAL') {
                             if(!active_family_added) {
                                 active_family_added = true;
                 if(families_sorted.length > 0) alarm_family_show(0);
 
                 // register bootstrap events
-                $('#alarms_all_accordion').on('show.bs.collapse', function (d) {
+                var $accordion = $('#alarms_all_accordion');
+                $accordion.on('show.bs.collapse', function (d) {
                     var target = $(d.target);
                     var id = $(target).data('alarm-id');
                     alarm_family_show(id);
                 });
-                $('#alarms_all_accordion').on('hidden.bs.collapse', function (d) {
+                $accordion.on('hidden.bs.collapse', function (d) {
                     var target = $(d.target);
                     var id = $(target).data('alarm-id');
                     $('#alarm_all_' + id.toString()).html('');
                             fileName: 'netdata_alarm_log'
                         },
                         rowStyle: function(row, index) {
+                            void(index);
+
                             switch(row.status) {
                                 case 'CRITICAL' : return { classes: 'danger'  }; break;
                                 case 'WARNING'  : return { classes: 'warning' }; break;
                                 title: 'Event Date',
                                 valign: 'middle',
                                 titleTooltip: 'The date and time the even took place',
-                                formatter: function(value, row, index) { return timestamp4human(value, ' '); },
+                                formatter: function(value, row, index) { void(row); void(index); return timestamp4human(value, ' '); },
                                 align: 'center',
-                                valign: 'middle',
                                 switchable: false,
                                 sortable: true
                             },
                                 valign: 'middle',
                                 titleTooltip: 'The host that generated this event',
                                 align: 'center',
-                                valign: 'middle',
                                 visible: false,
                                 sortable: true
                             },
                                 field: 'unique_id',
                                 title: 'Unique ID',
                                 titleTooltip: 'The host unique ID for this event',
-                                formatter: function(value, row, index) { return alarmid4human(value); },
+                                formatter: function(value, row, index) { void(row); void(index); return alarmid4human(value); },
                                 align: 'center',
                                 valign: 'middle',
                                 visible: false,
                                 field: 'alarm_id',
                                 title: 'Alarm ID',
                                 titleTooltip: 'The ID of the alarm that generated this event',
-                                formatter: function(value, row, index) { return alarmid4human(value); },
+                                formatter: function(value, row, index) { void(row); void(index); return alarmid4human(value); },
                                 align: 'center',
                                 valign: 'middle',
                                 visible: false,
                                 field: 'alarm_event_id',
                                 title: 'Alarm Event ID',
                                 titleTooltip: 'The incremental ID of this event for the given alarm',
-                                formatter: function(value, row, index) { return alarmid4human(value); },
+                                formatter: function(value, row, index) { void(row); void(index); return alarmid4human(value); },
                                 align: 'center',
                                 valign: 'middle',
                                 visible: false,
                                 title: 'Alarm',
                                 titleTooltip: 'The alarm name that generated this event',
                                 formatter: function(value, row, index) {
+                                    void(row);
+                                    void(index);
                                     return value.toString().replace(/_/g, ' ');
                                 },
                                 align: 'center',
                                 title: 'Old Value',
                                 titleTooltip: 'The value of the alarm, just before this event',
                                 formatter: function(value, row, index) {
+                                    void(row);
+                                    void(index);
                                     return ((value !== null)?Math.round(value * 100) / 100:'NaN').toString();
                                 },
                                 align: 'center',
                                 title: 'Value',
                                 titleTooltip: 'The value of the alarm, that triggered this event',
                                 formatter: function(value, row, index) {
+                                    void(row);
+                                    void(index);
                                     return ((value !== null)?Math.round(value * 100) / 100:'NaN').toString();
                                 },
                                 align: 'right',
                                 field: 'duration',
                                 title: 'Last Duration',
                                 titleTooltip: 'The duration the alarm was at its previous state, just before this event',
-                                formatter: function(value, row, index) { return seconds4human(value, { negative_suffix: '', space: ' ', now: 'no time' }); },
+                                formatter: function(value, row, index) {
+                                    void(row);
+                                    void(index);
+                                    return seconds4human(value, { negative_suffix: '', space: ' ', now: 'no time' });
+                                },
                                 align: 'center',
                                 valign: 'middle',
                                 visible: false,
                                 field: 'non_clear_duration',
                                 title: 'Raised Duration',
                                 titleTooltip: 'The duration the alarm was raised, just before this event',
-                                formatter: function(value, row, index) { return seconds4human(value, { negative_suffix: '', space: ' ', now: 'no time' }); },
+                                formatter: function(value, row, index) {
+                                    void(row);
+                                    void(index);
+                                    return seconds4human(value, { negative_suffix: '', space: ' ', now: 'no time' });
+                                },
                                 align: 'center',
                                 valign: 'middle',
                                 visible: false,
                                 title: 'Processed Status',
                                 titleTooltip: 'True when this event is processed',
                                 formatter: function(value, row, index) {
+                                    void(row);
+                                    void(index);
+
                                     if(value === true)
                                         return 'DONE';
                                     else
                                 title: 'Updated Status',
                                 titleTooltip: 'True when this event has been updated by another event',
                                 formatter: function(value, row, index) {
+                                    void(row);
+                                    void(index);
+
                                     if(value === true)
                                         return 'UPDATED';
                                     else
                                 field: 'updated_by_id',
                                 title: 'Updated By ID',
                                 titleTooltip: 'The unique ID of the event that obsoleted this one',
-                                formatter: function(value, row, index) { return alarmid4human(value); },
+                                formatter: function(value, row, index) { void(row); void(index); return alarmid4human(value); },
                                 align: 'center',
                                 valign: 'middle',
                                 visible: false,
                                 field: 'updates_id',
                                 title: 'Updates ID',
                                 titleTooltip: 'The unique ID of the event obsoleted because of this event',
-                                formatter: function(value, row, index) { return alarmid4human(value); },
+                                formatter: function(value, row, index) { void(row); void(index); return alarmid4human(value); },
                                 align: 'center',
                                 valign: 'middle',
                                 visible: false,
                                 field: 'exec_run',
                                 title: 'Script Run At',
                                 titleTooltip: 'The date and time the script has been ran',
-                                formatter: function(value, row, index) { return timestamp4human(value, ' '); },
+                                formatter: function(value, row, index) { void(row); void(index); return timestamp4human(value, ' '); },
                                 align: 'center',
                                 valign: 'middle',
                                 visible: false,
                                 title: 'Script Return Value',
                                 titleTooltip: 'The return code of the script',
                                 formatter: function(value, row, index) {
+                                    void(row);
+                                    void(index);
+
                                     if(value === 0)
                                         return 'OK (returned 0)';
                                     else
                                 field: 'delay',
                                 title: 'Script Delay',
                                 titleTooltip: 'The hysteresis of the notification',
-                                formatter: function(value, row, index) { return seconds4human(value, { negative_suffix: '', space: ' ', now: 'no time' }); },
+                                formatter: function(value, row, index) {
+                                    void(row);
+                                    void(index);
+
+                                    return seconds4human(value, { negative_suffix: '', space: ' ', now: 'no time' });
+                                },
                                 align: 'center',
                                 valign: 'middle',
                                 visible: false,
                                 field: 'delay_up_to_timestamp',
                                 title: 'Script Delay Run At',
                                 titleTooltip: 'The date and time the script should be run, after hysteresis',
-                                formatter: function(value, row, index) { return timestamp4human(value, ' '); },
+                                formatter: function(value, row, index) { void(row); void(index); return timestamp4human(value, ' '); },
                                 align: 'center',
                                 valign: 'middle',
                                 visible: false,
             });
         }
 
+        function seconds4human(seconds, options) {
+            var default_options = {
+                now: 'now',
+                space: '&nbsp;',
+                negative_suffix: 'ago',
+                hour: 'hour',
+                hours: 'hours',
+                minute: 'minute',
+                minutes: 'minutes',
+                second: 'second',
+                seconds: 'seconds',
+                and: 'and'
+            };
+
+            if(typeof options !== 'object')
+                options = default_options;
+            else {
+                var x;
+                for(x in default_options) {
+                    if(typeof options[x] !== 'string')
+                        options[x] = default_options[x];
+                }
+            }
+
+            if(typeof seconds === 'string')
+                seconds = parseInt(seconds);
+
+            if(seconds === 0)
+                return options.now;
+
+            var suffix = '';
+            if(seconds < 0) {
+                seconds = -seconds;
+                if(options.negative_suffix !== '') suffix = options.space + options.negative_suffix;
+            }
+
+            var hours = Math.floor(seconds / 3600);
+            seconds -= (hours * 3600);
+
+            var minutes = Math.floor(seconds / 60);
+            seconds -= (minutes * 60);
+
+            var txt = '';
+
+            if(hours > 1) txt += hours.toString() + options.space + options.hours;
+            else if(hours === 1) txt += hours.toString() + options.space + options.hour;
+
+            if(hours > 0 && minutes > 0 && seconds == 0)
+                txt += options.space + options.and + options.space;
+            else if(hours > 0 && minutes > 0 && seconds > 0)
+                txt += ',' + options.space;
+
+            if(minutes > 1) txt += minutes.toString() + options.space + options.minutes;
+            else if(minutes === 1) txt += minutes.toString() + options.space + options.minute;
+
+            if((minutes > 0 || minutes > 0) && seconds > 0)
+                txt += options.space + options.and + options.space;
+
+            if(seconds > 1) txt += Math.floor(seconds).toString() + options.space + options.seconds;
+            else if(seconds === 1) txt += Math.floor(seconds).toString() + options.space + options.second;
+
+            return txt + suffix;
+        }
+
         function alarmsCallback(data) {
             var count = 0;
             for(x in data.alarms) {
+                if(!data.alarms.hasOwnProperty(x)) continue;
+
                 var alarm = data.alarms[x];
                 if(alarm.status === 'WARNING' || alarm.status === 'CRITICAL')
                     count++;
                 document.getElementById('alarms_count_badge').innerHTML = '';
         }
 
+        function initializeDynamicDashboardWithData(data) {
+            if(data !== null) {
+                options.hostname = data.hostname;
+                options.data = data;
+                options.version = data.version;
+                netdataDashboard.os = data.os;
+
+                if(typeof data.hosts != 'undefined')
+                    options.hosts = data.hosts;
+
+                // update the dashboard hostname
+                document.getElementById('hostname').innerHTML = options.hostname;
+                document.getElementById('hostname').href = NETDATA.serverDefault;
+                document.getElementById('netdataVersion').innerHTML = options.version;
+
+                // update the dashboard title
+                document.title = options.hostname + ' netdata dashboard';
+
+                // close the splash screen
+                $("#loadOverlay").css("display","none");
+
+                // create a chart_by_name index
+                data.charts_by_name = {};
+                var charts = data.charts;
+                var x;
+                for(x in charts) {
+                    if(!charts.hasOwnProperty(x)) continue;
+
+                    var chart = charts[x];
+                    data.charts_by_name[chart.name] = chart;
+                }
+
+                // render all charts
+                renderChartsAndMenu(data);
+            }
+        }
+
         function initializeDynamicDashboard(netdata_url) {
             if(typeof netdata_url === 'undefined' || netdata_url === null)
                 netdata_url = NETDATA.serverDefault;
 
                 // download all the charts the server knows
                 NETDATA.chartRegistry.downloadAll(netdata_url, function(data) {
-                    if(data !== null) {
-                        options.hostname = data.hostname;
-                        options.data = data;
-
-                        // update the dashboard hostname
-                        document.getElementById('hostname').innerHTML = options.hostname;
-                        document.getElementById('hostname').href = NETDATA.serverDefault;
-
-                        // update the dashboard title
-                        document.title = options.hostname + ' netdata dashboard';
-
-                        // close the splash screen
-                        $("#loadOverlay").css("display","none");
-
-                        // create a chart_by_name index
-                        data.charts_by_name = {};
-                        var charts = data.charts;
-                        var x;
-                        for(x in charts) {
-                            var chart = charts[x];
-                            data.charts_by_name[chart.name] = chart;
+                    if(data != null) {
+                        if(typeof data.custom_info !== 'undefined' && data.custom_info !== "") {
+                            loadJs(data.custom_info, function () {
+                                $.extend(true, netdataDashboard, customDashboard);
+                                initializeDynamicDashboardWithData(data);
+                            });
+                        }
+                        else {
+                            initializeDynamicDashboardWithData(data);
                         }
-
-                        // render all charts
-                        renderChartsAndMenu(data);
                     }
                 });
             });
             document.getElementById('versionCheckLog').innerHTML = msg;
         }
 
-        function getNetdataVersion(callback) {
-            versionLog('Downloading installed version info from netdata...');
+        function getNetdataCommitIdFromVersion() {
+            var s = options.version.split('-');
+
+            if(s.length !== 3) return null;
+            if(s[2][0] == 'g') {
+                var v = s[2].split('_')[0].substring(1, 8);
+                if(v.length === 7) {
+                    versionLog('Installed git commit id of netdata is ' + v);
+                    document.getElementById('netdataCommitId').innerHTML = v;
+                    return v;
+                }
+            }
+            return null;
+        }
+
+        function getNetdataCommitId(force, callback) {
+            versionLog('Downloading installed git commit id from netdata...');
 
             $.ajax({
                 url: 'version.txt',
             .done(function(data) {
                 data = data.replace(/(\r\n|\n|\r| |\t)/gm,"");
                 if(data.length !== 40) {
-                    versionLog('Received version string is invalid.');
-                    callback(null);
+                    var c = getNetdataCommitIdFromVersion();
+                    if(c === null) versionLog('Cannot find the git commit id of netdata.');
+                    callback(c);
                 }
                 else {
-                    versionLog('Installed version of netdata is ' + data);
-                    document.getElementById('netdataVersion').innerHTML = data;
+                    versionLog('Installed git commit id of netdata is ' + data);
+                    document.getElementById('netdataCommitId').innerHTML = data.substring(0, 7);
                     callback(data);
                 }
             })
             .fail(function() {
-                versionLog('Failed to download installed version info from netdata!');
-                callback(null);
+                versionLog('Failed to download installed git commit id from netdata!');
+
+                if(force === true) {
+                    var c = getNetdataCommitIdFromVersion();
+                    if(c === null) versionLog('Cannot find the git commit id of netdata.');
+                    callback(c);
+                }
+                else
+                    callback(null);
             });
         }
 
         function getGithubLatestCommit(callback) {
-            versionLog('Downloading latest version info from github...');
+            versionLog('Downloading latest git commit id info from github...');
 
             $.ajax({
                 url: 'https://api.github.com/repos/firehol/netdata/commits',
                 cache: false
             })
             .done(function(data) {
-                versionLog('Latest version info from github is ' + data[0].sha);
+                versionLog('Latest git commit id from github is ' + data[0].sha);
                 callback(data[0].sha);
             })
             .fail(function() {
-                versionLog('Failed to download installed version info from github!');
+                versionLog('Failed to download installed git commit id from github!');
                 callback(null);
             });
         }
 
-        function checkForUpdate(callback) {
-            getNetdataVersion(function(sha1) {
+        function checkForUpdate(force, callback) {
+            getNetdataCommitId(force, function(sha1) {
                 if(sha1 === null) callback(null, null);
 
                 getGithubLatestCommit(function(sha2) {
                 }
             }
 
-            checkForUpdate(function(sha1, sha2) {
+            checkForUpdate(force, function(sha1, sha2) {
                 var save = false;
 
                 if(sha1 === null) {
                     save = false;
-                    versionLog('<p><big>Failed to get your netdata version!</big></p><p>You can always get the latest version of netdata from <a href="https://github.com/firehol/netdata" target="_blank">its github page</a>.</p>');
+                    versionLog('<p><big>Failed to get your netdata git commit id!</big></p><p>You can always get the latest netdata from <a href="https://github.com/firehol/netdata" target="_blank">its github page</a>.</p>');
                 }
                 else if(sha2 === null) {
                     save = false;
-                    versionLog('<p><big>Failed to get the latest version from github.</big></p><p>You can always get the latest version of netdata from <a href="https://github.com/firehol/netdata" target="_blank">its github page</a>.</p>');
+                    versionLog('<p><big>Failed to get the latest git commit id from github.</big></p><p>You can always get the latest netdata from <a href="https://github.com/firehol/netdata" target="_blank">its github page</a>.</p>');
                 }
                 else if(sha1 === sha2) {
                     save = true;
-                    versionLog('<p><big>You already have the latest version of netdata!</big></p><p>No update yet?<br/>Probably, we need some motivation to keep going on!</p><p>If you haven\'t already, <a href="https://github.com/firehol/netdata" target="_blank">give netdata a <b>Star</b> at its github page</a>.</p>');
+                    versionLog('<p><big>You already have the latest netdata!</big></p><p>No update yet?<br/>Probably, we need some motivation to keep going on!</p><p>If you haven\'t already, <a href="https://github.com/firehol/netdata" target="_blank">give netdata a <b>Star</b> at its github page</a>.</p>');
                 }
                 else {
                     save = true;
                     var compare = 'https://github.com/firehol/netdata/compare/' + sha1.toString() + '...' + sha2.toString();
 
-                    versionLog('<p><big><strong>New version of netdata available!</strong></big></p><p>Latest version: ' + sha2.toString() + '</p><p><a href="' + compare + '" target="_blank">Click here for the changes log</a> since your installed version, and<br/><a href="https://github.com/firehol/netdata/wiki/Updating-Netdata" target="_blank">click here for directions on updating</a> your netdata installation.</p><p>We suggest to review the changes log for new features you may be interested, or important bug fixes you may need.<br/>Keeping your netdata updated, is generally a good idea.</p>');
+                    versionLog('<p><big><strong>New version of netdata available!</strong></big></p><p>Latest commit: <b><code>' + sha2.substring(0, 7).toString() + '</code></b></p><p><a href="' + compare + '" target="_blank">Click here for the changes log</a> since your installed version, and<br/><a href="https://github.com/firehol/netdata/wiki/Updating-Netdata" target="_blank">click here for directions on updating</a> your netdata installation.</p><p>We suggest to review the changes log for new features you may be interested, or important bug fixes you may need.<br/>Keeping your netdata updated, is generally a good idea.</p>');
 
                     document.getElementById('update_badge').innerHTML = '!';
                 }
                                         {
                                             //console.log('They were open tags');
                                             //console.log(openTags);
-                                            for (j = 0; j < openTags.length; j++) {
+                                            for (var j = 0; j < openTags.length; j++) {
                                                 //console.log('Cierro tag ' + openTags[j]);
                                                 bag += '</' + openTags[j] + '>'; // Close all tags that were opened
 
                 //console.log('hash = ' + urlOptions.hash);
             }
 
+            var $sidebar = $('#sidebar');
             /* activate bootstrap sidebar (affix) */
-            $('#sidebar').affix({
+            $sidebar.affix({
                 offset: {
                     top: (isdemo())?150:0,
                     bottom: 0
             /* fix scrolling of very long affix lists
                http://stackoverflow.com/questions/21691585/bootstrap-3-1-0-affix-too-long
              */
-            $('#sidebar').on('affixed.bs.affix', function() {
+            $sidebar.on('affixed.bs.affix', function() {
                 $(this).removeAttr('style');
             });
 
             });
 
             // change the URL based on the current position of the screen
-            $('#sidebar').on('activate.bs.scrollspy', function (e) {
+            $sidebar.on('activate.bs.scrollspy', function (e) {
                 // console.log(e);
                 var el = $(e.target);
                 //if(el.find('ul').size() == 0) {
                         // console.log('switching ' + option.toString());
                         self.bootstrapToggle(NETDATA.getOption(option)?'on':'off');
                     }
-                }
+                };
 
                 var theme_sync_option = function(option) {
                     var self = $('#' + option);
 
                     self.bootstrapToggle(netdataTheme === 'slate'?'on':'off');
-                }
+                };
 
                 sync_option('eliminate_zero_dimensions');
                 sync_option('destroy_on_hide');
                     netdataReload();
             });
 
-            $('#updateModal').on('show.bs.modal', function() {
+            var $updateModal = $('#updateModal');
+            $updateModal.on('show.bs.modal', function() {
                 versionLog('checking, please wait...');
             });
 
-            $('#updateModal').on('shown.bs.modal', function() {
+            $updateModal.on('shown.bs.modal', function() {
                 notifyForUpdate(true);
             });
 
-            $('#alarmsModal').on('shown.bs.modal', function() {
+            var $alarmsModal = $('#alarmsModal');
+            $alarmsModal.on('shown.bs.modal', function() {
                 NETDATA.pause(alarmsUpdateModal);
             });
 
-            $('#alarmsModal').on('hidden.bs.modal', function() {
+            $alarmsModal.on('hidden.bs.modal', function() {
                 NETDATA.unpause();
                 document.getElementById('alarms_active').innerHTML =
                         document.getElementById('alarms_all').innerHTML =
             });
 
             NETDATA.requiredJs.push({
-                url: NETDATA.serverDefault + 'dashboard_info.js?v20170115-1',
+                url: NETDATA.serverDefault + 'dashboard_info.js?v20170325-1',
                 async: false,
                 isAlreadyLoaded: function() { return false; }
             });
                     <h4 class="modal-title" id="updateModalLabel">Update Check</h4>
                 </div>
                 <div class="modal-body">
-                    Your netdata version: <b><code><span id="netdataVersion">Unknown</span></code></b>
+                    Your netdata version: <b><code><span id="netdataVersion">Unknown</span></code></b><br/>
+                    Your netdata commit: <b><code><span id="netdataCommitId">Unknown</span></code></b>
                     <br/>
                     <div style="padding: 10px;"></div>
                     <div id="versionCheckLog">Not checked yet. Please press the Check Now button.</div>
     </div>
 </body>
 </html>
-<script type="text/javascript" src="dashboard.js?v20170127-1"></script>
+<script type="text/javascript" src="dashboard.js?v20170325-1"></script>