summaryrefslogtreecommitdiffstats
path: root/src/web/gui/v1/dashboard-react.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-07-24 09:54:23 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-07-24 09:54:44 +0000
commit836b47cb7e99a977c5a23b059ca1d0b5065d310e (patch)
tree1604da8f482d02effa033c94a84be42bc0c848c3 /src/web/gui/v1/dashboard-react.js
parentReleasing debian version 1.44.3-2. (diff)
downloadnetdata-836b47cb7e99a977c5a23b059ca1d0b5065d310e.tar.xz
netdata-836b47cb7e99a977c5a23b059ca1d0b5065d310e.zip
Merging upstream version 1.46.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/web/gui/v1/dashboard-react.js')
-rw-r--r--src/web/gui/v1/dashboard-react.js675
1 files changed, 675 insertions, 0 deletions
diff --git a/src/web/gui/v1/dashboard-react.js b/src/web/gui/v1/dashboard-react.js
new file mode 100644
index 00000000..aa067a72
--- /dev/null
+++ b/src/web/gui/v1/dashboard-react.js
@@ -0,0 +1,675 @@
+/* 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, '&lt;')
+ .replace(/>/g, '&gt;')
+ .replace(/"/g, '&quot;')
+ .replace(/'/g, '&#39;');
+ },
+
+ 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