summaryrefslogtreecommitdiffstats
path: root/web/dashboard.js
diff options
context:
space:
mode:
Diffstat (limited to 'web/dashboard.js')
-rw-r--r--web/dashboard.js378
1 files changed, 333 insertions, 45 deletions
diff --git a/web/dashboard.js b/web/dashboard.js
index b6c62ae3c..27847a243 100644
--- a/web/dashboard.js
+++ b/web/dashboard.js
@@ -12,6 +12,9 @@
// var netdataNoBootstrap = true; // do not load bootstrap
// var netdataDontStart = true; // do not start the thread to process the charts
// var netdataErrorCallback = null; // Callback function that will be invoked upon error
+// var netdataNoRegistry = true; // Don't update the registry for this access
+// var netdataRegistryCallback = null; // Callback function that will be invoked with one param,
+// the URLs from the registry
//
// You can also set the default netdata server, using the following.
// When this variable is not set, we assume the page is hosted on your
@@ -19,8 +22,28 @@
// var netdataServer = "http://yourhost:19999"; // set your NetData server
//(function(window, document, undefined) {
+
+ // ------------------------------------------------------------------------
+ // compatibility fixes
+
// fix IE issue with console
- if(!window.console){ window.console = {log: function(){} }; }
+ if(!window.console) { window.console = { log: function(){} }; }
+
+ // if string.endsWith is not defined, define it
+ if(typeof String.prototype.endsWith !== 'function') {
+ String.prototype.endsWith = function(s) {
+ if(s.length > this.length) return false;
+ return this.slice(-s.length) === s;
+ };
+ }
+
+ // if string.startsWith is not defined, define it
+ if(typeof String.prototype.startsWith !== 'function') {
+ String.prototype.startsWith = function(s) {
+ if(s.length > this.length) return false;
+ return this.slice(s.length) === s;
+ };
+ }
// global namespace
var NETDATA = window.NETDATA || {};
@@ -53,7 +76,11 @@
NETDATA.serverDefault = netdataServer;
else {
var s = NETDATA._scriptSource();
- NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
+ if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
+ else {
+ console.log('WARNING: Cannot detect the URL of the netdata server.');
+ NETDATA.serverDefault = null;
+ }
}
if(NETDATA.serverDefault === null)
@@ -80,7 +107,7 @@
NETDATA.google_js = 'https://www.google.com/jsapi';
NETDATA.themes = {
- default: {
+ white: {
bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.min.css',
dashboard_css: NETDATA.serverDefault + 'dashboard.css',
background: '#FFFFFF',
@@ -95,7 +122,7 @@
easypiechart_scale: '#dfe0e0',
gauge_pointer: '#C0C0C0',
gauge_stroke: '#F0F0F0',
- gauge_gradient: true
+ gauge_gradient: false
},
slate: {
bootstrap_css: NETDATA.serverDefault + 'css/bootstrap.slate.min.css',
@@ -124,7 +151,7 @@
if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
NETDATA.themes.current = NETDATA.themes[netdataTheme];
else
- NETDATA.themes.current = NETDATA.themes.default;
+ NETDATA.themes.current = NETDATA.themes.white;
NETDATA.colors = NETDATA.themes.current.colors;
@@ -188,8 +215,6 @@
last_resized: new Date().getTime(), // the timestamp of the last resize request
- crossDomainAjax: false, // enable this to request crossDomain AJAX
-
last_page_scroll: 0, // the timestamp the last time the page was scrolled
// the current profile
@@ -284,6 +309,12 @@
}
};
+ NETDATA.statistics = {
+ refreshes_total: 0,
+ refreshes_active: 0,
+ refreshes_active_max: 0
+ };
+
// ----------------------------------------------------------------------------------------------------------------
// local storage options
@@ -461,7 +492,15 @@
403: { message: "Chart library not enabled/is failed", alert: false },
404: { message: "Chart not found", alert: false },
405: { message: "Cannot download charts index from server", alert: true },
- 406: { message: "Invalid charts index downloaded from server", alert: true }
+ 406: { message: "Invalid charts index downloaded from server", alert: true },
+ 407: { message: "Cannot HELLO netdata server", alert: false },
+ 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
+ 409: { message: "Cannot ACCESS netdata registry", alert: false },
+ 410: { message: "Netdata registry ACCESS failed", alert: false },
+ 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
+ 412: { message: "Netdata registry DELETE failed", alert: false },
+ 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
+ 414: { message: "Netdata registry SWITCH failed", alert: false }
};
NETDATA.errorLast = {
code: 0,
@@ -541,7 +580,6 @@
$.ajax({
url: host + '/api/v1/charts',
- crossDomain: NETDATA.options.crossDomainAjax,
async: true,
cache: false
})
@@ -975,6 +1013,7 @@
this.units = self.data('units') || null; // the units of the chart dimensions
this.append_options = self.data('append-options') || null; // the units of the chart dimensions
+ this.running = false; // boolean - true when the chart is being refreshed now
this.validated = false; // boolean - has the chart been validated?
this.enabled = true; // boolean - is the chart enabled for refresh?
this.paused = false; // boolean - is the chart paused for any reason?
@@ -1098,7 +1137,7 @@
var w = that.element.offsetWidth;
if(w === null || w === 0) {
// the div is hidden
- // this is resize the chart when next viewed
+ // this will resize the chart when next viewed
that.tm.last_resized = 0;
}
else
@@ -1167,7 +1206,7 @@
var lost = Math.max(h * 0.2, 5);
h -= lost;
- // center the text, verically
+ // center the text, vertically
var paddingTop = (lost - 5) / 2;
// but check the width too
@@ -1247,8 +1286,8 @@
if(isHidden() === true) return;
if(that.chart_created === true) {
- // we should destroy it
if(NETDATA.options.current.destroy_on_hide === true) {
+ // we should destroy it
init();
}
else {
@@ -1256,6 +1295,12 @@
that.element_chart.style.display = 'none';
if(that.element_legend !== null) that.element_legend.style.display = 'none';
that.tm.last_hidden = new Date().getTime();
+
+ // de-allocate data
+ // This works, but I not sure there are no corner cases somewhere
+ // so it is commented - if the user has memory issues he can
+ // set Destroy on Hide for all charts
+ // that.data = null;
}
}
@@ -2580,8 +2625,6 @@
if(this.debug === true)
this.log('updateChartWithData() called.');
- this._updating = false;
-
// this may force the chart to be re-created
resizeChart();
@@ -2676,8 +2719,8 @@
if(NETDATA.globalPanAndZoom.isActive())
this.tm.last_autorefreshed = 0;
else {
- if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes)
- this.tm.last_autorefreshed = Math.round(now / this.data_update_every) * this.data_update_every;
+ if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
+ this.tm.last_autorefreshed = now - (now % this.data_update_every);
else
this.tm.last_autorefreshed = now;
}
@@ -2739,11 +2782,16 @@
if(this.debug === true)
this.log('updating from ' + this.data_url);
+ NETDATA.statistics.refreshes_total++;
+ NETDATA.statistics.refreshes_active++;
+
+ if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
+ NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
+
this._updating = true;
this.xhr = $.ajax( {
url: this.data_url,
- crossDomain: NETDATA.options.crossDomainAjax,
cache: false,
async: true
})
@@ -2757,6 +2805,7 @@
error('data download failed for url: ' + that.data_url);
})
.always(function() {
+ NETDATA.statistics.refreshes_active--;
that._updating = false;
if(typeof callback === 'function') callback();
});
@@ -2813,13 +2862,20 @@
}
};
- this.isAutoRefreshed = function() {
+ this.isAutoRefreshable = function() {
return (this.current.autorefresh);
};
this.canBeAutoRefreshed = function() {
var now = new Date().getTime();
+ if(this.running === true) {
+ if(this.debug === true)
+ this.log('I am already running');
+
+ return false;
+ }
+
if(this.enabled === false) {
if(this.debug === true)
this.log('I am not enabled');
@@ -2850,7 +2906,7 @@
return true;
}
- if(this.isAutoRefreshed() === true) {
+ if(this.isAutoRefreshable() === true) {
// allow the first update, even if the page is not visible
if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
if(NETDATA.options.debug.focus === true || this.debug === true)
@@ -2910,8 +2966,16 @@
};
this.autoRefresh = function(callback) {
- if(this.canBeAutoRefreshed() === true) {
- this.updateChart(callback);
+ if(this.canBeAutoRefreshed() === true && this.running === false) {
+ var state = this;
+
+ state.running = true;
+ state.updateChart(function() {
+ state.running = false;
+
+ if(typeof callback !== 'undefined')
+ callback();
+ });
}
else {
if(typeof callback !== 'undefined')
@@ -2948,7 +3012,6 @@
$.ajax( {
url: this.host + this.chart_url,
- crossDomain: NETDATA.options.crossDomainAjax,
cache: false,
async: true
})
@@ -3234,11 +3297,12 @@
var parallel = new Array();
var targets = NETDATA.options.targets;
var len = targets.length;
+ var state;
while(len--) {
- if(targets[len].isVisible() === false)
+ state = targets[len];
+ if(state.isVisible() === false || state.running === true)
continue;
- var state = targets[len];
if(state.library.initialized === false) {
if(state.library.enabled === true) {
state.library.initialize(NETDATA.chartRefresher);
@@ -3253,24 +3317,15 @@
}
if(parallel.length > 0) {
- var parallel_jobs = parallel.length;
-
// this will execute the jobs in parallel
$(parallel).each(function() {
- this.autoRefresh(function() {
- parallel_jobs--;
-
- if(parallel_jobs === 0) {
- setTimeout(NETDATA.chartRefresher,
- NETDATA.chartRefresherWaitTime());
- }
- });
+ this.autoRefresh();
})
}
- else {
- setTimeout(NETDATA.chartRefresher,
- NETDATA.chartRefresherWaitTime());
- }
+
+ // run the next refresh iteration
+ setTimeout(NETDATA.chartRefresher,
+ NETDATA.chartRefresherWaitTime());
};
NETDATA.parseDom = function(callback) {
@@ -3330,7 +3385,14 @@
$('.modal').on('hidden.bs.modal', NETDATA.onscroll);
$('.modal').on('shown.bs.modal', NETDATA.onscroll);
+ // bootstrap collapse switching
+ $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
+ $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
+
NETDATA.parseDom(NETDATA.chartRefresher);
+
+ // Registry initialization
+ setTimeout(NETDATA.registry.init, 3000);
};
// ----------------------------------------------------------------------------------------------------------------
@@ -4623,7 +4685,7 @@
state.easyPieChartEvent.timer = null;
}
- if(state.isAutoRefreshed() === true && state.data !== null) {
+ if(state.isAutoRefreshable() === true && state.data !== null) {
NETDATA.easypiechartChartUpdate(state, state.data);
}
else {
@@ -4674,7 +4736,7 @@
NETDATA.easypiechartChartUpdate = function(state, data) {
var value, max, pcent;
- if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshed() === false) {
+ if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
value = null;
max = 0;
pcent = 0;
@@ -4877,7 +4939,7 @@
state.gaugeEvent.timer = null;
}
- if(state.isAutoRefreshed() === true && state.data !== null) {
+ if(state.isAutoRefreshable() === true && state.data !== null) {
NETDATA.gaugeChartUpdate(state, state.data);
}
else {
@@ -4931,7 +4993,7 @@
NETDATA.gaugeChartUpdate = function(state, data) {
var value, min, max;
- if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshed() === false) {
+ if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
value = 0;
min = 0;
max = 1;
@@ -4993,11 +5055,33 @@
colorStop: stopColor, // just experiment with them
strokeColor: strokeColor, // to see which ones work best for you
limitMax: true,
- generateGradient: generateGradient,
+ generateGradient: (generateGradient === true)?true:false,
gradientType: 0
};
- if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
+ if (generateGradient.constructor === Array) {
+ // example options:
+ // data-gauge-generate-gradient="[0, 50, 100]"
+ // data-gauge-gradient-percent-color-0="#FFFFFF"
+ // data-gauge-gradient-percent-color-50="#999900"
+ // data-gauge-gradient-percent-color-100="#000000"
+
+ options.percentColors = new Array();
+ var len = generateGradient.length;
+ while(len--) {
+ var pcent = generateGradient[len];
+ var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
+ if(color !== false) {
+ var a = new Array();
+ a[0] = pcent / 100;
+ a[1] = color;
+ options.percentColors.unshift(a);
+ }
+ }
+ if(options.percentColors.length === 0)
+ delete options.percentColors;
+ }
+ else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
options.percentColors = [
[0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
[0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
@@ -5304,12 +5388,13 @@
};
// ----------------------------------------------------------------------------------------------------------------
- // Start up
+ // Load required JS libraries and CSS
NETDATA.requiredJs = [
{
url: NETDATA.serverDefault + 'lib/bootstrap.min.js',
isAlreadyLoaded: function() {
+ // check if bootstrap is loaded
if(typeof $().emulateTransitionEnd == 'function')
return true;
else {
@@ -5401,11 +5486,214 @@
NETDATA.loadRequiredCSS(++index);
};
+
+ // ----------------------------------------------------------------------------------------------------------------
+ // Registry of netdata hosts
+
+ NETDATA.registry = {
+ server: null, // the netdata registry server
+ person_guid: null, // the unique ID of this browser / user
+ machine_guid: null, // the unique ID the netdata server that served dashboard.js
+ hostname: null, // the hostname of the netdata server that served dashboard.js
+ urls: null, // the user's other URLs
+ urls_array: null, // the user's other URLs in an array
+
+ parsePersonUrls: function(person_urls) {
+ // console.log(person_urls);
+
+ if(person_urls) {
+ NETDATA.registry.urls = {};
+ NETDATA.registry.urls_array = new Array();
+
+ var now = new Date().getTime();
+ var apu = person_urls;
+ var i = apu.length;
+ while(i--) {
+ if(typeof NETDATA.registry.urls[apu[i][0]] === 'undefined') {
+ // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
+
+ var obj = {
+ guid: apu[i][0],
+ url: apu[i][1],
+ last_t: apu[i][2],
+ accesses: apu[i][3],
+ name: apu[i][4],
+ alternate_urls: new Array()
+ };
+
+ NETDATA.registry.urls[apu[i][0]] = obj;
+ NETDATA.registry.urls_array.push(obj);
+ }
+ else {
+ // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
+
+ var pu = NETDATA.registry.urls[apu[i][0]];
+ if(pu.last_t < apu[i][2]) {
+ pu.url = apu[i][1];
+ pu.last_t = apu[i][2];
+ pu.name = apu[i][4];
+ }
+ pu.accesses += apu[i][3];
+ pu.alternate_urls.push(apu[i][1]);
+ }
+ }
+ }
+
+ if(typeof netdataRegistryCallback === 'function')
+ netdataRegistryCallback(NETDATA.registry.urls_array);
+ },
+
+ init: function() {
+ if(typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry)
+ return;
+
+ NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
+ if(data) {
+ NETDATA.registry.server = data.registry;
+ NETDATA.registry.machine_guid = data.machine_guid;
+ NETDATA.registry.hostname = data.hostname;
+
+ NETDATA.registry.access(10, function (person_urls) {
+ NETDATA.registry.parsePersonUrls(person_urls);
+
+ });
+ }
+ });
+ },
+
+ hello: function(host, callback) {
+ // send HELLO to a netdata server:
+ // 1. verifies the server is reachable
+ // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
+ $.ajax({
+ url: host + '/api/v1/registry?action=hello',
+ async: true,
+ cache: false,
+ xhrFields: { withCredentials: true } // required for the cookie
+ })
+ .done(function(data) {
+ if(typeof data.status !== 'string' || data.status !== 'ok') {
+ NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
+ data = null;
+ }
+
+ if(typeof callback === 'function')
+ callback(data);
+ })
+ .fail(function() {
+ NETDATA.error(407, host);
+
+ if(typeof callback === 'function')
+ callback(null);
+ });
+ },
+
+ access: function(max_redirects, callback) {
+ // send ACCESS to a netdata registry:
+ // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
+ // 2. it responds with a list of netdata servers we know
+ // the registry identifies us using a cookie it sets the first time we access it
+ // the registry may respond with a redirect URL to send us to another registry
+ $.ajax({
+ url: NETDATA.registry.server + '/api/v1/registry?action=access&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(NETDATA.registry.hostname) + '&url=' + encodeURIComponent(NETDATA.serverDefault), // + '&visible_url=' + encodeURIComponent(document.location),
+ async: true,
+ cache: false,
+ xhrFields: { withCredentials: true } // required for the cookie
+ })
+ .done(function(data) {
+ var redirect = null;
+ if(typeof data.registry === 'string')
+ redirect = data.registry;
+
+ if(typeof data.status !== 'string' || data.status !== 'ok') {
+ NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
+ data = null;
+ }
+
+ if(data === null && redirect !== null && max_redirects > 0) {
+ NETDATA.registry.server = redirect;
+ NETDATA.registry.access(max_redirects - 1, callback);
+ }
+ else {
+ if(typeof data.person_guid === 'string')
+ NETDATA.registry.person_guid = data.person_guid;
+
+ if(typeof callback === 'function')
+ callback(data.urls);
+ }
+ })
+ .fail(function() {
+ NETDATA.error(410, NETDATA.registry.server);
+
+ if(typeof callback === 'function')
+ callback(null);
+ });
+ },
+
+ delete: function(delete_url, callback) {
+ // send DELETE to a netdata registry:
+ $.ajax({
+ url: NETDATA.registry.server + '/api/v1/registry?action=delete&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(NETDATA.registry.hostname) + '&url=' + encodeURIComponent(NETDATA.serverDefault) + '&delete_url=' + encodeURIComponent(delete_url),
+ async: true,
+ cache: false,
+ xhrFields: { withCredentials: true } // required for the cookie
+ })
+ .done(function(data) {
+ if(typeof data.status !== 'string' || data.status !== 'ok') {
+ NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
+ data = null;
+ }
+
+ if(typeof callback === 'function')
+ callback(data);
+ })
+ .fail(function() {
+ NETDATA.error(412, NETDATA.registry.server);
+
+ if(typeof callback === 'function')
+ callback(null);
+ });
+ },
+
+ switch: function(new_person_guid, callback) {
+ // impersonate
+ $.ajax({
+ url: NETDATA.registry.server + '/api/v1/registry?action=switch&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(NETDATA.registry.hostname) + '&url=' + encodeURIComponent(NETDATA.serverDefault) + '&to=' + new_person_guid,
+ async: true,
+ cache: false,
+ xhrFields: { withCredentials: true } // required for the cookie
+ })
+ .done(function(data) {
+ if(typeof data.status !== 'string' || data.status !== 'ok') {
+ NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
+ data = null;
+ }
+
+ if(typeof callback === 'function')
+ callback(data);
+ })
+ .fail(function() {
+ NETDATA.error(414, NETDATA.registry.server);
+
+ if(typeof callback === 'function')
+ callback(null);
+ });
+ }
+ };
+
+ // ----------------------------------------------------------------------------------------------------------------
+ // Boot it!
+
NETDATA.errorReset();
NETDATA.loadRequiredCSS(0);
NETDATA._loadjQuery(function() {
NETDATA.loadRequiredJs(0, function() {
+ if(typeof $().emulateTransitionEnd !== 'function') {
+ // bootstrap is not available
+ NETDATA.options.current.show_help = false;
+ }
+
if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
if(NETDATA.options.debug.main_loop === true)
console.log('starting chart refresh thread');