Browse Source

Small updates to dash.html (#7757)

* dash.html docs and styling improvements

* Fixing whitespace

* Futher whitespace fixing

* Finally fixed whitespace

* Tabs to spaces

* Fix Netdata capitalization

Co-Authored-By: Joel Hans <joel.g.hans@gmail.com>

* Fix Netdata capitalization

Co-Authored-By: Joel Hans <joel.g.hans@gmail.com>

Co-authored-by: Joel Hans <joel.g.hans@gmail.com>
Thomas Anderson 5 years ago
parent
commit
5f78990701
2 changed files with 738 additions and 721 deletions
  1. 25 15
      web/gui/custom/README.md
  2. 713 706
      web/gui/dash-example.html

+ 25 - 15
web/gui/custom/README.md

@@ -16,7 +16,7 @@ source](../demo.html)**.
 
 You should also look at the [custom dashboard
 template](https://my-netdata.io/dashboard.html), which contains samples of all
-supported charts. The code is [here](../dashboard.html). 
+supported charts. The code is [here](../dashboard.html).
 
 If you plan to put the dashboard on TV, check out [tv.html](../tv.html). Here's
 is a screenshot of it, monitoring two servers on the same page:
@@ -65,7 +65,11 @@ header:
 
 ## Dash (Multi-Host Dashboard)
 
-`dash-example.html` is an all-in-one page that automatically fetches graphs from all your hosts. Just add your graphs and charts (or use the defaults) one time using the `dash-*` syntax and it will be automatically replicated for all of your hosts; showing alarms and graphs for all your hosts on **one page!**
+`dash-example.html` is an all-in-one page that automatically fetches graphs from all your hosts. Just add your graphs and charts (or use the defaults) one time using the `dash-*` syntax, and your selections will be automatically replicated for all of your hosts; showing alarms and graphs for all your hosts on **one page!**
+
+__**Dash will only work if you have implemented netdata streaming using `stream.conf`**__
+
+`dash-example.html` was created as an experiment to demonstrate the capabilities of netdata in a multi-host environment. If you desire more features, submit a pull request or check out Netdata Cloud!
 
 ### Configure Dash
 
@@ -74,29 +78,31 @@ First, rename the file so it doesn't get overwritten. For instance, with a webro
 cp /usr/share/netdata/web/dash-example.html /usr/share/netdata/web/dash.html
 ```
 
-Change the following line in `dash.html` to reflect your URLs. The second URL is used if you access your netdata dashboard from a reverse proxy. The reverse proxy URL is optional, if it is not set then both will use the netdata host URL.
+Find and change the following line in `dash.html` to reflect your Netdata URLs. The second URL is only used if you access your Netdata dashboard through a reverse proxy. The reverse proxy URL is optional; if it is not set then both will use the Netdata host URL.
 
 ```js
 /*
 * TUTORIAL: Change this to the URL of your netdata host
 * If you use netdata behind a reverse proxy, add a second parameter for the reverse proxy url like so:
-* 		new Dash('http://localhost:19999', 'https://my-domain.com/stats');
+*         new Dash('http://localhost:19999', 'https://my-domain.com/stats');
 */
 var dash = new Dash('http://localhost:19999');
 ```
 
-If you want to change the graphs or styling to fit your needs, just add an element to the page as shown. child divs will be generated to create your graph/chart:
+### The `dash-*` Syntax
+
+If you want to change the graphs or styling to fit your needs, just add an element to the page as shown. Child divs will be generated to create your graph/chart:
 ```
-<div class="dash-graph"                     <---- 	Use class dash-graph for line graphs, etc
-	data-dash-netdata="system.cpu"          <---- 	REQUIRED: Use data-dash-netdata to set the data source
-	data-dygraph-valuerange="[0, 100]">     <---- 	OPTIONAL: This overrides the default config. Any other data-* attributes will
-</div>								                          be added to the generated div, so you can set any desired options here
+<div class="dash-graph"                     <----     Use class dash-graph for line graphs, etc
+    data-dash-netdata="system.cpu"          <----     REQUIRED: Use data-dash-netdata to set the data source
+    data-dygraph-valuerange="[0, 100]">     <----     OPTIONAL: This overrides the default config. Any other data-* attributes will
+</div>                                                          be added to the generated div, so you can set any desired options here
 
-<div class="dash-chart"                     <---- 	Use class dash-chart for pie charts, etc. CHARTS ARE SQUARE
-	data-dash-netdata="system.io"           <---- 	REQUIRED: Use data-dash-netdata to set the data source
-	data-dimensions="in"                    <---- 	Use this to override or append default options
-	data-title="Disk Read"                  <---- 	Use this to override or append default options
-	data-common-units="dash.io">			<---- 	Use this to override or append default options
+<div class="dash-chart"                     <----     Use class dash-chart for pie charts, etc. CHARTS ARE SQUARE
+    data-dash-netdata="system.io"           <----     REQUIRED: Use data-dash-netdata to set the data source
+    data-dimensions="in"                    <----     Use this to override or append default options
+    data-title="Disk Read"                  <----     Use this to override or append default options
+    data-common-units="dash.io">            <----     Use this to override or append default options
 </div>
 ```
 
@@ -104,7 +110,7 @@ To change the sizes of graphs and charts, find the `Dash.options` object in `das
 ```js
 /*
 * TUTORIAL: Change your graph/chart dimensions here. Host columns will automatically adjust.
-* 			Charts are square! Their width is the same as their height.
+*           Charts are square! Their width is the same as their height.
 */
 this.options = {
     graph_width: '40em',
@@ -115,6 +121,10 @@ this.options = {
 
 To change the display order of your hosts, which is saved in localStorage, click the settings gear in the lower right corner
 
+We hope you like it!
+
+---
+
 
 ## dashboard.js
 

+ 713 - 706
web/gui/dash-example.html

@@ -1,104 +1,106 @@
 <!DOCTYPE html>
 <html lang="en">
 <head>
-	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-	<meta charset="utf-8">
-	<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
-	<meta name="viewport" content="width=device-width, initial-scale=1">
-	<meta name="apple-mobile-web-app-capable" content="yes">
-	<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <meta name="apple-mobile-web-app-capable" content="yes">
+    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
 </head>
 <body>
-	<div id="alarms" class="collapsed">
-		<div class="alarm-collapse-button" onclick="dash.toggle_alarm_collapse()">&rsaquo;</div>
-		<span class="alarm-count"></span>
-		<h1>Alarms</h1>
-		<div class="alarm-host-list"></div>
-		<div class="settings-button" onclick="dash.reorder_hosts()">&#9881;</div>
-	</div>
-	<div id="dash">
-		<div class="netdata-host-stats-container template">
-			<div class="drag-anchor"></div>
-			<div class="netdata-host-name">host</div>
-			<div class="netdata-host-stats">
-				<div class="dash-graph"
-					data-dash-netdata="system.cpu"
-					data-dygraph-valuerange="[0, 100]">
-				</div>
-				<div class="dash-graph"
-					data-dash-netdata="system.load">
-				</div>
-				<div class="dash-graph"
-					data-dash-netdata="system.ram">
-				</div>
-				<div class="dash-graph"
-					data-dash-netdata="system.net">
-				</div>
-				<div class="dash-graph"
-					data-dash-netdata="system.processes">
-				</div>
-				<div class="dash-graph"
-					data-dash-netdata="apps.cpu">
-				</div>
-				<div class="dash-graph"
-					data-dash-netdata="apps.mem">
-				</div>
-				<div class="dash-charts">
-					<div class="dash-chart"
-						data-dash-netdata="system.io"
-						data-dimensions="in"
-						data-title="Disk Read"
-						data-common-units="dash.io">
-					</div>
-					<div class="dash-chart"
-						data-dash-netdata="system.io"
-						data-dimensions="out"
-						data-title="Disk Write"
-						data-common-units="dash.io">
-					</div>
-					<div class="dash-chart"
-						data-dash-netdata="system.cpu"
-						data-chart-library="gauge"
-						data-title="CPU"
-						data-units="%"
-						data-gauge-max-value="100"
-						data-colors="#22AA99"
-						data-gauge-stroke-color="#373B40">
-					</div>
-					<div class="dash-chart"
-						data-dash-netdata="system.net"
-						data-dimensions="received"
-						data-title="Net Inbound"
-						data-common-units="dash.net">
-					</div>
-					<div class="dash-chart"
-						data-dash-netdata="system.net"
-						data-dimensions="sent"
-						data-title="Net Outbound"
-						data-common-units="dash.net">
-					</div>
-					<div class="dash-chart"
-						data-dash-netdata="system.ram"
-						data-dimensions="used|buffers|active|wired"
-						data-append-options="percentage"
-						data-title="Used RAM"
-						data-units="%"
-						data-easypiechart-max-value="100"
-						data-colors="#EE9911">
-					</div>
-				</div>
-			</div>
-		</div>
-	</div>
+    <div id="alarms" class="collapsed">
+        <div class="alarm-collapse-button" onclick="dash.toggle_alarm_collapse()">&rsaquo;</div>
+        <span class="alarm-count"></span>
+        <h1>Alarms</h1>
+        <div class="alarm-host-list"></div>
+        <div class="settings-button" onclick="dash.reorder_hosts()">&#9881;&#xFE0E;</div>
+    </div>
+    <div id="dash">
+        <div class="netdata-host-stats-container template">
+            <div class="netdata-host-name">host</div>
+            <div class="netdata-host-stats">
+                <div class="dash-graph"
+                    data-dash-netdata="system.cpu"
+                    data-dygraph-valuerange="[0, 100]">
+                </div>
+                <div class="dash-graph"
+                    data-dash-netdata="system.load">
+                </div>
+                <div class="dash-graph"
+                    data-dash-netdata="system.ram">
+                </div>
+                <div class="dash-graph"
+                    data-dash-netdata="disk_space._">
+                </div>
+                <div class="dash-graph"
+                    data-dash-netdata="system.net">
+                </div>
+                <div class="dash-graph"
+                    data-dash-netdata="system.processes">
+                </div>
+                <div class="dash-graph"
+                    data-dash-netdata="apps.cpu">
+                </div>
+                <div class="dash-graph"
+                    data-dash-netdata="apps.mem">
+                </div>
+                <div class="dash-charts">
+                    <div class="dash-chart"
+                        data-dash-netdata="system.io"
+                        data-dimensions="in"
+                        data-title="Disk Read"
+                        data-common-units="dash.io">
+                    </div>
+                    <div class="dash-chart"
+                        data-dash-netdata="system.io"
+                        data-dimensions="out"
+                        data-title="Disk Write"
+                        data-common-units="dash.io">
+                    </div>
+                    <div class="dash-chart"
+                        data-dash-netdata="system.cpu"
+                        data-chart-library="gauge"
+                        data-title="CPU"
+                        data-units="%"
+                        data-gauge-max-value="100"
+                        data-colors="#22AA99"
+                        data-gauge-stroke-color="#373B40">
+                    </div>
+                    <div class="dash-chart"
+                        data-dash-netdata="system.net"
+                        data-dimensions="received"
+                        data-title="Net Inbound"
+                        data-common-units="dash.net">
+                    </div>
+                    <div class="dash-chart"
+                        data-dash-netdata="system.net"
+                        data-dimensions="sent"
+                        data-title="Net Outbound"
+                        data-common-units="dash.net">
+                    </div>
+                    <div class="dash-chart"
+                        data-dash-netdata="system.ram"
+                        data-dimensions="used|buffers|active|wired"
+                        data-append-options="percentage"
+                        data-title="Used RAM"
+                        data-units="%"
+                        data-easypiechart-max-value="100"
+                        data-colors="#EE9911">
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
 </body>
 <script
-	src="https://code.jquery.com/jquery-3.4.1.min.js"
-	integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
-	crossorigin="anonymous">
+    src="https://code.jquery.com/jquery-3.4.1.min.js"
+    integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
+    crossorigin="anonymous">
 </script>
 <script type="text/javascript">
 class PickNSort {
-	// PickNSort.js
+    // PickNSort.js
 
     constructor (dark) {
         this.items = [];
@@ -294,6 +296,9 @@ class PickNSort {
     }
 
     add_css_to_page (dark) {
+        /*
+        * TUTORIAL: Change your theme colors here. Not sure how well it will work...
+        */
         var light_theme = {
             windowbg: "#FFF",
             itembg: "#F4F4F4",
@@ -304,10 +309,10 @@ class PickNSort {
             itembg: "#333",
             itemcolor: "#DBDBDB"
         }
+
         var current_theme = (dark) ? dark_theme : light_theme;
-        $("<style>")
-            .prop("type", "text/css")
-            .html(`
+
+        var css_string = `
 #picknsort-container {
     position: fixed;
     z-index: 9999 !important;
@@ -390,624 +395,626 @@ class PickNSort {
     padding: 0em 0.5em;
     height: calc(100% - 0.2em);
 }
-        `).appendTo("head");
+        ` // End multiline string
+
+
+        $("<style>")
+            .prop("type", "text/css")
+            .html(css_string).appendTo("head");
     }
 }
 </script>
 <script type="text/javascript">
-	// Dash JS
-
-	function picknsort_setup () {
-		if (localStorage.getItem('netdata_ordered_hosts')) {
-			picknsort.last_output = JSON.parse(localStorage.getItem('netdata_ordered_hosts'));
-		} else {
-			picknsort.last_output = {
-				enabled: dash.netdata_info.mirrored_hosts,
-				disabled: []
-			}
-		}
-	}
-
-	class Dash {
-		constructor (base_url, link_base_url) {
-			this.base_url = base_url; // URL of netdata host, with port
-			this.link_base_url = link_base_url || base_url; // Reverse proxy URL (Optional)
-			this.current_alarms = {};
-			this.new_alarms = {};
-			this.first_build = true;
-			/*
-			* TUTORIAL: Change your graph/chart dimensions here. Host columns will automatically adjust.
-			* 			Charts are square! Their width is the same as their height.
-			*/
-			this.options = {
-				graph_width: '40em',
-				graph_height: '20em',
-				chart_width: '10em' // Charts are square
-			};
-
-			this.init();
-		}
-
-		reorder_hosts () {
-			picknsort.popup(dash.update_ordered_hosts, dash.find_new_hosts);
-		}
-
-		get_enabled_hosts () {
-			try {
-				return JSON.parse(localStorage.getItem('netdata_ordered_hosts')).enabled
-			} catch (e) {
-				return this.netdata_info.mirrored_hosts;
-			}
-		}
-
-		get_ordered_hosts () {
-			try {
-				return JSON.parse(localStorage.getItem('netdata_ordered_hosts'));
-			} catch (e) {
-				return null;
-			}
-		}
-
-		set_ordered_hosts (hosts) {
-			localStorage.setItem('netdata_ordered_hosts', JSON.stringify(hosts));
-			location.reload();
-		}
-
-		update_ordered_hosts (enabled, disabled) {
-			if (enabled === null && disabled === null) {
-				return;
-			}
-			dash.set_ordered_hosts({
-				enabled: enabled,
-				disabled: disabled
-			});
-		}
-
-		find_new_hosts () {
-			var newhosts = dash.netdata_info.mirrored_hosts.slice();
-			var currenthosts = dash.get_ordered_hosts();
-
-			for (var i=0,len=currenthosts.enabled.length; i<len; i++) {
-				var found = newhosts.indexOf(currenthosts.enabled[i]);
-
-				if (found > -1) {
-					newhosts.splice(found, 1);
-				} else {
-					currenthosts.enabled.splice(i, 1);
-				}
-			}
-
-			for (var i=0,len=currenthosts.disabled.length; i<len; i++) {
-				var found = newhosts.indexOf(currenthosts.disabled[i]);
-
-				if (found > -1) {
-					newhosts.splice(found, 1);
-				} else {
-					currenthosts.enabled.splice(i, 1);
-				}
-			}
-
-			for (var i=0,len=newhosts.length; i<len; i++) {
-				currenthosts.enabled.push(newhosts[i]);
-			}
-
-			dash.set_ordered_hosts(currenthosts);
-		}
-
-		get_host_url (hostname, link) {
-			var base = ( link ) ? this.link_base_url : this.base_url;
-			return (hostname) ? base + '/host/' + hostname : base;
-		}
-
-		get_api_url (hostname) {
-			return this.get_host_url(hostname) + '/api/v1'
-		}
-
-
-		init () {
-			var that = this;
-			$.get(this.get_api_url() + '/info', function (data) {
-				that.netdata_info = data;
-				picknsort_setup()
-				that.build();
-			});
-		}
-
-		digest () {
-			this.fetch_active_alarms();
-			this.fix_layout_errors();
-		}
-
-		build () {
-			// Fix vertically misaligned stats on load error
-			$('.dash-graph').css({
-				height: this.options.graph_height,
-				width: this.options.graph_width
-			});
-			$('.dash-chart').css({
-				height: this.options.chart_width, // Charts are square
-				width: this.options.chart_width
-			});
-
-			// Fix chart alignment
-			$('.netdata-host-stats-container').css({
-				width: 'calc(' + this.options.graph_width + ' + (2 * ' + $('.netdata-host-stats-container').css('padding-left') + '))'
-			});
-
-			var $template = $('.netdata-host-stats-container').first();
-
-			var hosts = this.get_enabled_hosts();
-
-			for (var i=0, len=hosts.length; i<len; i++) {
-				var hostname = hosts[i];
-				$('#alarms .alarm-host-list').append('<div class="host-alarms ' + hostname + '"><a target="_blank" href="' + this.get_host_url(hostname, true) + '/"><h2>' + hostname + '</h2></a></div>');
-
-				$template.clone().removeClass('template').appendTo('#dash');
-				var $newest = $('.netdata-host-stats-container').last();
-				this.build_stats($newest, hostname);
-			}
-
-
-			if (this.first_build === true) {
-				this.remove_template_elements();
-				this.first_build = false;
-			}
-		}
-
-		build_stats ($hoststats, hostname) {
-			var that = this;
-
-			$hoststats.find('.netdata-host-stats .dash-graph').each(function () {
-				that.build_graph($(this), hostname);
-			});
-
-			$hoststats.find('.netdata-host-stats .dash-chart').each(function () {
-				that.build_chart($(this), hostname);
-			});
-
-			$hoststats.find('.netdata-host-name').html('<a target="_blank" href="' + that.get_host_url(hostname, true) + '/">' + hostname + '</a>');
-		}
-
-		build_graph ($wrapper, hostname) {
-			var that = this;
-			var $graph = $('<div>', {
-				// Defaults
-				'data-netdata': $wrapper.attr('data-dash-netdata'),
-				'data-host': that.get_host_url(hostname),
-				'data-before': '0',
-				'data-after': '-540',
-				'role': 'application',
-				'data-width': that.options.graph_width,
-				'data-height': that.options.graph_height
-			});
-
-			$.each($wrapper[0].attributes, function (key, node) {
-				if ( node.name.match(/^data-(?!dash).*/ ) ) {
-					$graph.attr(node.name, node.value);
-				}
-			});
-
-			$graph.appendTo($wrapper);
-		}
-
-		build_chart ($wrapper, hostname) {
-			var that = this;
-			var $graph = $('<div>', {
-				// Defaults
-				'data-netdata': $wrapper.attr('data-dash-netdata'),
-				'data-host': that.get_host_url(hostname),
-				'data-before': '0',
-				'data-after': '-540',
-				'data-points': '540',
-				'role': 'application',
-				'data-width': that.options.chart_width,
-				'data-title': ' ',
-				'data-easypiechart-trackcolor': "#373B40",
-				'data-chart-library': 'easypiechart'
-
-			})
-
-			$.each($wrapper[0].attributes, function (key, node) {
-				if ( node.name.match(/^data-(?!dash).*/ ) ) {
-					$graph.attr(node.name, node.value);
-				}
-			});
-
-			$graph.appendTo($wrapper);
-		}
-
-		all_alarms_are_warnings () {
-			var out = true;
-			$.each(this.current_alarms, function (host, alarms) {
-				if (out === false) {
-					return false; // Maximum efficiency
-				}
-				$.each(alarms, function (name, data) {
-					if (data.status != "WARNING") {
-						out = false;
-						return false;
-					}
-				});
-			});
-			return out;
-		}
-
-		update_alarm_count () {
-			console.log(this.all_alarms_are_warnings());
-			var count = $('img.alarm-badge').length;
-			$('.alarm-count').text($('img.alarm-badge').length);
-			$('.alarm-count').removeClass('no-alarms warnings-only');
-			if ( count === 0 ) {
-				$('.alarm-count').addClass('no-alarms');
-				return
-			}
-			if (this.all_alarms_are_warnings()) {
-				$('.alarm-count').addClass('warnings-only');
-			}
-		}
-
-		fetch_active_alarms () {
-			var that = this;
-			console.log("Getting alarms...");
-			var hosts = this.get_enabled_hosts();
-			for (var i=0, len=hosts.length; i<len; i++) {
-				var hostname = hosts[i];
-
-				$.get(this.get_api_url(hostname) + '/alarms', function (data) {
-					that.new_alarms[ data.hostname ] = data.alarms;
-					if ( Object.keys(that.new_alarms).length === len ) {
-						// All responses received
-						if ( that.check_new_current_alarms() ) {
-							console.log("No alarm changes detected... Refreshing images.");
-							that.new_alarms = {};
-							that.refresh_alarm_images();
-							return;
-						}
-
-						that.current_alarms = that.new_alarms;
-						that.new_alarms = {};
-						that.draw_alarms();
-						that.update_alarm_count();
-					}
-				});
-			}
-		}
-
-		toggle_alarm_collapse () {
-			$('#alarms').toggleClass('collapsed');
-		}
-
-		check_new_current_alarms () {
-			// Create arrays of property names
-			var a = this.current_alarms;
-			var b = this.new_alarms;
-			var aProps = Object.keys(a);
-			var bProps = Object.keys(b);
-
-			// If number of properties is different,
-			// objects are not equivalent
-			if (aProps.length != bProps.length) {
-				return false;
-			}
-
-			for (var i = 0; i < aProps.length; i++) {
-				var propName = aProps[i];
-				var aa = a[propName];
-				var aaProps = Object.keys(aa);
-				var bb = b[propName];
-				var bbProps = Object.keys(bb);
-
-				if (aaProps.length != bbProps.length) {
-					return false;
-				}
-
-				for (var n = 0, len=aaProps.length; i < len; i++) {
-					if ( bb[aaProps[n]] === undefined ) {
-						return false;
-					}
-				}
-			}
-
-			// If we made it this far, objects
-			// are considered equivalent
-			return true;
-		}
-
-		refresh_alarm_images () {
-			// Use Math.random() to "reload" the image
-			$('.alarm-badge').each(function () {
-				var old_src = $(this).attr('src').replace(/&rand=.*/g, "");
-				$(this).attr('src', old_src + "&rand=" + Math.random());
-			});
-		}
-
-		draw_alarms () {
-			var that = this;
-			console.log("Drawing...", that.current_alarms);
-			$.each(that.current_alarms, function(hostname, alarms) {
-				$('#alarms .host-alarms.' + hostname + ' .alarm-badge').remove();
-				$.each(alarms, function (index, alarm) {
-					that.draw_alarm(hostname, alarm);
-				})
-			});
-		}
-
-		draw_alarm (hostname, alarm) {
-			var queryStr = '/badge.svg?alarm=' + alarm.name + '&chart=' + alarm.chart
-			$('<img />', {
-				src: this.get_api_url(hostname) + queryStr,
-				class: 'alarm-badge'
-			}).appendTo($('#alarms .host-alarms.' + hostname));
-		}
-
-		remove_template_elements () {
-			$('.netdata-host-stats-container').first().remove();
-		}
-
-		fix_layout_errors () {
-			$('.dash-graph:contains("chart not found")').html('<div class="loading-error">Not found</div>');
-			$('.dash-chart:contains("chart not found")').html('<div class="loading-error">Not found</div>');
-		}
-	}
-
-	/*
-	* TUTORIAL: Change this to the URL of your netdata host
-	* If you use netdata behind a reverse proxy, add a second parameter for the reverse proxy url like so:
-	* 		new Dash('http://localhost:19999', 'https://my-domain.com/stats');
-	*/
-	var dash = new Dash('http://localhost:19999');
-
-	var picknsort = new PickNSort(true);
-
-	// Import dashboard.js
-	$.getScript(dash.base_url + '/dashboard.js', function() {
-		console.log("Loaded dashboard.js");
-		setTimeout(function () {
-			$('#alarms').css("visibility", "visible");
-		}, 400);
-	});
-
-	setInterval(function () {
-		dash.digest();
-	}, 6 * 1000);
+    // Dash JS
+
+    function picknsort_setup () {
+        if (localStorage.getItem('netdata_ordered_hosts')) {
+            picknsort.last_output = JSON.parse(localStorage.getItem('netdata_ordered_hosts'));
+        } else {
+            picknsort.last_output = {
+                enabled: dash.netdata_info.mirrored_hosts,
+                disabled: []
+            }
+        }
+    }
+
+    class Dash {
+        constructor (base_url, link_base_url) {
+            this.base_url = base_url; // URL of netdata host, with port
+            this.link_base_url = link_base_url || base_url; // Reverse proxy URL (Optional)
+            this.current_alarms = {};
+            this.new_alarms = {};
+            this.first_build = true;
+            /*
+            * TUTORIAL: Change your graph/chart dimensions here. Host columns will automatically adjust.
+            *             Charts are square! Their width is the same as their height.
+            */
+            this.options = {
+                graph_width: '40em',
+                graph_height: '20em',
+                chart_width: '10em' // Charts are square
+            };
+
+            this.init();
+        }
+
+        reorder_hosts () {
+            picknsort.popup(dash.update_ordered_hosts, dash.find_new_hosts);
+        }
+
+        get_enabled_hosts () {
+            try {
+                return JSON.parse(localStorage.getItem('netdata_ordered_hosts')).enabled
+            } catch (e) {
+                return this.netdata_info.mirrored_hosts;
+            }
+        }
+
+        get_ordered_hosts () {
+            try {
+                return JSON.parse(localStorage.getItem('netdata_ordered_hosts'));
+            } catch (e) {
+                return null;
+            }
+        }
+
+        set_ordered_hosts (hosts) {
+            localStorage.setItem('netdata_ordered_hosts', JSON.stringify(hosts));
+            location.reload();
+        }
+
+        update_ordered_hosts (enabled, disabled) {
+            if (enabled === null && disabled === null) {
+                return;
+            }
+            dash.set_ordered_hosts({
+                enabled: enabled,
+                disabled: disabled
+            });
+        }
+
+        find_new_hosts () {
+            var newhosts = dash.netdata_info.mirrored_hosts.slice();
+            var currenthosts = dash.get_ordered_hosts();
+
+            for (var i=0,len=currenthosts.enabled.length; i<len; i++) {
+                var found = newhosts.indexOf(currenthosts.enabled[i]);
+
+                if (found > -1) {
+                    newhosts.splice(found, 1);
+                } else {
+                    currenthosts.enabled.splice(i, 1);
+                }
+            }
+
+            for (var i=0,len=currenthosts.disabled.length; i<len; i++) {
+                var found = newhosts.indexOf(currenthosts.disabled[i]);
+
+                if (found > -1) {
+                    newhosts.splice(found, 1);
+                } else {
+                    currenthosts.enabled.splice(i, 1);
+                }
+            }
+
+            for (var i=0,len=newhosts.length; i<len; i++) {
+                currenthosts.enabled.push(newhosts[i]);
+            }
+
+            dash.set_ordered_hosts(currenthosts);
+        }
+
+        get_host_url (hostname, link) {
+            var base = ( link ) ? this.link_base_url : this.base_url;
+            return (hostname) ? base + '/host/' + hostname : base;
+        }
+
+        get_api_url (hostname) {
+            return this.get_host_url(hostname) + '/api/v1'
+        }
+
+
+        init () {
+            var that = this;
+            $.get(this.get_api_url() + '/info', function (data) {
+                that.netdata_info = data;
+                picknsort_setup()
+                that.build();
+            });
+        }
+
+        digest () {
+            this.fetch_active_alarms();
+            this.fix_layout_errors();
+        }
+
+        build () {
+            // Fix vertically misaligned stats on load error
+            $('.dash-graph').css({
+                height: this.options.graph_height,
+                width: this.options.graph_width
+            });
+            $('.dash-chart').css({
+                height: this.options.chart_width, // Charts are square
+                width: this.options.chart_width
+            });
+
+            // Fix chart alignment
+            $('.netdata-host-stats-container').css({
+                width: 'calc(' + this.options.graph_width + ' + (2 * ' + $('.netdata-host-stats-container').css('padding-left') + '))'
+            });
+
+            var $template = $('.netdata-host-stats-container').first();
+
+            var hosts = this.get_enabled_hosts();
+
+            for (var i=0, len=hosts.length; i<len; i++) {
+                var hostname = hosts[i];
+                $('#alarms .alarm-host-list').append('<div class="host-alarms ' + hostname + '"><a target="_blank" href="' + this.get_host_url(hostname, true) + '/"><h2>' + hostname + '</h2></a></div>');
+
+                $template.clone().removeClass('template').appendTo('#dash');
+                var $newest = $('.netdata-host-stats-container').last();
+                this.build_stats($newest, hostname);
+            }
+
+
+            if (this.first_build === true) {
+                this.remove_template_elements();
+                this.first_build = false;
+            }
+        }
+
+        build_stats ($hoststats, hostname) {
+            var that = this;
+
+            $hoststats.find('.netdata-host-stats .dash-graph').each(function () {
+                that.build_graph($(this), hostname);
+            });
+
+            $hoststats.find('.netdata-host-stats .dash-chart').each(function () {
+                that.build_chart($(this), hostname);
+            });
+
+            $hoststats.find('.netdata-host-name').html('<a target="_blank" href="' + that.get_host_url(hostname, true) + '/">' + hostname + '</a>');
+        }
+
+        build_graph ($wrapper, hostname) {
+            var that = this;
+            var $graph = $('<div>', {
+                // Defaults
+                'data-netdata': $wrapper.attr('data-dash-netdata'),
+                'data-host': that.get_host_url(hostname),
+                'data-before': '0',
+                'data-after': '-540',
+                'role': 'application',
+                'data-width': that.options.graph_width,
+                'data-height': that.options.graph_height
+            });
+
+            $.each($wrapper[0].attributes, function (key, node) {
+                if ( node.name.match(/^data-(?!dash).*/ ) ) {
+                    $graph.attr(node.name, node.value);
+                }
+            });
+
+            $graph.appendTo($wrapper);
+        }
+
+        build_chart ($wrapper, hostname) {
+            var that = this;
+            var $graph = $('<div>', {
+                // Defaults
+                'data-netdata': $wrapper.attr('data-dash-netdata'),
+                'data-host': that.get_host_url(hostname),
+                'data-before': '0',
+                'data-after': '-540',
+                'data-points': '540',
+                'role': 'application',
+                'data-width': that.options.chart_width,
+                'data-title': ' ',
+                'data-easypiechart-trackcolor': "#373B40",
+                'data-chart-library': 'easypiechart'
+
+            })
+
+            $.each($wrapper[0].attributes, function (key, node) {
+                if ( node.name.match(/^data-(?!dash).*/ ) ) {
+                    $graph.attr(node.name, node.value);
+                }
+            });
+
+            $graph.appendTo($wrapper);
+        }
+
+        all_alarms_are_warnings () {
+            var out = true;
+            $.each(this.current_alarms, function (host, alarms) {
+                if (out === false) {
+                    return false; // Maximum efficiency
+                }
+                $.each(alarms, function (name, data) {
+                    if (data.status != "WARNING") {
+                        out = false;
+                        return false;
+                    }
+                });
+            });
+            return out;
+        }
+
+        update_alarm_count () {
+            console.log(this.all_alarms_are_warnings());
+            var count = $('img.alarm-badge').length;
+            $('.alarm-count').text($('img.alarm-badge').length);
+            $('.alarm-count').removeClass('no-alarms warnings-only');
+            if ( count === 0 ) {
+                $('.alarm-count').addClass('no-alarms');
+                return
+            }
+            if (this.all_alarms_are_warnings()) {
+                $('.alarm-count').addClass('warnings-only');
+            }
+        }
+
+        fetch_active_alarms () {
+            var that = this;
+            console.log("Getting alarms...");
+            var hosts = this.get_enabled_hosts();
+            for (var i=0, len=hosts.length; i<len; i++) {
+                var hostname = hosts[i];
+
+                $.get(this.get_api_url(hostname) + '/alarms', function (data) {
+                    that.new_alarms[ data.hostname ] = data.alarms;
+                    if ( Object.keys(that.new_alarms).length === len ) {
+                        // All responses received
+                        if ( that.check_new_current_alarms() ) {
+                            console.log("No alarm changes detected... Refreshing images.");
+                            that.new_alarms = {};
+                            that.refresh_alarm_images();
+                            return;
+                        }
+
+                        that.current_alarms = that.new_alarms;
+                        that.new_alarms = {};
+                        that.draw_alarms();
+                        that.update_alarm_count();
+                    }
+                });
+            }
+        }
+
+        toggle_alarm_collapse () {
+            $('#alarms').toggleClass('collapsed');
+        }
+
+        check_new_current_alarms () {
+            // Create arrays of property names
+            var a = this.current_alarms;
+            var b = this.new_alarms;
+            var aProps = Object.keys(a);
+            var bProps = Object.keys(b);
+
+            // If number of properties is different,
+            // objects are not equivalent
+            if (aProps.length != bProps.length) {
+                return false;
+            }
+
+            for (var i = 0; i < aProps.length; i++) {
+                var propName = aProps[i];
+                var aa = a[propName];
+                var aaProps = Object.keys(aa);
+                var bb = b[propName];
+                var bbProps = Object.keys(bb);
+
+                if (aaProps.length != bbProps.length) {
+                    return false;
+                }
+
+                for (var n = 0, len=aaProps.length; i < len; i++) {
+                    if ( bb[aaProps[n]] === undefined ) {
+                        return false;
+                    }
+                }
+            }
+
+            // If we made it this far, objects
+            // are considered equivalent
+            return true;
+        }
+
+        refresh_alarm_images () {
+            // Use Math.random() to "reload" the image
+            $('.alarm-badge').each(function () {
+                var old_src = $(this).attr('src').replace(/&rand=.*/g, "");
+                $(this).attr('src', old_src + "&rand=" + Math.random());
+            });
+        }
+
+        draw_alarms () {
+            var that = this;
+            console.log("Drawing...", that.current_alarms);
+            $.each(that.current_alarms, function(hostname, alarms) {
+                $('#alarms .host-alarms.' + hostname + ' .alarm-badge').remove();
+                $.each(alarms, function (index, alarm) {
+                    that.draw_alarm(hostname, alarm);
+                })
+            });
+        }
+
+        draw_alarm (hostname, alarm) {
+            var queryStr = '/badge.svg?alarm=' + alarm.name + '&chart=' + alarm.chart
+            $('<img />', {
+                src: this.get_api_url(hostname) + queryStr,
+                class: 'alarm-badge'
+            }).appendTo($('#alarms .host-alarms.' + hostname));
+        }
+
+        remove_template_elements () {
+            $('.netdata-host-stats-container').first().remove();
+        }
+
+        fix_layout_errors () {
+            $('.dash-graph:contains("chart not found")').html('<div class="loading-error">Not found</div>');
+            $('.dash-chart:contains("chart not found")').html('<div class="loading-error">Not found</div>');
+        }
+    }
+
+    /*
+    * TUTORIAL: Change this to the URL of your netdata host
+    * If you use netdata behind a reverse proxy, add a second parameter for the reverse proxy url like so:
+    *         new Dash('http://localhost:19999', 'https://my-domain.com/stats');
+    */
+    var dash = new Dash('http://localhost:19999');
+
+    var picknsort = new PickNSort(true);
+
+    // Import dashboard.js
+    $.getScript(dash.base_url + '/dashboard.js', function() {
+        console.log("Loaded dashboard.js");
+        setTimeout(function () {
+            $('#alarms').css("visibility", "visible");
+        }, 400);
+    });
+
+    setInterval(function () {
+        dash.digest();
+    }, 6 * 1000);
 
 </script>
 <style type="text/css">
-	body {
-		background-color: #272b30;
-	}
-
-	a, a:hover {
-		color: #AAA !important;
-	}
-
-	#dash {
-		overflow: scroll;
-		width: 100vw;
-		white-space: nowrap;
-		height: 100vh;
-
-	}
-
-	#alarms {
-		display: block;
-		z-index: 9999;
-		position: fixed;
-		right: 0;
-		top: 0;
-		bottom: 0;
-		background-color: #111;
-		width: 23em;
-		padding: 1em;
-		color: #AAA;
-		box-shadow: 0 0 3em #060606;
-		border: solid 1px #2d2d2d;
-		visibility: hidden;
-	}
-
-
-
-	#alarms h1 {
-		text-align: center;
-		margin: 0;
-		margin-bottom: 0.5em;
-		margin-top: -0.1em;
-		font-size: 2em;
-	}
-
-	#alarms h2 {
-		font-size: 1.5em;
-	}
-
-	#alarms .alarm-collapse-button {
-		position: absolute;
-		top: 0;
-		left: 0;
-		width: 0.9em;
-		height: 0.9em;
-		font-size: 4em;
-		line-height: 0.8;
-		background: #111;
-		text-align: center;
-		cursor: default;
-	}
-
-	#alarms .alarm-collapse-button:hover {
-		background: #000;
-	}
-
-	#alarms.collapsed {
-		width: 4em;
-	}
-
-	#alarms.collapsed .alarm-collapse-button {
-		position: fixed;
-		left: auto;
-		right: 0;
-		top: 0;
-		bottom: 0;
-		width: 4em;
-		height: 100vh;
-		opacity: 0;
-		z-index: 9999;
-	}
-
-	#alarms.collapsed h1 {
-		transform: rotate(-90deg);
-		position: absolute;
-		left: -9.1em;
-		top: 4em;
-		width: 20em;
-	}
-
-	#alarms .alarm-host-list {
-		margin-top: 1em;
-		height: 100%;
-		overflow-y: scroll;
-		scrollbar-width: thin;
-	}
-
-	#alarms.collapsed .alarm-host-list {
-		display: none;
-	}
-
-
-
-	#alarms .host-alarms {
-		background: #1d1d1d;
-		margin-bottom: 1em;
-		padding: 0.2em 1em;
-		padding-bottom: 1.5em;
-		border-top: 1px solid;
-		position: relative;
-	}
-
-	.host-alarms a:last-child:after {
-		position: absolute;
-		display: block;
-		content: 'no alarms';
-		width: 6em;
-		height: 1em;
-		right: 0;
-		top: 2em;
-		color: #646464;
-	}
-
-	#alarms .host-alarms img.alarm-badge {
-		margin: .25em;
-		height: 1.2em;
-	}
-
-	#alarms .alarm-count {
-		width: 1.5em;
-		height: 1.5em;
-		text-align: center;
-		position: absolute;
-		right: 0.5em;
-		top: 0.5em;
-		font-size: 1.5em;
-		background: #6f1515;
-		border-radius: 50%;
-	}
-
-	#alarms .alarm-count:empty {
-		background: #333;
-	}
-
-	#alarms .no-alarms {
-		background: #156f15;
-		color: rgba(0,0,0,0);
-	}
-
-	#alarms .warnings-only {
-		background: #f48041;
-		color: #EEE;
-	}
-
-	.settings-button {
-		position: absolute;
-		bottom: 0;
-		right: 0;
-		font-size: 3em;
-		width: 1.3em;
-		height: 1.3em;
-		z-index: 9999;
-		text-align: center;
-		line-height: 1.2;
-		cursor: default;
-	}
-
-	.settings-button:hover {
-		background: #424242;
-	}
-
-	.drag-anchor {
-		position: absolute;
-		top: 0;
-		left: 0;
-		width: 100%;
-		height: 2em;
-		background: #111;
-
-	}
-
-	.netdata-host-name {
-		font-size: 3em;
-		color: #FFF;
-		text-align: center;
-		white-space: nowrap;
-	}
-
-	.netdata-host-stats-container {
-		position: relative;
-		margin: 0 1em;
-		padding: 3em 2em;
-		display: inline-block;
-		text-align: center;
-		color: #CCC;
-		background: #232323;
-	}
-
-	.netdata-host-stats-container:last-of-type {
-		margin-right: 27em;
-	}
-
-	.netdata-host-stats {
-
-	}
-
-	.netdata-message {
-	}
-
-	.dash-graph {
-		margin-bottom: 1em;
-	}
-
-	.netdata-legend-resize-handler {
-		display: none
-	}
-
-	.dash-charts {
-		white-space: normal;
-		margin: 2em 0;
-	}
-
-	.dash-chart {
-		display: inline-block;
-		vertical-align: top;
-	}
-
-	.easyPieChartLabel {
-		color: #FFF;
-	}
-
-	.loading-error {
-		padding-top: 2em;
-		font-size: 2em;
-	}
-
-	.netdata-legend-value,  .netdata-legend-toolbox,  .netdata-legend-toolbox-button, .netdata-legend-resize-handler {
-		background: initial;
-	}
+    body {
+        background-color: #272b30;
+    }
+
+    a, a:hover {
+        color: #AAA !important;
+    }
+
+    #dash {
+        overflow: scroll;
+        width: 100vw;
+        white-space: nowrap;
+        height: 100vh;
+
+    }
+
+    #alarms {
+        display: block;
+        z-index: 9999;
+        position: fixed;
+        right: 0;
+        top: 0;
+        bottom: 0;
+        background-color: #111;
+        width: 23em;
+        padding: 1em;
+        color: #AAA;
+        box-shadow: 0 0 3em #060606;
+        border: solid 1px #2d2d2d;
+        visibility: hidden;
+    }
+
+
+
+    #alarms h1 {
+        text-align: center;
+        margin: 0;
+        margin-bottom: 0.5em;
+        margin-top: -0.1em;
+        font-size: 2em;
+    }
+
+    #alarms h2 {
+        font-size: 1.5em;
+    }
+
+    #alarms .alarm-collapse-button {
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 0.9em;
+        height: 0.9em;
+        font-size: 4em;
+        line-height: 0.8;
+        background: #111;
+        text-align: center;
+        cursor: default;
+    }
+
+    #alarms .alarm-collapse-button:hover {
+        background: #000;
+    }
+
+    #alarms.collapsed {
+        width: 4em;
+    }
+
+    #alarms.collapsed .alarm-collapse-button {
+        position: fixed;
+        left: auto;
+        right: 0;
+        top: 0;
+        bottom: 0;
+        width: 4em;
+        height: 100vh;
+        opacity: 0;
+        z-index: 9999;
+    }
+
+    #alarms.collapsed h1 {
+        transform: rotate(-90deg);
+        position: absolute;
+        left: -9.1em;
+        top: 4em;
+        width: 20em;
+    }
+
+    #alarms .alarm-host-list {
+        margin-top: 1em;
+        height: 100%;
+        overflow-y: scroll;
+        scrollbar-width: thin;
+    }
+
+    #alarms.collapsed .alarm-host-list {
+        display: none;
+    }
+
+
+
+    #alarms .host-alarms {
+        background: #1d1d1d;
+        margin-bottom: 1em;
+        padding: 0.2em 1em;
+        padding-bottom: 1.5em;
+        border-top: 1px solid;
+        position: relative;
+    }
+
+    .host-alarms a:last-child:after {
+        position: absolute;
+        display: block;
+        content: 'no alarms';
+        width: 6em;
+        height: 1em;
+        right: 0;
+        top: 2em;
+        color: #646464;
+    }
+
+    #alarms .host-alarms img.alarm-badge {
+        margin: .25em;
+        height: 1.2em;
+    }
+
+    #alarms .alarm-count {
+        width: 1.5em;
+        height: 1.5em;
+        text-align: center;
+        position: absolute;
+        right: 0.5em;
+        top: 0.5em;
+        font-size: 1.5em;
+        background: #6f1515;
+        border-radius: 50%;
+    }
+
+    #alarms .alarm-count:empty {
+        background: #333;
+    }
+
+    #alarms .no-alarms {
+        background: #156f15;
+        color: rgba(0,0,0,0);
+    }
+
+    #alarms .warnings-only {
+        background: #f48041;
+        color: #EEE;
+    }
+
+    .settings-button {
+        position: absolute;
+        bottom: 0;
+        right: 0;
+        font-size: 3em;
+        width: 1.3em;
+        height: 1.3em;
+        z-index: 9999;
+        text-align: center;
+        line-height: 1.2;
+        cursor: default;
+    }
+
+    .settings-button:hover {
+        background: #424242;
+    }
+
+    .netdata-host-name {
+        font-size: 3em;
+        color: #FFF;
+        text-align: center;
+        white-space: nowrap;
+        background: inherit;
+        position: sticky;
+        top: 0;
+        z-index: 9998;
+        box-shadow: 0 0.5em 1em #232323;
+        margin-bottom: 0.5em;
+        border-bottom: solid 0.1em #AAA;
+    }
+
+    .netdata-host-stats-container {
+        position: relative;
+        margin: 0 1em;
+        padding: 1em 2em;
+        display: inline-block;
+        text-align: center;
+        color: #CCC;
+        background: #232323;
+    }
+
+    .netdata-host-stats-container:last-of-type {
+        margin-right: 27em;
+    }
+
+    .netdata-host-stats {
+
+    }
+
+    .netdata-message {
+    }
+
+    .dash-graph {
+        margin-bottom: 1em;
+    }
+
+    .netdata-legend-resize-handler {
+        display: none
+    }
+
+    .dash-charts {
+        white-space: normal;
+        margin: 2em 0;
+    }
+
+    .dash-chart {
+        display: inline-block;
+        vertical-align: top;
+    }
+
+    .easyPieChartLabel {
+        color: #FFF;
+    }
+
+    .loading-error {
+        padding-top: 2em;
+        font-size: 2em;
+    }
+
+    .netdata-legend-value,  .netdata-legend-toolbox,  .netdata-legend-toolbox-button, .netdata-legend-resize-handler {
+        background: initial;
+    }
 </style>
 </html>