]> arthur.barton.de Git - netdata.git/blobdiff - web/index.html
allow custom dashboards to link dimensions to DOM elements by name or id; fixes ...
[netdata.git] / web / index.html
index 30c72313771eeb56147901fed0ff7f0d635c3f06..f24389687878d32df7ca6057cf5abfb1c36b1a6d 100644 (file)
     <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
     <meta name="author" content="costa@tsaousis.gr">
 
-    <link rel="shortcut icon" href="images/seo-performance-multi-size.ico">
+    <!-- <link rel="shortcut icon" href="images/seo-performance-multi-size.ico"> -->
 
-    <link rel="apple-touch-icon" href="images/seo-performance-72.png">
-    <link rel="apple-touch-icon" sizes="72x72" href="images/seo-performance-72.png">
-    <link rel="apple-touch-icon" sizes="114x114" href="images/seo-performance-114.png">
+    <!-- <link rel="apple-touch-icon" href="images/seo-performance-72.png"> -->
+    <!-- <link rel="apple-touch-icon" sizes="72x72" href="images/seo-performance-72.png"> -->
+    <!-- <link rel="apple-touch-icon" sizes="114x114" href="images/seo-performance-114.png"> -->
 
-    <link rel="icon" type="image/png" sizes="512x512" href="images/seo-performance-512.png">
-    <link rel="icon" type="image/png" sizes="256x256" href="images/seo-performance-256.png">
-    <link rel="icon" type="image/png" sizes="128x128" href="images/seo-performance-128.png">
-    <link rel="icon" type="image/png" sizes="64x64" href="images/seo-performance-64.png">
-    <link rel="icon" type="image/png" sizes="48x48" href="images/seo-performance-48.png">
-    <link rel="icon" type="image/png" sizes="32x32" href="images/seo-performance-32.png">
-    <link rel="icon" type="image/png" sizes="24x24" href="images/seo-performance-24.png">
-    <link rel="icon" type="image/png" sizes="16x16" href="images/seo-performance-16.png">
+    <!-- <link rel="icon" type="image/png" sizes="512x512" href="images/seo-performance-512.png"> -->
+    <!-- <link rel="icon" type="image/png" sizes="256x256" href="images/seo-performance-256.png"> -->
+    <!-- <link rel="icon" type="image/png" sizes="128x128" href="images/seo-performance-128.png"> -->
+    <!-- <link rel="icon" type="image/png" sizes="64x64" href="images/seo-performance-64.png"> -->
+    <!-- <link rel="icon" type="image/png" sizes="48x48" href="images/seo-performance-48.png"> -->
+    <!-- <link rel="icon" type="image/png" sizes="24x24" href="images/seo-performance-24.png"> -->
+    <!-- <link rel="icon" type="image/png" sizes="16x16" href="images/seo-performance-16.png"> -->
+    <!-- <link rel="icon" type="image/png" sizes="32x32" href="images/seo-performance-32.png"> -->
+
+    <link rel="icon" type="image/png" sizes="32x32" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACNklEQVRYhcXXv2tUQRAH8M+FEIJISBHCIWIhIQSUILERi4AiiqCggiIiomAjlhaC4j+ghYWISgqNohZaCBZBC8Ei8QdEUCutFBsxCBqDYkgci/cunkfuJffjJQPD8mZm5/vd2WV2HzlJ0Bs8CvrywsgCHwy+BpGOg0sJfjj4nYKX9FdwKG9gwZlgtgK8pLOpPxfw1mCoCnClDgWtzQTvCEYWCV7SkWAlFBoEb8dlDKBF8t2bMWUSH/AHr3CiEfz5CPUusPJLkRCdk5ZqyeqUrQv4R7E5TwK7M3zTeIKduRAIitiWEfIY69GdCwGcRFuG/xqONRkzkaA7+J5x+MaDtWmHvJ4HgeEM8Nn0bridfv9HoOFyBAdwJCPkqqTzHWwUaz7wgeBHxupfBKuCj2W25mxBsCGYyAB/FxTT27HcPlyep64tCLbjKbqqhLzBlgKfF8pVE4FgRXABI+ioEnYfOyzcFWsCbg+OV+xlpU4ER4O+4HVwL51b3xYEXcGu4Ao+YQhr5gmdxHmsQyfG0b/YxbWmLfRWmnxa0s06VbTMCpnBS9zFzQKTwR5cXCzwHIE02Sl8wSZsRI/kgLVJqjSd+t9LVjiG1diPszhdK3A5gR48k5zYMTwscC59sfT799CYKvA8EttbSeXgTr3gJQKl91kR+yTlvyG5uUbLYh9gb+ovltkb6qYtNSRo3kOygsBSzGlKsubf43USWLYK5CLLXoFWyU/CtzLbVDpW2n+m40yN9ukqdvAX9ac/EIgOapcAAAAASUVORK5CYII=">
 
     <meta property="og:locale" content="en_US" />
-    <meta property="og:image" content="http://my-netdata.io/images/post.png"/>
+    <meta property="og:image" content="https://cloud.githubusercontent.com/assets/2662304/19168687/f6a567be-8c19-11e6-8561-ce8d589e8346.gif"/>
     <meta property="og:url" content="http://my-netdata.io/"/>
     <meta property="og:type" content="website"/>
     <meta property="og:site_name" content="netdata"/>
             width: 233px !important;
         }
     }
-    
+
     @media (min-width: 1360px) {
         .container {
             padding-left: 3% !important;
 
     // enable registry updates
     var netdataRegistry = true;
-    
+
     // --------------------------------------------------------------------
     // urlOptions
 
         hash: '#',
         theme: null,
         help: null,
+        update_always: false,
         pan_and_zoom: false,
         after: 0,
         before: 0,
         alarm_unique_id: 0,
         alarm_id: 0,
         alarm_event_id: 0,
+
         hasProperty: function(property) {
             // console.log('checking property ' + property + ' of type ' + typeof(this[property]));
             return typeof this[property] !== 'undefined';
-        }
-    };
-
-    function netdataPanAndZoomCallback(status, after, before) {
-        urlOptions.pan_and_zoom = status;
-        urlOptions.after = after;
-        urlOptions.before = before;
-        netdataHashUpdate();
-    }
+        },
 
-    function netdataHashUpdate() {
-        history.replaceState(null, '', netdataHash());
-    }
+        genHash: function() {
+            var hash = urlOptions.hash;
 
-    function netdataHash() {
-        var hash = urlOptions.hash;
+            if(urlOptions.pan_and_zoom === true) {
+                hash += ';after='  + urlOptions.after.toString() +
+                        ';before=' + urlOptions.before.toString();
+            }
 
-        if(urlOptions.pan_and_zoom === true) {
-            hash += ';after='  + urlOptions.after.toString() +
-                    ';before=' + urlOptions.before.toString();
-        }
+            if(urlOptions.theme !== null)
+                hash += ';theme=' + urlOptions.theme.toString();
 
-        if(urlOptions.theme !== null)
-            hash += ';theme=' + urlOptions.theme.toString();
+            if(urlOptions.help !== null)
+                hash += ';help=' + urlOptions.help.toString();
 
-        if(urlOptions.help !== null)
-            hash += ';help=' + urlOptions.help.toString();
+            if(urlOptions.update_always === true)
+                hash += ';update_always=true';
 
-        return hash;
-    }
+            return hash;
+        },
 
-    function netdataHashParse() {
-        var variables = document.location.hash.split(';');
-        var len = variables.length;
-        while(len--) {
-            if(len !== 0) {
-                var p = variables[len].split('=');
-                if(urlOptions.hasProperty(p[0]) && typeof p[1] !== 'undefined')
-                    urlOptions[p[0]] = decodeURIComponent(p[1]);
+        parseHash: function() {
+            var variables = document.location.hash.split(';');
+            var len = variables.length;
+            while(len--) {
+                if(len !== 0) {
+                    var p = variables[len].split('=');
+                    if(urlOptions.hasProperty(p[0]) && typeof p[1] !== 'undefined')
+                        urlOptions[p[0]] = decodeURIComponent(p[1]);
+                }
+                else {
+                    if(variables[len].length > 0)
+                        urlOptions.hash = variables[len];
+                }
             }
-            else {
-                if(variables[len].length > 0)
-                    urlOptions.hash = variables[len];
+
+            var booleans = [ 'nowelcome', 'show_alarms', 'pan_and_zoom', 'update_always' ];
+            len = booleans.length;
+            while(len--) {
+                if(urlOptions[booleans[len]] === 'true' || urlOptions[booleans[len]] === true || urlOptions[booleans[len]] === '1' || urlOptions[booleans[len]] === 1)
+                    urlOptions[booleans[len]] = true;
+                else
+                    urlOptions[booleans[len]] = false;
             }
-        }
 
-        var booleans = [ 'nowelcome', 'show_alarms', 'pan_and_zoom' ];
-        len = booleans.length;
-        while(len--) {
-            if(urlOptions[booleans[len]] === 'true' || urlOptions[booleans[len]] === true || urlOptions[booleans[len]] === '1' || urlOptions[booleans[len]] === 1)
-                urlOptions[booleans[len]] = true;
+            if(urlOptions.before > 0 && urlOptions.after > 0) {
+                urlOptions.pan_and_zoom = true;
+                urlOptions.nowelcome = true;
+            }
             else
-                urlOptions[booleans[len]] = false;
-        }
+                urlOptions.pan_and_zoom = false;
 
-        if(urlOptions.before > 0 && urlOptions.after > 0) {
-            urlOptions.pan_and_zoom = true;
-            urlOptions.nowelcome = true;
+            // console.log(urlOptions);
+        },
+
+        hashUpdate: function() {
+            history.replaceState(null, '', urlOptions.genHash());
+        },
+
+        netdataPanAndZoomCallback: function(status, after, before) {
+            urlOptions.pan_and_zoom = status;
+            urlOptions.after = after;
+            urlOptions.before = before;
+            urlOptions.hashUpdate();
         }
-        else
-            urlOptions.pan_and_zoom = false;
 
-        // console.log(urlOptions);
-    }
+    };
 
-    netdataHashParse();
+    urlOptions.parseHash();
 
     // --------------------------------------------------------------------
     // check options that should be processed before loading netdata.js
-    
+
     function loadLocalStorage(name) {
         var ret = null;
 
         if(url.indexOf('#') !== -1)
             url = url.substring(0, url.indexOf('#'));
 
-        var hash = netdataHash();
+        var hash = urlOptions.genHash();
 
         // console.log('netdataURL: ' + url + hash);
 
         var error = 'failed';
 
         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;
+            // we penalize https only if the current url is http
+            // to allow the user walk through all its servers.
+            penaldy = 500;
 
         else if(document.location.toString().startsWith('https://') && url.toString().startsWith('http://'))
             error = 'can\'t check';
-        
+
         var finalURL = netdataURL(url);
 
         setTimeout(function() {
                             gotoServerMiddleClick = false;
                             document.getElementById('gotoServerResponse').innerHTML = '<b>Opening new window to ' + NETDATA.registry.machines[guid].name + '<br/><a href="' + finalURL + '">' + url + '</a></b><br/>(check your pop-up blocker if it fails)';
                         }
-                        else
+                        else {
+                            document.getElementById('gotoServerResponse').innerHTML += 'found it! It is at:<br/><small>' + url + '</small>';
                             document.location = finalURL;
+                        }
                     }
                 }
                 else {
             if(gotoServerStop === false) {
                 document.getElementById('gotoServerResponse').innerHTML = '<b>Added all the known URLs for this machine.</b>';
                 NETDATA.registry.search(guid, function(data) {
-                    console.log(data);
+                    // console.log(data);
                     len = data.urls.length;
                     while(len--) {
                         var url = data.urls[len][1];
-                        console.log(url);
+                        // console.log(url);
                         if(typeof checked[url] === 'undefined') {
                             gotoServerValidateRemaining++;
                             checked[url] = true;
     // scroll to a section, without changing the browser history
 
     function scrollToId(hash) {
-        if(hash && hash != '') {
+        if(hash && hash != '' && document.getElementById(hash) !== null) {
             var offset = $('#' + hash).offset();
             if(typeof offset !== 'undefined')
                 $('html, body').animate({ scrollTop: offset.top }, 0);
                 return '';
         },
 
+        contextValueRange: function(id) {
+            if(typeof netdataDashboard.context[id] !== 'undefined' && typeof netdataDashboard.context[id].valueRange !== 'undefined')
+                return netdataDashboard.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;
 
             case 'apache':
             case 'exim':
+            case 'dovecot':
+            case 'hddtemp':
+            case 'ipfs':
             case 'memcached':
             case 'mysql':
             case 'named':
             case 'nut':
             case 'phpfpm':
             case 'postfix':
+            case 'postgres':
             case 'redis':
             case 'retroshare':
-            case 'ipfs':
             case 'smawebbox':
-            case 'squid':
             case 'snmp':
+            case 'squid':
             case 'tomcat':
                 chart.menu = chart.type;
                 chart.menu_pattern = tmp;
 
         if(typeof charts['system.swap'] !== 'undefined')
             head += '<div style="margin-right: 10px;" data-netdata="system.swap"'
-            + ' data-dimensions="free"'
+            + ' data-dimensions="used"'
             + ' data-append-options="percentage"'
             + ' data-chart-library="easypiechart"'
-            + ' data-title="Free Swap"'
+            + ' data-title="Used Swap"'
             + ' data-units="%"'
             + ' data-easypiechart-max-value="100"'
             + ' data-width="8%"'
 
         if(typeof charts['system.ram'] !== 'undefined')
             head += '<div style="margin-right: 10px;" data-netdata="system.ram"'
-            + ' data-dimensions="cached|free"'
+            + ' data-dimensions="used|buffers"'
             + ' data-append-options="percentage"'
             + ' data-chart-library="easypiechart"'
-            + ' data-title="Available RAM"'
+            + ' data-title="Used RAM"'
             + ' data-units="%"'
             + ' data-easypiechart-max-value="100"'
             + ' data-width="8%"'
                     chtml += netdataDashboard.contextInfo(chart.context) + '<div id="chart_' + NETDATA.name2id(chart.id) + '" data-netdata="' + chart.id + '"'
                         + ' data-width="' + pcent_width.toString() + '%"'
                         + ' data-height="' + netdataDashboard.contextHeight(chart.context, options.chartsHeight).toString() + 'px"'
+                        + ' data-dygraph-valuerange="' + netdataDashboard.contextValueRange(chart.context) + '"'
                         + ' data-before="0"'
                         + ' data-after="-' + duration.toString() + '"'
                         + ' data-id="' + NETDATA.name2id(options.hostname + '/' + chart.id) + '"'
             html += mhead + shtml + '</div></div><hr role="separator"/>';
         }
 
+        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).toLocaleString() + ' ' + ((data.history == 3600)?'hour':'hours').toString() + ' of real-time history.</small></li>';
         sidebar += '</ul>';
         div.innerHTML = html;
         document.getElementById('sidebar').innerHTML = sidebar;
 
     // ----------------------------------------------------------------------------
 
+    function loadJs(url, callback) {
+        $.ajax({
+            url: url,
+            cache: true,
+            dataType: "script",
+            xhrFields: { withCredentials: true } // required for the cookie
+        })
+        .fail(function() {
+            alert('Cannot load required JS library: ' + url);
+        })
+        .always(function() {
+            if(typeof callback === 'function')
+                callback();
+        })
+    }
+
+    var bootstrapTableLoaded = false;
+    function loadBootstrapTable(callback) {
+        if(bootstrapTableLoaded === false) {
+            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);
+                })
+            });
+        }
+        else callback();
+    }
+
     function alarmsUpdateModal() {
         var active = '<h3>Raised Alarms</h3><table class="table">';
         var all = '<h3>All Running Alarms</h3><div class="panel-group" id="alarms_all_accordion" role="tablist" aria-multiselectable="true">';
-        var log = '<h3>Alarm Log</h3><table class="table"><tr><th>When</th><th>Chart</th><th>Alarm</th><th>Status</th>';
         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) {
                 return;
             }
 
-            function frequency_text(seconds, sfx) {
+            function alarmid4human(id) {
+                if(id === 0)
+                    return '-';
+
+                return id.toString();
+            }
+
+            function timestamp4human(timestamp, space) {
+                if(timestamp === 0)
+                    return '-';
+
+                if(typeof space === 'undefined')
+                    space = '&nbsp;';
+
+                var t = new Date(timestamp * 1000);
+                var now = new Date();
+
+                if(t.toDateString() == now.toDateString())
+                    return t.toLocaleTimeString();
+
+                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 'now';
+                    return options.now;
 
                 var suffix = '';
                 if(seconds < 0) {
                     seconds = -seconds;
-                    if(sfx) suffix = '&nbsp;ago';
+                    if(options.negative_suffix !== '') suffix = options.space + options.negative_suffix;
                 }
 
                 var hours = Math.floor(seconds / 3600);
                 seconds -= (minutes * 60);
 
                 var txt = '';
-                
-                if(hours > 1) txt += hours.toString() + '&nbsp;hours';
-                else if(hours === 1) txt += hours.toString() + '&nbsp;hour';
+
+                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 += '&nbsp;and&nbsp;';
+                    txt += options.space + options.and + options.space;
                 else if(hours > 0 && minutes > 0 && seconds > 0)
-                    txt += ',&nbsp;';
+                    txt += ',' + options.space;
 
-                if(minutes > 1) txt += minutes.toString() + '&nbsp;minutes';
-                else if(minutes === 1) txt += minutes.toString() + '&nbsp;minute';
+                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 += '&nbsp;and&nbsp;';
+                    txt += options.space + options.and + options.space;
 
-                if(seconds > 1) txt += seconds.toString() + '&nbsp;seconds';
-                else if(seconds === 1) txt += seconds.toString() + '&nbsp;second';
+                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;
             }
 
                 return '<code>' + alarm.lookup_method + '</code> '
                     + dimensions + ', of chart <code>' + alarm.chart + '</code>'
-                    + ', starting <code>' + frequency_text(alarm.lookup_after + alarm.lookup_before, true) + '</code> and up to <code>' + frequency_text(alarm.lookup_before, true) + '</code>'
+                    + ', starting <code>' + seconds4human(alarm.lookup_after + alarm.lookup_before) + '</code> and up to <code>' + seconds4human(alarm.lookup_before) + '</code>'
                     + ((alarm.lookup_options)?(', with options <code>' + alarm.lookup_options.replace(' ', ',&nbsp;') + '</code>'):'')
                     + '.';
             }
                 var delay = '';
                 if((alarm.delay_up_duration > 0 || alarm.delay_down_duration > 0) && alarm.delay_multiplier != 0 && alarm.delay_max_duration > 0) {
                     if(alarm.delay_up_duration == alarm.delay_down_duration) {
-                        delay += '<small><br/>hysteresis ' + frequency_text(alarm.delay_up_duration);
+                        delay += '<small><br/>hysteresis ' + seconds4human(alarm.delay_up_duration, { negative_suffix: '' });
                     }
                     else {
                         delay = '<small><br/>hysteresis ';
                         if(alarm.delay_up_duration > 0) {
-                            delay += 'on&nbsp;escalation&nbsp;<code>' + frequency_text(alarm.delay_up_duration) + '</code>, ';
+                            delay += 'on&nbsp;escalation&nbsp;<code>' + seconds4human(alarm.delay_up_duration, { negative_suffix: '' }) + '</code>, ';
                         }
                         if(alarm.delay_down_duration > 0) {
-                            delay += 'on&nbsp;recovery&nbsp;<code>' + frequency_text(alarm.delay_down_duration) + '</code>, ';
+                            delay += 'on&nbsp;recovery&nbsp;<code>' + seconds4human(alarm.delay_down_duration, { negative_suffix: '' }) + '</code>, ';
                         }
                     }
                     if(alarm.delay_multiplier != 1.0) {
                         delay += 'multiplied&nbsp;by&nbsp;<code>' + alarm.delay_multiplier.toString() + '</code>';
-                        delay += ',&nbsp;up&nbsp;to&nbsp;<code>' + frequency_text(alarm.delay_max_duration) + '</code>';
+                        delay += ',&nbsp;up&nbsp;to&nbsp;<code>' + seconds4human(alarm.delay_max_duration, { negative_suffix: '' }) + '</code>';
                     }
                     delay += '</small>';
                 }
 
-                html += '<tr><td width="10%" style="text-align:right">check&nbsp;every</td><td>' + frequency_text(alarm.update_every) + '</td></tr>'
+                html += '<tr><td width="10%" style="text-align:right">check&nbsp;every</td><td>' + seconds4human(alarm.update_every, { negative_suffix: '' }) + '</td></tr>'
                     + '<tr><td width="10%" style="text-align:right">execute</td><td><span style="font-family: monospace;">' + alarm.exec + '</span>' + delay + '</td></tr>'
                     + '<tr><td width="10%" style="text-align:right">source</td><td><span style="font-family: monospace;">' + alarm.source + '</span></td></tr>'
                     + '</table></td></tr>';
             }
 
             // find the proper family of each alarm
-            var now = new Date().getTime();
+            var now = Date.now();
             var x;
             var count_active = 0;
             var count_all = 0;
                 $('#alarm_all_' + id.toString()).html('');
             });
 
-            NETDATA.alarms.get_log(0, function(data) {
-                if(data === null) {
-                    document.getElementById('alarms_log').innerHTML =
-                            'failed to load alarm data!';
-                    return;
-                }
-
-                var i = 0;
-                var len = data.length;
-                if(len > 50) len = 50;
-                while(i < len) {
-                    var time = new Date(data[i].when * 1000);
-                    log += '<tr><td>'
-                            + time.toLocaleDateString() + ' '
-                            + time.toLocaleTimeString() + '</td><td>'
-                            + data[i].chart.toString() + '</td><td>'
-                            + data[i].name.toString() + ' = ' + ((data[i].value !== null)?Math.floor(data[i].value):'NaN').toString() + ' ' + data[i].units.toString() + '</td><td>'
-                            + data[i].status.toString() + '</td></tr>';
-                    i++;
-                }
-                log += "</table>";
-
-                if(i == 0)
-                    log += "<h4>No alarms have been logged in this system.</h4>";
-
-                document.getElementById('alarms_log').innerHTML = log;
-            })
+            document.getElementById('alarms_log').innerHTML = '<h3>Alarm Log</h3><table id="alarms_log_table"></table>';
+
+            loadBootstrapTable(function () {
+                $('#alarms_log_table').bootstrapTable({
+                    url: NETDATA.alarms.server + '/api/v1/alarm_log?all',
+                    cache: false,
+                    pagination: true,
+                    pageSize: 10,
+                    showPaginationSwitch: false,
+                    search: true,
+                    searchTimeOut: 300,
+                    searchAlign: 'left',
+                    showColumns: true,
+                    showExport: true,
+                    exportDataType: 'basic',
+                    exportOptions: {
+                        fileName: 'netdata_alarm_log'
+                    },
+                    rowStyle: function(row, index) {
+                        switch(row.status) {
+                            case 'CRITICAL' : return { classes: 'danger'  }; break;
+                            case 'WARNING'  : return { classes: 'warning' }; break;
+                            case 'UNDEFINED': return { classes: 'info'    }; break;
+                            case 'CLEAR'    : return { classes: 'success' }; break;
+                        }
+                        return {};
+                    },
+                    showFooter: false,
+                    showHeader: true,
+                    showRefresh: true,
+                    showToggle: false,
+                    sortable: true,
+                    silentSort: false,
+                    columns: [
+                        {
+                            field: 'when',
+                            title: 'Event Date',
+                            valign: 'middle',
+                            titleTooltip: 'The date and time the even took place',
+                            formatter: function(value, row, index) { return timestamp4human(value, ' '); },
+                            align: 'center',
+                            valign: 'middle',
+                            switchable: false,
+                            sortable: true
+                        },
+                        {
+                            field: 'hostname',
+                            title: 'Host',
+                            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); },
+                            align: 'center',
+                            valign: 'middle',
+                            visible: false,
+                            sortable: true
+                        },
+                        {
+                            field: 'alarm_id',
+                            title: 'Alarm ID',
+                            titleTooltip: 'The ID of the alarm that generated this event',
+                            formatter: function(value, row, index) { return alarmid4human(value); },
+                            align: 'center',
+                            valign: 'middle',
+                            visible: false,
+                            sortable: true
+                        },
+                        {
+                            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); },
+                            align: 'center',
+                            valign: 'middle',
+                            visible: false,
+                            sortable: true
+                        },
+                        {
+                            field: 'chart',
+                            title: 'Chart',
+                            titleTooltip: 'The chart the alarm is attached to',
+                            align: 'center',
+                            valign: 'middle',
+                            switchable: false,
+                            sortable: true
+                        },
+                        {
+                            field: 'family',
+                            title: 'Family',
+                            titleTooltip: 'The family of the chart the alarm is attached to',
+                            align: 'center',
+                            valign: 'middle',
+                            visible: false,
+                            sortable: true
+                        },
+                        {
+                            field: 'name',
+                            title: 'Alarm',
+                            titleTooltip: 'The alarm name that generated this event',
+                            formatter: function(value, row, index) {
+                                return value.toString().replace(/_/g, ' ');
+                            },
+                            align: 'center',
+                            valign: 'middle',
+                            switchable: false,
+                            sortable: true
+                        },
+                        {
+                            field: 'old_value',
+                            title: 'Old Value',
+                            titleTooltip: 'The value of the alarm, just before this event',
+                            formatter: function(value, row, index) {
+                                return ((value !== null)?Math.round(value * 100) / 100:'NaN').toString();
+                            },
+                            align: 'center',
+                            valign: 'middle',
+                            visible: false,
+                            sortable: true
+                        },
+                        {
+                            field: 'value',
+                            title: 'Value',
+                            titleTooltip: 'The value of the alarm, that triggered this event',
+                            formatter: function(value, row, index) {
+                                return ((value !== null)?Math.round(value * 100) / 100:'NaN').toString();
+                            },
+                            align: 'right',
+                            valign: 'middle',
+                            sortable: true
+                        },
+                        {
+                            field: 'units',
+                            title: 'Units',
+                            titleTooltip: 'The units of the value of the alarm',
+                            align: 'left',
+                            valign: 'middle',
+                            sortable: true
+                        },
+                        {
+                            field: 'old_status',
+                            title: 'Old Status',
+                            titleTooltip: 'The status of the alarm, just before this event',
+                            align: 'center',
+                            valign: 'middle',
+                            visible: false,
+                            sortable: true
+                        },
+                        {
+                            field: 'status',
+                            title: 'Status',
+                            titleTooltip: 'The status of the alarm, that was set due to this event',
+                            align: 'center',
+                            valign: 'middle',
+                            switchable: false,
+                            sortable: true
+                        },
+                        {
+                            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' }); },
+                            align: 'center',
+                            valign: 'middle',
+                            visible: false,
+                            sortable: true
+                        },
+                        {
+                            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' }); },
+                            align: 'center',
+                            valign: 'middle',
+                            visible: false,
+                            sortable: true
+                        },
+                        {
+                            field: 'recipient',
+                            title: 'Recipient',
+                            titleTooltip: 'The recipient of this event',
+                            align: 'center',
+                            valign: 'middle',
+                            visible: false,
+                            sortable: true
+                        },
+                        {
+                            field: 'processed',
+                            title: 'Processed Status',
+                            titleTooltip: 'True when this event is processed',
+                            formatter: function(value, row, index) {
+                                if(value === true)
+                                    return 'DONE';
+                                else
+                                    return 'PENDING';
+                            },
+                            align: 'center',
+                            valign: 'middle',
+                            visible: false,
+                            sortable: true
+                        },
+                        {
+                            field: 'updated',
+                            title: 'Updated Status',
+                            titleTooltip: 'True when this event has been updated by another event',
+                            formatter: function(value, row, index) {
+                                if(value === true)
+                                    return 'UPDATED';
+                                else
+                                    return 'CURRENT';
+                            },
+                            align: 'center',
+                            valign: 'middle',
+                            visible: false,
+                            sortable: true
+                        },
+                        {
+                            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); },
+                            align: 'center',
+                            valign: 'middle',
+                            visible: false,
+                            sortable: true
+                        },
+                        {
+                            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); },
+                            align: 'center',
+                            valign: 'middle',
+                            visible: false,
+                            sortable: true
+                        },
+                        {
+                            field: 'exec',
+                            title: 'Script',
+                            titleTooltip: 'The script to handle the event notification',
+                            align: 'center',
+                            valign: 'middle',
+                            visible: false,
+                            sortable: true
+                        },
+                        {
+                            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, ' '); },
+                            align: 'center',
+                            valign: 'middle',
+                            visible: false,
+                            sortable: true
+                        },
+                        {
+                            field: 'exec_code',
+                            title: 'Script Return Value',
+                            titleTooltip: 'The return code of the script',
+                            formatter: function(value, row, index) {
+                                if(value === 0)
+                                    return 'OK (returned 0)';
+                                else
+                                    return 'FAILED (with code ' + value.toString() + ')';
+                            },
+                            align: 'center',
+                            valign: 'middle',
+                            visible: false,
+                            sortable: true
+                        },
+                        {
+                            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' }); },
+                            align: 'center',
+                            valign: 'middle',
+                            visible: false,
+                            sortable: true
+                        },
+                        {
+                            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, ' '); },
+                            align: 'center',
+                            valign: 'middle',
+                            visible: false,
+                            sortable: true
+                        },
+                        {
+                            field: 'info',
+                            title: 'Description',
+                            titleTooltip: 'A short description of the alarm',
+                            align: 'center',
+                            valign: 'middle',
+                            visible: false,
+                            sortable: true
+                        },
+                        {
+                            field: 'source',
+                            title: 'Alarm Source',
+                            titleTooltip: 'The source of configuration of the alarm',
+                            align: 'center',
+                            valign: 'middle',
+                            visible: false,
+                            sortable: true
+                        }
+                    ]
+                });
+                // console.log($('#alarms_log_table').bootstrapTable('getOptions'));
+            });
         });
     }
 
         $.ajax({
             url: 'version.txt',
             async: true,
-            cache: false
+            cache: false,
+            xhrFields: { withCredentials: true } // required for the cookie
         })
         .done(function(data) {
             data = data.replace(/(\r\n|\n|\r| |\t)/gm,"");
     function notifyForUpdate(force) {
         versionLog('<p>checking for updates...</p>');
 
-        var now = new Date().getTime();
+        var now = Date.now();
 
         if(typeof force === 'undefined' || force !== true) {
             var last = loadLocalStorage('last_update_check');
         // ------------------------------------------------------------------------
 
         // callback for us to track PanAndZoom operations
-        NETDATA.globalPanAndZoom.callback = netdataPanAndZoomCallback;
+        NETDATA.globalPanAndZoom.callback = urlOptions.netdataPanAndZoomCallback;
 
         // let it run (update the charts)
         NETDATA.unpause();
             if(typeof hash === 'string' && hash.substring(0, 1) === '#' && urlOptions.hash.startsWith(hash + '_submenu_') === false) {
                 urlOptions.hash = hash;
                 //console.log(urlOptions.hash);
-                netdataHashUpdate();
+                urlOptions.hashUpdate();
             }
             //else console.log('hash: not accepting ' + hash);
             //}
 
             sync_option('eliminate_zero_dimensions');
             sync_option('destroy_on_hide');
+            sync_option('async_on_scroll');
             sync_option('parallel_refresher');
             sync_option('concurrent_refreshes');
             sync_option('sync_selection');
         // handle options changes
         $('#eliminate_zero_dimensions').change(function()       { NETDATA.setOption('eliminate_zero_dimensions', $(this).prop('checked')); });
         $('#destroy_on_hide').change(function()                 { NETDATA.setOption('destroy_on_hide', $(this).prop('checked')); });
+        $('#async_on_scroll').change(function()                 { NETDATA.setOption('async_on_scroll', $(this).prop('checked')); });
         $('#parallel_refresher').change(function()              { NETDATA.setOption('parallel_refresher', $(this).prop('checked')); });
         $('#concurrent_refreshes').change(function()            { NETDATA.setOption('concurrent_refreshes', $(this).prop('checked')); });
         $('#sync_selection').change(function()                  { NETDATA.setOption('sync_selection', $(this).prop('checked')); });
         $('#sync_pan_and_zoom').change(function()               { NETDATA.setOption('sync_pan_and_zoom', $(this).prop('checked')); });
-        $('#stop_updates_when_focus_is_lost').change(function() { NETDATA.setOption('stop_updates_when_focus_is_lost', $(this).prop('checked')); });
+        $('#stop_updates_when_focus_is_lost').change(function() {
+            urlOptions.update_always = !$(this).prop('checked');
+            urlOptions.hashUpdate();
+
+            NETDATA.setOption('stop_updates_when_focus_is_lost', !urlOptions.update_always);
+        });
         $('#smooth_plot').change(function()                     { NETDATA.setOption('smooth_plot', $(this).prop('checked')); });
         $('#pan_and_zoom_data_padding').change(function()       { NETDATA.setOption('pan_and_zoom_data_padding', $(this).prop('checked')); });
         $('#show_help').change(function()                       {
             urlOptions.help = $(this).prop('checked');
+            urlOptions.hashUpdate();
+
             NETDATA.setOption('show_help', urlOptions.help);
             netdataReload();
         });
         // it reloads the page
         $('#netdata_theme_control').change(function() {
             urlOptions.theme = $(this).prop('checked')?'slate':'white';
+            urlOptions.hashUpdate();
+
             if(setTheme(urlOptions.theme))
                 netdataReload();
         });
     // callback to add the dashboard info to the
     // parallel javascript downloader in netdata
     var netdataPrepCallback = function() {
+        NETDATA.requiredCSS.push({
+            url: NETDATA.serverDefault + 'css/bootstrap-toggle-2.2.2.min.css',
+            isAlreadyLoaded: function() { return false; }
+        });
+
+        NETDATA.requiredJs.push({
+            url: NETDATA.serverDefault + 'lib/bootstrap-toggle-2.2.2.min.js',
+            isAlreadyLoaded: function() { return false; }
+        });
+
         NETDATA.requiredJs.push({
-            url: NETDATA.serverDefault + 'dashboard_info.js?v57',
+            url: NETDATA.serverDefault + 'dashboard_info.js?v20161112-6',
+            async: false,
             isAlreadyLoaded: function() { return false; }
         });
-        
+
         if(isdemo()) {
             document.getElementById('masthead').style.display = 'block';
         }
+        else {
+            if(urlOptions.update_always === true)
+                NETDATA.setOption('stop_updates_when_focus_is_lost', !urlOptions.update_always);
+        }
     }
 
     // our entry point
                         <i class="fa fa-circle"></i> The excellent <a href="http://dygraphs.com/" target="_blank">Dygraphs.com</a> web chart library,
                         <i class="fa fa-copyright"></i> Copyright 2009, Dan Vanderkam, <a href="http://dygraphs.com/legal.html" target="_blank">MIT License</a>
 
-                        <i class="fa fa-circle"></i> <a href="http://omnipotent.net/jquery.sparkline/" target="_blank">jQuery Sparklines</a> web chart library,
-                        <i class="fa fa-copyright"></i> Copyright 2009-2012, Splunk Inc., <a href="http://opensource.org/licenses/BSD-3-Clause" target="_blank">New BSD License</a>
-
-                        <i class="fa fa-circle"></i> <a href="http://benpickles.github.io/peity/" target="_blank">Peity</a> web chart library,
-                        <i class="fa fa-copyright"></i> Copyright 2009-2015, Ben Pickles, <a href="https://github.com/benpickles/peity/blob/master/MIT-LICENCE" target="_blank">MIT License</a>
-
                         <i class="fa fa-circle"></i> <a href="https://rendro.github.io/easy-pie-chart/" target="_blank">Easy Pie Chart</a> web chart library,
                         <i class="fa fa-copyright"></i> Copyright 2013, Robert Fleischmann, <a href="https://github.com/rendro/easy-pie-chart/blob/master/LICENSE" target="_blank">MIT License</a>
 
                         <i class="fa fa-circle"></i> <a href="https://jamesflorentino.github.io/nanoScrollerJS/" target="_blank">NanoScroller</a>,
                         <i class="fa fa-copyright"></i> Copyright 2012, James Florentino, <a href="https://github.com/jamesflorentino/nanoScrollerJS/blob/master/LICENSE" target="_blank">MIT License</a>
 
-                        <i class="fa fa-circle"></i> <a href="https://github.com/marcj/css-element-queries" target="_blank">CSS Element Queries</a>,
-                        <i class="fa fa-copyright"></i> Copyright Marc J. Schmidt, <a href="https://github.com/marcj/css-element-queries/blob/master/LICENSE" target="_blank">MIT License</a>
-
                         <i class="fa fa-circle"></i> <a href="https://fortawesome.github.io/Font-Awesome/" target="_blank">FontAwesome</a>,
                         <i class="fa fa-copyright"></i> Created by Dave Gandy, Font: <a href="http://scripts.sil.org/OFL" target="_blank">SIL OFL 1.1 License</a>, CSS: <a href="http://opensource.org/licenses/mit-license.html" target="_blank">MIT License</a>
 
                         <i class="fa fa-circle"></i> <a href="http://www.iconsdb.com/soylent-red-icons/seo-performance-icon.html" target="_blank">IconsDB.com Icons</a>, Icons provided as CC0 1.0 Universal (CC0 1.0) Public Domain Dedication.
 
-                        <i class="fa fa-circle"></i> <a href="http://morrisjs.github.io/morris.js/" target="_blank">morris.js</a>,
-                        <i class="fa fa-copyright"></i> Copyright 2013, Olly Smith, <a href="http://morrisjs.github.io/morris.js/" target="_blank">Simplified BSD License</a>
-
-                        <i class="fa fa-circle"></i> <a href="http://raphaeljs.com/" target="_blank">RaphaĆ«l</a>,
-                        <i class="fa fa-copyright"></i> Copyright 2008, Dmitry Baranovskiy, <a href="http://raphaeljs.com/license.html" target="_blank">MIT License</a>
-
-                        <i class="fa fa-circle"></i> <a href="http://C3js.org/" target="_blank">C3</a>,
-                        <i class="fa fa-copyright"></i> Copyright 2013, Masayuki Tanaka, <a href="https://github.com/masayuki0812/c3/blob/master/LICENSE" target="_blank">MIT License</a>
+                        <i class="fa fa-circle"></i> <a href="http://bootstrap-table.wenzhixin.net.cn/" target="_blank">bootstrap-table</a>,
+                        <i class="fa fa-copyright"></i> Copyright (c) 2012-2016 Zhixin Wen, <a href="https://github.com/wenzhixin/bootstrap-table/blob/master/LICENSE" target="_blank">MIT License</a>
 
-                        <i class="fa fa-circle"></i> <a href="http://D3js.org/" target="_blank">D3</a>,
-                        <i class="fa fa-copyright"></i> Copyright 2015, Mike Bostock, <a href="http://opensource.org/licenses/BSD-3-Clause" target="_blank">BSD License</a>
+                        <i class="fa fa-circle"></i> <a href="https://github.com/hhurz/tableExport.jquery.plugin" target="_blank">tableExport.jquery.plugin</a>,
+                        <i class="fa fa-copyright"></i> Copyright (c) 2015,2016 hhurz, <a href="http://rawgit.com/hhurz/tableExport.jquery.plugin/master/tableExport.js" target="_blank">MIT License</a>
 
                     </small>
                 </div>
                             </div>
                             <hr/>
                             <div class="p">
-                                <h4>SHIFT + Mouse Wheel <small>(does not work on firefox and IE/Edge)</small></h4>
+                                <h4>SHIFT + Mouse Wheel</h4>
                                 While pressing the shift key and the mouse pointer is over the contents of a chart, scroll the mouse wheel to zoom in or out. This kind of zooming is aligned to center below the mouse pointer. The other charts will follow too.
                                 <br/>
                                 Once a chart is zoomed, auto refreshing stops for all charts. To enable it again, <b>double click</b> a zoomed chart.
     </div>
 
     <div class="modal fade" id="alarmsModal" tabindex="-1" role="dialog" aria-labelledby="alarmsModalLabel">
-        <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-dialog modal-lg" role="document"  style="display: table;"> <!-- allow the modal to expand horizontally -->
             <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>
                                     <tr class="option-row">
                                         <td class="option-control"><input id="destroy_on_hide" type="checkbox" data-toggle="toggle" data-on="Destroy" data-off="Hide" data-width="110px"></td>
                                         <td class="option-info"><strong>How to handle hidden charts?</strong><br/>
-                                            <small>When set to <b>Destroy</b>, charts that are not in the current viewport of the browser (are above, or below the visible area of the page), will be destroyed and re-created if and when they become visible again. When set to <b>Hide</b>, the not-visible charts will be just hidden, to simplify the DOM and speed up your browser. Set it to <b>Destroy</b>, to lower the memory requirements of your browser. Set it to <b>Hide</b> for smoother page scrolling.</small>
+                                            <small>When set to <b>Destroy</b>, charts that are not in the current viewport of the browser (are above, or below the visible area of the page), will be destroyed and re-created if and when they become visible again. When set to <b>Hide</b>, the not-visible charts will be just hidden, to simplify the DOM and speed up your browser. Set it to <b>Destroy</b>, to lower the memory requirements of your browser. Set it to <b>Hide</b> for faster restoration of charts on page scrolling.</small>
+                                        </td>
+                                        </tr>
+                                    <tr class="option-row">
+                                        <td class="option-control"><input id="async_on_scroll" type="checkbox" data-toggle="toggle" data-on="Async" data-off="Sync" data-width="110px"></td>
+                                        <td class="option-info"><strong>Page scroll handling?</strong><br/>
+                                            <small>When set to <b>Sync</b>, charts will be examined for their visibility synchronously. On slow computers this may impact the smoothness of page scrolling. To work asynchronously set it to <b>Async</b>. When set to <b>Sync</b>, the performance of page scrolling is monitored and this setting switches automatically to <b>Async</b> if the browser is found slow. Set it to <b>Sync</b> for best visual experience. Set it to <b>Async</b> for smoother page scrolling.</small>
                                         </td>
                                         </tr>
                                     </table>
                                         <td class="option-info"><strong>Which chart refresh policy to use?</strong><br/>
                                             <small>When set to <b>parallel</b>, visible charts are refreshed in parallel (all queries are sent to netdata server in parallel) and are rendered asynchronously. When set to <b>sequential</b> charts are refreshed one after another. Set it to parallel if your browser can cope with it (most modern browsers do), set it to sequential if you work on an older/slower computer.</small>
                                         </td>
-                                        </tr>
+                                    </tr>
                                     <tr class="option-row" id="concurrent_refreshes_row">
-                                        <td class="option-control"></td>
-                                        <td class="option-info">
-                                            <table>
-                                            <tr class="option-row">
-                                            <td class="option-control">
-                                            <input id="concurrent_refreshes" type="checkbox" checked data-toggle="toggle" data-on="Resync" data-off="Best Effort" data-width="110px">
-                                            </td>
-                                            <td class="option-info">
-                                            <strong>Shall we re-sync chart refreshes?</strong><br/>
+                                        <td class="option-control"><input id="concurrent_refreshes" type="checkbox" checked data-toggle="toggle" data-on="Resync" data-off="Best Effort" data-width="110px"></td>
+                                        <td class="option-info"><strong>Shall we re-sync chart refreshes?</strong><br/>
                                             <small>When set to <b>Resync</b>, the dashboard will attempt to re-synchronize all the charts so that they are refreshed concurrently. When set to <b>Best Effort</b>, each chart may be refreshed with a little time difference to the others. Normally, the dashboard starts refreshing them in parallel, but depending on the speed of your computer and the network latencies, charts start having a slight time difference. Setting this to <b>Resync</b> will attempt to re-synchronize the charts on every update. Setting it to <b>Best Effort</b> may lower the pressure on your browser and the network.</small>
-                                            </td>
-                                            </tr>
-                                            </table>
                                         </td>
-                                        </tr>
+                                    </tr>
                                     <tr class="option-row">
                                         <td class="option-control"><input id="sync_selection" type="checkbox" checked data-toggle="toggle" data-on="Sync" data-off="Don't Sync" data-onstyle="success" data-offstyle="danger" data-width="110px"></td>
                                         <td class="option-info"><strong>Sync hover selection on all charts?</strong><br/>
     </div>
 </body>
 </html>
-<script async type="text/javascript" src="dashboard.js?v57"></script>
+<script type="text/javascript" src="dashboard.js?v20161208-1"></script>