netdata/web/gui/dashboard/dash-example.html
Austin S. Hemmelgarn 9574cb4c95
Bundle the react dashboard code into the agent repo directly. (#11139)
* Remove code for bundling the dashoard on install.

* Bundle the dashboard code directly into the agent repo.

This diffstat looks huge, but it’s actually relatively simple. The only
_actual_ changes are in the Makefiles, `configure.ac`, and the addition of
`generate_dashboard_makefile.py`. Everything else consists of removing
files that are included in the dashboard tarball, and extracting the
contents of the tarball into `web/gui/dashboard`.

* CI cleanup.

* Automate bundling of the dashboard code.

This replaces the makefile generator script with one that handles
bundling of the dashboard code in it’s entirety, and updates the GHA
workflow used for generating dashboard PRs to use that instead of the
existing shell commands.

It also removes the packaging/dashboard.* files, as they are no longer
needed.
2021-05-14 11:41:16 -04:00

1021 lines
29 KiB
HTML

<!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">
</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;&#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">
</script>
<script type="text/javascript">
class PickNSort {
// PickNSort.js
constructor (dark) {
this.items = [];
this.callback = function (output, disabled) { console.log(output, disabled) };
this.reset = function () { this.items = [] };
this.last_output = {
enabled: [],
disabled: []
}
this.add_css_to_page(dark);
}
create_modal () {
$("<div></div>", {
id: "picknsort-container"
}).appendTo('body');
$("<div></div>", {
id: 'picknsort-window'
}).appendTo('#picknsort-container');
$("<div></div>", {
id: "picknsort-close-button",
text: '\u2573'
}).appendTo('#picknsort-window');
$("<div></div>", {
id: "picknsort-item-list"
}).appendTo('#picknsort-window')
$("<div></div>", {
text: "Apply",
id: "picknsort-apply-button"
}).appendTo('#picknsort-window');
$("<div></div>", {
text: "Reset",
id: "picknsort-reset-button"
}).appendTo('#picknsort-window');
$('#picknsort-close-button').click(function () {
picknsort.destroy_modal();
picknsort.callback(null, null);
});
$('#picknsort-apply-button').click(function () {
picknsort.apply();
});
$('#picknsort-reset-button').click(function () {
picknsort.reset();
});
}
destroy_modal () {
$('#picknsort-container').remove();
}
populate_list () {
this.clear_list();
for (var i=0, len=this.items.length; i<len; i++) {
this.draw_item(i);
}
}
draw_item (index) {
var item = this.items[index];
var $item = $("<div></div>").addClass("picknsort-item");
var $checkbox= $("<div></div>").addClass('picknsort-item-checkbox-wrapper');
$("<input>", {
type: "checkbox",
checked: item.enabled
}).addClass('picknsort-item-checkbox')
.appendTo($checkbox);
var $value = $("<div></div>", {
text: item.value
}).addClass("picknsort-item-value");
var $nav = $("<div></div>").addClass("picknsort-item-nav")
.append("<div class='picknsort-item-down' data-index='" + index + "' onclick='picknsort.shift_down(this)'>&#8595;</div>")
.append("<div class='picknsort-item-up' data-index='" + index + "' onclick='picknsort.shift_up(this)'>&#8593;</div>");
$item.append($checkbox).append($value).append($nav).appendTo('#picknsort-item-list');
}
clear_list () {
$('#picknsort-item-list').html('');
}
popup (callback, reset, options={}) {
this.callback = callback || this.callback;
this.reset = reset || this.reset;
if (!options.enabled) {
options.enabled = this.last_output.enabled;
}
if (!options.disabled) {
options.disabled = this.last_output.disabled;
}
this.parse_items(options.enabled, options.disabled);
if (this.items.length) {
this.create_modal();
this.populate_list();
}
}
parse_items (enabled, disabled) {
this.items = [];
for (var i=0, len=enabled.length; i<len; i++) {
this.items.push({
value: enabled[i],
enabled: true
});
}
for (var i=0, len=disabled.length; i<len; i++) {
this.items.push({
value: disabled[i],
enabled: false
});
}
}
shift_down (el) {
var index = $(el).data('index');
if (index === this.items.length - 1) {
return;
}
var temp = this.items[index];
this.items[index] = this.items[index + 1];
this.items[index + 1] = temp;
this.move_element_down(index);
}
shift_up (el) {
var index = $(el).data('index');
if (index === 0) {
return;
}
var temp = this.items[index];
this.items[index] = this.items[index - 1];
this.items[index - 1] = temp;
this.move_element_up(index);
}
move_element_up (index) {
var $src = $('.picknsort-item:eq(' + index + ')');
var $dest = $('.picknsort-item:eq(' + (index-1) + ')');
$src.insertBefore($dest);
// Update data-index
$src.find('.picknsort-item-down, .picknsort-item-up').data("index", index-1);
$dest.find('.picknsort-item-down, .picknsort-item-up').data("index", index);
}
move_element_down (index) {
var $src = $('.picknsort-item:eq(' + index + ')');
var $dest = $('.picknsort-item:eq(' + (index+1) + ')');
$src.insertAfter($dest);
// Update data-index
$src.find('.picknsort-item-down, .picknsort-item-up').data("index", index+1);
$dest.find('.picknsort-item-down, .picknsort-item-up').data("index", index);
}
apply () {
var out = [];
for (var i=0, len=this.items.length; i<len; i++) {
out.push(this.items[i].value);
}
var disabled = [];
$('input.picknsort-item-checkbox:not(:checked)').each(function (elindex) {
var itemindex = $('input.picknsort-item-checkbox').index($(this));
// Adjust for deleted elements
var spliceindex = itemindex - (1 * elindex);
var del = out.splice(spliceindex, 1);
disabled.push(del[0]);
});
this.callback(out, disabled);
this.last_output = {
enabled: out,
disabled: disabled
}
this.destroy_modal();
}
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",
itemcolor: "#535353"
}
var dark_theme = {
windowbg: "#444",
itembg: "#333",
itemcolor: "#DBDBDB"
}
var current_theme = (dark) ? dark_theme : light_theme;
var css_string = `
#picknsort-container {
position: fixed;
z-index: 9999 !important;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(0,0,0,0.5);
}
#picknsort-window {
max-height: 90vh;
width: 40em;
background: ${current_theme.windowbg};
position: absolute;
top: 5em;
left: calc(50% - 20em);
padding: 2em;
padding-top: 4em;
}
#picknsort-close-button {
position: absolute;
top: 1em;
font-size: 1.5em;
color: #666;
right: 1em;
cursor: pointer;
}
#picknsort-item-list {
max-height: 60vh;
overflow-y: scroll;
margin-bottom: 1em;
}
#picknsort-apply-button, #picknsort-reset-button {
text-align: center;
font-size: 1.5em;
padding: 1em;
background: rgb(106, 232, 165);
font-weight: bold;
color: #FFF;
cursor: pointer;
margin-top: 1em;
}
#picknsort-reset-button {
background: #CCC;
}
.picknsort-item {
padding: 1em;
border-top: solid 1px #CCC;
margin-bottom: 1em;
position: relative;
background: ${current_theme.itembg};
color: ${current_theme.itemcolor}
}
.picknsort-item-checkbox-wrapper {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 3em;
text-align: center;
}
.picknsort-item-checkbox {
transform: scale(1.5);
margin-top: 1.3em !important;
}
.picknsort-item-value {
margin-left: 3em;
}
.picknsort-item-nav {
font-size: 2em;
position: absolute;
right: 0;
top: 0;
bottom: 0;
color: #CCC;
cursor: pointer;
}
.picknsort-item-down, .picknsort-item-up {
display: inline-block;
padding: 0em 0.5em;
height: calc(100% - 0.2em);
}
` // 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);
</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;
}
.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>