src/web/gui/v1/dashboard-react.js
/* eslint-disable */
/**
* after react-dashboard refractor, this file can be renamed to 'dashboard.js'
* and it will:
* - setup global objects, so any assignments like 'NETDATA.options.current.destroy_on_hide = true'
* will not break. we need to add it in places where 'dashboard.js' is
* - create react root DOM node
* - load react app
*
* Later, for performance improvement, the bundle can be added to dashboard-rect.js,
* but we need to run the react-app part after DOM is created and ready
*/
// ----------------------------------------------------------------------------
// global namespace
// Should stay var!
var NETDATA = window.NETDATA || {};
window.NETDATA = NETDATA // when imported as npm module
/// A heuristic for detecting slow devices.
let isSlowDeviceResult;
const isSlowDevice = function () {
if (!isSlowDeviceResult) {
return isSlowDeviceResult;
}
try {
let ua = navigator.userAgent.toLowerCase();
let iOS = /ipad|iphone|ipod/.test(ua) && !window.MSStream;
let android = /android/.test(ua) && !window.MSStream;
isSlowDeviceResult = (iOS || android);
} catch (e) {
isSlowDeviceResult = false;
}
return isSlowDeviceResult;
};
if (typeof window.netdataSnapshotData === 'undefined') {
window.netdataSnapshotData = null;
}
if (typeof window.netdataShowHelp === 'undefined') {
window.netdataShowHelp = true;
}
if (typeof window.netdataShowAlarms === 'undefined') {
window.netdataShowAlarms = false;
}
if (typeof window.netdataRegistryAfterMs !== 'number' || window.netdataRegistryAfterMs < 0) {
window.netdataRegistryAfterMs = 0; // 1500;
}
if (typeof window.netdataRegistry === 'undefined') {
// backward compatibility
window.netdataRegistry = (typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry === false);
}
if (window.netdataRegistry === false && typeof netdataRegistryCallback === 'function') {
window.netdataRegistry = true;
}
// ----------------------------------------------------------------------------------------------------------------
// the defaults for all charts
// if the user does not specify any of these, the following will be used
NETDATA.chartDefaults = {
width: '100%', // the chart width - can be null
height: '100%', // the chart height - can be null
min_width: null, // the chart minimum width - can be null
library: 'dygraph', // the graphing library to use
method: 'average', // the grouping method
before: 0, // panning
after: -600, // panning
pixels_per_point: 1, // the detail of the chart
fill_luminance: 0.8 // luminance of colors in solid areas
};
// ----------------------------------------------------------------------------------------------------------------
// global options
NETDATA.options = {
pauseCallback: null, // a callback when we are really paused
pause: false, // when enabled we don't auto-refresh the charts
targets: [], // an array of all the state objects that are
// currently active (independently of their
// viewport visibility)
updated_dom: true, // when true, the DOM has been updated with
// new elements we have to check.
auto_refresher_fast_weight: 0, // this is the current time in ms, spent
// rendering charts continuously.
// used with .current.fast_render_timeframe
page_is_visible: true, // when true, this page is visible
auto_refresher_stop_until: 0, // timestamp in ms - used internally, to stop the
// auto-refresher for some time (when a chart is
// performing pan or zoom, we need to stop refreshing
// all other charts, to have the maximum speed for
// rendering the chart that is panned or zoomed).
// Used with .current.global_pan_sync_time
on_scroll_refresher_stop_until: 0, // timestamp in ms - used to stop evaluating
// charts for some time, after a page scroll
last_page_resize: Date.now(), // the timestamp of the last resize request
last_page_scroll: 0, // the timestamp the last time the page was scrolled
browser_timezone: (Intl && Intl.DateTimeFormat)
? Intl.DateTimeFormat().resolvedOptions().timeZone // timezone detected by javascript
: "cannot-detect-it",
server_timezone: 'unknown', // timezone reported by the server
force_data_points: 0, // force the number of points to be returned for charts
fake_chart_rendering: false, // when set to true, the dashboard will download data but will not render the charts
passive_events: null, // true if the browser supports passive events
// the current profile
// we may have many...
current: {
units: 'auto', // can be 'auto' or 'original'
temperature: 'celsius', // can be 'celsius' or 'fahrenheit'
seconds_as_time: true, // show seconds as DDd:HH:MM:SS ?
timezone: 'default', // the timezone to use, or 'default'
user_set_server_timezone: 'default', // as set by the user on the dashboard
legend_toolbox: true, // show the legend toolbox on charts
resize_charts: true, // show the resize handler on charts
pixels_per_point: isSlowDevice() ? 5 : 1, // the minimum pixels per point for all charts
// increase this to speed javascript up
// each chart library has its own limit too
// the max of this and the chart library is used
// the final is calculated every time, so a change
// here will have immediate effect on the next chart
// update
idle_between_charts: 100, // ms - how much time to wait between chart updates
fast_render_timeframe: 200, // ms - render continuously until this time of continuous
// rendering has been reached
// this setting is used to make it render e.g. 10
// charts at once, sleep idle_between_charts time
// and continue for another 10 charts.
idle_between_loops: 500, // ms - if all charts have been updated, wait this
// time before starting again.
idle_parallel_loops: 100, // ms - the time between parallel refresher updates
idle_lost_focus: 500, // ms - when the window does not have focus, check
// if focus has been regained, every this time
global_pan_sync_time: 300, // ms - when you pan or zoom a chart, the background
// auto-refreshing of charts is paused for this amount
// of time
sync_selection_delay: 400, // ms - when you pan or zoom a chart, wait this amount
// of time before setting up synchronized selections
// on hover.
sync_selection: true, // enable or disable selection sync
pan_and_zoom_delay: 50, // when panning or zooming, how often to update the chart
sync_pan_and_zoom: true, // enable or disable pan and zoom sync
pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
update_only_visible: true, // enable or disable visibility management / used for printing
parallel_refresher: !isSlowDevice(), // enable parallel refresh of charts
concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
destroy_on_hide: isSlowDevice(), // destroy charts when they are not visible
// when enabled the charts will show some help
// when there's no bootstrap, we can't show it
show_help: netdataShowHelp && !window.netdataNoBootstrap,
show_help_delay_show_ms: 500,
show_help_delay_hide_ms: 0,
eliminate_zero_dimensions: true, // do not show dimensions with just zeros
stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
smooth_plot: !isSlowDevice(), // enable smooth plot, where possible
color_fill_opacity_line: 1.0,
color_fill_opacity_area: 0.2,
color_fill_opacity_fake_stacked: 1,
color_fill_opacity_stacked: 0.8,
pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
pan_and_zoom_factor_multiplier_control: 2.0,
pan_and_zoom_factor_multiplier_shift: 3.0,
pan_and_zoom_factor_multiplier_alt: 4.0,
abort_ajax_on_scroll: false, // kill pending ajax page scroll
async_on_scroll: false, // sync/async onscroll handler
onscroll_worker_duration_threshold: 30, // time in ms, for async scroll handler
retries_on_data_failures: 3, // how many retries to make if we can't fetch chart data from the server
setOptionCallback: function () {
}
},
debug: {
show_boxes: false,
main_loop: false,
focus: false,
visibility: false,
chart_data_url: false,
chart_errors: true, // remember to set it to false before merging
chart_timing: false,
chart_calls: false,
libraries: false,
dygraph: false,
globalSelectionSync: false,
globalPanAndZoom: false
}
};
NETDATA.statistics = {
refreshes_total: 0,
refreshes_active: 0,
refreshes_active_max: 0
};
NETDATA.themes = {
white: {
bootstrap_css: "css/bootstrap-3.3.7.css",
dashboard_css: "css/dashboard.css?v20180210-1",
background: "#FFFFFF",
foreground: "#000000",
grid: "#F0F0F0",
axis: "#F0F0F0",
highlight: "#F5F5F5",
colors: ["#3366CC", "#DC3912", "#109618", "#FF9900", "#990099", "#DD4477",
"#3B3EAC", "#66AA00", "#0099C6", "#B82E2E", "#AAAA11", "#5574A6",
"#994499", "#22AA99", "#6633CC", "#E67300", "#316395", "#8B0707",
"#329262", "#3B3EAC"],
easypiechart_track: "#f0f0f0",
easypiechart_scale: "#dfe0e0",
gauge_pointer: "#C0C0C0",
gauge_stroke: "#F0F0F0",
gauge_gradient: false,
gauge_stop_color: "#FC8D5E",
gauge_start_color: "#B0E952",
d3pie: {
title: "#333333",
subtitle: "#666666",
footer: "#888888",
other: "#aaaaaa",
mainlabel: "#333333",
percentage: "#dddddd",
value: "#aaaa22",
tooltip_bg: "#000000",
tooltip_fg: "#efefef",
segment_stroke: "#ffffff",
gradient_color: "#000000",
},
},
slate: {
bootstrap_css: "css/bootstrap-slate-flat-3.3.7.css?v20161229-1",
dashboard_css: "css/dashboard.slate.css?v20180210-1",
background: "#272b30",
foreground: "#C8C8C8",
grid: "#283236",
axis: "#283236",
highlight: "#383838",
colors: ["#66AA00", "#FE3912", "#3366CC", "#D66300", "#0099C6", "#DDDD00",
"#5054e6", "#EE9911", "#BB44CC", "#e45757", "#ef0aef", "#CC7700",
"#22AA99", "#109618", "#905bfd", "#f54882", "#4381bf", "#ff3737",
"#329262", "#3B3EFF"],
easypiechart_track: "#373b40",
easypiechart_scale: "#373b40",
gauge_pointer: "#474b50",
gauge_stroke: "#373b40",
gauge_gradient: false,
gauge_stop_color: "#FC8D5E",
gauge_start_color: "#B0E952",
d3pie: {
title: "#C8C8C8",
subtitle: "#283236",
footer: "#283236",
other: "#283236",
mainlabel: "#C8C8C8",
percentage: "#dddddd",
value: "#cccc44",
tooltip_bg: "#272b30",
tooltip_fg: "#C8C8C8",
segment_stroke: "#283236",
gradient_color: "#000000",
},
},
}
// Codacy declarations
/* global netdataTheme */
NETDATA.updateTheme = function () {
if (typeof window.netdataTheme !== 'undefined'
&& typeof NETDATA.themes[netdataTheme] !== 'undefined'
) {
NETDATA.themes.current = NETDATA.themes[window.netdataTheme];
} else {
NETDATA.themes.current = NETDATA.themes.white;
}
NETDATA.colors = NETDATA.themes.current.colors;
}
NETDATA.updateTheme()
// these are the colors Google Charts are using
// we have them here to attempt emulate their look and feel on the other chart libraries
// http://there4.io/2012/05/02/google-chart-color-list/
//NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
// '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
// '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
// an alternative set
// http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
// (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
//NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
// dygraph
// local storage options
NETDATA.localStorage = {
default: {},
current: {},
callback: {} // only used for resetting back to defaults
};
// todo temporary stuff which was originally in dashboard.js
// but needs to be refactored
NETDATA.name2id = function (s) {
return s
.replace(/ /g, '_')
.replace(/:/g, '_')
.replace(/\(/g, '_')
.replace(/\)/g, '_')
.replace(/\./g, '_')
.replace(/\//g, '_');
};
NETDATA.globalChartUnderlay = {
clear: () => {},
init: () => {},
}
NETDATA.globalPanAndZoom = {
callback: () => {},
}
NETDATA.unpause = () => {}
// ----------------------------------------------------------------------------------------------------------------
// XSS checks
NETDATA.xss = {
enabled: (typeof netdataCheckXSS === 'undefined') ? false : netdataCheckXSS,
enabled_for_data: (typeof netdataCheckXSS === 'undefined') ? false : netdataCheckXSS,
string: function (s) {
return s.toString()
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
},
object: function (name, obj, ignore_regex) {
if (typeof ignore_regex !== 'undefined' && ignore_regex.test(name)) {
// console.log('XSS: ignoring "' + name + '"');
return obj;
}
switch (typeof(obj)) {
case 'string':
const ret = this.string(obj);
if (ret !== obj) {
console.log('XSS protection changed string ' + name + ' from "' + obj + '" to "' + ret + '"');
}
return ret;
case 'object':
if (obj === null) {
return obj;
}
if (Array.isArray(obj)) {
// console.log('checking array "' + name + '"');
let len = obj.length;
while (len--) {
obj[len] = this.object(name + '[' + len + ']', obj[len], ignore_regex);
}
} else {
// console.log('checking object "' + name + '"');
for (var i in obj) {
if (obj.hasOwnProperty(i) === false) {
continue;
}
if (this.string(i) !== i) {
console.log('XSS protection removed invalid object member "' + name + '.' + i + '"');
delete obj[i];
} else {
obj[i] = this.object(name + '.' + i, obj[i], ignore_regex);
}
}
}
return obj;
default:
return obj;
}
},
checkOptional: function (name, obj, ignore_regex) {
if (this.enabled) {
//console.log('XSS: checking optional "' + name + '"...');
return this.object(name, obj, ignore_regex);
}
return obj;
},
checkAlways: function (name, obj, ignore_regex) {
//console.log('XSS: checking always "' + name + '"...');
return this.object(name, obj, ignore_regex);
},
checkData: function (name, obj, ignore_regex) {
if (this.enabled_for_data) {
//console.log('XSS: checking data "' + name + '"...');
return this.object(name, obj, ignore_regex);
}
return obj;
}
};
const fixHost = (host) => {
while (host.slice(-1) === '/') {
host = host.substring(0, host.length - 1);
}
return host.replace(/\/v1\/?$/, "");
}
NETDATA.chartRegistry = {
charts: {},
globalReset: function () {
this.charts = {};
},
add: function (host, id, data) {
if (typeof this.charts[host] === 'undefined') {
this.charts[host] = {};
}
//console.log('added ' + host + '/' + id);
this.charts[host][id] = data;
},
get: function (host, id) {
if (typeof this.charts[host] === 'undefined') {
return null;
}
if (typeof this.charts[host][id] === 'undefined') {
return null;
}
//console.log('cached ' + host + '/' + id);
return this.charts[host][id];
},
downloadAll: function (host, callback) {
host = fixHost(host);
let self = this;
function got_data(h, data, callback) {
if (data !== null) {
self.charts[h] = data.charts;
window.charts = data.charts
// update the server timezone in our options
if (typeof data.timezone === 'string') {
NETDATA.options.server_timezone = data.timezone;
}
} else {
NETDATA.error(406, h + '/api/v1/charts');
}
if (typeof callback === 'function') {
callback(data);
}
}
if (window.netdataSnapshotData !== null) {
got_data(host, window.netdataSnapshotData.charts, callback);
} else {
$.ajax({
url: host + '/api/v1/charts',
async: true,
cache: false,
xhrFields: {withCredentials: true} // required for the cookie
})
.done(function (data) {
data = NETDATA.xss.checkOptional('/api/v1/charts', data);
got_data(host, data, callback);
})
.fail(function () {
NETDATA.error(405, host + '/api/v1/charts');
if (typeof callback === 'function') {
callback(null);
}
});
}
}
};
NETDATA.fixHost = function (host) {
while (host.slice(-1) === '/') {
host = host.substring(0, host.length - 1);
}
return host.replace(/\/v1\/?$/, "");
};
NETDATA.registryHello = function (host, callback) {
host = NETDATA.fixHost(host);
// 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,
headers: {
'Cache-Control': 'no-cache, no-store',
'Pragma': 'no-cache'
},
xhrFields: {withCredentials: true} // required for the cookie
})
.done(function (data) {
data = NETDATA.xss.checkOptional('/api/v1/registry?action=hello', data);
if (typeof data.status !== 'string' || data.status !== 'ok') {
// NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
data = null;
}
if (typeof callback === 'function') {
return callback(data);
}
})
.fail(function () {
// NETDATA.error(407, host);
if (typeof callback === 'function') {
return callback(null);
}
});
}
NETDATA.registrySearch = function (machine_guid, getFromRegistry, serverDefault, callback) {
// SEARCH for the URLs of a machine:
$.ajax({
url: getFromRegistry("registryServer") + '/api/v1/registry?action=search&machine='
+ getFromRegistry("machineGuid") + '&name=' + encodeURIComponent(getFromRegistry("hostname"))
+ '&url=' + encodeURIComponent(serverDefault) + '&for=' + machine_guid,
async: true,
cache: false,
headers: {
'Cache-Control': 'no-cache, no-store',
'Pragma': 'no-cache'
},
xhrFields: {withCredentials: true} // required for the cookie
})
.done(function (data) {
data = NETDATA.xss.checkAlways('/api/v1/registry?action=search', data);
if (typeof data.status !== 'string' || data.status !== 'ok') {
// NETDATA.error(417, getFromRegistry("registryServer") + ' responded with: ' + JSON.stringify(data));
console.warn(getFromRegistry("registryServer") + ' responded with: ' + JSON.stringify(data));
data = null;
}
if (typeof callback === 'function') {
return callback(data);
}
})
.fail(function () {
// NETDATA.error(418, getFromRegistry("registryServer"));
console.warn("registry search call failed", getFromRegistry("registryServer"))
if (typeof callback === 'function') {
return callback(null);
}
});
}
NETDATA.registryDelete = function (getFromRegistry, serverDefault, delete_url, callback) {
// send DELETE to a netdata registry:
$.ajax({
url: getFromRegistry("registryServer") + '/api/v1/registry?action=delete&machine='
+ getFromRegistry("machineGuid") + '&name=' + encodeURIComponent(getFromRegistry("hostname"))
+ '&url=' + encodeURIComponent(serverDefault) + '&delete_url=' + encodeURIComponent(delete_url),
// + '&url=' + encodeURIComponent("http://n5.katsuna.com:19999/") + '&delete_url=' + encodeURIComponent(delete_url),
async: true,
cache: false,
headers: {
'Cache-Control': 'no-cache, no-store',
'Pragma': 'no-cache'
},
xhrFields: {withCredentials: true} // required for the cookie
})
.done(function (data) {
// data = NETDATA.xss.checkAlways('/api/v1/registry?action=delete', data);
if (typeof data.status !== 'string' || data.status !== 'ok') {
// NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
console.warn(411, getFromRegistry("registryServer") + ' responded with: ' + JSON.stringify(data));
data = null;
}
if (typeof callback === 'function') {
return callback(data);
}
})
.fail(function () {
// NETDATA.error(412, NETDATA.registry.server);
console.warn(412, getFromRegistry("registryServer"));
if (typeof callback === 'function') {
return callback(null);
}
});
}
// NETDATA.currentScript = document.currentScript