@cryptotaxi247 / netdata-1 / commits / 1eecab0ed

dash.html (#6603)

Multi-Host custom dashboard: dash.html is a single-file solution to show graphs, charts, and alarms from all streamed hosts on one page. It takes one defined set of graphs and charts and replicates it for each host. It is quite simple, but quite effective.

Thomas Anderson committed Sep 4, 2019 at 12:48 UTC 1eecab0ed87994cf64e230ad6ff53b8a5c26ffa2
3 files changed +1072 -1
web/gui/Makefile.am
+1
@@ -67,6 +67,7 @@ dist_web_DATA = \
67 refresh-badges.js \
68 sitemap.xml \
69 tv.html \
70 + dash-example.html \
71 version.txt \
72 $(NULL)
73
web/gui/custom/README.md
+56 -1
@@ -11,12 +11,13 @@ Netdata charts can also be added to existing web pages.
11
12 Check this **[very simple working example of a custom dashboard](http://netdata.firehol.org/demo.html)**, and its **[html source](../demo.html)**.
13
14 -You should also look at the **[custom dashboard template](https://my-netdata.io/dashboard.html)**, that contains samples of all supported charts. The code is [here](../dashboard.html).
14 +You should also look at the **[custom dashboard template](https://my-netdata.io/dashboard.html)**, that contains samples of all supported charts. The code is [here](../dashboard.html).
15
16 If you plan to put the dashboard on TV, check **[tv.html](../tv.html)**. This is a screenshot of it, monitoring 2 servers on the same page:
17
18 ![image](https://cloud.githubusercontent.com/assets/2662304/14252187/d8d5f78e-fa8e-11e5-990d-99821d38c874.png)
19 +
20 +--
21
22 ## Web directory
23
@@ -51,6 +52,59 @@ If you need to create a new dashboard on an empty page, we suggest the following
52 </html>
53 ```
54
55 +## Dash (Multi-Host Dashboard)
56 +
57 +`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!**
58 +
59 +### Configure Dash
60 +
61 +First, rename the file so it doesn't get overwritten. For instance, with a webroot at `/usr/share/netdata/web`:
62 +```
63 +cp /usr/share/netdata/web/dash-example.html /usr/share/netdata/web/dash.html
64 +```
65 +
66 +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.
67 +
68 +```js
69 +/*
70 +* TUTORIAL: Change this to the URL of your netdata host
71 +* If you use netdata behind a reverse proxy, add a second parameter for the reverse proxy url like so:
72 +* new Dash('http://localhost:19999', 'https://my-domain.com/stats');
73 +*/
74 +var dash = new Dash('http://localhost:19999');
75 +```
76 +
77 +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:
78 +```
79 +<div class="dash-graph" <---- Use class dash-graph for line graphs, etc
80 + data-dash-netdata="system.cpu" <---- REQUIRED: Use data-dash-netdata to set the data source
81 + data-dygraph-valuerange="[0, 100]"> <---- OPTIONAL: This overrides the default config. Any other data-* attributes will
82 +</div> be added to the generated div, so you can set any desired options here
83 +
84 +<div class="dash-chart" <---- Use class dash-chart for pie charts, etc. CHARTS ARE SQUARE
85 + data-dash-netdata="system.io" <---- REQUIRED: Use data-dash-netdata to set the data source
86 + data-dimensions="in" <---- Use this to override or append default options
87 + data-title="Disk Read" <---- Use this to override or append default options
88 + data-common-units="dash.io"> <---- Use this to override or append default options
89 +</div>
90 +```
91 +
92 +To change the sizes of graphs and charts, find the `Dash.options` object in `dash.html` and set your preferences:
93 +```js
94 +/*
95 +* TUTORIAL: Change your graph/chart dimensions here. Host columns will automatically adjust.
96 +* Charts are square! Their width is the same as their height.
97 +*/
98 +this.options = {
99 + graph_width: '40em',
100 + graph_height: '20em',
101 + chart_width: '10em' // Charts are square
102 +};
103 +```
104 +
105 +To change the display order of your hosts, which is saved in localStorage, click the settings gear in the lower right corner
106 +
107 +
108 ## dashboard.js
109
110 To add Netdata charts to any web page (dedicated to Netdata or not), you need to include the `/dashboard.js` file of a Netdata server.
web/gui/dash-example.html new
+1015
@@ -0,0 +1,1015 @@
1 +<!DOCTYPE html>
2 +<html lang="en">
3 +<head>
4 + <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5 + <meta charset="utf-8">
6 + <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
7 + <meta name="viewport" content="width=device-width, initial-scale=1">
8 + <meta name="apple-mobile-web-app-capable" content="yes">
9 + <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
10 +</head>
11 +<body>
12 + <div id="alarms" class="collapsed">
13 + <div class="alarm-collapse-button" onclick="dash.toggle_alarm_collapse()">&rsaquo;</div>
14 + <span class="alarm-count"></span>
15 + <h1>Alarms</h1>
16 + <div class="alarm-host-list"></div>
17 + <div class="settings-button" onclick="dash.reorder_hosts()">&#9881;</div>
18 + </div>
19 + <div id="dash">
20 + <div class="netdata-host-stats-container template">
21 + <div class="drag-anchor"></div>
22 + <div class="netdata-host-name">host</div>
23 + <div class="netdata-host-stats">
24 + <div class="dash-graph"
25 + data-dash-netdata="system.cpu"
26 + data-dygraph-valuerange="[0, 100]">
27 + </div>
28 + <div class="dash-graph"
29 + data-dash-netdata="system.load">
30 + </div>
31 + <div class="dash-graph"
32 + data-dash-netdata="system.ram">
33 + </div>
34 + <div class="dash-graph"
35 + data-dash-netdata="system.net">
36 + </div>
37 + <div class="dash-graph"
38 + data-dash-netdata="system.processes">
39 + </div>
40 + <div class="dash-graph"
41 + data-dash-netdata="apps.cpu">
42 + </div>
43 + <div class="dash-graph"
44 + data-dash-netdata="apps.mem">
45 + </div>
46 + <div class="dash-charts">
47 + <div class="dash-chart"
48 + data-dash-netdata="system.io"
49 + data-dimensions="in"
50 + data-title="Disk Read"
51 + data-common-units="dash.io">
52 + </div>
53 + <div class="dash-chart"
54 + data-dash-netdata="system.io"
55 + data-dimensions="out"
56 + data-title="Disk Write"
57 + data-common-units="dash.io">
58 + </div>
59 + <div class="dash-chart"
60 + data-dash-netdata="system.cpu"
61 + data-chart-library="gauge"
62 + data-title="CPU"
63 + data-units="%"
64 + data-gauge-max-value="100"
65 + data-colors="#22AA99"
66 + data-gauge-stroke-color="#373B40">
67 + </div>
68 + <div class="dash-chart"
69 + data-dash-netdata="system.net"
70 + data-dimensions="received"
71 + data-title="Net Inbound"
72 + data-common-units="dash.net">
73 + </div>
74 + <div class="dash-chart"
75 + data-dash-netdata="system.net"
76 + data-dimensions="sent"
77 + data-title="Net Outbound"
78 + data-common-units="dash.net">
79 + </div>
80 + <div class="dash-chart"
81 + data-dash-netdata="system.ram"
82 + data-dimensions="used|buffers|active|wired"
83 + data-append-options="percentage"
84 + data-title="Used RAM"
85 + data-units="%"
86 + data-easypiechart-max-value="100"
87 + data-colors="#EE9911">
88 + </div>
89 + </div>
90 + </div>
91 + </div>
92 + </div>
93 +</body>
94 +<script
95 + src="https://code.jquery.com/jquery-3.4.1.min.js"
96 + integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
97 + crossorigin="anonymous">
98 +</script>
99 +<script type="text/javascript">
100 +class PickNSort {
101 + // PickNSort.js
102 +
103 + constructor (dark) {
104 + this.items = [];
105 + this.callback = function (output, disabled) { console.log(output, disabled) };
106 + this.reset = function () { this.items = [] };
107 + this.last_output = {
108 + enabled: [],
109 + disabled: []
110 + }
111 +
112 + this.add_css_to_page(dark);
113 + }
114 +
115 + create_modal () {
116 + $("<div></div>", {
117 + id: "picknsort-container"
118 + }).appendTo('body');
119 +
120 + $("<div></div>", {
121 + id: 'picknsort-window'
122 + }).appendTo('#picknsort-container');
123 +
124 + $("<div></div>", {
125 + id: "picknsort-close-button",
126 + text: '\u2573'
127 + }).appendTo('#picknsort-window');
128 +
129 + $("<div></div>", {
130 + id: "picknsort-item-list"
131 + }).appendTo('#picknsort-window')
132 +
133 + $("<div></div>", {
134 + text: "Apply",
135 + id: "picknsort-apply-button"
136 + }).appendTo('#picknsort-window');
137 +
138 + $("<div></div>", {
139 + text: "Reset",
140 + id: "picknsort-reset-button"
141 + }).appendTo('#picknsort-window');
142 +
143 + $('#picknsort-close-button').click(function () {
144 + picknsort.destroy_modal();
145 + picknsort.callback(null, null);
146 + });
147 +
148 + $('#picknsort-apply-button').click(function () {
149 + picknsort.apply();
150 + });
151 +
152 + $('#picknsort-reset-button').click(function () {
153 + picknsort.reset();
154 + });
155 + }
156 +
157 + destroy_modal () {
158 + $('#picknsort-container').remove();
159 + }
160 +
161 + populate_list () {
162 + this.clear_list();
163 +
164 + for (var i=0, len=this.items.length; i<len; i++) {
165 + this.draw_item(i);
166 + }
167 + }
168 +
169 + draw_item (index) {
170 + var item = this.items[index];
171 +
172 + var $item = $("<div></div>").addClass("picknsort-item");
173 +
174 + var $checkbox= $("<div></div>").addClass('picknsort-item-checkbox-wrapper');
175 +
176 + $("<input>", {
177 + type: "checkbox",
178 + checked: item.enabled
179 + }).addClass('picknsort-item-checkbox')
180 + .appendTo($checkbox);
181 +
182 + var $value = $("<div></div>", {
183 + text: item.value
184 + }).addClass("picknsort-item-value");
185 +
186 + var $nav = $("<div></div>").addClass("picknsort-item-nav")
187 + .append("<div class='picknsort-item-down' data-index='" + index + "' onclick='picknsort.shift_down(this)'>&#8595;</div>")
188 + .append("<div class='picknsort-item-up' data-index='" + index + "' onclick='picknsort.shift_up(this)'>&#8593;</div>");
189 +
190 + $item.append($checkbox).append($value).append($nav).appendTo('#picknsort-item-list');
191 + }
192 +
193 + clear_list () {
194 + $('#picknsort-item-list').html('');
195 + }
196 +
197 + popup (callback, reset, options={}) {
198 + this.callback = callback || this.callback;
199 + this.reset = reset || this.reset;
200 + if (!options.enabled) {
201 + options.enabled = this.last_output.enabled;
202 + }
203 + if (!options.disabled) {
204 + options.disabled = this.last_output.disabled;
205 + }
206 +
207 + this.parse_items(options.enabled, options.disabled);
208 + if (this.items.length) {
209 + this.create_modal();
210 + this.populate_list();
211 + }
212 +
213 + }
214 +
215 + parse_items (enabled, disabled) {
216 + this.items = [];
217 +
218 + for (var i=0, len=enabled.length; i<len; i++) {
219 + this.items.push({
220 + value: enabled[i],
221 + enabled: true
222 + });
223 + }
224 +
225 + for (var i=0, len=disabled.length; i<len; i++) {
226 + this.items.push({
227 + value: disabled[i],
228 + enabled: false
229 + });
230 + }
231 + }
232 +
233 + shift_down (el) {
234 + var index = $(el).data('index');
235 + if (index === this.items.length - 1) {
236 + return;
237 + }
238 + var temp = this.items[index];
239 + this.items[index] = this.items[index + 1];
240 + this.items[index + 1] = temp;
241 + this.move_element_down(index);
242 + }
243 +
244 + shift_up (el) {
245 + var index = $(el).data('index');
246 + if (index === 0) {
247 + return;
248 + }
249 + var temp = this.items[index];
250 + this.items[index] = this.items[index - 1];
251 + this.items[index - 1] = temp;
252 + this.move_element_up(index);
253 + }
254 +
255 + move_element_up (index) {
256 + var $src = $('.picknsort-item:eq(' + index + ')');
257 + var $dest = $('.picknsort-item:eq(' + (index-1) + ')');
258 + $src.insertBefore($dest);
259 +
260 + // Update data-index
261 + $src.find('.picknsort-item-down, .picknsort-item-up').data("index", index-1);
262 + $dest.find('.picknsort-item-down, .picknsort-item-up').data("index", index);
263 + }
264 +
265 + move_element_down (index) {
266 + var $src = $('.picknsort-item:eq(' + index + ')');
267 + var $dest = $('.picknsort-item:eq(' + (index+1) + ')');
268 + $src.insertAfter($dest);
269 +
270 + // Update data-index
271 + $src.find('.picknsort-item-down, .picknsort-item-up').data("index", index+1);
272 + $dest.find('.picknsort-item-down, .picknsort-item-up').data("index", index);
273 + }
274 +
275 + apply () {
276 + var out = [];
277 + for (var i=0, len=this.items.length; i<len; i++) {
278 + out.push(this.items[i].value);
279 + }
280 + var disabled = [];
281 + $('input.picknsort-item-checkbox:not(:checked)').each(function (elindex) {
282 + var itemindex = $('input.picknsort-item-checkbox').index($(this));
283 + // Adjust for deleted elements
284 + var spliceindex = itemindex - (1 * elindex);
285 + var del = out.splice(spliceindex, 1);
286 + disabled.push(del[0]);
287 + });
288 + this.callback(out, disabled);
289 + this.last_output = {
290 + enabled: out,
291 + disabled: disabled
292 + }
293 + this.destroy_modal();
294 + }
295 +
296 + add_css_to_page (dark) {
297 + var light_theme = {
298 + windowbg: "#FFF",
299 + itembg: "#F4F4F4",
300 + itemcolor: "#535353"
301 + }
302 + var dark_theme = {
303 + windowbg: "#444",
304 + itembg: "#333",
305 + itemcolor: "#DBDBDB"
306 + }
307 + var current_theme = (dark) ? dark_theme : light_theme;
308 + $("<style>")
309 + .prop("type", "text/css")
310 + .html(`
311 +#picknsort-container {
312 + position: fixed;
313 + z-index: 9999 !important;
314 + top: 0;
315 + bottom: 0;
316 + left: 0;
317 + right: 0;
318 + background: rgba(0,0,0,0.5);
319 +}
320 +#picknsort-window {
321 + max-height: 90vh;
322 + width: 40em;
323 + background: ${current_theme.windowbg};
324 + position: absolute;
325 + top: 5em;
326 + left: calc(50% - 20em);
327 + padding: 2em;
328 + padding-top: 4em;
329 +}
330 +#picknsort-close-button {
331 + position: absolute;
332 + top: 1em;
333 + font-size: 1.5em;
334 + color: #666;
335 + right: 1em;
336 + cursor: pointer;
337 +}
338 +#picknsort-item-list {
339 + max-height: 60vh;
340 + overflow-y: scroll;
341 + margin-bottom: 1em;
342 +}
343 +#picknsort-apply-button, #picknsort-reset-button {
344 + text-align: center;
345 + font-size: 1.5em;
346 + padding: 1em;
347 + background: rgb(106, 232, 165);
348 + font-weight: bold;
349 + color: #FFF;
350 + cursor: pointer;
351 + margin-top: 1em;
352 +}
353 +#picknsort-reset-button {
354 + background: #CCC;
355 +}
356 +.picknsort-item {
357 + padding: 1em;
358 + border-top: solid 1px #CCC;
359 + margin-bottom: 1em;
360 + position: relative;
361 + background: ${current_theme.itembg};
362 + color: ${current_theme.itemcolor}
363 +}
364 +.picknsort-item-checkbox-wrapper {
365 + position: absolute;
366 + top: 0;
367 + left: 0;
368 + bottom: 0;
369 + width: 3em;
370 + text-align: center;
371 +}
372 +.picknsort-item-checkbox {
373 + transform: scale(1.5);
374 + margin-top: 1.3em !important;
375 +}
376 +.picknsort-item-value {
377 + margin-left: 3em;
378 +}
379 +.picknsort-item-nav {
380 + font-size: 2em;
381 + position: absolute;
382 + right: 0;
383 + top: 0;
384 + bottom: 0;
385 + color: #CCC;
386 + cursor: pointer;
387 +}
388 +.picknsort-item-down, .picknsort-item-up {
389 + display: inline-block;
390 + padding: 0em 0.5em;
391 + height: calc(100% - 0.2em);
392 +}
393 + `).appendTo("head");
394 + }
395 +}
396 +</script>
397 +<script type="text/javascript">
398 + // Dash JS
399 +
400 + function picknsort_setup () {
401 + if (localStorage.getItem('netdata_ordered_hosts')) {
402 + picknsort.last_output = JSON.parse(localStorage.getItem('netdata_ordered_hosts'));
403 + } else {
404 + picknsort.last_output = {
405 + enabled: dash.netdata_info.mirrored_hosts,
406 + disabled: []
407 + }
408 + }
409 + }
410 +
411 + class Dash {
412 + constructor (base_url, link_base_url) {
413 + this.base_url = base_url; // URL of netdata host, with port
414 + this.link_base_url = link_base_url || base_url; // Reverse proxy URL (Optional)
415 + this.link_base_url;
416 + this.netdata_info;
417 + this.current_alarms = {};
418 + this.new_alarms = {};
419 + this.first_build = true;
420 + /*
421 + * TUTORIAL: Change your graph/chart dimensions here. Host columns will automatically adjust.
422 + * Charts are square! Their width is the same as their height.
423 + */
424 + this.options = {
425 + graph_width: '40em',
426 + graph_height: '20em',
427 + chart_width: '10em' // Charts are square
428 + };
429 +
430 + this.init();
431 + }
432 +
433 + reorder_hosts () {
434 + picknsort.popup(dash.update_ordered_hosts, dash.find_new_hosts);
435 + }
436 +
437 + get_enabled_hosts () {
438 + try {
439 + return JSON.parse(localStorage.getItem('netdata_ordered_hosts')).enabled
440 + } catch (e) {
441 + return this.netdata_info.mirrored_hosts;
442 + }
443 + }
444 +
445 + get_ordered_hosts () {
446 + try {
447 + return JSON.parse(localStorage.getItem('netdata_ordered_hosts'));
448 + } catch (e) {
449 + return null;
450 + }
451 + }
452 +
453 + set_ordered_hosts (hosts) {
454 + localStorage.setItem('netdata_ordered_hosts', JSON.stringify(hosts));
455 + location.reload();
456 + }
457 +
458 + update_ordered_hosts (enabled, disabled) {
459 + if (enabled === null && disabled === null) {
460 + return;
461 + }
462 + dash.set_ordered_hosts({
463 + enabled: enabled,
464 + disabled: disabled
465 + });
466 + }
467 +
468 + find_new_hosts () {
469 + var newhosts = dash.netdata_info.mirrored_hosts.slice();
470 + var currenthosts = dash.get_ordered_hosts();
471 +
472 + for (var i=0,len=currenthosts.enabled.length; i<len; i++) {
473 + var found = newhosts.indexOf(currenthosts.enabled[i]);
474 +
475 + if (found > -1) {
476 + newhosts.splice(found, 1);
477 + } else {
478 + currenthosts.enabled.splice(i, 1);
479 + }
480 + }
481 +
482 + for (var i=0,len=currenthosts.disabled.length; i<len; i++) {
483 + var found = newhosts.indexOf(currenthosts.disabled[i]);
484 +
485 + if (found > -1) {
486 + newhosts.splice(found, 1);
487 + } else {
488 + currenthosts.enabled.splice(i, 1);
489 + }
490 + }
491 +
492 + for (var i=0,len=newhosts.length; i<len; i++) {
493 + currenthosts.enabled.push(newhosts[i]);
494 + }
495 +
496 + dash.set_ordered_hosts(currenthosts);
497 + }
498 +
499 + get_host_url (hostname, link) {
500 + var base = ( link ) ? this.link_base_url : this.base_url;
501 + return (hostname) ? base + '/host/' + hostname : base;
502 + }
503 +
504 + get_api_url (hostname) {
505 + return this.get_host_url(hostname) + '/api/v1'
506 + }
507 +
508 +
509 + init () {
510 + var that = this;
511 + $.get(this.get_api_url() + '/info', function (data) {
512 + that.netdata_info = data;
513 + picknsort_setup()
514 + that.build();
515 + });
516 + }
517 +
518 + digest () {
519 + this.fetch_active_alarms();
520 + this.fix_layout_errors();
521 + }
522 +
523 + build () {
524 + // Fix vertically misaligned stats on load error
525 + $('.dash-graph').css({
526 + height: this.options.graph_height,
527 + width: this.options.graph_width
528 + });
529 + $('.dash-chart').css({
530 + height: this.options.chart_width, // Charts are square
531 + width: this.options.chart_width
532 + });
533 +
534 + // Fix chart alignment
535 + $('.netdata-host-stats-container').css({
536 + width: 'calc(' + this.options.graph_width + ' + (2 * ' + $('.netdata-host-stats-container').css('padding-left') + '))'
537 + });
538 +
539 + var $template = $('.netdata-host-stats-container').first();
540 +
541 + var hosts = this.get_enabled_hosts();
542 +
543 + for (var i=0, len=hosts.length; i<len; i++) {
544 + var hostname = hosts[i];
545 + $('#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>');
546 +
547 + $template.clone().removeClass('template').appendTo('#dash');
548 + var $newest = $('.netdata-host-stats-container').last();
549 + this.build_stats($newest, hostname);
550 + }
551 +
552 +
553 + if (this.first_build === true) {
554 + this.remove_template_elements();
555 + this.first_build = false;
556 + }
557 + }
558 +
559 + build_stats ($hoststats, hostname) {
560 + var that = this;
561 +
562 + $hoststats.find('.netdata-host-stats .dash-graph').each(function () {
563 + that.build_graph($(this), hostname);
564 + });
565 +
566 + $hoststats.find('.netdata-host-stats .dash-chart').each(function () {
567 + that.build_chart($(this), hostname);
568 + });
569 +
570 + $hoststats.find('.netdata-host-name').html('<a target="_blank" href="' + that.get_host_url(hostname, true) + '/">' + hostname + '</a>');
571 + }
572 +
573 + build_graph ($wrapper, hostname) {
574 + var that = this;
575 + var $graph = $('<div>', {
576 + // Defaults
577 + 'data-netdata': $wrapper.attr('data-dash-netdata'),
578 + 'data-host': that.get_host_url(hostname),
579 + 'data-before': '0',
580 + 'data-after': '-540',
581 + 'role': 'application',
582 + 'data-width': that.options.graph_width,
583 + 'data-height': that.options.graph_height
584 + });
585 +
586 + $.each($wrapper[0].attributes, function (key, node) {
587 + if ( node.name.match(/^data-(?!dash).*/ ) ) {
588 + $graph.attr(node.name, node.value);
589 + }
590 + });
591 +
592 + $graph.appendTo($wrapper);
593 + }
594 +
595 + build_chart ($wrapper, hostname) {
596 + var that = this;
597 + var $graph = $('<div>', {
598 + // Defaults
599 + 'data-netdata': $wrapper.attr('data-dash-netdata'),
600 + 'data-host': that.get_host_url(hostname),
601 + 'data-before': '0',
602 + 'data-after': '-540',
603 + 'data-points': '540',
604 + 'role': 'application',
605 + 'data-width': that.options.chart_width,
606 + 'data-title': ' ',
607 + 'data-easypiechart-trackcolor': "#373B40",
608 + 'data-chart-library': 'easypiechart'
609 +
610 + })
611 +
612 + $.each($wrapper[0].attributes, function (key, node) {
613 + if ( node.name.match(/^data-(?!dash).*/ ) ) {
614 + $graph.attr(node.name, node.value);
615 + }
616 + });
617 +
618 + $graph.appendTo($wrapper);
619 + }
620 +
621 + all_alarms_are_warnings () {
622 + var out = true;
623 + $.each(this.current_alarms, function (host, alarms) {
624 + if (out === false) {
625 + return false; // Maximum efficiency
626 + }
627 + $.each(alarms, function (name, data) {
628 + if (data.status != "WARNING") {
629 + out = false;
630 + return false;
631 + }
632 + });
633 + });
634 + return out;
635 + }
636 +
637 + update_alarm_count () {
638 + console.log(this.all_alarms_are_warnings());
639 + var count = $('img.alarm-badge').length;
640 + $('.alarm-count').text($('img.alarm-badge').length);
641 + $('.alarm-count').removeClass('no-alarms warnings-only');
642 + if ( count === 0 ) {
643 + $('.alarm-count').addClass('no-alarms');
644 + return
645 + }
646 + if (this.all_alarms_are_warnings()) {
647 + $('.alarm-count').addClass('warnings-only');
648 + }
649 + }
650 +
651 + fetch_active_alarms () {
652 + var that = this;
653 + console.log("Getting alarms...");
654 + var hosts = this.get_enabled_hosts();
655 + for (var i=0, len=hosts.length; i<len; i++) {
656 + var hostname = hosts[i];
657 +
658 + $.get(this.get_api_url(hostname) + '/alarms', function (data) {
659 + that.new_alarms[ data.hostname ] = data.alarms;
660 + if ( Object.keys(that.new_alarms).length === len ) {
661 + // All responses received
662 + if ( that.check_new_current_alarms() ) {
663 + console.log("No alarm changes detected... Refreshing images.");
664 + that.new_alarms = {};
665 + that.refresh_alarm_images();
666 + return;
667 + }
668 +
669 + that.current_alarms = that.new_alarms;
670 + that.new_alarms = {};
671 + that.draw_alarms();
672 + that.update_alarm_count();
673 + }
674 + });
675 + }
676 + }
677 +
678 + toggle_alarm_collapse () {
679 + $('#alarms').toggleClass('collapsed');
680 + }
681 +
682 + check_new_current_alarms () {
683 + // Create arrays of property names
684 + var a = this.current_alarms;
685 + var b = this.new_alarms;
686 + var aProps = Object.keys(a);
687 + var bProps = Object.keys(b);
688 +
689 + // If number of properties is different,
690 + // objects are not equivalent
691 + if (aProps.length != bProps.length) {
692 + return false;
693 + }
694 +
695 + for (var i = 0; i < aProps.length; i++) {
696 + var propName = aProps[i];
697 + var aa = a[propName]
698 + var aaProps = Object.keys(aa);
699 + var bb = b[propName];
700 + var bbProps = Object.keys(bb);
701 +
702 + if (aaProps.length != bbProps.length) {
703 + return false;
704 + }
705 +
706 + for (var n = 0, len=aaProps.length; i < len; i++) {
707 + if ( bb[aaProps[n]] === undefined ) {
708 + return false;
709 + }
710 + }
711 + }
712 +
713 + // If we made it this far, objects
714 + // are considered equivalent
715 + return true;
716 + }
717 +
718 + refresh_alarm_images () {
719 + // Use Math.random() to "reload" the image
720 + $('.alarm-badge').each(function () {
721 + var old_src = $(this).attr('src').replace(/&rand=.*/g, "");
722 + $(this).attr('src', old_src + "&rand=" + Math.random());
723 + });
724 + }
725 +
726 + draw_alarms () {
727 + var that = this;
728 + console.log("Drawing...", that.current_alarms);
729 + $.each(that.current_alarms, function(hostname, alarms) {
730 + $('#alarms .host-alarms.' + hostname + ' .alarm-badge').remove();
731 + $.each(alarms, function (index, alarm) {
732 + that.draw_alarm(hostname, alarm);
733 + })
734 + });
735 + }
736 +
737 + draw_alarm (hostname, alarm) {
738 + var queryStr = '/badge.svg?alarm=' + alarm.name + '&chart=' + alarm.chart
739 + $('<img />', {
740 + src: this.get_api_url(hostname) + queryStr,
741 + class: 'alarm-badge'
742 + }).appendTo($('#alarms .host-alarms.' + hostname));
743 + }
744 +
745 + remove_template_elements () {
746 + $('.netdata-host-stats-container').first().remove();
747 + }
748 +
749 + fix_layout_errors () {
750 + $('.dash-graph:contains("chart not found")').html('<div class="loading-error">Not found</div>');
751 + $('.dash-chart:contains("chart not found")').html('<div class="loading-error">Not found</div>');
752 + }
753 + }
754 +
755 + /*
756 + * TUTORIAL: Change this to the URL of your netdata host
757 + * If you use netdata behind a reverse proxy, add a second parameter for the reverse proxy url like so:
758 + * new Dash('http://localhost:19999', 'https://my-domain.com/stats');
759 + */
760 + var dash = new Dash('http://localhost:19999');
761 +
762 + var picknsort = new PickNSort(true);
763 +
764 + // Import dashboard.js
765 + $.getScript(dash.base_url + '/dashboard.js', function() {
766 + console.log("Loaded dashboard.js");
767 + setTimeout(function () {
768 + $('#alarms').css("visibility", "visible");
769 + }, 400);
770 + });
771 +
772 + setInterval(function () {
773 + dash.digest();
774 + }, 6 * 1000);
775 +
776 +</script>
777 +<style type="text/css">
778 + body {
779 + background-color: #272b30;
780 + }
781 +
782 + a, a:hover {
783 + color: #AAA !important;
784 + }
785 +
786 + #dash {
787 + overflow: scroll;
788 + width: 100vw;
789 + white-space: nowrap;
790 + height: 100vh;
791 +
792 + }
793 +
794 + #alarms {
795 + display: block;
796 + z-index: 9999;
797 + position: fixed;
798 + right: 0;
799 + top: 0;
800 + bottom: 0;
801 + background-color: #111;
802 + width: 23em;
803 + padding: 1em;
804 + color: #AAA;
805 + box-shadow: 0 0 3em #060606;
806 + border: solid 1px #2d2d2d;
807 + visibility: hidden;
808 + }
809 +
810 +
811 +
812 + #alarms h1 {
813 + text-align: center;
814 + margin: 0;
815 + margin-bottom: 0.5em;
816 + margin-top: -0.1em;
817 + font-size: 2em;
818 + }
819 +
820 + #alarms h2 {
821 + font-size: 1.5em;
822 + }
823 +
824 + #alarms .alarm-collapse-button {
825 + position: absolute;
826 + top: 0;
827 + left: 0;
828 + width: 0.9em;
829 + height: 0.9em;
830 + font-size: 4em;
831 + line-height: 0.8;
832 + background: #111;
833 + text-align: center;
834 + cursor: default;
835 + }
836 +
837 + #alarms .alarm-collapse-button:hover {
838 + background: #000;
839 + }
840 +
841 + #alarms.collapsed {
842 + width: 4em;
843 + }
844 +
845 + #alarms.collapsed .alarm-collapse-button {
846 + position: fixed;
847 + left: auto;
848 + right: 0;
849 + top: 0;
850 + bottom: 0;
851 + width: 4em;
852 + height: 100vh;
853 + opacity: 0;
854 + z-index: 9999;
855 + }
856 +
857 + #alarms.collapsed h1 {
858 + transform: rotate(-90deg);
859 + position: absolute;
860 + left: -9.1em;
861 + top: 4em;
862 + width: 20em;
863 + }
864 +
865 + #alarms .alarm-host-list {
866 + margin-top: 1em;
867 + height: 100%;
868 + overflow-y: scroll;
869 + scrollbar-width: thin;
870 + }
871 +
872 + #alarms.collapsed .alarm-host-list {
873 + display: none;
874 + }
875 +
876 +
877 +
878 + #alarms .host-alarms {
879 + background: #1d1d1d;
880 + margin-bottom: 1em;
881 + padding: 0.2em 1em;
882 + padding-bottom: 1.5em;
883 + border-top: 1px solid;
884 + position: relative;
885 + }
886 +
887 + .host-alarms a:last-child:after {
888 + position: absolute;
889 + display: block;
890 + content: 'no alarms';
891 + width: 6em;
892 + height: 1em;
893 + right: 0;
894 + top: 2em;
895 + color: #646464;
896 + }
897 +
898 + #alarms .host-alarms img.alarm-badge {
899 + margin: .25em;
900 + height: 1.2em;
901 + }
902 +
903 + #alarms .alarm-count {
904 + width: 1.5em;
905 + height: 1.5em;
906 + text-align: center;
907 + position: absolute;
908 + right: 0.5em;
909 + top: 0.5em;
910 + font-size: 1.5em;
911 + background: #6f1515;
912 + border-radius: 50%;
913 + }
914 +
915 + #alarms .alarm-count:empty {
916 + background: #333;
917 + }
918 +
919 + #alarms .no-alarms {
920 + background: #156f15;
921 + color: rgba(0,0,0,0);
922 + }
923 +
924 + #alarms .warnings-only {
925 + background: #f48041;
926 + color: #EEE;
927 + }
928 +
929 + .settings-button {
930 + position: absolute;
931 + bottom: 0;
932 + right: 0;
933 + font-size: 3em;
934 + width: 1.3em;
935 + height: 1.3em;
936 + z-index: 9999;
937 + text-align: center;
938 + line-height: 1.2;
939 + cursor: default;
940 + }
941 +
942 + .settings-button:hover {
943 + background: #424242;
944 + }
945 +
946 + .drag-anchor {
947 + position: absolute;
948 + top: 0;
949 + left: 0;
950 + width: 100%;
951 + height: 2em;
952 + background: #111;
953 +
954 + }
955 +
956 + .netdata-host-name {
957 + font-size: 3em;
958 + color: #FFF;
959 + text-align: center;
960 + white-space: nowrap;
961 + }
962 +
963 + .netdata-host-stats-container {
964 + position: relative;
965 + margin: 0 1em;
966 + padding: 3em 2em;
967 + display: inline-block;
968 + text-align: center;
969 + color: #CCC;
970 + background: #232323;
971 + }
972 +
973 + .netdata-host-stats-container:last-of-type {
974 + margin-right: 27em;
975 + }
976 +
977 + .netdata-host-stats {
978 +
979 + }
980 +
981 + .netdata-message {
982 + }
983 +
984 + .dash-graph {
985 + margin-bottom: 1em;
986 + }
987 +
988 + .netdata-legend-resize-handler {
989 + display: none
990 + }
991 +
992 + .dash-charts {
993 + white-space: normal;
994 + margin: 2em 0;
995 + }
996 +
997 + .dash-chart {
998 + display: inline-block;
999 + vertical-align: top;
1000 + }
1001 +
1002 + .easyPieChartLabel {
1003 + color: #FFF;
1004 + }
1005 +
1006 + .loading-error {
1007 + padding-top: 2em;
1008 + font-size: 2em;
1009 + }
1010 +
1011 + .netdata-legend-value, .netdata-legend-toolbox, .netdata-legend-toolbox-button, .netdata-legend-resize-handler {
1012 + background: initial;
1013 + }
1014 +</style>
1015 +</html>