1
// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+// DO NOT EDIT: This file is automatically generated from the source files in src/
4
+
5
// ----------------------------------------------------------------------------
6
// You can set the following variables before loading this script:
7
8
+// 'use strict';
9
+
10
/*global netdataNoDygraphs *//* boolean, disable dygraph charts
11
* (default: false) */
12
/*global netdataNoSparklines *//* boolean, disable sparkline charts
77
// ----------------------------------------------------------------------------
78
// global namespace
79
75
-var NETDATA = window.NETDATA || {};
80
+const NETDATA = window.NETDATA || {};
81
82
(function(window, document, $, undefined) {
83
79
- NETDATA.encodeURIComponent = function(s) {
80
- if(typeof(s) === 'string')
81
- return encodeURIComponent(s);
84
+NETDATA.encodeURIComponent = function (s) {
85
+ if (typeof(s) === 'string') {
86
+ return encodeURIComponent(s);
87
+ }
88
83
- return s;
84
- };
89
+ return s;
90
+};
91
86
- // ------------------------------------------------------------------------
87
- // compatibility fixes
92
+/// A heuristic for detecting slow devices.
93
+let isSlowDeviceResult = undefined;
94
+const isSlowDevice = function () {
95
+ if (!isSlowDeviceResult) {
96
+ return isSlowDeviceResult;
97
+ }
98
89
- // fix IE issue with console
90
- if(!window.console) { window.console = { log: function(){} }; }
99
+ try {
100
+ let ua = navigator.userAgent.toLowerCase();
101
92
- // if string.endsWith is not defined, define it
93
- if(typeof String.prototype.endsWith !== 'function') {
94
- String.prototype.endsWith = function(s) {
95
- if(s.length > this.length) return false;
96
- return this.slice(-s.length) === s;
97
- };
102
+ let iOS = /ipad|iphone|ipod/.test(ua) && !window.MSStream;
103
+ let android = /android/.test(ua) && !window.MSStream;
104
+ isSlowDeviceResult = (iOS || android);
105
+ } catch (e) {
106
+ isSlowDeviceResult = false;
107
}
108
100
- // if string.startsWith is not defined, define it
101
- if(typeof String.prototype.startsWith !== 'function') {
102
- String.prototype.startsWith = function(s) {
103
- if(s.length > this.length) return false;
104
- return this.slice(s.length) === s;
105
- };
106
- }
109
+ return isSlowDeviceResult;
110
+};
111
108
- NETDATA.name2id = function(s) {
109
- return s
110
- .replace(/ /g, '_')
111
- .replace(/\(/g, '_')
112
- .replace(/\)/g, '_')
113
- .replace(/\./g, '_')
114
- .replace(/\//g, '_');
115
- };
112
+NETDATA.guid = function () {
113
+ function s4() {
114
+ return Math.floor((1 + Math.random()) * 0x10000)
115
+ .toString(16)
116
+ .substring(1);
117
+ }
118
117
- // ----------------------------------------------------------------------------------------------------------------
118
- // XSS checks
119
-
120
- NETDATA.xss = {
121
- enabled: (typeof netdataCheckXSS === 'undefined')?false:netdataCheckXSS,
122
- enabled_for_data: (typeof netdataCheckXSS === 'undefined')?false:netdataCheckXSS,
123
-
124
- string: function (s) {
125
- return s.toString()
126
- .replace(/</g, '<')
127
- .replace(/>/g, '>')
128
- .replace(/"/g, '"')
129
- .replace(/'/g, ''');
130
- },
119
+ return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
120
+};
121
132
- object: function(name, obj, ignore_regex) {
133
- if(typeof ignore_regex !== 'undefined' && ignore_regex.test(name) === true) {
134
- // console.log('XSS: ignoring "' + name + '"');
135
- return obj;
122
+NETDATA.zeropad = function (x) {
123
+ if (x > -10 && x < 10) {
124
+ return '0' + x.toString();
125
+ } else {
126
+ return x.toString();
127
+ }
128
+};
129
+
130
+NETDATA.seconds4human = function (seconds, options) {
131
+ let default_options = {
132
+ now: 'now',
133
+ space: ' ',
134
+ negative_suffix: 'ago',
135
+ day: 'day',
136
+ days: 'days',
137
+ hour: 'hour',
138
+ hours: 'hours',
139
+ minute: 'min',
140
+ minutes: 'mins',
141
+ second: 'sec',
142
+ seconds: 'secs',
143
+ and: 'and'
144
+ };
145
+
146
+ if (typeof options !== 'object') {
147
+ options = default_options;
148
+ } else {
149
+ for (const x in default_options) {
150
+ if (typeof options[x] !== 'string') {
151
+ options[x] = default_options[x];
152
}
153
+ }
154
+ }
155
138
- switch (typeof(obj)) {
139
- case 'string':
140
- var ret = this.string(obj);
141
- if(ret !== obj) console.log('XSS protection changed string ' + name + ' from "' + obj + '" to "' + ret + '"');
142
- return ret;
156
+ if (typeof seconds === 'string') {
157
+ seconds = parseInt(seconds, 10);
158
+ }
159
144
- case 'object':
145
- if(obj === null) return obj;
160
+ if (seconds === 0) {
161
+ return options.now;
162
+ }
163
147
- if(Array.isArray(obj) === true) {
148
- // console.log('checking array "' + name + '"');
164
+ let suffix = '';
165
+ if (seconds < 0) {
166
+ seconds = -seconds;
167
+ if (options.negative_suffix !== '') {
168
+ suffix = options.space + options.negative_suffix;
169
+ }
170
+ }
171
150
- var len = obj.length;
151
- while(len--)
152
- obj[len] = this.object(name + '[' + len + ']', obj[len], ignore_regex);
153
- }
154
- else {
155
- // console.log('checking object "' + name + '"');
172
+ let days = Math.floor(seconds / 86400);
173
+ seconds -= (days * 86400);
174
157
- for(var i in obj) {
158
- if(obj.hasOwnProperty(i) === false) continue;
159
- if(this.string(i) !== i) {
160
- console.log('XSS protection removed invalid object member "' + name + '.' + i + '"');
161
- delete obj[i];
162
- }
163
- else
164
- obj[i] = this.object(name + '.' + i, obj[i], ignore_regex);
165
- }
166
- }
167
- return obj;
175
+ let hours = Math.floor(seconds / 3600);
176
+ seconds -= (hours * 3600);
177
169
- default:
170
- return obj;
171
- }
172
- },
178
+ let minutes = Math.floor(seconds / 60);
179
+ seconds -= (minutes * 60);
180
174
- checkOptional: function(name, obj, ignore_regex) {
175
- if(this.enabled === true) {
176
- //console.log('XSS: checking optional "' + name + '"...');
177
- return this.object(name, obj, ignore_regex);
178
- }
179
- return obj;
180
- },
181
+ let strings = [];
182
182
- checkAlways: function(name, obj, ignore_regex) {
183
- //console.log('XSS: checking always "' + name + '"...');
184
- return this.object(name, obj, ignore_regex);
185
- },
183
+ if (days > 1) {
184
+ strings.push(days.toString() + options.space + options.days);
185
+ } else if (days === 1) {
186
+ strings.push(days.toString() + options.space + options.day);
187
+ }
188
187
- checkData: function(name, obj, ignore_regex) {
188
- if(this.enabled_for_data === true) {
189
- //console.log('XSS: checking data "' + name + '"...');
190
- return this.object(name, obj, ignore_regex);
191
- }
192
- return obj;
193
- }
194
- };
189
+ if (hours > 1) {
190
+ strings.push(hours.toString() + options.space + options.hours);
191
+ } else if (hours === 1) {
192
+ strings.push(hours.toString() + options.space + options.hour);
193
+ }
194
196
- // ----------------------------------------------------------------------------------------------------------------
197
- // Detect the netdata server
195
+ if (minutes > 1) {
196
+ strings.push(minutes.toString() + options.space + options.minutes);
197
+ } else if (minutes === 1) {
198
+ strings.push(minutes.toString() + options.space + options.minute);
199
+ }
200
+
201
+ if (seconds > 1) {
202
+ strings.push(Math.floor(seconds).toString() + options.space + options.seconds);
203
+ } else if (seconds === 1) {
204
+ strings.push(Math.floor(seconds).toString() + options.space + options.second);
205
+ }
206
+
207
+ if (strings.length === 1) {
208
+ return strings.pop() + suffix;
209
+ }
210
199
- // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
200
- // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
201
- NETDATA._scriptSource = function() {
202
- var script = null;
211
+ let last = strings.pop();
212
+ return strings.join(", ") + " " + options.and + " " + last + suffix;
213
+};
214
204
- if(typeof document.currentScript !== 'undefined') {
205
- script = document.currentScript;
215
+// ----------------------------------------------------------------------------------------------------------------
216
+// element data attributes
217
+
218
+NETDATA.dataAttribute = function (element, attribute, def) {
219
+ let key = 'data-' + attribute.toString();
220
+ if (element.hasAttribute(key)) {
221
+ let data = element.getAttribute(key);
222
+
223
+ if (data === 'true') {
224
+ return true;
225
}
207
- else {
208
- var all_scripts = document.getElementsByTagName('script');
209
- script = all_scripts[all_scripts.length - 1];
226
+ if (data === 'false') {
227
+ return false;
228
+ }
229
+ if (data === 'null') {
230
+ return null;
231
}
232
212
- if (typeof script.getAttribute.length !== 'undefined')
213
- script = script.src;
214
- else
215
- script = script.getAttribute('src', -1);
216
-
217
- return script;
218
- };
233
+ // Only convert to a number if it doesn't change the string
234
+ if (data === +data + '') {
235
+ return +data;
236
+ }
237
220
- if(typeof netdataServer !== 'undefined')
221
- NETDATA.serverDefault = netdataServer;
222
- else {
223
- var s = NETDATA._scriptSource();
224
- if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)?$/g, "");
225
- else {
226
- console.log('WARNING: Cannot detect the URL of the netdata server.');
227
- NETDATA.serverDefault = null;
238
+ if (/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/.test(data)) {
239
+ return JSON.parse(data);
240
}
241
+
242
+ return data;
243
+ } else {
244
+ return def;
245
}
246
+};
247
231
- if(NETDATA.serverDefault === null)
232
- NETDATA.serverDefault = '';
233
- else if(NETDATA.serverDefault.slice(-1) !== '/')
234
- NETDATA.serverDefault += '/';
248
+NETDATA.dataAttributeBoolean = function (element, attribute, def) {
249
+ let value = NETDATA.dataAttribute(element, attribute, def);
250
236
- if(typeof netdataServerStatic !== 'undefined' && netdataServerStatic !== null && netdataServerStatic !== '') {
237
- NETDATA.serverStatic = netdataServerStatic;
238
- if(NETDATA.serverStatic.slice(-1) !== '/')
239
- NETDATA.serverStatic += '/';
251
+ if (value === true || value === false) // gmosx: Love this :)
252
+ {
253
+ return value;
254
}
241
- else {
242
- NETDATA.serverStatic = NETDATA.serverDefault;
243
- }
244
-
245
-
246
- // default URLs for all the external files we need
247
- // make them RELATIVE so that the whole thing can also be
248
- // installed under a web server
249
- NETDATA.jQuery = NETDATA.serverStatic + 'lib/jquery-2.2.4.min.js';
250
- NETDATA.peity_js = NETDATA.serverStatic + 'lib/jquery.peity-3.2.0.min.js';
251
- NETDATA.sparkline_js = NETDATA.serverStatic + 'lib/jquery.sparkline-2.1.2.min.js';
252
- NETDATA.easypiechart_js = NETDATA.serverStatic + 'lib/jquery.easypiechart-97b5824.min.js';
253
- NETDATA.gauge_js = NETDATA.serverStatic + 'lib/gauge-1.3.2.min.js';
254
- NETDATA.dygraph_js = NETDATA.serverStatic + 'lib/dygraph-c91c859.min.js';
255
- NETDATA.dygraph_smooth_js = NETDATA.serverStatic + 'lib/dygraph-smooth-plotter-c91c859.js';
256
- NETDATA.raphael_js = NETDATA.serverStatic + 'lib/raphael-2.2.4-min.js';
257
- NETDATA.c3_js = NETDATA.serverStatic + 'lib/c3-0.4.18.min.js';
258
- NETDATA.c3_css = NETDATA.serverStatic + 'css/c3-0.4.18.min.css';
259
- NETDATA.d3pie_js = NETDATA.serverStatic + 'lib/d3pie-0.2.1-netdata-3.js';
260
- NETDATA.d3_js = NETDATA.serverStatic + 'lib/d3-4.12.2.min.js';
261
- NETDATA.morris_js = NETDATA.serverStatic + 'lib/morris-0.5.1.min.js';
262
- NETDATA.morris_css = NETDATA.serverStatic + 'css/morris-0.5.1.css';
263
- NETDATA.google_js = 'https://www.google.com/jsapi';
264
-
265
- NETDATA.themes = {
266
- white: {
267
- bootstrap_css: NETDATA.serverStatic + 'css/bootstrap-3.3.7.css',
268
- dashboard_css: NETDATA.serverStatic + 'dashboard.css?v20180210-1',
269
- background: '#FFFFFF',
270
- foreground: '#000000',
271
- grid: '#F0F0F0',
272
- axis: '#F0F0F0',
273
- highlight: '#F5F5F5',
274
- colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
275
- '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
276
- '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
277
- '#329262', '#3B3EAC' ],
278
- easypiechart_track: '#f0f0f0',
279
- easypiechart_scale: '#dfe0e0',
280
- gauge_pointer: '#C0C0C0',
281
- gauge_stroke: '#F0F0F0',
282
- gauge_gradient: false,
283
- d3pie: {
284
- title: '#333333',
285
- subtitle: '#666666',
286
- footer: '#888888',
287
- other: '#aaaaaa',
288
- mainlabel: '#333333',
289
- percentage: '#dddddd',
290
- value: '#aaaa22',
291
- tooltip_bg: '#000000',
292
- tooltip_fg: '#efefef',
293
- segment_stroke: "#ffffff",
294
- gradient_color: '#000000'
295
- }
296
- },
297
- slate: {
298
- bootstrap_css: NETDATA.serverStatic + 'css/bootstrap-slate-flat-3.3.7.css?v20161229-1',
299
- dashboard_css: NETDATA.serverStatic + 'dashboard.slate.css?v20180210-1',
300
- background: '#272b30',
301
- foreground: '#C8C8C8',
302
- grid: '#283236',
303
- axis: '#283236',
304
- highlight: '#383838',
305
-/* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
306
- '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
307
- '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
308
- '#a6a479', '#a66da8' ],
309
-*/
310
- colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
311
- '#5054e6', '#EE9911', '#BB44CC', '#e45757', '#ef0aef', '#CC7700',
312
- '#22AA99', '#109618', '#905bfd', '#f54882', '#4381bf', '#ff3737',
313
- '#329262', '#3B3EFF' ],
314
- easypiechart_track: '#373b40',
315
- easypiechart_scale: '#373b40',
316
- gauge_pointer: '#474b50',
317
- gauge_stroke: '#373b40',
318
- gauge_gradient: false,
319
- d3pie: {
320
- title: '#C8C8C8',
321
- subtitle: '#283236',
322
- footer: '#283236',
323
- other: '#283236',
324
- mainlabel: '#C8C8C8',
325
- percentage: '#dddddd',
326
- value: '#cccc44',
327
- tooltip_bg: '#272b30',
328
- tooltip_fg: '#C8C8C8',
329
- segment_stroke: "#283236",
330
- gradient_color: '#000000'
331
- }
332
- }
333
- };
255
335
- if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
336
- NETDATA.themes.current = NETDATA.themes[netdataTheme];
337
- else
338
- NETDATA.themes.current = NETDATA.themes.white;
339
-
340
- NETDATA.colors = NETDATA.themes.current.colors;
341
-
342
- // these are the colors Google Charts are using
343
- // we have them here to attempt emulate their look and feel on the other chart libraries
344
- // http://there4.io/2012/05/02/google-chart-color-list/
345
- //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
346
- // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
347
- // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
348
-
349
- // an alternative set
350
- // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
351
- // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
352
- //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
353
-
354
- NETDATA.icons = {
355
- left: '<i class="fas fa-backward"></i>',
356
- reset: '<i class="fas fa-play"></i>',
357
- right: '<i class="fas fa-forward"></i>',
358
- zoomIn: '<i class="fas fa-plus"></i>',
359
- zoomOut: '<i class="fas fa-minus"></i>',
360
- resize: '<i class="fas fa-sort"></i>',
361
- lineChart: '<i class="fas fa-chart-line"></i>',
362
- areaChart: '<i class="fas fa-chart-area"></i>',
363
- noChart: '<i class="fas fa-chart-area"></i>',
364
- loading: '<i class="fas fa-sync-alt"></i>',
365
- noData: '<i class="fas fa-exclamation-triangle"></i>'
366
- };
256
+ if (typeof(value) === 'string') {
257
+ if (value === 'yes' || value === 'on') {
258
+ return true;
259
+ }
260
368
- if(typeof netdataIcons === 'object') {
369
- for(var icon in NETDATA.icons) {
370
- if(NETDATA.icons.hasOwnProperty(icon) && typeof(netdataIcons[icon]) === 'string')
371
- NETDATA.icons[icon] = netdataIcons[icon];
261
+ if (value === '' || value === 'no' || value === 'off' || value === 'null') {
262
+ return false;
263
}
264
+
265
+ return def;
266
}
267
375
- if(typeof netdataSnapshotData === 'undefined')
376
- netdataSnapshotData = null;
268
+ if (typeof(value) === 'number') {
269
+ return value !== 0;
270
+ }
271
378
- if(typeof netdataShowHelp === 'undefined')
379
- netdataShowHelp = true;
272
+ return def;
273
+};
274
381
- if(typeof netdataShowAlarms === 'undefined')
382
- netdataShowAlarms = false;
275
+// ----------------------------------------------------------------------------------------------------------------
276
+// fast numbers formatting
277
384
- if(typeof netdataRegistryAfterMs !== 'number' || netdataRegistryAfterMs < 0)
385
- netdataRegistryAfterMs = 1500;
278
+NETDATA.fastNumberFormat = {
279
+ formatters_fixed: [],
280
+ formatters_zero_based: [],
281
387
- if(typeof netdataRegistry === 'undefined') {
388
- // backward compatibility
389
- netdataRegistry = (typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry === false);
390
- }
391
- if(netdataRegistry === false && typeof netdataRegistryCallback === 'function')
392
- netdataRegistry = true;
282
+ // this is the fastest and the preferred
283
+ getIntlNumberFormat: function (min, max) {
284
+ let key = max;
285
+ if (min === max) {
286
+ if (typeof this.formatters_fixed[key] === 'undefined') {
287
+ this.formatters_fixed[key] = new Intl.NumberFormat(undefined, {
288
+ // style: 'decimal',
289
+ // minimumIntegerDigits: 1,
290
+ // minimumSignificantDigits: 1,
291
+ // maximumSignificantDigits: 1,
292
+ useGrouping: true,
293
+ minimumFractionDigits: min,
294
+ maximumFractionDigits: max
295
+ });
296
+ }
297
298
+ return this.formatters_fixed[key];
299
+ } else if (min === 0) {
300
+ if (typeof this.formatters_zero_based[key] === 'undefined') {
301
+ this.formatters_zero_based[key] = new Intl.NumberFormat(undefined, {
302
+ // style: 'decimal',
303
+ // minimumIntegerDigits: 1,
304
+ // minimumSignificantDigits: 1,
305
+ // maximumSignificantDigits: 1,
306
+ useGrouping: true,
307
+ minimumFractionDigits: min,
308
+ maximumFractionDigits: max
309
+ });
310
+ }
311
395
- // ----------------------------------------------------------------------------------------------------------------
396
- // detect if this is probably a slow device
312
+ return this.formatters_zero_based[key];
313
+ } else {
314
+ // this is never used
315
+ // it is added just for completeness
316
+ return new Intl.NumberFormat(undefined, {
317
+ // style: 'decimal',
318
+ // minimumIntegerDigits: 1,
319
+ // minimumSignificantDigits: 1,
320
+ // maximumSignificantDigits: 1,
321
+ useGrouping: true,
322
+ minimumFractionDigits: min,
323
+ maximumFractionDigits: max
324
+ });
325
+ }
326
+ },
327
398
- var isSlowDeviceResult = undefined;
399
- var isSlowDevice = function() {
400
- if(isSlowDeviceResult !== undefined)
401
- return isSlowDeviceResult;
328
+ // this respects locale
329
+ getLocaleString: function (min, max) {
330
+ let key = max;
331
+ if (min === max) {
332
+ if (typeof this.formatters_fixed[key] === 'undefined') {
333
+ this.formatters_fixed[key] = {
334
+ format: function (value) {
335
+ return value.toLocaleString(undefined, {
336
+ // style: 'decimal',
337
+ // minimumIntegerDigits: 1,
338
+ // minimumSignificantDigits: 1,
339
+ // maximumSignificantDigits: 1,
340
+ useGrouping: true,
341
+ minimumFractionDigits: min,
342
+ maximumFractionDigits: max
343
+ });
344
+ }
345
+ };
346
+ }
347
403
- try {
404
- var ua = navigator.userAgent.toLowerCase();
348
+ return this.formatters_fixed[key];
349
+ } else if (min === 0) {
350
+ if (typeof this.formatters_zero_based[key] === 'undefined') {
351
+ this.formatters_zero_based[key] = {
352
+ format: function (value) {
353
+ return value.toLocaleString(undefined, {
354
+ // style: 'decimal',
355
+ // minimumIntegerDigits: 1,
356
+ // minimumSignificantDigits: 1,
357
+ // maximumSignificantDigits: 1,
358
+ useGrouping: true,
359
+ minimumFractionDigits: min,
360
+ maximumFractionDigits: max
361
+ });
362
+ }
363
+ };
364
+ }
365
406
- var iOS = /ipad|iphone|ipod/.test(ua) && !window.MSStream;
407
- var android = /android/.test(ua) && !window.MSStream;
408
- isSlowDeviceResult = (iOS === true || android === true);
409
- }
410
- catch (e) {
411
- isSlowDeviceResult = false;
366
+ return this.formatters_zero_based[key];
367
+ } else {
368
+ return {
369
+ format: function (value) {
370
+ return value.toLocaleString(undefined, {
371
+ // style: 'decimal',
372
+ // minimumIntegerDigits: 1,
373
+ // minimumSignificantDigits: 1,
374
+ // maximumSignificantDigits: 1,
375
+ useGrouping: true,
376
+ minimumFractionDigits: min,
377
+ maximumFractionDigits: max
378
+ });
379
+ }
380
+ };
381
}
382
+ },
383
414
- return isSlowDeviceResult;
415
- };
416
-
417
- // ----------------------------------------------------------------------------------------------------------------
418
- // the defaults for all charts
419
-
420
- // if the user does not specify any of these, the following will be used
421
-
422
- NETDATA.chartDefaults = {
423
- width: '100%', // the chart width - can be null
424
- height: '100%', // the chart height - can be null
425
- min_width: null, // the chart minimum width - can be null
426
- library: 'dygraph', // the graphing library to use
427
- method: 'average', // the grouping method
428
- before: 0, // panning
429
- after: -600, // panning
430
- pixels_per_point: 1, // the detail of the chart
431
- fill_luminance: 0.8 // luminance of colors in solid areas
432
- };
384
+ // the fallback
385
+ getFixed: function (min, max) {
386
+ let key = max;
387
+ if (min === max) {
388
+ if (typeof this.formatters_fixed[key] === 'undefined') {
389
+ this.formatters_fixed[key] = {
390
+ format: function (value) {
391
+ if (value === 0) {
392
+ return "0";
393
+ }
394
+ return value.toFixed(max);
395
+ }
396
+ };
397
+ }
398
434
- // ----------------------------------------------------------------------------------------------------------------
435
- // global options
399
+ return this.formatters_fixed[key];
400
+ } else if (min === 0) {
401
+ if (typeof this.formatters_zero_based[key] === 'undefined') {
402
+ this.formatters_zero_based[key] = {
403
+ format: function (value) {
404
+ if (value === 0) {
405
+ return "0";
406
+ }
407
+ return value.toFixed(max);
408
+ }
409
+ };
410
+ }
411
437
- NETDATA.options = {
438
- pauseCallback: null, // a callback when we are really paused
412
+ return this.formatters_zero_based[key];
413
+ } else {
414
+ return {
415
+ format: function (value) {
416
+ if (value === 0) {
417
+ return "0";
418
+ }
419
+ return value.toFixed(max);
420
+ }
421
+ };
422
+ }
423
+ },
424
440
- pause: false, // when enabled we don't auto-refresh the charts
425
+ testIntlNumberFormat: function () {
426
+ let value = 1.12345;
427
+ let e1 = "1.12", e2 = "1,12";
428
+ let s = "";
429
442
- targets: [], // an array of all the state objects that are
443
- // currently active (independently of their
444
- // viewport visibility)
430
+ try {
431
+ let x = new Intl.NumberFormat(undefined, {
432
+ useGrouping: true,
433
+ minimumFractionDigits: 2,
434
+ maximumFractionDigits: 2
435
+ });
436
446
- updated_dom: true, // when true, the DOM has been updated with
447
- // new elements we have to check.
437
+ s = x.format(value);
438
+ } catch (e) {
439
+ s = "";
440
+ }
441
449
- auto_refresher_fast_weight: 0, // this is the current time in ms, spent
450
- // rendering charts continuously.
451
- // used with .current.fast_render_timeframe
442
+ // console.log('NumberFormat: ', s);
443
+ return (s === e1 || s === e2);
444
+ },
445
453
- page_is_visible: true, // when true, this page is visible
446
+ testLocaleString: function () {
447
+ let value = 1.12345;
448
+ let e1 = "1.12", e2 = "1,12";
449
+ let s = "";
450
455
- auto_refresher_stop_until: 0, // timestamp in ms - used internally, to stop the
456
- // auto-refresher for some time (when a chart is
457
- // performing pan or zoom, we need to stop refreshing
458
- // all other charts, to have the maximum speed for
459
- // rendering the chart that is panned or zoomed).
460
- // Used with .current.global_pan_sync_time
451
+ try {
452
+ s = value.toLocaleString(undefined, {
453
+ useGrouping: true,
454
+ minimumFractionDigits: 2,
455
+ maximumFractionDigits: 2
456
+ });
457
+ } catch (e) {
458
+ s = "";
459
+ }
460
+
461
+ // console.log('localeString: ', s);
462
+ return (s === e1 || s === e2);
463
+ },
464
+
465
+ // on first run we decide which formatter to use
466
+ get: function (min, max) {
467
+ if (this.testIntlNumberFormat()) {
468
+ // console.log('numberformat');
469
+ this.get = this.getIntlNumberFormat;
470
+ } else if (this.testLocaleString()) {
471
+ // console.log('localestring');
472
+ this.get = this.getLocaleString;
473
+ } else {
474
+ // console.log('fixed');
475
+ this.get = this.getFixed;
476
+ }
477
+ return this.get(min, max);
478
+ }
479
+};
480
+// Error Handling
481
+
482
+NETDATA.errorCodes = {
483
+ 100: {message: "Cannot load chart library", alert: true},
484
+ 101: {message: "Cannot load jQuery", alert: true},
485
+ 402: {message: "Chart library not found", alert: false},
486
+ 403: {message: "Chart library not enabled/is failed", alert: false},
487
+ 404: {message: "Chart not found", alert: false},
488
+ 405: {message: "Cannot download charts index from server", alert: true},
489
+ 406: {message: "Invalid charts index downloaded from server", alert: true},
490
+ 407: {message: "Cannot HELLO netdata server", alert: false},
491
+ 408: {message: "Netdata servers sent invalid response to HELLO", alert: false},
492
+ 409: {message: "Cannot ACCESS netdata registry", alert: false},
493
+ 410: {message: "Netdata registry ACCESS failed", alert: false},
494
+ 411: {message: "Netdata registry server send invalid response to DELETE ", alert: false},
495
+ 412: {message: "Netdata registry DELETE failed", alert: false},
496
+ 413: {message: "Netdata registry server send invalid response to SWITCH ", alert: false},
497
+ 414: {message: "Netdata registry SWITCH failed", alert: false},
498
+ 415: {message: "Netdata alarms download failed", alert: false},
499
+ 416: {message: "Netdata alarms log download failed", alert: false},
500
+ 417: {message: "Netdata registry server send invalid response to SEARCH ", alert: false},
501
+ 418: {message: "Netdata registry SEARCH failed", alert: false}
502
+};
503
+
504
+NETDATA.errorLast = {
505
+ code: 0,
506
+ message: "",
507
+ datetime: 0
508
+};
509
+
510
+NETDATA.error = function (code, msg) {
511
+ NETDATA.errorLast.code = code;
512
+ NETDATA.errorLast.message = msg;
513
+ NETDATA.errorLast.datetime = Date.now();
514
+
515
+ console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
516
+
517
+ let ret = true;
518
+ if (typeof netdataErrorCallback === 'function') {
519
+ ret = netdataErrorCallback('system', code, msg);
520
+ }
521
462
- on_scroll_refresher_stop_until: 0, // timestamp in ms - used to stop evaluating
463
- // charts for some time, after a page scroll
522
+ if (ret && NETDATA.errorCodes[code].alert) {
523
+ alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
524
+ }
525
+};
526
465
- last_page_resize: Date.now(), // the timestamp of the last resize request
527
+NETDATA.errorReset = function () {
528
+ NETDATA.errorLast.code = 0;
529
+ NETDATA.errorLast.message = "You are doing fine!";
530
+ NETDATA.errorLast.datetime = 0;
531
+};
532
+// ------------------------------------------------------------------------
533
+// compatibility fixes
534
467
- last_page_scroll: 0, // the timestamp the last time the page was scrolled
535
+// fix IE issue with console
536
+if (!window.console) {
537
+ window.console = {
538
+ log: function () {
539
+ }
540
+ };
541
+}
542
469
- browser_timezone: 'unknown', // timezone detected by javascript
470
- server_timezone: 'unknown', // timezone reported by the server
543
+// if string.endsWith is not defined, define it
544
+if (typeof String.prototype.endsWith !== 'function') {
545
+ String.prototype.endsWith = function (s) {
546
+ if (s.length > this.length) {
547
+ return false;
548
+ }
549
+ return this.slice(-s.length) === s;
550
+ };
551
+}
552
472
- force_data_points: 0, // force the number of points to be returned for charts
473
- fake_chart_rendering: false, // when set to true, the dashboard will download data but will not render the charts
553
+// if string.startsWith is not defined, define it
554
+if (typeof String.prototype.startsWith !== 'function') {
555
+ String.prototype.startsWith = function (s) {
556
+ if (s.length > this.length) {
557
+ return false;
558
+ }
559
+ return this.slice(s.length) === s;
560
+ };
561
+}
562
+
563
+NETDATA.name2id = function (s) {
564
+ return s
565
+ .replace(/ /g, '_')
566
+ .replace(/\(/g, '_')
567
+ .replace(/\)/g, '_')
568
+ .replace(/\./g, '_')
569
+ .replace(/\//g, '_');
570
+};
571
+// ----------------------------------------------------------------------------------------------------------------
572
+// XSS checks
573
+
574
+NETDATA.xss = {
575
+ enabled: (typeof netdataCheckXSS === 'undefined') ? false : netdataCheckXSS,
576
+ enabled_for_data: (typeof netdataCheckXSS === 'undefined') ? false : netdataCheckXSS,
577
+
578
+ string: function (s) {
579
+ return s.toString()
580
+ .replace(/</g, '<')
581
+ .replace(/>/g, '>')
582
+ .replace(/"/g, '"')
583
+ .replace(/'/g, ''');
584
+ },
585
+
586
+ object: function (name, obj, ignore_regex) {
587
+ if (typeof ignore_regex !== 'undefined' && ignore_regex.test(name)) {
588
+ // console.log('XSS: ignoring "' + name + '"');
589
+ return obj;
590
+ }
591
475
- passive_events: null, // true if the browser supports passive events
592
+ switch (typeof(obj)) {
593
+ case 'string':
594
+ const ret = this.string(obj);
595
+ if (ret !== obj) {
596
+ console.log('XSS protection changed string ' + name + ' from "' + obj + '" to "' + ret + '"');
597
+ }
598
+ return ret;
599
477
- // the current profile
478
- // we may have many...
479
- current: {
480
- units: 'auto', // can be 'auto' or 'original'
481
- temperature: 'celsius', // can be 'celsius' or 'fahrenheit'
482
- seconds_as_time: true, // show seconds as DDd:HH:MM:SS ?
483
- timezone: 'default', // the timezone to use, or 'default'
484
- user_set_server_timezone: 'default', // as set by the user on the dashboard
600
+ case 'object':
601
+ if (obj === null) {
602
+ return obj;
603
+ }
604
486
- legend_toolbox: true, // show the legend toolbox on charts
487
- resize_charts: true, // show the resize handler on charts
605
+ if (Array.isArray(obj)) {
606
+ // console.log('checking array "' + name + '"');
607
489
- pixels_per_point: isSlowDevice()?5:1, // the minimum pixels per point for all charts
490
- // increase this to speed javascript up
491
- // each chart library has its own limit too
492
- // the max of this and the chart library is used
493
- // the final is calculated every time, so a change
494
- // here will have immediate effect on the next chart
495
- // update
608
+ let len = obj.length;
609
+ while (len--) {
610
+ obj[len] = this.object(name + '[' + len + ']', obj[len], ignore_regex);
611
+ }
612
+ } else {
613
+ // console.log('checking object "' + name + '"');
614
497
- idle_between_charts: 100, // ms - how much time to wait between chart updates
615
+ for (const i in obj) {
616
+ if (obj.hasOwnProperty(i) === false) {
617
+ continue;
618
+ }
619
+ if (this.string(i) !== i) {
620
+ console.log('XSS protection removed invalid object member "' + name + '.' + i + '"');
621
+ delete obj[i];
622
+ } else {
623
+ obj[i] = this.object(name + '.' + i, obj[i], ignore_regex);
624
+ }
625
+ }
626
+ }
627
+ return obj;
628
499
- fast_render_timeframe: 200, // ms - render continuously until this time of continuous
500
- // rendering has been reached
501
- // this setting is used to make it render e.g. 10
502
- // charts at once, sleep idle_between_charts time
503
- // and continue for another 10 charts.
629
+ default:
630
+ return obj;
631
+ }
632
+ },
633
505
- idle_between_loops: 500, // ms - if all charts have been updated, wait this
506
- // time before starting again.
634
+ checkOptional: function (name, obj, ignore_regex) {
635
+ if (this.enabled) {
636
+ //console.log('XSS: checking optional "' + name + '"...');
637
+ return this.object(name, obj, ignore_regex);
638
+ }
639
+ return obj;
640
+ },
641
508
- idle_parallel_loops: 100, // ms - the time between parallel refresher updates
642
+ checkAlways: function (name, obj, ignore_regex) {
643
+ //console.log('XSS: checking always "' + name + '"...');
644
+ return this.object(name, obj, ignore_regex);
645
+ },
646
510
- idle_lost_focus: 500, // ms - when the window does not have focus, check
511
- // if focus has been regained, every this time
647
+ checkData: function (name, obj, ignore_regex) {
648
+ if (this.enabled_for_data) {
649
+ //console.log('XSS: checking data "' + name + '"...');
650
+ return this.object(name, obj, ignore_regex);
651
+ }
652
+ return obj;
653
+ }
654
+};
655
+NETDATA.colorHex2Rgb = function (hex) {
656
+ // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
657
+ let shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
658
+ hex = hex.replace(shorthandRegex, function (m, r, g, b) {
659
+ return r + r + g + g + b + b;
660
+ });
661
513
- global_pan_sync_time: 300, // ms - when you pan or zoom a chart, the background
514
- // auto-refreshing of charts is paused for this amount
515
- // of time
662
+ let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
663
+ return result ? {
664
+ r: parseInt(result[1], 16),
665
+ g: parseInt(result[2], 16),
666
+ b: parseInt(result[3], 16)
667
+ } : null;
668
+};
669
+
670
+NETDATA.colorLuminance = function (hex, lum) {
671
+ // validate hex string
672
+ hex = String(hex).replace(/[^0-9a-f]/gi, '');
673
+ if (hex.length < 6) {
674
+ hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
675
+ }
676
517
- sync_selection_delay: 400, // ms - when you pan or zoom a chart, wait this amount
518
- // of time before setting up synchronized selections
519
- // on hover.
677
+ lum = lum || 0;
678
521
- sync_selection: true, // enable or disable selection sync
679
+ // convert to decimal and change luminosity
680
+ let rgb = "#";
681
+ for (let i = 0; i < 3; i++) {
682
+ let c = parseInt(hex.substr(i * 2, 2), 16);
683
+ c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
684
+ rgb += ("00" + c).substr(c.length);
685
+ }
686
523
- pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
687
+ return rgb;
688
+};
689
+NETDATA.unitsConversion = {
690
+ keys: {}, // keys for data-common-units
691
+ latest: {}, // latest selected units for data-common-units
692
+
693
+ globalReset: function () {
694
+ this.keys = {};
695
+ this.latest = {};
696
+ },
697
+
698
+ scalableUnits: {
699
+ 'packets/s': {
700
+ 'pps': 1,
701
+ 'Kpps': 1000,
702
+ 'Mpps': 1000000
703
+ },
704
+ 'pps': {
705
+ 'pps': 1,
706
+ 'Kpps': 1000,
707
+ 'Mpps': 1000000
708
+ },
709
+ 'kilobits/s': {
710
+ 'bits/s': 1 / 1000,
711
+ 'kilobits/s': 1,
712
+ 'megabits/s': 1000,
713
+ 'gigabits/s': 1000000,
714
+ 'terabits/s': 1000000000
715
+ },
716
+ 'kilobytes/s': {
717
+ 'bytes/s': 1 / 1024,
718
+ 'kilobytes/s': 1,
719
+ 'megabytes/s': 1024,
720
+ 'gigabytes/s': 1024 * 1024,
721
+ 'terabytes/s': 1024 * 1024 * 1024
722
+ },
723
+ 'KB/s': {
724
+ 'B/s': 1 / 1024,
725
+ 'KB/s': 1,
726
+ 'MB/s': 1024,
727
+ 'GB/s': 1024 * 1024,
728
+ 'TB/s': 1024 * 1024 * 1024
729
+ },
730
+ 'KB': {
731
+ 'B': 1 / 1024,
732
+ 'KB': 1,
733
+ 'MB': 1024,
734
+ 'GB': 1024 * 1024,
735
+ 'TB': 1024 * 1024 * 1024
736
+ },
737
+ 'MB': {
738
+ 'B': 1 / (1024 * 1024),
739
+ 'KB': 1 / 1024,
740
+ 'MB': 1,
741
+ 'GB': 1024,
742
+ 'TB': 1024 * 1024,
743
+ 'PB': 1024 * 1024 * 1024
744
+ },
745
+ 'GB': {
746
+ 'B': 1 / (1024 * 1024 * 1024),
747
+ 'KB': 1 / (1024 * 1024),
748
+ 'MB': 1 / 1024,
749
+ 'GB': 1,
750
+ 'TB': 1024,
751
+ 'PB': 1024 * 1024,
752
+ 'EB': 1024 * 1024 * 1024
753
+ }
754
+ /*
755
+ 'milliseconds': {
756
+ 'seconds': 1000
757
+ },
758
+ 'seconds': {
759
+ 'milliseconds': 0.001,
760
+ 'seconds': 1,
761
+ 'minutes': 60,
762
+ 'hours': 3600,
763
+ 'days': 86400
764
+ }
765
+ */
766
+ },
767
+
768
+ convertibleUnits: {
769
+ 'Celsius': {
770
+ 'Fahrenheit': {
771
+ check: function (max) {
772
+ void(max);
773
+ return NETDATA.options.current.temperature === 'fahrenheit';
774
+ },
775
+ convert: function (value) {
776
+ return value * 9 / 5 + 32;
777
+ }
778
+ }
779
+ },
780
+ 'celsius': {
781
+ 'fahrenheit': {
782
+ check: function (max) {
783
+ void(max);
784
+ return NETDATA.options.current.temperature === 'fahrenheit';
785
+ },
786
+ convert: function (value) {
787
+ return value * 9 / 5 + 32;
788
+ }
789
+ }
790
+ },
791
+ 'seconds': {
792
+ 'time': {
793
+ check: function (max) {
794
+ void(max);
795
+ return NETDATA.options.current.seconds_as_time;
796
+ },
797
+ convert: function (seconds) {
798
+ return NETDATA.unitsConversion.seconds2time(seconds);
799
+ }
800
+ }
801
+ },
802
+ 'milliseconds': {
803
+ 'milliseconds': {
804
+ check: function (max) {
805
+ return NETDATA.options.current.seconds_as_time && max < 1000;
806
+ },
807
+ convert: function (milliseconds) {
808
+ let tms = Math.round(milliseconds * 10);
809
+ milliseconds = Math.floor(tms / 10);
810
525
- sync_pan_and_zoom: true, // enable or disable pan and zoom sync
811
+ tms -= milliseconds * 10;
812
527
- pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
813
+ return (milliseconds).toString() + '.' + tms.toString();
814
+ }
815
+ },
816
+ 'seconds': {
817
+ check: function (max) {
818
+ return NETDATA.options.current.seconds_as_time && max >= 1000 && max < 60000;
819
+ },
820
+ convert: function (milliseconds) {
821
+ milliseconds = Math.round(milliseconds);
822
529
- update_only_visible: true, // enable or disable visibility management / used for printing
823
+ let seconds = Math.floor(milliseconds / 1000);
824
+ milliseconds -= seconds * 1000;
825
531
- parallel_refresher: (isSlowDevice() === false), // enable parallel refresh of charts
826
+ milliseconds = Math.round(milliseconds / 10);
827
533
- concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
828
+ return seconds.toString() + '.'
829
+ + NETDATA.zeropad(milliseconds);
830
+ }
831
+ },
832
+ 'M:SS.ms': {
833
+ check: function (max) {
834
+ return NETDATA.options.current.seconds_as_time && max >= 60000;
835
+ },
836
+ convert: function (milliseconds) {
837
+ milliseconds = Math.round(milliseconds);
838
535
- destroy_on_hide: (isSlowDevice() === true), // destroy charts when they are not visible
839
+ let minutes = Math.floor(milliseconds / 60000);
840
+ milliseconds -= minutes * 60000;
841
537
- show_help: netdataShowHelp, // when enabled the charts will show some help
538
- show_help_delay_show_ms: 500,
539
- show_help_delay_hide_ms: 0,
842
+ let seconds = Math.floor(milliseconds / 1000);
843
+ milliseconds -= seconds * 1000;
844
541
- eliminate_zero_dimensions: true, // do not show dimensions with just zeros
845
+ milliseconds = Math.round(milliseconds / 10);
846
543
- stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
544
- stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
847
+ return minutes.toString() + ':'
848
+ + NETDATA.zeropad(seconds) + '.'
849
+ + NETDATA.zeropad(milliseconds);
850
+ }
851
+ }
852
+ }
853
+ },
854
546
- double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
855
+ seconds2time: function (seconds) {
856
+ seconds = Math.abs(seconds);
857
548
- smooth_plot: (isSlowDevice() === false), // enable smooth plot, where possible
858
+ let days = Math.floor(seconds / 86400);
859
+ seconds -= days * 86400;
860
550
- color_fill_opacity_line: 1.0,
551
- color_fill_opacity_area: 0.2,
552
- color_fill_opacity_stacked: 0.8,
861
+ let hours = Math.floor(seconds / 3600);
862
+ seconds -= hours * 3600;
863
554
- pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
555
- pan_and_zoom_factor_multiplier_control: 2.0,
556
- pan_and_zoom_factor_multiplier_shift: 3.0,
557
- pan_and_zoom_factor_multiplier_alt: 4.0,
864
+ let minutes = Math.floor(seconds / 60);
865
+ seconds -= minutes * 60;
866
559
- abort_ajax_on_scroll: false, // kill pending ajax page scroll
560
- async_on_scroll: false, // sync/async onscroll handler
561
- onscroll_worker_duration_threshold: 30, // time in ms, for async scroll handler
867
+ seconds = Math.round(seconds);
868
563
- retries_on_data_failures: 3, // how many retries to make if we can't fetch chart data from the server
869
+ let ms_txt = '';
870
+ /*
871
+ let ms = seconds - Math.floor(seconds);
872
+ seconds -= ms;
873
+ ms = Math.round(ms * 1000);
874
565
- setOptionCallback: function() { }
566
- },
875
+ if (ms > 1) {
876
+ if (ms < 10)
877
+ ms_txt = '.00' + ms.toString();
878
+ else if (ms < 100)
879
+ ms_txt = '.0' + ms.toString();
880
+ else
881
+ ms_txt = '.' + ms.toString();
882
+ }
883
+ */
884
+
885
+ return ((days > 0) ? days.toString() + 'd:' : '').toString()
886
+ + NETDATA.zeropad(hours) + ':'
887
+ + NETDATA.zeropad(minutes) + ':'
888
+ + NETDATA.zeropad(seconds)
889
+ + ms_txt;
890
+ },
891
+
892
+ // get a function that converts the units
893
+ // + every time units are switched call the callback
894
+ get: function (uuid, min, max, units, desired_units, common_units_name, switch_units_callback) {
895
+ // validate the parameters
896
+ if (typeof units === 'undefined') {
897
+ units = 'undefined';
898
+ }
899
+
900
+ // check if we support units conversion
901
+ if (typeof this.scalableUnits[units] === 'undefined' && typeof this.convertibleUnits[units] === 'undefined') {
902
+ // we can't convert these units
903
+ //console.log('DEBUG: ' + uuid.toString() + ' can\'t convert units: ' + units.toString());
904
+ return function (value) {
905
+ return value;
906
+ };
907
+ }
908
568
- debug: {
569
- show_boxes: false,
570
- main_loop: false,
571
- focus: false,
572
- visibility: false,
573
- chart_data_url: false,
574
- chart_errors: false, // remember to set it to false before merging
575
- chart_timing: false,
576
- chart_calls: false,
577
- libraries: false,
578
- dygraph: false,
579
- globalSelectionSync:false,
580
- globalPanAndZoom: false
909
+ // check if the caller wants the original units
910
+ if (typeof desired_units === 'undefined' || desired_units === null || desired_units === 'original' || desired_units === units) {
911
+ //console.log('DEBUG: ' + uuid.toString() + ' original units wanted');
912
+ switch_units_callback(units);
913
+ return function (value) {
914
+ return value;
915
+ };
916
}
582
- };
917
584
- NETDATA.statistics = {
585
- refreshes_total: 0,
586
- refreshes_active: 0,
587
- refreshes_active_max: 0
588
- };
918
+ // now we know we can convert the units
919
+ // and the caller wants some kind of conversion
920
921
+ let tunits = null;
922
+ let tdivider = 0;
923
591
- // ----------------------------------------------------------------------------------------------------------------
924
+ if (typeof this.scalableUnits[units] !== 'undefined') {
925
+ // units that can be scaled
926
+ // we decide a divider
927
593
- NETDATA.timeout = {
594
- // by default, these are just wrappers to setTimeout() / clearTimeout()
928
+ // console.log('NETDATA.unitsConversion.get(' + units.toString() + ', ' + desired_units.toString() + ', function()) decide divider with min = ' + min.toString() + ', max = ' + max.toString());
929
596
- step: function(callback) {
597
- return window.setTimeout(callback, 1000 / 60);
598
- },
930
+ if (desired_units === 'auto') {
931
+ // the caller wants to auto-scale the units
932
600
- set: function(callback, delay) {
601
- return window.setTimeout(callback, delay);
602
- },
933
+ // find the absolute maximum value that is rendered on the chart
934
+ // based on this we decide the scale
935
+ min = Math.abs(min);
936
+ max = Math.abs(max);
937
+ if (min > max) {
938
+ max = min;
939
+ }
940
604
- clear: function(id) {
605
- return window.clearTimeout(id);
606
- },
941
+ // find the smallest scale that provides integers
942
+ // for (x in this.scalableUnits[units]) {
943
+ // if (this.scalableUnits[units].hasOwnProperty(x)) {
944
+ // let m = this.scalableUnits[units][x];
945
+ // if (m <= max && m > tdivider) {
946
+ // tunits = x;
947
+ // tdivider = m;
948
+ // }
949
+ // }
950
+ // }
951
+ const sunit = this.scalableUnits[units];
952
+ for (const x of Object.keys(sunit)) {
953
+ let m = sunit[x];
954
+ if (m <= max && m > tdivider) {
955
+ tunits = x;
956
+ tdivider = m;
957
+ }
958
+ }
959
608
- init: function() {
609
- var custom = true;
960
+ if (tunits === null || tdivider <= 0) {
961
+ // we couldn't find one
962
+ //console.log('DEBUG: ' + uuid.toString() + ' cannot find an auto-scaling candidate for units: ' + units.toString() + ' (max: ' + max.toString() + ')');
963
+ switch_units_callback(units);
964
+ return function (value) {
965
+ return value;
966
+ };
967
+ }
968
611
- if(window.requestAnimationFrame) {
612
- this.step = function(callback) {
613
- return window.requestAnimationFrame(callback);
614
- };
969
+ if (typeof common_units_name === 'string' && typeof uuid === 'string') {
970
+ // the caller wants several charts to have the same units
971
+ // data-common-units
972
616
- this.clear = function(handle) {
617
- return window.cancelAnimationFrame(handle.value);
618
- };
619
- }
620
- else if(window.webkitRequestAnimationFrame) {
621
- this.step = function(callback) {
622
- return window.webkitRequestAnimationFrame(callback);
623
- };
973
+ let common_units_key = common_units_name + '-' + units;
974
625
- if(window.webkitCancelAnimationFrame) {
626
- this.clear = function (handle) {
627
- return window.webkitCancelAnimationFrame(handle.value);
628
- };
629
- }
630
- else if(window.webkitCancelRequestAnimationFrame) {
631
- this.clear = function (handle) {
632
- return window.webkitCancelRequestAnimationFrame(handle.value);
975
+ // add our divider into the list of keys
976
+ let t = this.keys[common_units_key];
977
+ if (typeof t === 'undefined') {
978
+ this.keys[common_units_key] = {};
979
+ t = this.keys[common_units_key];
980
+ }
981
+ t[uuid] = {
982
+ units: tunits,
983
+ divider: tdivider
984
};
634
- }
635
- }
636
- else if(window.mozRequestAnimationFrame) {
637
- this.step = function(callback) {
638
- return window.mozRequestAnimationFrame(callback);
639
- };
985
641
- this.clear = function(handle) {
642
- return window.mozCancelRequestAnimationFrame(handle.value);
643
- };
644
- }
645
- else if(window.oRequestAnimationFrame) {
646
- this.step = function(callback) {
647
- return window.oRequestAnimationFrame(callback);
648
- };
649
-
650
- this.clear = function(handle) {
651
- return window.oCancelRequestAnimationFrame(handle.value);
652
- };
653
- }
654
- else if(window.msRequestAnimationFrame) {
655
- this.step = function(callback) {
656
- return window.msRequestAnimationFrame(callback);
657
- };
658
-
659
- this.clear = function(handle) {
660
- return window.msCancelRequestAnimationFrame(handle.value);
661
- };
662
- }
663
- else
664
- custom = false;
665
-
666
-
667
- if(custom === true) {
668
- // we have installed custom .step() / .clear() functions
669
- // overwrite the .set() too
670
-
671
- this.set = function(callback, delay) {
672
- var that = this;
986
+ // find the max divider of all charts
987
+ let common_units = t[uuid];
988
+ for (const x in t) {
989
+ if (t.hasOwnProperty(x) && t[x].divider > common_units.divider) {
990
+ common_units = t[x];
991
+ }
992
+ }
993
674
- var start = Date.now(),
675
- handle = new Object();
994
+ // save our common_max to the latest keys
995
+ let latest = this.latest[common_units_key];
996
+ if (typeof latest === 'undefined') {
997
+ this.latest[common_units_key] = {};
998
+ latest = this.latest[common_units_key];
999
+ }
1000
+ latest.units = common_units.units;
1001
+ latest.divider = common_units.divider;
1002
+
1003
+ tunits = latest.units;
1004
+ tdivider = latest.divider;
1005
+
1006
+ //console.log('DEBUG: ' + uuid.toString() + ' converted units: ' + units.toString() + ' to units: ' + tunits.toString() + ' with divider ' + tdivider.toString() + ', common-units=' + common_units_name.toString() + ((t[uuid].divider !== tdivider)?' USED COMMON, mine was ' + t[uuid].units:' set common').toString());
1007
+
1008
+ // apply it to this chart
1009
+ switch_units_callback(tunits);
1010
+ return function (value) {
1011
+ if (tdivider !== latest.divider) {
1012
+ // another chart switched our common units
1013
+ // we should switch them too
1014
+ //console.log('DEBUG: ' + uuid + ' switching units due to a common-units change, from ' + tunits.toString() + ' to ' + latest.units.toString());
1015
+ tunits = latest.units;
1016
+ tdivider = latest.divider;
1017
+ switch_units_callback(tunits);
1018
+ }
1019
677
- function loop() {
678
- var current = Date.now(),
679
- delta = current - start;
1020
+ return value / tdivider;
1021
+ };
1022
+ } else {
1023
+ // the caller did not give data-common-units
1024
+ // this chart auto-scales independently of all others
1025
+ //console.log('DEBUG: ' + uuid.toString() + ' converted units: ' + units.toString() + ' to units: ' + tunits.toString() + ' with divider ' + tdivider.toString() + ', autonomously');
1026
+
1027
+ switch_units_callback(tunits);
1028
+ return function (value) {
1029
+ return value / tdivider;
1030
+ };
1031
+ }
1032
+ } else {
1033
+ // the caller wants specific units
1034
681
- if(delta >= delay) {
682
- callback.call();
683
- }
684
- else {
685
- handle.value = that.step(loop);
1035
+ if (typeof this.scalableUnits[units][desired_units] !== 'undefined') {
1036
+ // all good, set the new units
1037
+ tdivider = this.scalableUnits[units][desired_units];
1038
+ // console.log('DEBUG: ' + uuid.toString() + ' converted units: ' + units.toString() + ' to units: ' + desired_units.toString() + ' with divider ' + tdivider.toString() + ', by reference');
1039
+ switch_units_callback(desired_units);
1040
+ return function (value) {
1041
+ return value / tdivider;
1042
+ };
1043
+ } else {
1044
+ // oops! switch back to original units
1045
+ console.log('Units conversion from ' + units.toString() + ' to ' + desired_units.toString() + ' is not supported.');
1046
+ switch_units_callback(units);
1047
+ return function (value) {
1048
+ return value;
1049
+ };
1050
+ }
1051
+ }
1052
+ } else if (typeof this.convertibleUnits[units] !== 'undefined') {
1053
+ // units that can be converted
1054
+ if (desired_units === 'auto') {
1055
+ for (const x in this.convertibleUnits[units]) {
1056
+ if (this.convertibleUnits[units].hasOwnProperty(x)) {
1057
+ if (this.convertibleUnits[units][x].check(max)) {
1058
+ //console.log('DEBUG: ' + uuid.toString() + ' converting ' + units.toString() + ' to: ' + x.toString());
1059
+ switch_units_callback(x);
1060
+ return this.convertibleUnits[units][x].convert;
1061
}
1062
}
1063
+ }
1064
689
- handle.value = that.step(loop);
690
- return handle;
1065
+ // none checked ok
1066
+ //console.log('DEBUG: ' + uuid.toString() + ' no conversion available for ' + units.toString() + ' to: ' + desired_units.toString());
1067
+ switch_units_callback(units);
1068
+ return function (value) {
1069
+ return value;
1070
+ };
1071
+ } else if (typeof this.convertibleUnits[units][desired_units] !== 'undefined') {
1072
+ switch_units_callback(desired_units);
1073
+ return this.convertibleUnits[units][desired_units].convert;
1074
+ } else {
1075
+ console.log('Units conversion from ' + units.toString() + ' to ' + desired_units.toString() + ' is not supported.');
1076
+ switch_units_callback(units);
1077
+ return function (value) {
1078
+ return value;
1079
};
1080
}
1081
+ } else {
1082
+ // hm... did we forget to implement the new type?
1083
+ console.log(`Unmatched unit conversion method for units ${units.toString()}`);
1084
+ switch_units_callback(units);
1085
+ return function (value) {
1086
+ return value;
1087
+ };
1088
}
694
- };
695
-
696
- NETDATA.timeout.init();
697
-
698
-
699
- // ----------------------------------------------------------------------------------------------------------------
700
- // local storage options
701
-
702
- NETDATA.localStorage = {
703
- default: {},
704
- current: {},
705
- callback: {} // only used for resetting back to defaults
706
- };
707
-
708
- NETDATA.localStorageTested = -1;
709
- NETDATA.localStorageTest = function() {
710
- if(NETDATA.localStorageTested !== -1)
711
- return NETDATA.localStorageTested;
712
-
713
- if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
714
- var test = 'test';
715
- try {
716
- localStorage.setItem(test, test);
717
- localStorage.removeItem(test);
718
- NETDATA.localStorageTested = true;
719
- }
720
- catch (e) {
721
- NETDATA.localStorageTested = false;
722
- }
1089
+ }
1090
+};
1091
+
1092
+NETDATA.icons = {
1093
+ left: '<i class="fas fa-backward"></i>',
1094
+ reset: '<i class="fas fa-play"></i>',
1095
+ right: '<i class="fas fa-forward"></i>',
1096
+ zoomIn: '<i class="fas fa-plus"></i>',
1097
+ zoomOut: '<i class="fas fa-minus"></i>',
1098
+ resize: '<i class="fas fa-sort"></i>',
1099
+ lineChart: '<i class="fas fa-chart-line"></i>',
1100
+ areaChart: '<i class="fas fa-chart-area"></i>',
1101
+ noChart: '<i class="fas fa-chart-area"></i>',
1102
+ loading: '<i class="fas fa-sync-alt"></i>',
1103
+ noData: '<i class="fas fa-exclamation-triangle"></i>'
1104
+};
1105
+
1106
+if (typeof netdataIcons === 'object') {
1107
+ // for (let icon in NETDATA.icons) {
1108
+ // if (NETDATA.icons.hasOwnProperty(icon) && typeof(netdataIcons[icon]) === 'string')
1109
+ // NETDATA.icons[icon] = netdataIcons[icon];
1110
+ // }
1111
+ for (const icon of Object.keys(NETDATA.icons)) {
1112
+ if (typeof(netdataIcons[icon]) === 'string') {
1113
+ NETDATA.icons[icon] = netdataIcons[icon]
1114
}
724
- else
725
- NETDATA.localStorageTested = false;
726
-
727
- return NETDATA.localStorageTested;
728
- };
729
-
730
- NETDATA.localStorageGet = function(key, def, callback) {
731
- var ret = def;
1115
+ }
1116
+}
1117
733
- if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
734
- NETDATA.localStorage.default[key.toString()] = def;
735
- NETDATA.localStorage.callback[key.toString()] = callback;
736
- }
1118
+if (typeof netdataSnapshotData === 'undefined') {
1119
+ netdataSnapshotData = null;
1120
+}
1121
738
- if(NETDATA.localStorageTest() === true) {
739
- try {
740
- // console.log('localStorage: loading "' + key.toString() + '"');
741
- ret = localStorage.getItem(key.toString());
742
- // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
743
- if(ret === null || ret === 'undefined') {
744
- // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
745
- localStorage.setItem(key.toString(), JSON.stringify(def));
746
- ret = def;
747
- }
748
- else {
749
- // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
750
- ret = JSON.parse(ret);
751
- // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
752
- }
753
- }
754
- catch(error) {
755
- console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
756
- ret = def;
757
- }
758
- }
1122
+if (typeof netdataShowHelp === 'undefined') {
1123
+ netdataShowHelp = true;
1124
+}
1125
760
- if(typeof ret === 'undefined' || ret === 'undefined') {
761
- console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
762
- ret = def;
763
- }
1126
+if (typeof netdataShowAlarms === 'undefined') {
1127
+ netdataShowAlarms = false;
1128
+}
1129
765
- NETDATA.localStorage.current[key.toString()] = ret;
766
- return ret;
767
- };
1130
+if (typeof netdataRegistryAfterMs !== 'number' || netdataRegistryAfterMs < 0) {
1131
+ netdataRegistryAfterMs = 1500;
1132
+}
1133
769
- NETDATA.localStorageSet = function(key, value, callback) {
770
- if(typeof value === 'undefined' || value === 'undefined') {
771
- console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
772
- }
1134
+if (typeof netdataRegistry === 'undefined') {
1135
+ // backward compatibility
1136
+ netdataRegistry = (typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry === false);
1137
+}
1138
774
- if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
775
- NETDATA.localStorage.default[key.toString()] = value;
776
- NETDATA.localStorage.current[key.toString()] = value;
777
- NETDATA.localStorage.callback[key.toString()] = callback;
778
- }
1139
+if (netdataRegistry === false && typeof netdataRegistryCallback === 'function') {
1140
+ netdataRegistry = true;
1141
+}
1142
780
- if(NETDATA.localStorageTest() === true) {
781
- // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
782
- try {
783
- localStorage.setItem(key.toString(), JSON.stringify(value));
784
- }
785
- catch(e) {
786
- console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
787
- }
788
- }
1143
+// ----------------------------------------------------------------------------------------------------------------
1144
+// the defaults for all charts
1145
790
- NETDATA.localStorage.current[key.toString()] = value;
791
- return value;
792
- };
1146
+// if the user does not specify any of these, the following will be used
1147
794
- NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
795
- var keys = Object.keys(obj);
796
- var len = keys.length;
797
- while(len--) {
798
- var i = keys[len];
1148
+NETDATA.chartDefaults = {
1149
+ width: '100%', // the chart width - can be null
1150
+ height: '100%', // the chart height - can be null
1151
+ min_width: null, // the chart minimum width - can be null
1152
+ library: 'dygraph', // the graphing library to use
1153
+ method: 'average', // the grouping method
1154
+ before: 0, // panning
1155
+ after: -600, // panning
1156
+ pixels_per_point: 1, // the detail of the chart
1157
+ fill_luminance: 0.8 // luminance of colors in solid areas
1158
+};
1159
800
- if(typeof obj[i] === 'object') {
801
- //console.log('object ' + prefix + '.' + i.toString());
802
- NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
803
- continue;
804
- }
1160
+// ----------------------------------------------------------------------------------------------------------------
1161
+// global options
1162
806
- obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
807
- }
808
- };
1163
+NETDATA.options = {
1164
+ pauseCallback: null, // a callback when we are really paused
1165
810
- NETDATA.setOption = function(key, value) {
811
- if(key.toString() === 'setOptionCallback') {
812
- if(typeof NETDATA.options.current.setOptionCallback === 'function') {
813
- NETDATA.options.current[key.toString()] = value;
814
- NETDATA.options.current.setOptionCallback();
815
- }
816
- }
817
- else if(NETDATA.options.current[key.toString()] !== value) {
818
- var name = 'options.' + key.toString();
1166
+ pause: false, // when enabled we don't auto-refresh the charts
1167
820
- if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
821
- console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
1168
+ targets: [], // an array of all the state objects that are
1169
+ // currently active (independently of their
1170
+ // viewport visibility)
1171
823
- //console.log(NETDATA.localStorage);
824
- //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
825
- //console.log(NETDATA.options);
826
- NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
1172
+ updated_dom: true, // when true, the DOM has been updated with
1173
+ // new elements we have to check.
1174
828
- if(typeof NETDATA.options.current.setOptionCallback === 'function')
829
- NETDATA.options.current.setOptionCallback();
830
- }
1175
+ auto_refresher_fast_weight: 0, // this is the current time in ms, spent
1176
+ // rendering charts continuously.
1177
+ // used with .current.fast_render_timeframe
1178
832
- return true;
833
- };
1179
+ page_is_visible: true, // when true, this page is visible
1180
835
- NETDATA.getOption = function(key) {
836
- return NETDATA.options.current[key.toString()];
837
- };
1181
+ auto_refresher_stop_until: 0, // timestamp in ms - used internally, to stop the
1182
+ // auto-refresher for some time (when a chart is
1183
+ // performing pan or zoom, we need to stop refreshing
1184
+ // all other charts, to have the maximum speed for
1185
+ // rendering the chart that is panned or zoomed).
1186
+ // Used with .current.global_pan_sync_time
1187
839
- // read settings from local storage
840
- NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
1188
+ on_scroll_refresher_stop_until: 0, // timestamp in ms - used to stop evaluating
1189
+ // charts for some time, after a page scroll
1190
842
- // always start with this option enabled.
843
- NETDATA.setOption('stop_updates_when_focus_is_lost', true);
1191
+ last_page_resize: Date.now(), // the timestamp of the last resize request
1192
845
- NETDATA.resetOptions = function() {
846
- var keys = Object.keys(NETDATA.localStorage.default);
847
- var len = keys.length;
848
- while(len--) {
849
- var i = keys[len];
850
- var a = i.split('.');
1193
+ last_page_scroll: 0, // the timestamp the last time the page was scrolled
1194
852
- if(a[0] === 'options') {
853
- if(a[1] === 'setOptionCallback') continue;
854
- if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
855
- if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
1195
+ browser_timezone: 'unknown', // timezone detected by javascript
1196
+ server_timezone: 'unknown', // timezone reported by the server
1197
857
- NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
858
- }
859
- else if(a[0] === 'chart_heights') {
860
- if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
861
- NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
862
- }
863
- }
864
- }
1198
+ force_data_points: 0, // force the number of points to be returned for charts
1199
+ fake_chart_rendering: false, // when set to true, the dashboard will download data but will not render the charts
1200
866
- NETDATA.dateTime.init(NETDATA.options.current.timezone);
867
- };
1201
+ passive_events: null, // true if the browser supports passive events
1202
869
- // ----------------------------------------------------------------------------------------------------------------
1203
+ // the current profile
1204
+ // we may have many...
1205
+ current: {
1206
+ units: 'auto', // can be 'auto' or 'original'
1207
+ temperature: 'celsius', // can be 'celsius' or 'fahrenheit'
1208
+ seconds_as_time: true, // show seconds as DDd:HH:MM:SS ?
1209
+ timezone: 'default', // the timezone to use, or 'default'
1210
+ user_set_server_timezone: 'default', // as set by the user on the dashboard
1211
871
- if(NETDATA.options.debug.main_loop === true)
872
- console.log('welcome to NETDATA');
1212
+ legend_toolbox: true, // show the legend toolbox on charts
1213
+ resize_charts: true, // show the resize handler on charts
1214
874
- NETDATA.onresizeCallback = null;
875
- NETDATA.onresize = function() {
876
- NETDATA.options.last_page_resize = Date.now();
877
- NETDATA.onscroll();
1215
+ pixels_per_point: isSlowDevice() ? 5 : 1, // the minimum pixels per point for all charts
1216
+ // increase this to speed javascript up
1217
+ // each chart library has its own limit too
1218
+ // the max of this and the chart library is used
1219
+ // the final is calculated every time, so a change
1220
+ // here will have immediate effect on the next chart
1221
+ // update
1222
879
- if(typeof NETDATA.onresizeCallback === 'function')
880
- NETDATA.onresizeCallback();
881
- };
1223
+ idle_between_charts: 100, // ms - how much time to wait between chart updates
1224
883
- NETDATA.abort_all_refreshes = function() {
884
- var targets = NETDATA.options.targets;
885
- var len = targets.length;
1225
+ fast_render_timeframe: 200, // ms - render continuously until this time of continuous
1226
+ // rendering has been reached
1227
+ // this setting is used to make it render e.g. 10
1228
+ // charts at once, sleep idle_between_charts time
1229
+ // and continue for another 10 charts.
1230
887
- while (len--) {
888
- if (targets[len].fetching_data === true) {
889
- if (typeof targets[len].xhr !== 'undefined') {
890
- targets[len].xhr.abort();
891
- targets[len].running = false;
892
- targets[len].fetching_data = false;
893
- }
894
- }
895
- }
896
- };
1231
+ idle_between_loops: 500, // ms - if all charts have been updated, wait this
1232
+ // time before starting again.
1233
898
- NETDATA.onscroll_start_delay = function() {
899
- NETDATA.options.last_page_scroll = Date.now();
1234
+ idle_parallel_loops: 100, // ms - the time between parallel refresher updates
1235
901
- NETDATA.options.on_scroll_refresher_stop_until =
902
- NETDATA.options.last_page_scroll
903
- + ((NETDATA.options.current.async_on_scroll === true) ? 1000 : 0);
904
- };
1236
+ idle_lost_focus: 500, // ms - when the window does not have focus, check
1237
+ // if focus has been regained, every this time
1238
906
- NETDATA.onscroll_end_delay = function() {
907
- NETDATA.options.on_scroll_refresher_stop_until =
908
- Date.now()
909
- + ((NETDATA.options.current.async_on_scroll === true) ? NETDATA.options.current.onscroll_worker_duration_threshold : 0);
910
- };
1239
+ global_pan_sync_time: 300, // ms - when you pan or zoom a chart, the background
1240
+ // auto-refreshing of charts is paused for this amount
1241
+ // of time
1242
912
- NETDATA.onscroll_updater_timeout_id = undefined;
913
- NETDATA.onscroll_updater = function() {
914
- NETDATA.globalSelectionSync.stop();
1243
+ sync_selection_delay: 400, // ms - when you pan or zoom a chart, wait this amount
1244
+ // of time before setting up synchronized selections
1245
+ // on hover.
1246
916
- if(NETDATA.options.abort_ajax_on_scroll === true)
917
- NETDATA.abort_all_refreshes();
1247
+ sync_selection: true, // enable or disable selection sync
1248
919
- // when the user scrolls he sees that we have
920
- // hidden all the not-visible charts
921
- // using this little function we try to switch
922
- // the charts back to visible quickly
1249
+ pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
1250
924
- if(NETDATA.intersectionObserver.enabled() === false) {
925
- if (NETDATA.options.current.parallel_refresher === false) {
926
- var targets = NETDATA.options.targets;
927
- var len = targets.length;
1251
+ sync_pan_and_zoom: true, // enable or disable pan and zoom sync
1252
929
- while (len--)
930
- if (targets[len].running === false)
931
- targets[len].isVisible();
932
- }
933
- }
1253
+ pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
1254
935
- NETDATA.onscroll_end_delay();
936
- };
1255
+ update_only_visible: true, // enable or disable visibility management / used for printing
1256
938
- NETDATA.scrollUp = false;
939
- NETDATA.scrollY = window.scrollY;
940
- NETDATA.onscroll = function() {
941
- //console.log('onscroll() begin');
1257
+ parallel_refresher: !isSlowDevice(), // enable parallel refresh of charts
1258
943
- NETDATA.onscroll_start_delay();
944
- NETDATA.chartRefresherReschedule();
1259
+ concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
1260
946
- NETDATA.scrollUp = (window.scrollY > NETDATA.scrollY);
947
- NETDATA.scrollY = window.scrollY;
1261
+ destroy_on_hide: isSlowDevice(), // destroy charts when they are not visible
1262
949
- if(NETDATA.onscroll_updater_timeout_id)
950
- NETDATA.timeout.clear(NETDATA.onscroll_updater_timeout_id);
1263
+ show_help: netdataShowHelp, // when enabled the charts will show some help
1264
+ show_help_delay_show_ms: 500,
1265
+ show_help_delay_hide_ms: 0,
1266
952
- NETDATA.onscroll_updater_timeout_id = NETDATA.timeout.set(NETDATA.onscroll_updater, 0);
953
- //console.log('onscroll() end');
954
- };
1267
+ eliminate_zero_dimensions: true, // do not show dimensions with just zeros
1268
956
- NETDATA.supportsPassiveEvents = function() {
957
- if(NETDATA.options.passive_events === null) {
958
- var supportsPassive = false;
959
- try {
960
- var opts = Object.defineProperty({}, 'passive', {
961
- get: function () {
962
- supportsPassive = true;
963
- }
964
- });
965
- window.addEventListener("test", null, opts);
966
- } catch (e) {
967
- console.log('browser does not support passive events');
968
- }
1269
+ stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
1270
+ stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
1271
970
- NETDATA.options.passive_events = supportsPassive;
971
- }
1272
+ double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
1273
973
- // console.log('passive ' + NETDATA.options.passive_events);
974
- return NETDATA.options.passive_events;
975
- };
1274
+ smooth_plot: !isSlowDevice(), // enable smooth plot, where possible
1275
977
- window.addEventListener('resize', NETDATA.onresize, NETDATA.supportsPassiveEvents() ? { passive: true } : false);
978
- window.addEventListener('scroll', NETDATA.onscroll, NETDATA.supportsPassiveEvents() ? { passive: true } : false);
979
- // window.onresize = NETDATA.onresize;
980
- // window.onscroll = NETDATA.onscroll;
1276
+ color_fill_opacity_line: 1.0,
1277
+ color_fill_opacity_area: 0.2,
1278
+ color_fill_opacity_stacked: 0.8,
1279
982
- // ----------------------------------------------------------------------------------------------------------------
983
- // Error Handling
984
-
985
- NETDATA.errorCodes = {
986
- 100: { message: "Cannot load chart library", alert: true },
987
- 101: { message: "Cannot load jQuery", alert: true },
988
- 402: { message: "Chart library not found", alert: false },
989
- 403: { message: "Chart library not enabled/is failed", alert: false },
990
- 404: { message: "Chart not found", alert: false },
991
- 405: { message: "Cannot download charts index from server", alert: true },
992
- 406: { message: "Invalid charts index downloaded from server", alert: true },
993
- 407: { message: "Cannot HELLO netdata server", alert: false },
994
- 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
995
- 409: { message: "Cannot ACCESS netdata registry", alert: false },
996
- 410: { message: "Netdata registry ACCESS failed", alert: false },
997
- 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
998
- 412: { message: "Netdata registry DELETE failed", alert: false },
999
- 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
1000
- 414: { message: "Netdata registry SWITCH failed", alert: false },
1001
- 415: { message: "Netdata alarms download failed", alert: false },
1002
- 416: { message: "Netdata alarms log download failed", alert: false },
1003
- 417: { message: "Netdata registry server send invalid response to SEARCH ", alert: false },
1004
- 418: { message: "Netdata registry SEARCH failed", alert: false }
1005
- };
1006
- NETDATA.errorLast = {
1007
- code: 0,
1008
- message: "",
1009
- datetime: 0
1010
- };
1280
+ pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
1281
+ pan_and_zoom_factor_multiplier_control: 2.0,
1282
+ pan_and_zoom_factor_multiplier_shift: 3.0,
1283
+ pan_and_zoom_factor_multiplier_alt: 4.0,
1284
1012
- NETDATA.error = function(code, msg) {
1013
- NETDATA.errorLast.code = code;
1014
- NETDATA.errorLast.message = msg;
1015
- NETDATA.errorLast.datetime = Date.now();
1285
+ abort_ajax_on_scroll: false, // kill pending ajax page scroll
1286
+ async_on_scroll: false, // sync/async onscroll handler
1287
+ onscroll_worker_duration_threshold: 30, // time in ms, for async scroll handler
1288
1017
- console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
1289
+ retries_on_data_failures: 3, // how many retries to make if we can't fetch chart data from the server
1290
1019
- var ret = true;
1020
- if(typeof netdataErrorCallback === 'function') {
1021
- ret = netdataErrorCallback('system', code, msg);
1291
+ setOptionCallback: function () {
1292
}
1293
+ },
1294
1024
- if(ret && NETDATA.errorCodes[code].alert)
1025
- alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
1026
- };
1027
-
1028
- NETDATA.errorReset = function() {
1029
- NETDATA.errorLast.code = 0;
1030
- NETDATA.errorLast.message = "You are doing fine!";
1031
- NETDATA.errorLast.datetime = 0;
1032
- };
1033
-
1034
- // ----------------------------------------------------------------------------------------------------------------
1035
- // fast numbers formatting
1036
-
1037
- NETDATA.fastNumberFormat = {
1038
- formatters_fixed: [],
1039
- formatters_zero_based: [],
1040
-
1041
- // this is the fastest and the preferred
1042
- getIntlNumberFormat: function(min, max) {
1043
- var key = max;
1044
- if(min === max) {
1045
- if(typeof this.formatters_fixed[key] === 'undefined')
1046
- this.formatters_fixed[key] = new Intl.NumberFormat(undefined, {
1047
- // style: 'decimal',
1048
- // minimumIntegerDigits: 1,
1049
- // minimumSignificantDigits: 1,
1050
- // maximumSignificantDigits: 1,
1051
- useGrouping: true,
1052
- minimumFractionDigits: min,
1053
- maximumFractionDigits: max
1054
- });
1055
-
1056
- return this.formatters_fixed[key];
1057
- }
1058
- else if(min === 0) {
1059
- if(typeof this.formatters_zero_based[key] === 'undefined')
1060
- this.formatters_zero_based[key] = new Intl.NumberFormat(undefined, {
1061
- // style: 'decimal',
1062
- // minimumIntegerDigits: 1,
1063
- // minimumSignificantDigits: 1,
1064
- // maximumSignificantDigits: 1,
1065
- useGrouping: true,
1066
- minimumFractionDigits: min,
1067
- maximumFractionDigits: max
1068
- });
1069
-
1070
- return this.formatters_zero_based[key];
1071
- }
1072
- else {
1073
- // this is never used
1074
- // it is added just for completeness
1075
- return new Intl.NumberFormat(undefined, {
1076
- // style: 'decimal',
1077
- // minimumIntegerDigits: 1,
1078
- // minimumSignificantDigits: 1,
1079
- // maximumSignificantDigits: 1,
1080
- useGrouping: true,
1081
- minimumFractionDigits: min,
1082
- maximumFractionDigits: max
1083
- });
1084
- }
1085
- },
1295
+ debug: {
1296
+ show_boxes: false,
1297
+ main_loop: false,
1298
+ focus: false,
1299
+ visibility: false,
1300
+ chart_data_url: false,
1301
+ chart_errors: true, // remember to set it to false before merging
1302
+ chart_timing: false,
1303
+ chart_calls: false,
1304
+ libraries: false,
1305
+ dygraph: false,
1306
+ globalSelectionSync: false,
1307
+ globalPanAndZoom: false
1308
+ }
1309
+};
1310
1087
- // this respects locale
1088
- getLocaleString: function(min, max) {
1089
- var key = max;
1090
- if(min === max) {
1091
- if(typeof this.formatters_fixed[key] === 'undefined')
1092
- this.formatters_fixed[key] = {
1093
- format: function (value) {
1094
- return value.toLocaleString(undefined, {
1095
- // style: 'decimal',
1096
- // minimumIntegerDigits: 1,
1097
- // minimumSignificantDigits: 1,
1098
- // maximumSignificantDigits: 1,
1099
- useGrouping: true,
1100
- minimumFractionDigits: min,
1101
- maximumFractionDigits: max
1102
- });
1103
- }
1104
- };
1311
+NETDATA.statistics = {
1312
+ refreshes_total: 0,
1313
+ refreshes_active: 0,
1314
+ refreshes_active_max: 0
1315
+};
1316
1106
- return this.formatters_fixed[key];
1107
- }
1108
- else if(min === 0) {
1109
- if(typeof this.formatters_zero_based[key] === 'undefined')
1110
- this.formatters_zero_based[key] = {
1111
- format: function (value) {
1112
- return value.toLocaleString(undefined, {
1113
- // style: 'decimal',
1114
- // minimumIntegerDigits: 1,
1115
- // minimumSignificantDigits: 1,
1116
- // maximumSignificantDigits: 1,
1117
- useGrouping: true,
1118
- minimumFractionDigits: min,
1119
- maximumFractionDigits: max
1120
- });
1121
- }
1122
- };
1317
+// local storage options
1318
1124
- return this.formatters_zero_based[key];
1125
- }
1126
- else {
1127
- return {
1128
- format: function (value) {
1129
- return value.toLocaleString(undefined, {
1130
- // style: 'decimal',
1131
- // minimumIntegerDigits: 1,
1132
- // minimumSignificantDigits: 1,
1133
- // maximumSignificantDigits: 1,
1134
- useGrouping: true,
1135
- minimumFractionDigits: min,
1136
- maximumFractionDigits: max
1137
- });
1138
- }
1139
- };
1140
- }
1141
- },
1319
+NETDATA.localStorage = {
1320
+ default: {},
1321
+ current: {},
1322
+ callback: {} // only used for resetting back to defaults
1323
+};
1324
1143
- // the fallback
1144
- getFixed: function(min, max) {
1145
- var key = max;
1146
- if(min === max) {
1147
- if(typeof this.formatters_fixed[key] === 'undefined')
1148
- this.formatters_fixed[key] = {
1149
- format: function (value) {
1150
- if(value === 0) return "0";
1151
- return value.toFixed(max);
1152
- }
1153
- };
1325
+NETDATA.localStorageTested = -1;
1326
+NETDATA.localStorageTest = function () {
1327
+ if (NETDATA.localStorageTested !== -1) {
1328
+ return NETDATA.localStorageTested;
1329
+ }
1330
1155
- return this.formatters_fixed[key];
1156
- }
1157
- else if(min === 0) {
1158
- if(typeof this.formatters_zero_based[key] === 'undefined')
1159
- this.formatters_zero_based[key] = {
1160
- format: function (value) {
1161
- if(value === 0) return "0";
1162
- return value.toFixed(max);
1163
- }
1164
- };
1331
+ if (typeof Storage !== "undefined" && typeof localStorage === 'object') {
1332
+ let test = 'test';
1333
+ try {
1334
+ localStorage.setItem(test, test);
1335
+ localStorage.removeItem(test);
1336
+ NETDATA.localStorageTested = true;
1337
+ } catch (e) {
1338
+ NETDATA.localStorageTested = false;
1339
+ }
1340
+ } else {
1341
+ NETDATA.localStorageTested = false;
1342
+ }
1343
1166
- return this.formatters_zero_based[key];
1167
- }
1168
- else {
1169
- return {
1170
- format: function (value) {
1171
- if(value === 0) return "0";
1172
- return value.toFixed(max);
1173
- }
1174
- };
1175
- }
1176
- },
1344
+ return NETDATA.localStorageTested;
1345
+};
1346
1178
- testIntlNumberFormat: function() {
1179
- var value = 1.12345;
1180
- var e1 = "1.12", e2 = "1,12";
1181
- var s = "";
1347
+NETDATA.localStorageGet = function (key, def, callback) {
1348
+ let ret = def;
1349
1183
- try {
1184
- var x = new Intl.NumberFormat(undefined, {
1185
- useGrouping: true,
1186
- minimumFractionDigits: 2,
1187
- maximumFractionDigits: 2
1188
- });
1350
+ if (typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
1351
+ NETDATA.localStorage.default[key.toString()] = def;
1352
+ NETDATA.localStorage.callback[key.toString()] = callback;
1353
+ }
1354
1190
- s = x.format(value);
1191
- }
1192
- catch(e) {
1193
- s = "";
1355
+ if (NETDATA.localStorageTest()) {
1356
+ try {
1357
+ // console.log('localStorage: loading "' + key.toString() + '"');
1358
+ ret = localStorage.getItem(key.toString());
1359
+ // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
1360
+ if (ret === null || ret === 'undefined') {
1361
+ // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
1362
+ localStorage.setItem(key.toString(), JSON.stringify(def));
1363
+ ret = def;
1364
+ } else {
1365
+ // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
1366
+ ret = JSON.parse(ret);
1367
+ // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
1368
}
1369
+ } catch (error) {
1370
+ console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
1371
+ ret = def;
1372
+ }
1373
+ }
1374
1196
- // console.log('NumberFormat: ', s);
1197
- return (s === e1 || s === e2);
1198
- },
1375
+ if (typeof ret === 'undefined' || ret === 'undefined') {
1376
+ console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
1377
+ ret = def;
1378
+ }
1379
1200
- testLocaleString: function() {
1201
- var value = 1.12345;
1202
- var e1 = "1.12", e2 = "1,12";
1203
- var s = "";
1380
+ NETDATA.localStorage.current[key.toString()] = ret;
1381
+ return ret;
1382
+};
1383
1205
- try {
1206
- s = value.toLocaleString(undefined, {
1207
- useGrouping: true,
1208
- minimumFractionDigits: 2,
1209
- maximumFractionDigits: 2
1210
- });
1211
- }
1212
- catch(e) {
1213
- s = "";
1214
- }
1384
+NETDATA.localStorageSet = function (key, value, callback) {
1385
+ if (typeof value === 'undefined' || value === 'undefined') {
1386
+ console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
1387
+ }
1388
1216
- // console.log('localeString: ', s);
1217
- return (s === e1 || s === e2);
1218
- },
1389
+ if (typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
1390
+ NETDATA.localStorage.default[key.toString()] = value;
1391
+ NETDATA.localStorage.current[key.toString()] = value;
1392
+ NETDATA.localStorage.callback[key.toString()] = callback;
1393
+ }
1394
1220
- // on first run we decide which formatter to use
1221
- get: function(min, max) {
1222
- if(this.testIntlNumberFormat()) {
1223
- // console.log('numberformat');
1224
- this.get = this.getIntlNumberFormat;
1225
- }
1226
- else if(this.testLocaleString()) {
1227
- // console.log('localestring');
1228
- this.get = this.getLocaleString;
1229
- }
1230
- else {
1231
- // console.log('fixed');
1232
- this.get = this.getFixed;
1233
- }
1234
- return this.get(min, max);
1395
+ if (NETDATA.localStorageTest()) {
1396
+ // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
1397
+ try {
1398
+ localStorage.setItem(key.toString(), JSON.stringify(value));
1399
+ } catch (e) {
1400
+ console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
1401
}
1236
- };
1237
-
1238
- // ----------------------------------------------------------------------------------------------------------------
1239
- // element data attributes
1240
-
1241
- NETDATA.dataAttribute = function(element, attribute, def) {
1242
- var key = 'data-' + attribute.toString();
1243
- if(element.hasAttribute(key) === true) {
1244
- var data = element.getAttribute(key);
1245
-
1246
- if(data === 'true') return true;
1247
- if(data === 'false') return false;
1248
- if(data === 'null') return null;
1402
+ }
1403
1250
- // Only convert to a number if it doesn't change the string
1251
- if(data === +data + '') return +data;
1404
+ NETDATA.localStorage.current[key.toString()] = value;
1405
+ return value;
1406
+};
1407
1253
- if(/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/.test(data))
1254
- return JSON.parse(data);
1408
+NETDATA.localStorageGetRecursive = function (obj, prefix, callback) {
1409
+ let keys = Object.keys(obj);
1410
+ let len = keys.length;
1411
+ while (len--) {
1412
+ let i = keys[len];
1413
1256
- return data;
1414
+ if (typeof obj[i] === 'object') {
1415
+ //console.log('object ' + prefix + '.' + i.toString());
1416
+ NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
1417
+ continue;
1418
}
1258
- else return def;
1259
- };
1419
1261
- NETDATA.dataAttributeBoolean = function(element, attribute, def) {
1262
- var value = NETDATA.dataAttribute(element, attribute, def);
1420
+ obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
1421
+ }
1422
+};
1423
1264
- if(value === true || value === false)
1265
- return value;
1424
+NETDATA.setOption = function (key, value) {
1425
+ if (key.toString() === 'setOptionCallback') {
1426
+ if (typeof NETDATA.options.current.setOptionCallback === 'function') {
1427
+ NETDATA.options.current[key.toString()] = value;
1428
+ NETDATA.options.current.setOptionCallback();
1429
+ }
1430
+ } else if (NETDATA.options.current[key.toString()] !== value) {
1431
+ let name = 'options.' + key.toString();
1432
1267
- if(typeof(value) === 'string') {
1268
- if(value === 'yes' || value === 'on')
1269
- return true;
1433
+ if (typeof NETDATA.localStorage.default[name.toString()] === 'undefined') {
1434
+ console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
1435
+ }
1436
1271
- if(value === '' || value === 'no' || value === 'off' || value === 'null')
1272
- return false;
1437
+ //console.log(NETDATA.localStorage);
1438
+ //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
1439
+ //console.log(NETDATA.options);
1440
+ NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
1441
1274
- return def;
1442
+ if (typeof NETDATA.options.current.setOptionCallback === 'function') {
1443
+ NETDATA.options.current.setOptionCallback();
1444
}
1445
+ }
1446
1277
- if(typeof(value) === 'number')
1278
- return value !== 0;
1279
-
1280
- return def;
1281
- };
1447
+ return true;
1448
+};
1449
1283
- // ----------------------------------------------------------------------------------------------------------------
1284
- // commonMin & commonMax
1450
+NETDATA.getOption = function (key) {
1451
+ return NETDATA.options.current[key.toString()];
1452
+};
1453
1286
- NETDATA.commonMin = {
1287
- keys: {},
1288
- latest: {},
1454
+// read settings from local storage
1455
+NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
1456
1290
- globalReset: function() {
1291
- this.keys = {};
1292
- this.latest = {};
1293
- },
1457
+// always start with this option enabled.
1458
+NETDATA.setOption('stop_updates_when_focus_is_lost', true);
1459
1295
- get: function(state) {
1296
- if(typeof state.tmp.__commonMin === 'undefined') {
1297
- // get the commonMin setting
1298
- state.tmp.__commonMin = NETDATA.dataAttribute(state.element, 'common-min', null);
1299
- }
1460
+NETDATA.resetOptions = function () {
1461
+ let keys = Object.keys(NETDATA.localStorage.default);
1462
+ let len = keys.length;
1463
1301
- var min = state.data.min;
1302
- var name = state.tmp.__commonMin;
1464
+ while (len--) {
1465
+ let i = keys[len];
1466
+ let a = i.split('.');
1467
1304
- if(name === null) {
1305
- // we don't need commonMin
1306
- //state.log('no need for commonMin');
1307
- return min;
1468
+ if (a[0] === 'options') {
1469
+ if (a[1] === 'setOptionCallback') {
1470
+ continue;
1471
}
1309
-
1310
- var t = this.keys[name];
1311
- if(typeof t === 'undefined') {
1312
- // add our commonMin
1313
- this.keys[name] = {};
1314
- t = this.keys[name];
1472
+ if (typeof NETDATA.localStorage.default[i] === 'undefined') {
1473
+ continue;
1474
}
1316
-
1317
- var uuid = state.uuid;
1318
- if(typeof t[uuid] !== 'undefined') {
1319
- if(t[uuid] === min) {
1320
- //state.log('commonMin ' + state.tmp.__commonMin + ' not changed: ' + this.latest[name]);
1321
- return this.latest[name];
1322
- }
1323
- else if(min < this.latest[name]) {
1324
- //state.log('commonMin ' + state.tmp.__commonMin + ' increased: ' + min);
1325
- t[uuid] = min;
1326
- this.latest[name] = min;
1327
- return min;
1328
- }
1475
+ if (NETDATA.options.current[i] === NETDATA.localStorage.default[i]) {
1476
+ continue;
1477
}
1478
1331
- // add our min
1332
- t[uuid] = min;
1333
-
1334
- // find the common min
1335
- var m = min;
1336
- for(var i in t)
1337
- if(t.hasOwnProperty(i) && t[i] < m) m = t[i];
1338
-
1339
- //state.log('commonMin ' + state.tmp.__commonMin + ' updated: ' + m);
1340
- this.latest[name] = m;
1341
- return m;
1479
+ NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
1480
+ } else if (a[0] === 'chart_heights') {
1481
+ if (typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
1482
+ NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
1483
+ }
1484
}
1343
- };
1485
+ }
1486
1345
- NETDATA.commonMax = {
1346
- keys: {},
1347
- latest: {},
1487
+ NETDATA.dateTime.init(NETDATA.options.current.timezone);
1488
+};
1489
+// dygraph
1490
1349
- globalReset: function() {
1350
- this.keys = {};
1351
- this.latest = {};
1352
- },
1491
+NETDATA.dygraph = {
1492
+ smooth: false
1493
+};
1494
1354
- get: function(state) {
1355
- if(typeof state.tmp.__commonMax === 'undefined') {
1356
- // get the commonMax setting
1357
- state.tmp.__commonMax = NETDATA.dataAttribute(state.element, 'common-max', null);
1358
- }
1495
+NETDATA.dygraphToolboxPanAndZoom = function (state, after, before) {
1496
+ if (after < state.netdata_first) {
1497
+ after = state.netdata_first;
1498
+ }
1499
1360
- var max = state.data.max;
1361
- var name = state.tmp.__commonMax;
1500
+ if (before > state.netdata_last) {
1501
+ before = state.netdata_last;
1502
+ }
1503
1363
- if(name === null) {
1364
- // we don't need commonMax
1365
- //state.log('no need for commonMax');
1366
- return max;
1367
- }
1504
+ state.setMode('zoom');
1505
+ NETDATA.globalSelectionSync.stop();
1506
+ NETDATA.globalSelectionSync.delay();
1507
+ state.tmp.dygraph_user_action = true;
1508
+ state.tmp.dygraph_force_zoom = true;
1509
+ // state.log('toolboxPanAndZoom');
1510
+ state.updateChartPanOrZoom(after, before);
1511
+ NETDATA.globalPanAndZoom.setMaster(state, after, before);
1512
+};
1513
+
1514
+NETDATA.dygraphSetSelection = function (state, t) {
1515
+ if (typeof state.tmp.dygraph_instance !== 'undefined') {
1516
+ let r = state.calculateRowForTime(t);
1517
+ if (r !== -1) {
1518
+ state.tmp.dygraph_instance.setSelection(r);
1519
+ return true;
1520
+ } else {
1521
+ state.tmp.dygraph_instance.clearSelection();
1522
+ state.legendShowUndefined();
1523
+ }
1524
+ }
1525
1369
- var t = this.keys[name];
1370
- if(typeof t === 'undefined') {
1371
- // add our commonMax
1372
- this.keys[name] = {};
1373
- t = this.keys[name];
1526
+ return false;
1527
+};
1528
+
1529
+NETDATA.dygraphClearSelection = function (state) {
1530
+ if (typeof state.tmp.dygraph_instance !== 'undefined') {
1531
+ state.tmp.dygraph_instance.clearSelection();
1532
+ }
1533
+ return true;
1534
+};
1535
+
1536
+NETDATA.dygraphSmoothInitialize = function (callback) {
1537
+ $.ajax({
1538
+ url: NETDATA.dygraph_smooth_js,
1539
+ cache: true,
1540
+ dataType: "script",
1541
+ xhrFields: {withCredentials: true} // required for the cookie
1542
+ })
1543
+ .done(function () {
1544
+ NETDATA.dygraph.smooth = true;
1545
+ smoothPlotter.smoothing = 0.3;
1546
+ })
1547
+ .fail(function () {
1548
+ NETDATA.dygraph.smooth = false;
1549
+ })
1550
+ .always(function () {
1551
+ if (typeof callback === "function") {
1552
+ return callback();
1553
}
1554
+ });
1555
+};
1556
1376
- var uuid = state.uuid;
1377
- if(typeof t[uuid] !== 'undefined') {
1378
- if(t[uuid] === max) {
1379
- //state.log('commonMax ' + state.tmp.__commonMax + ' not changed: ' + this.latest[name]);
1380
- return this.latest[name];
1381
- }
1382
- else if(max > this.latest[name]) {
1383
- //state.log('commonMax ' + state.tmp.__commonMax + ' increased: ' + max);
1384
- t[uuid] = max;
1385
- this.latest[name] = max;
1386
- return max;
1557
+NETDATA.dygraphInitialize = function (callback) {
1558
+ if (typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
1559
+ $.ajax({
1560
+ url: NETDATA.dygraph_js,
1561
+ cache: true,
1562
+ dataType: "script",
1563
+ xhrFields: {withCredentials: true} // required for the cookie
1564
+ })
1565
+ .done(function () {
1566
+ NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
1567
+ })
1568
+ .fail(function () {
1569
+ NETDATA.chartLibraries.dygraph.enabled = false;
1570
+ NETDATA.error(100, NETDATA.dygraph_js);
1571
+ })
1572
+ .always(function () {
1573
+ if (NETDATA.chartLibraries.dygraph.enabled && NETDATA.options.current.smooth_plot) {
1574
+ NETDATA.dygraphSmoothInitialize(callback);
1575
+ } else if (typeof callback === "function") {
1576
+ return callback();
1577
}
1388
- }
1578
+ });
1579
+ } else {
1580
+ NETDATA.chartLibraries.dygraph.enabled = false;
1581
+ if (typeof callback === "function") {
1582
+ return callback();
1583
+ }
1584
+ }
1585
+};
1586
1390
- // add our max
1391
- t[uuid] = max;
1587
+NETDATA.dygraphChartUpdate = function (state, data) {
1588
+ let dygraph = state.tmp.dygraph_instance;
1589
1393
- // find the common max
1394
- var m = max;
1395
- for(var i in t)
1396
- if(t.hasOwnProperty(i) && t[i] > m) m = t[i];
1590
+ if (typeof dygraph === 'undefined') {
1591
+ return NETDATA.dygraphChartCreate(state, data);
1592
+ }
1593
1398
- //state.log('commonMax ' + state.tmp.__commonMax + ' updated: ' + m);
1399
- this.latest[name] = m;
1400
- return m;
1401
- }
1594
+ // when the chart is not visible, and hidden
1595
+ // if there is a window resize, dygraph detects
1596
+ // its element size as 0x0.
1597
+ // this will make it re-appear properly
1598
+
1599
+ if (state.tm.last_unhidden > state.tmp.dygraph_last_rendered) {
1600
+ dygraph.resize();
1601
+ }
1602
+
1603
+ let options = {
1604
+ file: data.result.data,
1605
+ colors: state.chartColors(),
1606
+ labels: data.result.labels,
1607
+ //labelsDivWidth: state.chartWidth() - 70,
1608
+ includeZero: state.tmp.dygraph_include_zero,
1609
+ visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
1610
};
1611
1404
- NETDATA.commonColors = {
1405
- keys: {},
1612
+ if (state.tmp.dygraph_chart_type === 'stacked') {
1613
+ if (options.includeZero && state.dimensions_visibility.countSelected() < options.visibility.length) {
1614
+ options.includeZero = 0;
1615
+ }
1616
+ }
1617
1407
- globalReset: function() {
1408
- this.keys = {};
1409
- },
1618
+ if (!NETDATA.chartLibraries.dygraph.isSparkline(state)) {
1619
+ options.ylabel = state.units_current; // (state.units_desired === 'auto')?"":state.units_current;
1620
+ }
1621
1411
- get: function(state, label) {
1412
- var ret = this.refill(state);
1622
+ if (state.tmp.dygraph_force_zoom) {
1623
+ if (NETDATA.options.debug.dygraph || state.debug) {
1624
+ state.log('dygraphChartUpdate() forced zoom update');
1625
+ }
1626
1414
- if(typeof ret.assigned[label] === 'undefined')
1415
- ret.assigned[label] = ret.available.shift();
1627
+ options.dateWindow = (state.requested_padding !== null) ? [state.view_after, state.view_before] : null;
1628
+ //options.isZoomedIgnoreProgrammaticZoom = true;
1629
+ state.tmp.dygraph_force_zoom = false;
1630
+ } else if (state.current.name !== 'auto') {
1631
+ if (NETDATA.options.debug.dygraph || state.debug) {
1632
+ state.log('dygraphChartUpdate() loose update');
1633
+ }
1634
+ } else {
1635
+ if (NETDATA.options.debug.dygraph || state.debug) {
1636
+ state.log('dygraphChartUpdate() strict update');
1637
+ }
1638
1417
- return ret.assigned[label];
1418
- },
1639
+ options.dateWindow = (state.requested_padding !== null) ? [state.view_after, state.view_before] : null;
1640
+ //options.isZoomedIgnoreProgrammaticZoom = true;
1641
+ }
1642
1420
- refill: function(state) {
1421
- var ret, len;
1643
+ options.valueRange = state.tmp.dygraph_options.valueRange;
1644
1423
- if(typeof state.tmp.__commonColors === 'undefined')
1424
- ret = this.prepare(state);
1425
- else {
1426
- ret = this.keys[state.tmp.__commonColors];
1427
- if(typeof ret === 'undefined')
1428
- ret = this.prepare(state);
1429
- }
1645
+ let oldMax = null, oldMin = null;
1646
+ if (state.tmp.__commonMin !== null) {
1647
+ state.data.min = state.tmp.dygraph_instance.axes_[0].extremeRange[0];
1648
+ oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
1649
+ }
1650
+ if (state.tmp.__commonMax !== null) {
1651
+ state.data.max = state.tmp.dygraph_instance.axes_[0].extremeRange[1];
1652
+ oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
1653
+ }
1654
1431
- if(ret.available.length === 0) {
1432
- if(ret.copy_theme === true || ret.custom.length === 0) {
1433
- // copy the theme colors
1434
- len = NETDATA.themes.current.colors.length;
1435
- while (len--)
1436
- ret.available.unshift(NETDATA.themes.current.colors[len]);
1437
- }
1655
+ if (state.tmp.dygraph_smooth_eligible) {
1656
+ if ((NETDATA.options.current.smooth_plot && state.tmp.dygraph_options.plotter !== smoothPlotter)
1657
+ || (NETDATA.options.current.smooth_plot === false && state.tmp.dygraph_options.plotter === smoothPlotter)) {
1658
+ NETDATA.dygraphChartCreate(state, data);
1659
+ return;
1660
+ }
1661
+ }
1662
1439
- // copy the custom colors
1440
- len = ret.custom.length;
1441
- while (len--)
1442
- ret.available.unshift(ret.custom[len]);
1443
- }
1663
+ if (netdataSnapshotData !== null && NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(state) === false) {
1664
+ // pan and zoom on snapshots
1665
+ options.dateWindow = [NETDATA.globalPanAndZoom.force_after_ms, NETDATA.globalPanAndZoom.force_before_ms];
1666
+ //options.isZoomedIgnoreProgrammaticZoom = true;
1667
+ }
1668
1445
- state.colors_assigned = ret.assigned;
1446
- state.colors_available = ret.available;
1447
- state.colors_custom = ret.custom;
1669
+ if (NETDATA.chartLibraries.dygraph.isLogScale(state)) {
1670
+ if (Array.isArray(options.valueRange) && options.valueRange[0] <= 0) {
1671
+ options.valueRange[0] = null;
1672
+ }
1673
+ }
1674
1449
- return ret;
1450
- },
1675
+ dygraph.updateOptions(options);
1676
1452
- __read_custom_colors: function(state, ret) {
1453
- // add the user supplied colors
1454
- var c = NETDATA.dataAttribute(state.element, 'colors', undefined);
1455
- if (typeof c === 'string' && c.length > 0) {
1456
- c = c.split(' ');
1457
- var len = c.length;
1677
+ let redraw = false;
1678
+ if (oldMin !== null && oldMin > state.tmp.dygraph_instance.axes_[0].extremeRange[0]) {
1679
+ state.data.min = state.tmp.dygraph_instance.axes_[0].extremeRange[0];
1680
+ options.valueRange[0] = NETDATA.commonMin.get(state);
1681
+ redraw = true;
1682
+ }
1683
+ if (oldMax !== null && oldMax < state.tmp.dygraph_instance.axes_[0].extremeRange[1]) {
1684
+ state.data.max = state.tmp.dygraph_instance.axes_[0].extremeRange[1];
1685
+ options.valueRange[1] = NETDATA.commonMax.get(state);
1686
+ redraw = true;
1687
+ }
1688
1459
- if (len > 0 && c[len - 1] === 'ONLY') {
1460
- len--;
1461
- ret.copy_theme = false;
1462
- }
1689
+ if (redraw) {
1690
+ // state.log('forcing redraw to adapt to common- min/max');
1691
+ dygraph.updateOptions(options);
1692
+ }
1693
1464
- while (len--)
1465
- ret.custom.unshift(c[len]);
1466
- }
1467
- },
1694
+ state.tmp.dygraph_last_rendered = Date.now();
1695
+ return true;
1696
+};
1697
1469
- prepare: function(state) {
1470
- var has_custom_colors = false;
1698
+NETDATA.dygraphChartCreate = function (state, data) {
1699
+ if (NETDATA.options.debug.dygraph || state.debug) {
1700
+ state.log('dygraphChartCreate()');
1701
+ }
1702
1472
- if(typeof state.tmp.__commonColors === 'undefined') {
1473
- var defname = state.chart.context;
1703
+ state.tmp.dygraph_chart_type = NETDATA.dataAttribute(state.element, 'dygraph-type', state.chart.chart_type);
1704
+ if (state.tmp.dygraph_chart_type === 'stacked' && data.dimensions === 1) {
1705
+ state.tmp.dygraph_chart_type = 'area';
1706
+ }
1707
+ if (state.tmp.dygraph_chart_type === 'stacked' && NETDATA.chartLibraries.dygraph.isLogScale(state)) {
1708
+ state.tmp.dygraph_chart_type = 'area';
1709
+ }
1710
1475
- // if this chart has data-colors=""
1476
- // we should use the chart uuid as the default key (private palette)
1477
- // (data-common-colors="NAME" will be used anyways)
1478
- var c = NETDATA.dataAttribute(state.element, 'colors', undefined);
1479
- if (typeof c === 'string' && c.length > 0) {
1480
- defname = state.uuid;
1481
- has_custom_colors = true;
1711
+ let highlightCircleSize = NETDATA.chartLibraries.dygraph.isSparkline(state) ? 3 : 4;
1712
+
1713
+ let smooth = NETDATA.dygraph.smooth
1714
+ ? (NETDATA.dataAttributeBoolean(state.element, 'dygraph-smooth', (state.tmp.dygraph_chart_type === 'line' && NETDATA.chartLibraries.dygraph.isSparkline(state) === false)))
1715
+ : false;
1716
+
1717
+ state.tmp.dygraph_include_zero = NETDATA.dataAttribute(state.element, 'dygraph-includezero', (state.tmp.dygraph_chart_type === 'stacked'));
1718
+ let drawAxis = NETDATA.dataAttributeBoolean(state.element, 'dygraph-drawaxis', true);
1719
+
1720
+ state.tmp.dygraph_options = {
1721
+ colors: NETDATA.dataAttribute(state.element, 'dygraph-colors', state.chartColors()),
1722
+
1723
+ // leave a few pixels empty on the right of the chart
1724
+ rightGap: NETDATA.dataAttribute(state.element, 'dygraph-rightgap', 5),
1725
+ showRangeSelector: NETDATA.dataAttributeBoolean(state.element, 'dygraph-showrangeselector', false),
1726
+ showRoller: NETDATA.dataAttributeBoolean(state.element, 'dygraph-showroller', false),
1727
+ title: NETDATA.dataAttribute(state.element, 'dygraph-title', state.title),
1728
+ titleHeight: NETDATA.dataAttribute(state.element, 'dygraph-titleheight', 19),
1729
+ legend: NETDATA.dataAttribute(state.element, 'dygraph-legend', 'always'), // we need this to get selection events
1730
+ labels: data.result.labels,
1731
+ labelsDiv: NETDATA.dataAttribute(state.element, 'dygraph-labelsdiv', state.element_legend_childs.hidden),
1732
+ //labelsDivStyles: NETDATA.dataAttribute(state.element, 'dygraph-labelsdivstyles', { 'fontSize':'1px' }),
1733
+ //labelsDivWidth: NETDATA.dataAttribute(state.element, 'dygraph-labelsdivwidth', state.chartWidth() - 70),
1734
+ labelsSeparateLines: NETDATA.dataAttributeBoolean(state.element, 'dygraph-labelsseparatelines', true),
1735
+ labelsShowZeroValues: NETDATA.chartLibraries.dygraph.isLogScale(state) ? false : NETDATA.dataAttributeBoolean(state.element, 'dygraph-labelsshowzerovalues', true),
1736
+ labelsKMB: false,
1737
+ labelsKMG2: false,
1738
+ showLabelsOnHighlight: NETDATA.dataAttributeBoolean(state.element, 'dygraph-showlabelsonhighlight', true),
1739
+ hideOverlayOnMouseOut: NETDATA.dataAttributeBoolean(state.element, 'dygraph-hideoverlayonmouseout', true),
1740
+ includeZero: state.tmp.dygraph_include_zero,
1741
+ xRangePad: NETDATA.dataAttribute(state.element, 'dygraph-xrangepad', 0),
1742
+ yRangePad: NETDATA.dataAttribute(state.element, 'dygraph-yrangepad', 1),
1743
+ valueRange: NETDATA.dataAttribute(state.element, 'dygraph-valuerange', [null, null]),
1744
+ ylabel: state.units_current, // (state.units_desired === 'auto')?"":state.units_current,
1745
+ yLabelWidth: NETDATA.dataAttribute(state.element, 'dygraph-ylabelwidth', 12),
1746
+
1747
+ // the function to plot the chart
1748
+ plotter: null,
1749
+
1750
+ // The width of the lines connecting data points.
1751
+ // This can be used to increase the contrast or some graphs.
1752
+ strokeWidth: NETDATA.dataAttribute(state.element, 'dygraph-strokewidth', ((state.tmp.dygraph_chart_type === 'stacked') ? 0.1 : ((smooth === true) ? 1.5 : 0.7))),
1753
+ strokePattern: NETDATA.dataAttribute(state.element, 'dygraph-strokepattern', undefined),
1754
+
1755
+ // The size of the dot to draw on each point in pixels (see drawPoints).
1756
+ // A dot is always drawn when a point is "isolated",
1757
+ // i.e. there is a missing point on either side of it.
1758
+ // This also controls the size of those dots.
1759
+ drawPoints: NETDATA.dataAttributeBoolean(state.element, 'dygraph-drawpoints', false),
1760
+
1761
+ // Draw points at the edges of gaps in the data.
1762
+ // This improves visibility of small data segments or other data irregularities.
1763
+ drawGapEdgePoints: NETDATA.dataAttributeBoolean(state.element, 'dygraph-drawgapedgepoints', true),
1764
+ connectSeparatedPoints: NETDATA.chartLibraries.dygraph.isLogScale(state) ? false : NETDATA.dataAttributeBoolean(state.element, 'dygraph-connectseparatedpoints', false),
1765
+ pointSize: NETDATA.dataAttribute(state.element, 'dygraph-pointsize', 1),
1766
+
1767
+ // enabling this makes the chart with little square lines
1768
+ stepPlot: NETDATA.dataAttributeBoolean(state.element, 'dygraph-stepplot', false),
1769
+
1770
+ // Draw a border around graph lines to make crossing lines more easily
1771
+ // distinguishable. Useful for graphs with many lines.
1772
+ strokeBorderColor: NETDATA.dataAttribute(state.element, 'dygraph-strokebordercolor', NETDATA.themes.current.background),
1773
+ strokeBorderWidth: NETDATA.dataAttribute(state.element, 'dygraph-strokeborderwidth', (state.tmp.dygraph_chart_type === 'stacked') ? 0.0 : 0.0),
1774
+ fillGraph: NETDATA.dataAttribute(state.element, 'dygraph-fillgraph', (state.tmp.dygraph_chart_type === 'area' || state.tmp.dygraph_chart_type === 'stacked')),
1775
+ fillAlpha: NETDATA.dataAttribute(state.element, 'dygraph-fillalpha',
1776
+ ((state.tmp.dygraph_chart_type === 'stacked')
1777
+ ? NETDATA.options.current.color_fill_opacity_stacked
1778
+ : NETDATA.options.current.color_fill_opacity_area)
1779
+ ),
1780
+ stackedGraph: NETDATA.dataAttribute(state.element, 'dygraph-stackedgraph', (state.tmp.dygraph_chart_type === 'stacked')),
1781
+ stackedGraphNaNFill: NETDATA.dataAttribute(state.element, 'dygraph-stackedgraphnanfill', 'none'),
1782
+ drawAxis: drawAxis,
1783
+ axisLabelFontSize: NETDATA.dataAttribute(state.element, 'dygraph-axislabelfontsize', 10),
1784
+ axisLineColor: NETDATA.dataAttribute(state.element, 'dygraph-axislinecolor', NETDATA.themes.current.axis),
1785
+ axisLineWidth: NETDATA.dataAttribute(state.element, 'dygraph-axislinewidth', 1.0),
1786
+ drawGrid: NETDATA.dataAttributeBoolean(state.element, 'dygraph-drawgrid', true),
1787
+ gridLinePattern: NETDATA.dataAttribute(state.element, 'dygraph-gridlinepattern', null),
1788
+ gridLineWidth: NETDATA.dataAttribute(state.element, 'dygraph-gridlinewidth', 1.0),
1789
+ gridLineColor: NETDATA.dataAttribute(state.element, 'dygraph-gridlinecolor', NETDATA.themes.current.grid),
1790
+ maxNumberWidth: NETDATA.dataAttribute(state.element, 'dygraph-maxnumberwidth', 8),
1791
+ sigFigs: NETDATA.dataAttribute(state.element, 'dygraph-sigfigs', null),
1792
+ digitsAfterDecimal: NETDATA.dataAttribute(state.element, 'dygraph-digitsafterdecimal', 2),
1793
+ valueFormatter: NETDATA.dataAttribute(state.element, 'dygraph-valueformatter', undefined),
1794
+ highlightCircleSize: NETDATA.dataAttribute(state.element, 'dygraph-highlightcirclesize', highlightCircleSize),
1795
+ highlightSeriesOpts: NETDATA.dataAttribute(state.element, 'dygraph-highlightseriesopts', null), // TOO SLOW: { strokeWidth: 1.5 },
1796
+ highlightSeriesBackgroundAlpha: NETDATA.dataAttribute(state.element, 'dygraph-highlightseriesbackgroundalpha', null), // TOO SLOW: (state.tmp.dygraph_chart_type === 'stacked')?0.7:0.5,
1797
+ pointClickCallback: NETDATA.dataAttribute(state.element, 'dygraph-pointclickcallback', undefined),
1798
+ visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
1799
+ logscale: NETDATA.chartLibraries.dygraph.isLogScale(state) ? 'y' : undefined,
1800
+
1801
+ axes: {
1802
+ x: {
1803
+ pixelsPerLabel: NETDATA.dataAttribute(state.element, 'dygraph-xpixelsperlabel', 50),
1804
+ ticker: Dygraph.dateTicker,
1805
+ axisLabelWidth: NETDATA.dataAttribute(state.element, 'dygraph-xaxislabelwidth', 60),
1806
+ drawAxis: NETDATA.dataAttributeBoolean(state.element, 'dygraph-drawxaxis', drawAxis),
1807
+ axisLabelFormatter: function (d, gran) {
1808
+ void(gran);
1809
+ return NETDATA.dateTime.xAxisTimeString(d);
1810
}
1811
+ },
1812
+ y: {
1813
+ logscale: NETDATA.chartLibraries.dygraph.isLogScale(state) ? true : undefined,
1814
+ pixelsPerLabel: NETDATA.dataAttribute(state.element, 'dygraph-ypixelsperlabel', 15),
1815
+ axisLabelWidth: NETDATA.dataAttribute(state.element, 'dygraph-yaxislabelwidth', 50),
1816
+ drawAxis: NETDATA.dataAttributeBoolean(state.element, 'dygraph-drawyaxis', drawAxis),
1817
+ axisLabelFormatter: function (y) {
1818
+
1819
+ // unfortunately, we have to call this every single time
1820
+ state.legendFormatValueDecimalsFromMinMax(
1821
+ this.axes_[0].extremeRange[0],
1822
+ this.axes_[0].extremeRange[1]
1823
+ );
1824
1484
- // get the commonColors setting
1485
- state.tmp.__commonColors = NETDATA.dataAttribute(state.element, 'common-colors', defname);
1486
- }
1825
+ let old_units = this.user_attrs_.ylabel;
1826
+ let v = state.legendFormatValue(y);
1827
+ let new_units = state.units_current;
1828
+
1829
+ if (state.units_desired === 'auto' && typeof old_units !== 'undefined' && new_units !== old_units && !NETDATA.chartLibraries.dygraph.isSparkline(state)) {
1830
+ // console.log(this);
1831
+ // state.log('units discrepancy: old = ' + old_units + ', new = ' + new_units);
1832
+ let len = this.plugins_.length;
1833
+ while (len--) {
1834
+ // console.log(this.plugins_[len]);
1835
+ if (typeof this.plugins_[len].plugin.ylabel_div_ !== 'undefined'
1836
+ && this.plugins_[len].plugin.ylabel_div_ !== null
1837
+ && typeof this.plugins_[len].plugin.ylabel_div_.children !== 'undefined'
1838
+ && this.plugins_[len].plugin.ylabel_div_.children !== null
1839
+ && typeof this.plugins_[len].plugin.ylabel_div_.children[0].children !== 'undefined'
1840
+ && this.plugins_[len].plugin.ylabel_div_.children[0].children !== null
1841
+ ) {
1842
+ this.plugins_[len].plugin.ylabel_div_.children[0].children[0].innerHTML = new_units;
1843
+ this.user_attrs_.ylabel = new_units;
1844
+ break;
1845
+ }
1846
+ }
1847
1488
- var name = state.tmp.__commonColors;
1489
- var ret = this.keys[name];
1848
+ if (len < 0) {
1849
+ state.log('units discrepancy, but cannot find dygraphs div to change: old = ' + old_units + ', new = ' + new_units);
1850
+ }
1851
+ }
1852
1491
- if(typeof ret === 'undefined') {
1492
- // add our commonMax
1493
- this.keys[name] = {
1494
- assigned: {}, // name-value of dimensions and their colors
1495
- available: [], // an array of colors available to be used
1496
- custom: [], // the array of colors defined by the user
1497
- charts: {}, // the charts linked to this
1498
- copy_theme: true
1499
- };
1500
- ret = this.keys[name];
1853
+ return v;
1854
+ }
1855
+ }
1856
+ },
1857
+ legendFormatter: function (data) {
1858
+ if (state.tmp.dygraph_mouse_down) {
1859
+ return;
1860
}
1861
1503
- if(typeof ret.charts[state.uuid] === 'undefined') {
1504
- ret.charts[state.uuid] = state;
1862
+ let elements = state.element_legend_childs;
1863
1506
- if(has_custom_colors === true)
1507
- this.__read_custom_colors(state, ret);
1864
+ // if the hidden div is not there
1865
+ // we are not managing the legend
1866
+ if (elements.hidden === null) {
1867
+ return;
1868
}
1869
1510
- return ret;
1511
- }
1512
- };
1870
+ if (typeof data.x !== 'undefined') {
1871
+ state.legendSetDate(data.x);
1872
+ let i = data.series.length;
1873
+ while (i--) {
1874
+ let series = data.series[i];
1875
+ if (series.isVisible) {
1876
+ state.legendSetLabelValue(series.label, series.y);
1877
+ } else {
1878
+ state.legendSetLabelValue(series.label, null);
1879
+ }
1880
+ }
1881
+ }
1882
1514
- // ----------------------------------------------------------------------------------------------------------------
1515
- // Chart Registry
1883
+ return '';
1884
+ },
1885
+ drawCallback: function (dygraph, is_initial) {
1886
1517
- // When multiple charts need the same chart, we avoid downloading it
1518
- // multiple times (and having it in browser memory multiple time)
1519
- // by using this registry.
1887
+ // the user has panned the chart and this is called to re-draw the chart
1888
+ // 1. refresh this chart by adding data to it
1889
+ // 2. notify all the other charts about the update they need
1890
1521
- // Every time we download a chart definition, we save it here with .add()
1522
- // Then we try to get it back with .get(). If that fails, we download it.
1891
+ // to prevent an infinite loop (feedback), we use
1892
+ // state.tmp.dygraph_user_action
1893
+ // - when true, this is initiated by a user
1894
+ // - when false, this is feedback
1895
1524
- NETDATA.fixHost = function(host) {
1525
- while(host.slice(-1) === '/')
1526
- host = host.substring(0, host.length - 1);
1896
+ if (state.current.name !== 'auto' && state.tmp.dygraph_user_action) {
1897
+ state.tmp.dygraph_user_action = false;
1898
1528
- return host;
1529
- };
1899
+ let x_range = dygraph.xAxisRange();
1900
+ let after = Math.round(x_range[0]);
1901
+ let before = Math.round(x_range[1]);
1902
1531
- NETDATA.chartRegistry = {
1532
- charts: {},
1903
+ if (NETDATA.options.debug.dygraph) {
1904
+ state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): mode ' + state.current.name + ' ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
1905
+ //console.log(state);
1906
+ }
1907
1534
- globalReset: function() {
1535
- this.charts = {};
1908
+ if (before <= state.netdata_last && after >= state.netdata_first) {
1909
+ // update only when we are within the data limits
1910
+ state.updateChartPanOrZoom(after, before);
1911
+ }
1912
+ }
1913
},
1914
+ zoomCallback: function (minDate, maxDate, yRanges) {
1915
1538
- add: function(host, id, data) {
1539
- if(typeof this.charts[host] === 'undefined')
1540
- this.charts[host] = {};
1916
+ // the user has selected a range on the chart
1917
+ // 1. refresh this chart by adding data to it
1918
+ // 2. notify all the other charts about the update they need
1919
1542
- //console.log('added ' + host + '/' + id);
1543
- this.charts[host][id] = data;
1544
- },
1920
+ void(yRanges);
1921
1546
- get: function(host, id) {
1547
- if(typeof this.charts[host] === 'undefined')
1548
- return null;
1922
+ if (NETDATA.options.debug.dygraph) {
1923
+ state.log('dygraphZoomCallback(): ' + state.current.name);
1924
+ }
1925
1550
- if(typeof this.charts[host][id] === 'undefined')
1551
- return null;
1926
+ NETDATA.globalSelectionSync.stop();
1927
+ NETDATA.globalSelectionSync.delay();
1928
+ state.setMode('zoom');
1929
1553
- //console.log('cached ' + host + '/' + id);
1554
- return this.charts[host][id];
1930
+ // refresh it to the greatest possible zoom level
1931
+ state.tmp.dygraph_user_action = true;
1932
+ state.tmp.dygraph_force_zoom = true;
1933
+ state.updateChartPanOrZoom(minDate, maxDate);
1934
},
1935
+ highlightCallback: function (event, x, points, row, seriesName) {
1936
+ void(seriesName);
1937
1557
- downloadAll: function(host, callback) {
1558
- host = NETDATA.fixHost(host);
1938
+ state.pauseChart();
1939
1560
- var self = this;
1940
+ // there is a bug in dygraph when the chart is zoomed enough
1941
+ // the time it thinks is selected is wrong
1942
+ // here we calculate the time t based on the row number selected
1943
+ // which is ok
1944
+ // let t = state.data_after + row * state.data_update_every;
1945
+ // console.log('row = ' + row + ', x = ' + x + ', t = ' + t + ' ' + ((t === x)?'SAME':(Math.abs(x-t)<=state.data_update_every)?'SIMILAR':'DIFFERENT') + ', rows in db: ' + state.data_points + ' visible(x) = ' + state.timeIsVisible(x) + ' visible(t) = ' + state.timeIsVisible(t) + ' r(x) = ' + state.calculateRowForTime(x) + ' r(t) = ' + state.calculateRowForTime(t) + ' range: ' + state.data_after + ' - ' + state.data_before + ' real: ' + state.data.after + ' - ' + state.data.before + ' every: ' + state.data_update_every);
1946
1562
- function got_data(h, data, callback) {
1563
- if(data !== null) {
1564
- self.charts[h] = data.charts;
1947
+ if (state.tmp.dygraph_mouse_down !== true) {
1948
+ NETDATA.globalSelectionSync.sync(state, x);
1949
+ }
1950
1566
- // update the server timezone in our options
1567
- if(typeof data.timezone === 'string')
1568
- NETDATA.options.server_timezone = data.timezone;
1569
- }
1570
- else NETDATA.error(406, h + '/api/v1/charts');
1951
+ // fix legend zIndex using the internal structures of dygraph legend module
1952
+ // this works, but it is a hack!
1953
+ // state.tmp.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
1954
+ },
1955
+ unhighlightCallback: function (event) {
1956
+ void(event);
1957
1572
- if(typeof callback === 'function')
1573
- callback(data);
1958
+ if (state.tmp.dygraph_mouse_down) {
1959
+ return;
1960
}
1961
1576
- if(netdataSnapshotData !== null) {
1577
- got_data(host, netdataSnapshotData.charts, callback);
1578
- }
1579
- else {
1580
- $.ajax({
1581
- url: host + '/api/v1/charts',
1582
- async: true,
1583
- cache: false,
1584
- xhrFields: {withCredentials: true} // required for the cookie
1585
- })
1586
- .done(function (data) {
1587
- data = NETDATA.xss.checkOptional('/api/v1/charts', data);
1588
- got_data(host, data, callback);
1589
- })
1590
- .fail(function () {
1591
- NETDATA.error(405, host + '/api/v1/charts');
1592
-
1593
- if (typeof callback === 'function')
1594
- callback(null);
1595
- });
1962
+ if (NETDATA.options.debug.dygraph || state.debug) {
1963
+ state.log('dygraphUnhighlightCallback()');
1964
}
1597
- }
1598
- };
1599
-
1600
- // ----------------------------------------------------------------------------------------------------------------
1601
- // Global Pan and Zoom on charts
1602
-
1603
- // Using this structure are synchronize all the charts, so that
1604
- // when you pan or zoom one, all others are automatically refreshed
1605
- // to the same timespan.
1965
1607
- NETDATA.globalPanAndZoom = {
1608
- seq: 0, // timestamp ms
1609
- // every time a chart is panned or zoomed
1610
- // we set the timestamp here
1611
- // then we use it as a sequence number
1612
- // to find if other charts are synchronized
1613
- // to this time-range
1614
-
1615
- master: null, // the master chart (state), to which all others
1616
- // are synchronized
1617
-
1618
- force_before_ms: null, // the timespan to sync all other charts
1619
- force_after_ms: null,
1966
+ state.unpauseChart();
1967
+ NETDATA.globalSelectionSync.stop();
1968
+ },
1969
+ underlayCallback: function (canvas, area, g) {
1970
1621
- callback: null,
1971
+ // the chart is about to be drawn
1972
+ // this function renders global highlighted time-frame
1973
1623
- globalReset: function() {
1624
- this.clearMaster();
1625
- this.seq = 0;
1626
- this.master = null;
1627
- this.force_after_ms = null;
1628
- this.force_before_ms = null;
1629
- this.callback = null;
1630
- },
1974
+ if (NETDATA.globalChartUnderlay.isActive()) {
1975
+ let after = NETDATA.globalChartUnderlay.after;
1976
+ let before = NETDATA.globalChartUnderlay.before;
1977
1632
- delay: function() {
1633
- if(NETDATA.options.debug.globalPanAndZoom === true)
1634
- console.log('globalPanAndZoom.delay()');
1978
+ if (after < state.view_after) {
1979
+ after = state.view_after;
1980
+ }
1981
1636
- NETDATA.options.auto_refresher_stop_until = Date.now() + NETDATA.options.current.global_pan_sync_time;
1637
- },
1982
+ if (before > state.view_before) {
1983
+ before = state.view_before;
1984
+ }
1985
1639
- // set a new master
1640
- setMaster: function(state, after, before) {
1641
- this.delay();
1986
+ if (after < before) {
1987
+ let bottom_left = g.toDomCoords(after, -20);
1988
+ let top_right = g.toDomCoords(before, +20);
1989
1643
- if(NETDATA.options.current.sync_pan_and_zoom === false)
1644
- return;
1990
+ let left = bottom_left[0];
1991
+ let right = top_right[0];
1992
1646
- if(this.master === null) {
1647
- if(NETDATA.options.debug.globalPanAndZoom === true)
1648
- console.log('globalPanAndZoom.setMaster(' + state.id + ', ' + after + ', ' + before + ') SET MASTER');
1993
+ canvas.fillStyle = NETDATA.themes.current.highlight;
1994
+ canvas.fillRect(left, area.y, right - left, area.h);
1995
+ }
1996
}
1650
- else if(this.master !== state) {
1651
- if(NETDATA.options.debug.globalPanAndZoom === true)
1652
- console.log('globalPanAndZoom.setMaster(' + state.id + ', ' + after + ', ' + before + ') CHANGED MASTER');
1997
+ },
1998
+ interactionModel: {
1999
+ mousedown: function (event, dygraph, context) {
2000
+ if (NETDATA.options.debug.dygraph || state.debug) {
2001
+ state.log('interactionModel.mousedown()');
2002
+ }
2003
1654
- this.master.resetChart(true, true);
1655
- }
2004
+ state.tmp.dygraph_user_action = true;
2005
1657
- var now = Date.now();
1658
- this.master = state;
1659
- this.seq = now;
1660
- this.force_after_ms = after;
1661
- this.force_before_ms = before;
2006
+ if (NETDATA.options.debug.dygraph) {
2007
+ state.log('dygraphMouseDown()');
2008
+ }
2009
1663
- if(typeof this.callback === 'function')
1664
- this.callback(true, after, before);
1665
- },
2010
+ // Right-click should not initiate anything.
2011
+ if (event.button && event.button === 2) {
2012
+ return;
2013
+ }
2014
1667
- // clear the master
1668
- clearMaster: function() {
1669
- if(NETDATA.options.debug.globalPanAndZoom === true)
1670
- console.log('globalPanAndZoom.clearMaster()');
2015
+ NETDATA.globalSelectionSync.stop();
2016
+ NETDATA.globalSelectionSync.delay();
2017
1672
- if(this.master !== null) {
1673
- var st = this.master;
1674
- this.master = null;
1675
- st.resetChart();
1676
- }
2018
+ state.tmp.dygraph_mouse_down = true;
2019
+ context.initializeMouseDown(event, dygraph, context);
2020
1678
- this.master = null;
1679
- this.seq = 0;
1680
- this.force_after_ms = null;
1681
- this.force_before_ms = null;
1682
- NETDATA.options.auto_refresher_stop_until = 0;
2021
+ //console.log(event);
2022
+ if (event.button && event.button === 1) {
2023
+ if (event.shiftKey) {
2024
+ //console.log('middle mouse button dragging (PAN)');
2025
1684
- if(typeof this.callback === 'function')
1685
- this.callback(false, 0, 0);
1686
- },
2026
+ state.setMode('pan');
2027
+ // NETDATA.globalSelectionSync.delay();
2028
+ state.tmp.dygraph_highlight_after = null;
2029
+ Dygraph.startPan(event, dygraph, context);
2030
+ } else if (event.altKey || event.ctrlKey || event.metaKey) {
2031
+ //console.log('middle mouse button highlight');
2032
1688
- // is the given state the master of the global
1689
- // pan and zoom sync?
1690
- isMaster: function(state) {
1691
- return (this.master === state);
1692
- },
2033
+ if (!(event.offsetX && event.offsetY)) {
2034
+ event.offsetX = event.layerX - event.target.offsetLeft;
2035
+ event.offsetY = event.layerY - event.target.offsetTop;
2036
+ }
2037
+ state.tmp.dygraph_highlight_after = dygraph.toDataXCoord(event.offsetX);
2038
+ Dygraph.startZoom(event, dygraph, context);
2039
+ } else {
2040
+ //console.log('middle mouse button selection for zoom (ZOOM)');
2041
1694
- // are we currently have a global pan and zoom sync?
1695
- isActive: function() {
1696
- return (this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0);
1697
- },
2042
+ state.setMode('zoom');
2043
+ // NETDATA.globalSelectionSync.delay();
2044
+ state.tmp.dygraph_highlight_after = null;
2045
+ Dygraph.startZoom(event, dygraph, context);
2046
+ }
2047
+ } else {
2048
+ if (event.shiftKey) {
2049
+ //console.log('left mouse button selection for zoom (ZOOM)');
2050
1699
- // check if a chart, other than the master
1700
- // needs to be refreshed, due to the global pan and zoom
1701
- shouldBeAutoRefreshed: function(state) {
1702
- if(this.master === null || this.seq === 0)
1703
- return false;
2051
+ state.setMode('zoom');
2052
+ // NETDATA.globalSelectionSync.delay();
2053
+ state.tmp.dygraph_highlight_after = null;
2054
+ Dygraph.startZoom(event, dygraph, context);
2055
+ } else if (event.altKey || event.ctrlKey || event.metaKey) {
2056
+ //console.log('left mouse button highlight');
2057
1705
- //if(state.needsRecreation())
1706
- // return true;
2058
+ if (!(event.offsetX && event.offsetY)) {
2059
+ event.offsetX = event.layerX - event.target.offsetLeft;
2060
+ event.offsetY = event.layerY - event.target.offsetTop;
2061
+ }
2062
+ state.tmp.dygraph_highlight_after = dygraph.toDataXCoord(event.offsetX);
2063
+ Dygraph.startZoom(event, dygraph, context);
2064
+ } else {
2065
+ //console.log('left mouse button dragging (PAN)');
2066
1708
- return (state.tm.pan_and_zoom_seq !== this.seq);
1709
- }
1710
- };
2067
+ state.setMode('pan');
2068
+ // NETDATA.globalSelectionSync.delay();
2069
+ state.tmp.dygraph_highlight_after = null;
2070
+ Dygraph.startPan(event, dygraph, context);
2071
+ }
2072
+ }
2073
+ },
2074
+ mousemove: function (event, dygraph, context) {
2075
+ if (NETDATA.options.debug.dygraph || state.debug) {
2076
+ state.log('interactionModel.mousemove()');
2077
+ }
2078
1712
- // ----------------------------------------------------------------------------------------------------------------
1713
- // global chart underlay (time-frame highlighting)
1714
-
1715
- NETDATA.globalChartUnderlay = {
1716
- callback: null, // what to call when a highlighted range is setup
1717
- after: null, // highlight after this time
1718
- before: null, // highlight before this time
1719
- view_after: null, // the charts after_ms viewport when the highlight was setup
1720
- view_before: null, // the charts before_ms viewport, when the highlight was setup
1721
- state: null, // the chart the highlight was setup
1722
-
1723
- isActive: function() {
1724
- return (this.after !== null && this.before !== null);
1725
- },
2079
+ if (state.tmp.dygraph_highlight_after !== null) {
2080
+ //console.log('highlight selection...');
2081
1727
- hasViewport: function() {
1728
- return (this.state !== null && this.view_after !== null && this.view_before !== null);
1729
- },
2082
+ NETDATA.globalSelectionSync.stop();
2083
+ NETDATA.globalSelectionSync.delay();
2084
1731
- init: function(state, after, before, view_after, view_before) {
1732
- this.state = (typeof state !== 'undefined') ? state : null;
1733
- this.after = (typeof after !== 'undefined' && after !== null && after > 0) ? after : null;
1734
- this.before = (typeof before !== 'undefined' && before !== null && before > 0) ? before : null;
1735
- this.view_after = (typeof view_after !== 'undefined' && view_after !== null && view_after > 0) ? view_after : null;
1736
- this.view_before = (typeof view_before !== 'undefined' && view_before !== null && view_before > 0) ? view_before : null;
1737
- },
2085
+ state.tmp.dygraph_user_action = true;
2086
+ Dygraph.moveZoom(event, dygraph, context);
2087
+ event.preventDefault();
2088
+ } else if (context.isPanning) {
2089
+ //console.log('panning...');
2090
1739
- setup: function() {
1740
- if(this.isActive() === true) {
1741
- if (this.state === null)
1742
- this.state = NETDATA.options.targets[0];
2091
+ NETDATA.globalSelectionSync.stop();
2092
+ NETDATA.globalSelectionSync.delay();
2093
1744
- if (typeof this.callback === 'function')
1745
- this.callback(true, this.after, this.before);
1746
- }
1747
- else {
1748
- if (typeof this.callback === 'function')
1749
- this.callback(false, 0, 0);
1750
- }
1751
- },
2094
+ state.tmp.dygraph_user_action = true;
2095
+ //NETDATA.globalSelectionSync.stop();
2096
+ //NETDATA.globalSelectionSync.delay();
2097
+ state.setMode('pan');
2098
+ context.is2DPan = false;
2099
+ Dygraph.movePan(event, dygraph, context);
2100
+ } else if (context.isZooming) {
2101
+ //console.log('zooming...');
2102
1753
- set: function(state, after, before, view_after, view_before) {
1754
- if(after > before) {
1755
- var t = after;
1756
- after = before;
1757
- before = t;
1758
- }
2103
+ NETDATA.globalSelectionSync.stop();
2104
+ NETDATA.globalSelectionSync.delay();
2105
1760
- this.init(state, after, before, view_after, view_before);
2106
+ state.tmp.dygraph_user_action = true;
2107
+ //NETDATA.globalSelectionSync.stop();
2108
+ //NETDATA.globalSelectionSync.delay();
2109
+ state.setMode('zoom');
2110
+ Dygraph.moveZoom(event, dygraph, context);
2111
+ }
2112
+ },
2113
+ mouseup: function (event, dygraph, context) {
2114
+ state.tmp.dygraph_mouse_down = false;
2115
1762
- if (this.hasViewport() === true)
1763
- NETDATA.globalPanAndZoom.setMaster(this.state, this.view_after, this.view_before);
2116
+ if (NETDATA.options.debug.dygraph || state.debug) {
2117
+ state.log('interactionModel.mouseup()');
2118
+ }
2119
1765
- this.setup();
1766
- },
2120
+ if (state.tmp.dygraph_highlight_after !== null) {
2121
+ //console.log('done highlight selection');
2122
1768
- clear: function() {
1769
- this.after = null;
1770
- this.before = null;
1771
- this.state = null;
1772
- this.view_after = null;
1773
- this.view_before = null;
2123
+ NETDATA.globalSelectionSync.stop();
2124
+ NETDATA.globalSelectionSync.delay();
2125
1775
- if(typeof this.callback === 'function')
1776
- this.callback(false, 0, 0);
1777
- },
2126
+ if (!(event.offsetX && event.offsetY)) {
2127
+ event.offsetX = event.layerX - event.target.offsetLeft;
2128
+ event.offsetY = event.layerY - event.target.offsetTop;
2129
+ }
2130
1779
- focus: function() {
1780
- if(this.isActive() === true && this.hasViewport() === true) {
1781
- if(this.state === null)
1782
- this.state = NETDATA.options.targets[0];
2131
+ NETDATA.globalChartUnderlay.set(state
2132
+ , state.tmp.dygraph_highlight_after
2133
+ , dygraph.toDataXCoord(event.offsetX)
2134
+ , state.view_after
2135
+ , state.view_before
2136
+ );
2137
1784
- if(NETDATA.globalPanAndZoom.isMaster(this.state) === true)
1785
- NETDATA.globalPanAndZoom.clearMaster();
2138
+ state.tmp.dygraph_highlight_after = null;
2139
1787
- NETDATA.globalPanAndZoom.setMaster(this.state, this.view_after, this.view_before, true);
1788
- }
1789
- }
1790
- };
2140
+ context.isZooming = false;
2141
+ dygraph.clearZoomRect_();
2142
+ dygraph.drawGraph_(false);
2143
1792
- // ----------------------------------------------------------------------------------------------------------------
1793
- // dimensions selection
2144
+ // refresh all the charts immediately
2145
+ NETDATA.options.auto_refresher_stop_until = 0;
2146
+ } else if (context.isPanning) {
2147
+ //console.log('done panning');
2148
1795
- // TODO
1796
- // move color assignment to dimensions, here
2149
+ NETDATA.globalSelectionSync.stop();
2150
+ NETDATA.globalSelectionSync.delay();
2151
1798
- var dimensionStatus = function(parent, label, name_div, value_div, color) {
1799
- this.enabled = false;
1800
- this.parent = parent;
1801
- this.label = label;
1802
- this.name_div = null;
1803
- this.value_div = null;
1804
- this.color = NETDATA.themes.current.foreground;
1805
- this.selected = (parent.unselected_count === 0);
1806
-
1807
- this.setOptions(name_div, value_div, color);
1808
- };
2152
+ state.tmp.dygraph_user_action = true;
2153
+ Dygraph.endPan(event, dygraph, context);
2154
1810
- dimensionStatus.prototype.invalidate = function() {
1811
- this.name_div = null;
1812
- this.value_div = null;
1813
- this.enabled = false;
1814
- };
2155
+ // refresh all the charts immediately
2156
+ NETDATA.options.auto_refresher_stop_until = 0;
2157
+ } else if (context.isZooming) {
2158
+ //console.log('done zomming');
2159
1816
- dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
1817
- this.color = color;
2160
+ NETDATA.globalSelectionSync.stop();
2161
+ NETDATA.globalSelectionSync.delay();
2162
1819
- if(this.name_div !== name_div) {
1820
- this.name_div = name_div;
1821
- this.name_div.title = this.label;
1822
- this.name_div.style.setProperty('color', this.color, 'important');
1823
- if(this.selected === false)
1824
- this.name_div.className = 'netdata-legend-name not-selected';
1825
- else
1826
- this.name_div.className = 'netdata-legend-name selected';
1827
- }
2163
+ state.tmp.dygraph_user_action = true;
2164
+ Dygraph.endZoom(event, dygraph, context);
2165
1829
- if(this.value_div !== value_div) {
1830
- this.value_div = value_div;
1831
- this.value_div.title = this.label;
1832
- this.value_div.style.setProperty('color', this.color, 'important');
1833
- if(this.selected === false)
1834
- this.value_div.className = 'netdata-legend-value not-selected';
1835
- else
1836
- this.value_div.className = 'netdata-legend-value selected';
1837
- }
2166
+ // refresh all the charts immediately
2167
+ NETDATA.options.auto_refresher_stop_until = 0;
2168
+ }
2169
+ },
2170
+ click: function (event, dygraph, context) {
2171
+ void(dygraph);
2172
+ void(context);
2173
1839
- this.enabled = true;
1840
- this.setHandler();
1841
- };
2174
+ if (NETDATA.options.debug.dygraph || state.debug) {
2175
+ state.log('interactionModel.click()');
2176
+ }
2177
1843
- dimensionStatus.prototype.setHandler = function() {
1844
- if(this.enabled === false) return;
2178
+ event.preventDefault();
2179
+ },
2180
+ dblclick: function (event, dygraph, context) {
2181
+ void(event);
2182
+ void(dygraph);
2183
+ void(context);
2184
1846
- var ds = this;
2185
+ if (NETDATA.options.debug.dygraph || state.debug) {
2186
+ state.log('interactionModel.dblclick()');
2187
+ }
2188
+ NETDATA.resetAllCharts(state);
2189
+ },
2190
+ wheel: function (event, dygraph, context) {
2191
+ void(context);
2192
1848
- // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
1849
- this.name_div.onclick = this.value_div.onclick = function(e) {
1850
- e.preventDefault();
1851
- if(ds.isSelected()) {
1852
- // this is selected
1853
- if(e.shiftKey === true || e.ctrlKey === true) {
1854
- // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
1855
- ds.unselect();
2193
+ if (NETDATA.options.debug.dygraph || state.debug) {
2194
+ state.log('interactionModel.wheel()');
2195
+ }
2196
1857
- if(ds.parent.countSelected() === 0)
1858
- ds.parent.selectAll();
2197
+ // Take the offset of a mouse event on the dygraph canvas and
2198
+ // convert it to a pair of percentages from the bottom left.
2199
+ // (Not top left, bottom is where the lower value is.)
2200
+ function offsetToPercentage(g, offsetX, offsetY) {
2201
+ // This is calculating the pixel offset of the leftmost date.
2202
+ let xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
2203
+ let yar0 = g.yAxisRange(0);
2204
+
2205
+ // This is calculating the pixel of the highest value. (Top pixel)
2206
+ let yOffset = g.toDomCoords(null, yar0[1])[1];
2207
+
2208
+ // x y w and h are relative to the corner of the drawing area,
2209
+ // so that the upper corner of the drawing area is (0, 0).
2210
+ let x = offsetX - xOffset;
2211
+ let y = offsetY - yOffset;
2212
+
2213
+ // This is computing the rightmost pixel, effectively defining the
2214
+ // width.
2215
+ let w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
2216
+
2217
+ // This is computing the lowest pixel, effectively defining the height.
2218
+ let h = g.toDomCoords(null, yar0[0])[1] - yOffset;
2219
+
2220
+ // Percentage from the left.
2221
+ let xPct = w === 0 ? 0 : (x / w);
2222
+ // Percentage from the top.
2223
+ let yPct = h === 0 ? 0 : (y / h);
2224
+
2225
+ // The (1-) part below changes it from "% distance down from the top"
2226
+ // to "% distance up from the bottom".
2227
+ return [xPct, (1 - yPct)];
2228
}
1860
- else {
1861
- // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
1862
- if(ds.parent.countSelected() === 1) {
1863
- ds.parent.selectAll();
2229
+
2230
+ // Adjusts [x, y] toward each other by zoomInPercentage%
2231
+ // Split it so the left/bottom axis gets xBias/yBias of that change and
2232
+ // tight/top gets (1-xBias)/(1-yBias) of that change.
2233
+ //
2234
+ // If a bias is missing it splits it down the middle.
2235
+ function zoomRange(g, zoomInPercentage, xBias, yBias) {
2236
+ xBias = xBias || 0.5;
2237
+ yBias = yBias || 0.5;
2238
+
2239
+ function adjustAxis(axis, zoomInPercentage, bias) {
2240
+ let delta = axis[1] - axis[0];
2241
+ let increment = delta * zoomInPercentage;
2242
+ let foo = [increment * bias, increment * (1 - bias)];
2243
+
2244
+ return [axis[0] + foo[0], axis[1] - foo[1]];
2245
}
1865
- else {
1866
- ds.parent.selectNone();
1867
- ds.select();
2246
+
2247
+ let yAxes = g.yAxisRanges();
2248
+ let newYAxes = [];
2249
+ for (let i = 0; i < yAxes.length; i++) {
2250
+ newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
2251
}
2252
+
2253
+ return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
2254
}
1870
- }
1871
- else {
1872
- // this is not selected
1873
- if(e.shiftKey === true || e.ctrlKey === true) {
1874
- // control or shift key is pressed -> select this too
1875
- ds.select();
1876
- }
1877
- else {
1878
- // no key is pressed -> select only this
1879
- ds.parent.selectNone();
1880
- ds.select();
1881
- }
1882
- }
2255
1884
- ds.parent.state.redrawChart();
1885
- }
1886
- };
2256
+ if (event.altKey || event.shiftKey) {
2257
+ state.tmp.dygraph_user_action = true;
2258
1888
- dimensionStatus.prototype.select = function() {
1889
- if(this.enabled === false) return;
2259
+ NETDATA.globalSelectionSync.stop();
2260
+ NETDATA.globalSelectionSync.delay();
2261
1891
- this.name_div.className = 'netdata-legend-name selected';
1892
- this.value_div.className = 'netdata-legend-value selected';
1893
- this.selected = true;
1894
- };
2262
+ // http://dygraphs.com/gallery/interaction-api.js
2263
+ let normal_def;
2264
+ if (typeof event.wheelDelta === 'number' && !isNaN(event.wheelDelta))
2265
+ // chrome
2266
+ {
2267
+ normal_def = event.wheelDelta / 40;
2268
+ } else
2269
+ // firefox
2270
+ {
2271
+ normal_def = event.deltaY * -1.2;
2272
+ }
2273
1896
- dimensionStatus.prototype.unselect = function() {
1897
- if(this.enabled === false) return;
2274
+ let normal = (event.detail) ? event.detail * -1 : normal_def;
2275
+ let percentage = normal / 50;
2276
1899
- this.name_div.className = 'netdata-legend-name not-selected';
1900
- this.value_div.className = 'netdata-legend-value hidden';
1901
- this.selected = false;
1902
- };
2277
+ if (!(event.offsetX && event.offsetY)) {
2278
+ event.offsetX = event.layerX - event.target.offsetLeft;
2279
+ event.offsetY = event.layerY - event.target.offsetTop;
2280
+ }
2281
1904
- dimensionStatus.prototype.isSelected = function() {
1905
- return(this.enabled === true && this.selected === true);
1906
- };
2282
+ let percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
2283
+ let xPct = percentages[0];
2284
+ let yPct = percentages[1];
2285
1908
- // ----------------------------------------------------------------------------------------------------------------
2286
+ let new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
2287
+ let after = new_x_range[0];
2288
+ let before = new_x_range[1];
2289
1910
- var dimensionsVisibility = function(state) {
1911
- this.state = state;
1912
- this.len = 0;
1913
- this.dimensions = {};
1914
- this.selected_count = 0;
1915
- this.unselected_count = 0;
1916
- };
2290
+ let first = state.netdata_first + state.data_update_every;
2291
+ let last = state.netdata_last + state.data_update_every;
2292
1918
- dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
1919
- if(typeof this.dimensions[label] === 'undefined') {
1920
- this.len++;
1921
- this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
1922
- }
1923
- else
1924
- this.dimensions[label].setOptions(name_div, value_div, color);
2293
+ if (before > last) {
2294
+ after -= (before - last);
2295
+ before = last;
2296
+ }
2297
+ if (after < first) {
2298
+ after = first;
2299
+ }
2300
1926
- return this.dimensions[label];
1927
- };
1928
-
1929
- dimensionsVisibility.prototype.dimensionGet = function(label) {
1930
- return this.dimensions[label];
1931
- };
1932
-
1933
- dimensionsVisibility.prototype.invalidateAll = function() {
1934
- var keys = Object.keys(this.dimensions);
1935
- var len = keys.length;
1936
- while(len--)
1937
- this.dimensions[keys[len]].invalidate();
1938
- };
1939
-
1940
- dimensionsVisibility.prototype.selectAll = function() {
1941
- var keys = Object.keys(this.dimensions);
1942
- var len = keys.length;
1943
- while(len--)
1944
- this.dimensions[keys[len]].select();
1945
- };
1946
-
1947
- dimensionsVisibility.prototype.countSelected = function() {
1948
- var selected = 0;
1949
- var keys = Object.keys(this.dimensions);
1950
- var len = keys.length;
1951
- while(len--)
1952
- if(this.dimensions[keys[len]].isSelected()) selected++;
1953
-
1954
- return selected;
1955
- };
1956
-
1957
- dimensionsVisibility.prototype.selectNone = function() {
1958
- var keys = Object.keys(this.dimensions);
1959
- var len = keys.length;
1960
- while(len--)
1961
- this.dimensions[keys[len]].unselect();
1962
- };
1963
-
1964
- dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
1965
- var ret = [];
1966
- this.selected_count = 0;
1967
- this.unselected_count = 0;
1968
-
1969
- var len = array.length;
1970
- while(len--) {
1971
- var ds = this.dimensions[array[len]];
1972
- if(typeof ds === 'undefined') {
1973
- // console.log(array[i] + ' is not found');
1974
- ret.unshift(false);
1975
- }
1976
- else if(ds.isSelected()) {
1977
- ret.unshift(true);
1978
- this.selected_count++;
1979
- }
1980
- else {
1981
- ret.unshift(false);
1982
- this.unselected_count++;
1983
- }
1984
- }
1985
-
1986
- if(this.selected_count === 0 && this.unselected_count !== 0) {
1987
- this.selectAll();
1988
- return this.selected2BooleanArray(array);
1989
- }
1990
-
1991
- return ret;
1992
- };
1993
-
1994
-
1995
- // ----------------------------------------------------------------------------------------------------------------
1996
- // date/time conversion
2301
+ state.setMode('zoom');
2302
+ state.updateChartPanOrZoom(after, before, function () {
2303
+ dygraph.updateOptions({dateWindow: [after, before]});
2304
+ });
2305
1998
- NETDATA.dateTime = {
1999
- using_timezone: false,
2306
+ event.preventDefault();
2307
+ }
2308
+ },
2309
+ touchstart: function (event, dygraph, context) {
2310
+ state.tmp.dygraph_mouse_down = true;
2311
2001
- // these are the old netdata functions
2002
- // we fallback to these, if the new ones fail
2312
+ if (NETDATA.options.debug.dygraph || state.debug) {
2313
+ state.log('interactionModel.touchstart()');
2314
+ }
2315
2004
- localeDateStringNative: function(d) {
2005
- return d.toLocaleDateString();
2006
- },
2316
+ state.tmp.dygraph_user_action = true;
2317
+ state.setMode('zoom');
2318
+ state.pauseChart();
2319
2008
- localeTimeStringNative: function(d) {
2009
- return d.toLocaleTimeString();
2010
- },
2320
+ NETDATA.globalSelectionSync.stop();
2321
+ NETDATA.globalSelectionSync.delay();
2322
2012
- xAxisTimeStringNative: function(d) {
2013
- return NETDATA.zeropad(d.getHours()) + ":"
2014
- + NETDATA.zeropad(d.getMinutes()) + ":"
2015
- + NETDATA.zeropad(d.getSeconds());
2016
- },
2323
+ Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
2324
2018
- // initialize the new date/time conversion
2019
- // functions.
2020
- // if this fails, we fallback to the above
2021
- init: function(timezone) {
2022
- //console.log('init with timezone: ' + timezone);
2325
+ // we overwrite the touch directions at the end, to overwrite
2326
+ // the internal default of dygraph
2327
+ context.touchDirections = {x: true, y: false};
2328
2024
- // detect browser timezone
2025
- try {
2026
- NETDATA.options.browser_timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
2027
- }
2028
- catch(e) {
2029
- console.log('failed to detect browser timezone: ' + e.toString());
2030
- NETDATA.options.browser_timezone = 'cannot-detect-it';
2031
- }
2329
+ state.dygraph_last_touch_start = Date.now();
2330
+ state.dygraph_last_touch_move = 0;
2331
2033
- var ret = false;
2332
+ if (typeof event.touches[0].pageX === 'number') {
2333
+ state.dygraph_last_touch_page_x = event.touches[0].pageX;
2334
+ } else {
2335
+ state.dygraph_last_touch_page_x = 0;
2336
+ }
2337
+ },
2338
+ touchmove: function (event, dygraph, context) {
2339
+ if (NETDATA.options.debug.dygraph || state.debug) {
2340
+ state.log('interactionModel.touchmove()');
2341
+ }
2342
2035
- try {
2036
- var dateOptions ={
2037
- localeMatcher: 'best fit',
2038
- formatMatcher: 'best fit',
2039
- weekday: 'short',
2040
- year: 'numeric',
2041
- month: 'short',
2042
- day: '2-digit'
2043
- };
2343
+ NETDATA.globalSelectionSync.stop();
2344
+ NETDATA.globalSelectionSync.delay();
2345
2045
- var timeOptions = {
2046
- localeMatcher: 'best fit',
2047
- hour12: false,
2048
- formatMatcher: 'best fit',
2049
- hour: '2-digit',
2050
- minute: '2-digit',
2051
- second: '2-digit'
2052
- };
2346
+ state.tmp.dygraph_user_action = true;
2347
+ Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
2348
2054
- var xAxisOptions = {
2055
- localeMatcher: 'best fit',
2056
- hour12: false,
2057
- formatMatcher: 'best fit',
2058
- hour: '2-digit',
2059
- minute: '2-digit',
2060
- second: '2-digit'
2061
- };
2349
+ state.dygraph_last_touch_move = Date.now();
2350
+ },
2351
+ touchend: function (event, dygraph, context) {
2352
+ state.tmp.dygraph_mouse_down = false;
2353
2063
- if(typeof timezone === 'string' && timezone !== '' && timezone !== 'default') {
2064
- dateOptions.timeZone = timezone;
2065
- timeOptions.timeZone = timezone;
2066
- timeOptions.timeZoneName = 'short';
2067
- xAxisOptions.timeZone = timezone;
2068
- this.using_timezone = true;
2069
- }
2070
- else {
2071
- timezone = 'default';
2072
- this.using_timezone = false;
2354
+ if (NETDATA.options.debug.dygraph || state.debug) {
2355
+ state.log('interactionModel.touchend()');
2356
}
2357
2075
- this.dateFormat = new Intl.DateTimeFormat(navigator.language, dateOptions);
2076
- this.timeFormat = new Intl.DateTimeFormat(navigator.language, timeOptions);
2077
- this.xAxisFormat = new Intl.DateTimeFormat(navigator.language, xAxisOptions);
2358
+ NETDATA.globalSelectionSync.stop();
2359
+ NETDATA.globalSelectionSync.delay();
2360
2079
- this.localeDateString = function(d) {
2080
- return this.dateFormat.format(d);
2081
- };
2361
+ state.tmp.dygraph_user_action = true;
2362
+ Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
2363
2083
- this.localeTimeString = function(d) {
2084
- return this.timeFormat.format(d);
2085
- };
2364
+ // if it didn't move, it is a selection
2365
+ if (state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
2366
+ NETDATA.globalSelectionSync.dont_sync_before = 0;
2367
+ NETDATA.globalSelectionSync.setMaster(state);
2368
2087
- this.xAxisTimeString = function(d) {
2088
- return this.xAxisFormat.format(d);
2089
- };
2369
+ // internal api of dygraph
2370
+ let pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
2371
+ console.log('pct: ' + pct.toString());
2372
2091
- //var d = new Date();
2092
- //var t = this.dateFormat.format(d) + ' ' + this.timeFormat.format(d) + ' ' + this.xAxisFormat.format(d);
2373
+ let t = Math.round(state.view_after + (state.view_before - state.view_after) * pct);
2374
+ if (NETDATA.dygraphSetSelection(state, t)) {
2375
+ NETDATA.globalSelectionSync.sync(state, t);
2376
+ }
2377
+ }
2378
2094
- ret = true;
2095
- }
2096
- catch(e) {
2097
- console.log('Cannot setup Date/Time formatting: ' + e.toString());
2379
+ // if it was double tap within double click time, reset the charts
2380
+ let now = Date.now();
2381
+ if (typeof state.dygraph_last_touch_end !== 'undefined') {
2382
+ if (state.dygraph_last_touch_move === 0) {
2383
+ let dt = now - state.dygraph_last_touch_end;
2384
+ if (dt <= NETDATA.options.current.double_click_speed) {
2385
+ NETDATA.resetAllCharts(state);
2386
+ }
2387
+ }
2388
+ }
2389
2099
- timezone = 'default';
2100
- this.localeDateString = this.localeDateStringNative;
2101
- this.localeTimeString = this.localeTimeStringNative;
2102
- this.xAxisTimeString = this.xAxisTimeStringNative;
2103
- this.using_timezone = false;
2390
+ // remember the timestamp of the last touch end
2391
+ state.dygraph_last_touch_end = now;
2392
2105
- ret = false;
2393
+ // refresh all the charts immediately
2394
+ NETDATA.options.auto_refresher_stop_until = 0;
2395
}
2107
-
2108
- // save it
2109
- //console.log('init setOption timezone: ' + timezone);
2110
- NETDATA.setOption('timezone', timezone);
2111
-
2112
- return ret;
2396
}
2397
};
2115
- NETDATA.dateTime.init(NETDATA.options.current.timezone);
2116
-
2117
-
2118
- // ----------------------------------------------------------------------------------------------------------------
2119
- // units conversion
2120
-
2121
- NETDATA.unitsConversion = {
2122
- keys: {}, // keys for data-common-units
2123
- latest: {}, // latest selected units for data-common-units
2398
2125
- globalReset: function() {
2126
- this.keys = {};
2127
- this.latest = {};
2128
- },
2399
+ if (NETDATA.chartLibraries.dygraph.isLogScale(state)) {
2400
+ if (Array.isArray(state.tmp.dygraph_options.valueRange) && state.tmp.dygraph_options.valueRange[0] <= 0) {
2401
+ state.tmp.dygraph_options.valueRange[0] = null;
2402
+ }
2403
+ }
2404
2130
- scalableUnits: {
2131
- 'packets/s': {
2132
- 'pps': 1,
2133
- 'Kpps': 1000,
2134
- 'Mpps': 1000000
2135
- },
2136
- 'pps': {
2137
- 'pps': 1,
2138
- 'Kpps': 1000,
2139
- 'Mpps': 1000000
2140
- },
2141
- 'kilobits/s': {
2142
- 'bits/s': 1 / 1000,
2143
- 'kilobits/s': 1,
2144
- 'megabits/s': 1000,
2145
- 'gigabits/s': 1000000,
2146
- 'terabits/s': 1000000000
2147
- },
2148
- 'kilobytes/s': {
2149
- 'bytes/s': 1 / 1024,
2150
- 'kilobytes/s': 1,
2151
- 'megabytes/s': 1024,
2152
- 'gigabytes/s': 1024 * 1024,
2153
- 'terabytes/s': 1024 * 1024 * 1024
2154
- },
2155
- 'KB/s': {
2156
- 'B/s': 1 / 1024,
2157
- 'KB/s': 1,
2158
- 'MB/s': 1024,
2159
- 'GB/s': 1024 * 1024,
2160
- 'TB/s': 1024 * 1024 * 1024
2161
- },
2162
- 'KB': {
2163
- 'B': 1 / 1024,
2164
- 'KB': 1,
2165
- 'MB': 1024,
2166
- 'GB': 1024 * 1024,
2167
- 'TB': 1024 * 1024 * 1024
2168
- },
2169
- 'MB': {
2170
- 'B': 1 / (1024 * 1024),
2171
- 'KB': 1 / 1024,
2172
- 'MB': 1,
2173
- 'GB': 1024,
2174
- 'TB': 1024 * 1024,
2175
- 'PB': 1024 * 1024 * 1024
2176
- },
2177
- 'GB': {
2178
- 'B': 1 / (1024 * 1024 * 1024),
2179
- 'KB': 1 / (1024 * 1024),
2180
- 'MB': 1 / 1024,
2181
- 'GB': 1,
2182
- 'TB': 1024,
2183
- 'PB': 1024 * 1024,
2184
- 'EB': 1024 * 1024 * 1024
2185
- }
2186
- /*
2187
- 'milliseconds': {
2188
- 'seconds': 1000
2189
- },
2190
- 'seconds': {
2191
- 'milliseconds': 0.001,
2192
- 'seconds': 1,
2193
- 'minutes': 60,
2194
- 'hours': 3600,
2195
- 'days': 86400
2196
- }
2197
- */
2198
- },
2405
+ if (NETDATA.chartLibraries.dygraph.isSparkline(state)) {
2406
+ state.tmp.dygraph_options.drawGrid = false;
2407
+ state.tmp.dygraph_options.drawAxis = false;
2408
+ state.tmp.dygraph_options.title = undefined;
2409
+ state.tmp.dygraph_options.ylabel = undefined;
2410
+ state.tmp.dygraph_options.yLabelWidth = 0;
2411
+ //state.tmp.dygraph_options.labelsDivWidth = 120;
2412
+ //state.tmp.dygraph_options.labelsDivStyles.width = '120px';
2413
+ state.tmp.dygraph_options.labelsSeparateLines = true;
2414
+ state.tmp.dygraph_options.rightGap = 0;
2415
+ state.tmp.dygraph_options.yRangePad = 1;
2416
+ state.tmp.dygraph_options.axes.x.drawAxis = false;
2417
+ state.tmp.dygraph_options.axes.y.drawAxis = false;
2418
+ }
2419
2200
- convertibleUnits: {
2201
- 'Celsius': {
2202
- 'Fahrenheit': {
2203
- check: function(max) { void(max); return NETDATA.options.current.temperature === 'fahrenheit'; },
2204
- convert: function(value) { return value * 9 / 5 + 32; }
2205
- }
2206
- },
2207
- 'celsius': {
2208
- 'fahrenheit': {
2209
- check: function(max) { void(max); return NETDATA.options.current.temperature === 'fahrenheit'; },
2210
- convert: function(value) { return value * 9 / 5 + 32; }
2211
- }
2212
- },
2213
- 'seconds': {
2214
- 'time': {
2215
- check: function (max) { void(max); return NETDATA.options.current.seconds_as_time; },
2216
- convert: function(seconds) { return NETDATA.unitsConversion.seconds2time(seconds); }
2217
- }
2218
- },
2219
- 'milliseconds': {
2220
- 'milliseconds': {
2221
- check: function (max) { return NETDATA.options.current.seconds_as_time && max < 1000; },
2222
- convert: function(milliseconds) {
2223
- var tms = Math.round(milliseconds * 10);
2224
- milliseconds = Math.floor(tms / 10);
2420
+ if (smooth) {
2421
+ state.tmp.dygraph_smooth_eligible = true;
2422
2226
- tms -= milliseconds * 10;
2423
+ if (NETDATA.options.current.smooth_plot) {
2424
+ state.tmp.dygraph_options.plotter = smoothPlotter;
2425
+ }
2426
+ }
2427
+ else {
2428
+ state.tmp.dygraph_smooth_eligible = false;
2429
+ }
2430
2228
- return (milliseconds).toString() + '.' + tms.toString();
2229
- }
2230
- },
2231
- 'seconds': {
2232
- check: function (max) { return NETDATA.options.current.seconds_as_time && max >= 1000 && max < 60000; },
2233
- convert: function(milliseconds) {
2234
- milliseconds = Math.round(milliseconds);
2431
+ if (netdataSnapshotData !== null && NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(state) === false) {
2432
+ // pan and zoom on snapshots
2433
+ state.tmp.dygraph_options.dateWindow = [NETDATA.globalPanAndZoom.force_after_ms, NETDATA.globalPanAndZoom.force_before_ms];
2434
+ //state.tmp.dygraph_options.isZoomedIgnoreProgrammaticZoom = true;
2435
+ }
2436
2236
- var seconds = Math.floor(milliseconds / 1000);
2237
- milliseconds -= seconds * 1000;
2437
+ state.tmp.dygraph_instance = new Dygraph(state.element_chart,
2438
+ data.result.data, state.tmp.dygraph_options);
2439
2239
- milliseconds = Math.round(milliseconds / 10);
2440
+ state.tmp.dygraph_force_zoom = false;
2441
+ state.tmp.dygraph_user_action = false;
2442
+ state.tmp.dygraph_last_rendered = Date.now();
2443
+ state.tmp.dygraph_highlight_after = null;
2444
2241
- return seconds.toString() + '.'
2242
- + NETDATA.zeropad(milliseconds);
2243
- }
2244
- },
2245
- 'M:SS.ms': {
2246
- check: function (max) { return NETDATA.options.current.seconds_as_time && max >= 60000; },
2247
- convert: function(milliseconds) {
2248
- milliseconds = Math.round(milliseconds);
2445
+ if (state.tmp.dygraph_options.valueRange[0] === null && state.tmp.dygraph_options.valueRange[1] === null) {
2446
+ if (typeof state.tmp.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
2447
+ state.tmp.__commonMin = NETDATA.dataAttribute(state.element, 'common-min', null);
2448
+ state.tmp.__commonMax = NETDATA.dataAttribute(state.element, 'common-max', null);
2449
+ } else {
2450
+ state.log('incompatible version of Dygraph detected');
2451
+ state.tmp.__commonMin = null;
2452
+ state.tmp.__commonMax = null;
2453
+ }
2454
+ } else {
2455
+ // if the user gave a valueRange, respect it
2456
+ state.tmp.__commonMin = null;
2457
+ state.tmp.__commonMax = null;
2458
+ }
2459
2250
- var minutes = Math.floor(milliseconds / 60000);
2251
- milliseconds -= minutes * 60000;
2460
+ return true;
2461
+};
2462
+// ----------------------------------------------------------------------------------------------------------------
2463
+// sparkline
2464
2253
- var seconds = Math.floor(milliseconds / 1000);
2254
- milliseconds -= seconds * 1000;
2465
+NETDATA.sparklineInitialize = function (callback) {
2466
+ if (typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
2467
+ $.ajax({
2468
+ url: NETDATA.sparkline_js,
2469
+ cache: true,
2470
+ dataType: "script",
2471
+ xhrFields: {withCredentials: true} // required for the cookie
2472
+ })
2473
+ .done(function () {
2474
+ NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
2475
+ })
2476
+ .fail(function () {
2477
+ NETDATA.chartLibraries.sparkline.enabled = false;
2478
+ NETDATA.error(100, NETDATA.sparkline_js);
2479
+ })
2480
+ .always(function () {
2481
+ if (typeof callback === "function") {
2482
+ return callback();
2483
+ }
2484
+ });
2485
+ } else {
2486
+ NETDATA.chartLibraries.sparkline.enabled = false;
2487
+ if (typeof callback === "function") {
2488
+ return callback();
2489
+ }
2490
+ }
2491
+};
2492
+
2493
+NETDATA.sparklineChartUpdate = function (state, data) {
2494
+ state.sparkline_options.width = state.chartWidth();
2495
+ state.sparkline_options.height = state.chartHeight();
2496
+
2497
+ $(state.element_chart).sparkline(data.result, state.sparkline_options);
2498
+ return true;
2499
+};
2500
+
2501
+NETDATA.sparklineChartCreate = function (state, data) {
2502
+ let type = NETDATA.dataAttribute(state.element, 'sparkline-type', 'line');
2503
+ let lineColor = NETDATA.dataAttribute(state.element, 'sparkline-linecolor', state.chartCustomColors()[0]);
2504
+ let fillColor = NETDATA.dataAttribute(state.element, 'sparkline-fillcolor', ((state.chart.chart_type === 'line') ? NETDATA.themes.current.background : NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance)));
2505
+ let chartRangeMin = NETDATA.dataAttribute(state.element, 'sparkline-chartrangemin', undefined);
2506
+ let chartRangeMax = NETDATA.dataAttribute(state.element, 'sparkline-chartrangemax', undefined);
2507
+ let composite = NETDATA.dataAttribute(state.element, 'sparkline-composite', undefined);
2508
+ let enableTagOptions = NETDATA.dataAttribute(state.element, 'sparkline-enabletagoptions', undefined);
2509
+ let tagOptionPrefix = NETDATA.dataAttribute(state.element, 'sparkline-tagoptionprefix', undefined);
2510
+ let tagValuesAttribute = NETDATA.dataAttribute(state.element, 'sparkline-tagvaluesattribute', undefined);
2511
+ let disableHiddenCheck = NETDATA.dataAttribute(state.element, 'sparkline-disablehiddencheck', undefined);
2512
+ let defaultPixelsPerValue = NETDATA.dataAttribute(state.element, 'sparkline-defaultpixelspervalue', undefined);
2513
+ let spotColor = NETDATA.dataAttribute(state.element, 'sparkline-spotcolor', undefined);
2514
+ let minSpotColor = NETDATA.dataAttribute(state.element, 'sparkline-minspotcolor', undefined);
2515
+ let maxSpotColor = NETDATA.dataAttribute(state.element, 'sparkline-maxspotcolor', undefined);
2516
+ let spotRadius = NETDATA.dataAttribute(state.element, 'sparkline-spotradius', undefined);
2517
+ let valueSpots = NETDATA.dataAttribute(state.element, 'sparkline-valuespots', undefined);
2518
+ let highlightSpotColor = NETDATA.dataAttribute(state.element, 'sparkline-highlightspotcolor', undefined);
2519
+ let highlightLineColor = NETDATA.dataAttribute(state.element, 'sparkline-highlightlinecolor', undefined);
2520
+ let lineWidth = NETDATA.dataAttribute(state.element, 'sparkline-linewidth', undefined);
2521
+ let normalRangeMin = NETDATA.dataAttribute(state.element, 'sparkline-normalrangemin', undefined);
2522
+ let normalRangeMax = NETDATA.dataAttribute(state.element, 'sparkline-normalrangemax', undefined);
2523
+ let drawNormalOnTop = NETDATA.dataAttribute(state.element, 'sparkline-drawnormalontop', undefined);
2524
+ let xvalues = NETDATA.dataAttribute(state.element, 'sparkline-xvalues', undefined);
2525
+ let chartRangeClip = NETDATA.dataAttribute(state.element, 'sparkline-chartrangeclip', undefined);
2526
+ let chartRangeMinX = NETDATA.dataAttribute(state.element, 'sparkline-chartrangeminx', undefined);
2527
+ let chartRangeMaxX = NETDATA.dataAttribute(state.element, 'sparkline-chartrangemaxx', undefined);
2528
+ let disableInteraction = NETDATA.dataAttributeBoolean(state.element, 'sparkline-disableinteraction', false);
2529
+ let disableTooltips = NETDATA.dataAttributeBoolean(state.element, 'sparkline-disabletooltips', false);
2530
+ let disableHighlight = NETDATA.dataAttributeBoolean(state.element, 'sparkline-disablehighlight', false);
2531
+ let highlightLighten = NETDATA.dataAttribute(state.element, 'sparkline-highlightlighten', 1.4);
2532
+ let highlightColor = NETDATA.dataAttribute(state.element, 'sparkline-highlightcolor', undefined);
2533
+ let tooltipContainer = NETDATA.dataAttribute(state.element, 'sparkline-tooltipcontainer', undefined);
2534
+ let tooltipClassname = NETDATA.dataAttribute(state.element, 'sparkline-tooltipclassname', undefined);
2535
+ let tooltipFormat = NETDATA.dataAttribute(state.element, 'sparkline-tooltipformat', undefined);
2536
+ let tooltipPrefix = NETDATA.dataAttribute(state.element, 'sparkline-tooltipprefix', undefined);
2537
+ let tooltipSuffix = NETDATA.dataAttribute(state.element, 'sparkline-tooltipsuffix', ' ' + state.units_current);
2538
+ let tooltipSkipNull = NETDATA.dataAttributeBoolean(state.element, 'sparkline-tooltipskipnull', true);
2539
+ let tooltipValueLookups = NETDATA.dataAttribute(state.element, 'sparkline-tooltipvaluelookups', undefined);
2540
+ let tooltipFormatFieldlist = NETDATA.dataAttribute(state.element, 'sparkline-tooltipformatfieldlist', undefined);
2541
+ let tooltipFormatFieldlistKey = NETDATA.dataAttribute(state.element, 'sparkline-tooltipformatfieldlistkey', undefined);
2542
+ let numberFormatter = NETDATA.dataAttribute(state.element, 'sparkline-numberformatter', function (n) {
2543
+ return n.toFixed(2);
2544
+ });
2545
+ let numberDigitGroupSep = NETDATA.dataAttribute(state.element, 'sparkline-numberdigitgroupsep', undefined);
2546
+ let numberDecimalMark = NETDATA.dataAttribute(state.element, 'sparkline-numberdecimalmark', undefined);
2547
+ let numberDigitGroupCount = NETDATA.dataAttribute(state.element, 'sparkline-numberdigitgroupcount', undefined);
2548
+ let animatedZooms = NETDATA.dataAttributeBoolean(state.element, 'sparkline-animatedzooms', false);
2549
2256
- milliseconds = Math.round(milliseconds / 10);
2550
+ if (spotColor === 'disable') {
2551
+ spotColor = '';
2552
+ }
2553
+ if (minSpotColor === 'disable') {
2554
+ minSpotColor = '';
2555
+ }
2556
+ if (maxSpotColor === 'disable') {
2557
+ maxSpotColor = '';
2558
+ }
2559
2258
- return minutes.toString() + ':'
2259
- + NETDATA.zeropad(seconds) + '.'
2260
- + NETDATA.zeropad(milliseconds);
2261
- }
2560
+ // state.log('sparkline type ' + type + ', lineColor: ' + lineColor + ', fillColor: ' + fillColor);
2561
+
2562
+ state.sparkline_options = {
2563
+ type: type,
2564
+ lineColor: lineColor,
2565
+ fillColor: fillColor,
2566
+ chartRangeMin: chartRangeMin,
2567
+ chartRangeMax: chartRangeMax,
2568
+ composite: composite,
2569
+ enableTagOptions: enableTagOptions,
2570
+ tagOptionPrefix: tagOptionPrefix,
2571
+ tagValuesAttribute: tagValuesAttribute,
2572
+ disableHiddenCheck: disableHiddenCheck,
2573
+ defaultPixelsPerValue: defaultPixelsPerValue,
2574
+ spotColor: spotColor,
2575
+ minSpotColor: minSpotColor,
2576
+ maxSpotColor: maxSpotColor,
2577
+ spotRadius: spotRadius,
2578
+ valueSpots: valueSpots,
2579
+ highlightSpotColor: highlightSpotColor,
2580
+ highlightLineColor: highlightLineColor,
2581
+ lineWidth: lineWidth,
2582
+ normalRangeMin: normalRangeMin,
2583
+ normalRangeMax: normalRangeMax,
2584
+ drawNormalOnTop: drawNormalOnTop,
2585
+ xvalues: xvalues,
2586
+ chartRangeClip: chartRangeClip,
2587
+ chartRangeMinX: chartRangeMinX,
2588
+ chartRangeMaxX: chartRangeMaxX,
2589
+ disableInteraction: disableInteraction,
2590
+ disableTooltips: disableTooltips,
2591
+ disableHighlight: disableHighlight,
2592
+ highlightLighten: highlightLighten,
2593
+ highlightColor: highlightColor,
2594
+ tooltipContainer: tooltipContainer,
2595
+ tooltipClassname: tooltipClassname,
2596
+ tooltipChartTitle: state.title,
2597
+ tooltipFormat: tooltipFormat,
2598
+ tooltipPrefix: tooltipPrefix,
2599
+ tooltipSuffix: tooltipSuffix,
2600
+ tooltipSkipNull: tooltipSkipNull,
2601
+ tooltipValueLookups: tooltipValueLookups,
2602
+ tooltipFormatFieldlist: tooltipFormatFieldlist,
2603
+ tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
2604
+ numberFormatter: numberFormatter,
2605
+ numberDigitGroupSep: numberDigitGroupSep,
2606
+ numberDecimalMark: numberDecimalMark,
2607
+ numberDigitGroupCount: numberDigitGroupCount,
2608
+ animatedZooms: animatedZooms,
2609
+ width: state.chartWidth(),
2610
+ height: state.chartHeight()
2611
+ };
2612
+
2613
+ $(state.element_chart).sparkline(data.result, state.sparkline_options);
2614
+
2615
+ return true;
2616
+};
2617
+// google charts
2618
+
2619
+NETDATA.googleInitialize = function (callback) {
2620
+ if (typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
2621
+ $.ajax({
2622
+ url: NETDATA.google_js,
2623
+ cache: true,
2624
+ dataType: "script",
2625
+ xhrFields: {withCredentials: true} // required for the cookie
2626
+ })
2627
+ .done(function () {
2628
+ NETDATA.registerChartLibrary('google', NETDATA.google_js);
2629
+ google.load('visualization', '1.1', {
2630
+ 'packages': ['corechart', 'controls'],
2631
+ 'callback': callback
2632
+ });
2633
+ })
2634
+ .fail(function () {
2635
+ NETDATA.chartLibraries.google.enabled = false;
2636
+ NETDATA.error(100, NETDATA.google_js);
2637
+ if (typeof callback === "function") {
2638
+ return callback();
2639
}
2640
+ });
2641
+ } else {
2642
+ NETDATA.chartLibraries.google.enabled = false;
2643
+ if (typeof callback === "function") {
2644
+ return callback();
2645
+ }
2646
+ }
2647
+};
2648
+
2649
+NETDATA.googleChartUpdate = function (state, data) {
2650
+ let datatable = new google.visualization.DataTable(data.result);
2651
+ state.google_instance.draw(datatable, state.google_options);
2652
+ return true;
2653
+};
2654
+
2655
+NETDATA.googleChartCreate = function (state, data) {
2656
+ let datatable = new google.visualization.DataTable(data.result);
2657
+
2658
+ state.google_options = {
2659
+ colors: state.chartColors(),
2660
+
2661
+ // do not set width, height - the chart resizes itself
2662
+ //width: state.chartWidth(),
2663
+ //height: state.chartHeight(),
2664
+ lineWidth: 1,
2665
+ title: state.title,
2666
+ fontSize: 11,
2667
+ hAxis: {
2668
+ // title: "Time of Day",
2669
+ // format:'HH:mm:ss',
2670
+ viewWindowMode: 'maximized',
2671
+ slantedText: false,
2672
+ format: 'HH:mm:ss',
2673
+ textStyle: {
2674
+ fontSize: 9
2675
+ },
2676
+ gridlines: {
2677
+ color: '#EEE'
2678
}
2679
},
2265
-
2266
- seconds2time: function(seconds) {
2267
- seconds = Math.abs(seconds);
2268
-
2269
- var days = Math.floor(seconds / 86400);
2270
- seconds -= days * 86400;
2271
-
2272
- var hours = Math.floor(seconds / 3600);
2273
- seconds -= hours * 3600;
2274
-
2275
- var minutes = Math.floor(seconds / 60);
2276
- seconds -= minutes * 60;
2277
-
2278
- seconds = Math.round(seconds);
2279
-
2280
- var ms_txt = '';
2281
- /*
2282
- var ms = seconds - Math.floor(seconds);
2283
- seconds -= ms;
2284
- ms = Math.round(ms * 1000);
2285
-
2286
- if(ms > 1) {
2287
- if(ms < 10)
2288
- ms_txt = '.00' + ms.toString();
2289
- else if(ms < 100)
2290
- ms_txt = '.0' + ms.toString();
2291
- else
2292
- ms_txt = '.' + ms.toString();
2680
+ vAxis: {
2681
+ title: state.units_current,
2682
+ viewWindowMode: 'pretty',
2683
+ minValue: -0.1,
2684
+ maxValue: 0.1,
2685
+ direction: 1,
2686
+ textStyle: {
2687
+ fontSize: 9
2688
+ },
2689
+ gridlines: {
2690
+ color: '#EEE'
2691
}
2294
- */
2295
-
2296
- return ((days > 0)?days.toString() + 'd:':'').toString()
2297
- + NETDATA.zeropad(hours) + ':'
2298
- + NETDATA.zeropad(minutes) + ':'
2299
- + NETDATA.zeropad(seconds)
2300
- + ms_txt;
2692
},
2302
-
2303
- // get a function that converts the units
2304
- // + every time units are switched call the callback
2305
- get: function(uuid, min, max, units, desired_units, common_units_name, switch_units_callback) {
2306
- // validate the parameters
2307
- if(typeof units === 'undefined')
2308
- units = 'undefined';
2309
-
2310
- // check if we support units conversion
2311
- if(typeof this.scalableUnits[units] === 'undefined' && typeof this.convertibleUnits[units] === 'undefined') {
2312
- // we can't convert these units
2313
- //console.log('DEBUG: ' + uuid.toString() + ' can\'t convert units: ' + units.toString());
2314
- return function(value) { return value; };
2693
+ chartArea: {
2694
+ width: '65%',
2695
+ height: '80%'
2696
+ },
2697
+ focusTarget: 'category',
2698
+ annotation: {
2699
+ '1': {
2700
+ style: 'line'
2701
}
2316
-
2317
- // check if the caller wants the original units
2318
- if(typeof desired_units === 'undefined' || desired_units === null || desired_units === 'original' || desired_units === units) {
2319
- //console.log('DEBUG: ' + uuid.toString() + ' original units wanted');
2320
- switch_units_callback(units);
2321
- return function(value) { return value; };
2702
+ },
2703
+ pointsVisible: 0,
2704
+ titlePosition: 'out',
2705
+ titleTextStyle: {
2706
+ fontSize: 11
2707
+ },
2708
+ tooltip: {
2709
+ isHtml: false,
2710
+ ignoreBounds: true,
2711
+ textStyle: {
2712
+ fontSize: 9
2713
}
2714
+ },
2715
+ curveType: 'function',
2716
+ areaOpacity: 0.3,
2717
+ isStacked: false
2718
+ };
2719
+
2720
+ switch (state.chart.chart_type) {
2721
+ case "area":
2722
+ state.google_options.vAxis.viewWindowMode = 'maximized';
2723
+ state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
2724
+ state.google_instance = new google.visualization.AreaChart(state.element_chart);
2725
+ break;
2726
+
2727
+ case "stacked":
2728
+ state.google_options.isStacked = true;
2729
+ state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
2730
+ state.google_options.vAxis.viewWindowMode = 'maximized';
2731
+ state.google_options.vAxis.minValue = null;
2732
+ state.google_options.vAxis.maxValue = null;
2733
+ state.google_instance = new google.visualization.AreaChart(state.element_chart);
2734
+ break;
2735
+
2736
+ default:
2737
+ case "line":
2738
+ state.google_options.lineWidth = 2;
2739
+ state.google_instance = new google.visualization.LineChart(state.element_chart);
2740
+ break;
2741
+ }
2742
2324
- // now we know we can convert the units
2325
- // and the caller wants some kind of conversion
2326
-
2327
- var tunits = null;
2328
- var tdivider = 0;
2329
- var x;
2330
-
2331
- if(typeof this.scalableUnits[units] !== 'undefined') {
2332
- // units that can be scaled
2333
- // we decide a divider
2334
-
2335
- // console.log('NETDATA.unitsConversion.get(' + units.toString() + ', ' + desired_units.toString() + ', function()) decide divider with min = ' + min.toString() + ', max = ' + max.toString());
2336
-
2337
- if (desired_units === 'auto') {
2338
- // the caller wants to auto-scale the units
2339
-
2340
- // find the absolute maximum value that is rendered on the chart
2341
- // based on this we decide the scale
2342
- min = Math.abs(min);
2343
- max = Math.abs(max);
2344
- if (min > max) max = min;
2345
-
2346
- // find the smallest scale that provides integers
2347
- for (x in this.scalableUnits[units]) {
2348
- if (this.scalableUnits[units].hasOwnProperty(x)) {
2349
- var m = this.scalableUnits[units][x];
2350
- if (m <= max && m > tdivider) {
2351
- tunits = x;
2352
- tdivider = m;
2353
- }
2354
- }
2355
- }
2356
-
2357
- if(tunits === null || tdivider <= 0) {
2358
- // we couldn't find one
2359
- //console.log('DEBUG: ' + uuid.toString() + ' cannot find an auto-scaling candidate for units: ' + units.toString() + ' (max: ' + max.toString() + ')');
2360
- switch_units_callback(units);
2361
- return function(value) { return value; };
2362
- }
2363
-
2364
- if(typeof common_units_name === 'string' && typeof uuid === 'string') {
2365
- // the caller wants several charts to have the same units
2366
- // data-common-units
2367
-
2368
- var common_units_key = common_units_name + '-' + units;
2369
-
2370
- // add our divider into the list of keys
2371
- var t = this.keys[common_units_key];
2372
- if(typeof t === 'undefined') {
2373
- this.keys[common_units_key] = {};
2374
- t = this.keys[common_units_key];
2375
- }
2376
- t[uuid] = {
2377
- units: tunits,
2378
- divider: tdivider
2379
- };
2380
-
2381
- // find the max divider of all charts
2382
- var common_units = t[uuid];
2383
- for(x in t) {
2384
- if (t.hasOwnProperty(x) && t[x].divider > common_units.divider)
2385
- common_units = t[x];
2386
- }
2387
-
2388
- // save our common_max to the latest keys
2389
- var latest = this.latest[common_units_key];
2390
- if(typeof latest === 'undefined') {
2391
- this.latest[common_units_key] = {};
2392
- latest = this.latest[common_units_key];
2393
- }
2394
- latest.units = common_units.units;
2395
- latest.divider = common_units.divider;
2396
-
2397
- tunits = latest.units;
2398
- tdivider = latest.divider;
2399
-
2400
- //console.log('DEBUG: ' + uuid.toString() + ' converted units: ' + units.toString() + ' to units: ' + tunits.toString() + ' with divider ' + tdivider.toString() + ', common-units=' + common_units_name.toString() + ((t[uuid].divider !== tdivider)?' USED COMMON, mine was ' + t[uuid].units:' set common').toString());
2401
-
2402
- // apply it to this chart
2403
- switch_units_callback(tunits);
2404
- return function(value) {
2405
- if(tdivider !== latest.divider) {
2406
- // another chart switched our common units
2407
- // we should switch them too
2408
- //console.log('DEBUG: ' + uuid + ' switching units due to a common-units change, from ' + tunits.toString() + ' to ' + latest.units.toString());
2409
- tunits = latest.units;
2410
- tdivider = latest.divider;
2411
- switch_units_callback(tunits);
2412
- }
2413
-
2414
- return value / tdivider;
2415
- };
2416
- }
2417
- else {
2418
- // the caller did not give data-common-units
2419
- // this chart auto-scales independently of all others
2420
- //console.log('DEBUG: ' + uuid.toString() + ' converted units: ' + units.toString() + ' to units: ' + tunits.toString() + ' with divider ' + tdivider.toString() + ', autonomously');
2743
+ state.google_instance.draw(datatable, state.google_options);
2744
+ return true;
2745
+};
2746
+// gauge.js
2747
2422
- switch_units_callback(tunits);
2423
- return function (value) { return value / tdivider; };
2424
- }
2425
- }
2426
- else {
2427
- // the caller wants specific units
2428
-
2429
- if(typeof this.scalableUnits[units][desired_units] !== 'undefined') {
2430
- // all good, set the new units
2431
- tdivider = this.scalableUnits[units][desired_units];
2432
- // console.log('DEBUG: ' + uuid.toString() + ' converted units: ' + units.toString() + ' to units: ' + desired_units.toString() + ' with divider ' + tdivider.toString() + ', by reference');
2433
- switch_units_callback(desired_units);
2434
- return function (value) { return value / tdivider; };
2435
- }
2436
- else {
2437
- // oops! switch back to original units
2438
- console.log('Units conversion from ' + units.toString() + ' to ' + desired_units.toString() + ' is not supported.');
2439
- switch_units_callback(units);
2440
- return function (value) { return value; };
2441
- }
2442
- }
2443
- }
2444
- else if(typeof this.convertibleUnits[units] !== 'undefined') {
2445
- // units that can be converted
2446
- if(desired_units === 'auto') {
2447
- for(x in this.convertibleUnits[units]) {
2448
- if (this.convertibleUnits[units].hasOwnProperty(x)) {
2449
- if (this.convertibleUnits[units][x].check(max)) {
2450
- //console.log('DEBUG: ' + uuid.toString() + ' converting ' + units.toString() + ' to: ' + x.toString());
2451
- switch_units_callback(x);
2452
- return this.convertibleUnits[units][x].convert;
2453
- }
2454
- }
2455
- }
2456
-
2457
- // none checked ok
2458
- //console.log('DEBUG: ' + uuid.toString() + ' no conversion available for ' + units.toString() + ' to: ' + desired_units.toString());
2459
- switch_units_callback(units);
2460
- return function (value) { return value; };
2461
- }
2462
- else if(typeof this.convertibleUnits[units][desired_units] !== 'undefined') {
2463
- switch_units_callback(desired_units);
2464
- return this.convertibleUnits[units][desired_units].convert;
2465
- }
2466
- else {
2467
- console.log('Units conversion from ' + units.toString() + ' to ' + desired_units.toString() + ' is not supported.');
2468
- switch_units_callback(units);
2469
- return function (value) { return value; };
2748
+NETDATA.gaugeInitialize = function (callback) {
2749
+ if (typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
2750
+ $.ajax({
2751
+ url: NETDATA.gauge_js,
2752
+ cache: true,
2753
+ dataType: "script",
2754
+ xhrFields: {withCredentials: true} // required for the cookie
2755
+ })
2756
+ .done(function () {
2757
+ NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
2758
+ })
2759
+ .fail(function () {
2760
+ NETDATA.chartLibraries.gauge.enabled = false;
2761
+ NETDATA.error(100, NETDATA.gauge_js);
2762
+ })
2763
+ .always(function () {
2764
+ if (typeof callback === "function") {
2765
+ return callback();
2766
}
2471
- }
2472
- else {
2473
- // hm... did we forget to implement the new type?
2474
- console.log('Unmatched unit conversion method for units ' + units.toString());
2475
- switch_units_callback(units);
2476
- return function (value) { return value; };
2477
- }
2767
+ })
2768
+ }
2769
+ else {
2770
+ NETDATA.chartLibraries.gauge.enabled = false;
2771
+ if (typeof callback === "function") {
2772
+ return callback();
2773
}
2479
- };
2480
-
2481
- // ----------------------------------------------------------------------------------------------------------------
2482
- // global selection sync
2483
-
2484
- NETDATA.globalSelectionSync = {
2485
- state: null,
2486
- dont_sync_before: 0,
2487
- last_t: 0,
2488
- slaves: [],
2489
- timeout_id: undefined,
2490
-
2491
- globalReset: function() {
2492
- this.stop();
2493
- this.state = null;
2494
- this.dont_sync_before = 0;
2495
- this.last_t = 0;
2496
- this.slaves = [];
2497
- this.timeout_id = undefined;
2498
- },
2774
+ }
2775
+};
2776
2500
- active: function() {
2501
- return (this.state !== null);
2502
- },
2777
+NETDATA.gaugeAnimation = function (state, status) {
2778
+ let speed = 32;
2779
2504
- // return true if global selection sync can be enabled now
2505
- enabled: function() {
2506
- // console.log('enabled()');
2507
- // can we globally apply selection sync?
2508
- if(NETDATA.options.current.sync_selection === false)
2509
- return false;
2780
+ if (typeof status === 'boolean' && status === false) {
2781
+ speed = 1000000000;
2782
+ } else if (typeof status === 'number') {
2783
+ speed = status;
2784
+ }
2785
2511
- return (this.dont_sync_before <= Date.now());
2512
- },
2786
+ // console.log('gauge speed ' + speed);
2787
+ state.tmp.gauge_instance.animationSpeed = speed;
2788
+ state.tmp.___gaugeOld__.speed = speed;
2789
+};
2790
2514
- // set the global selection sync master
2515
- setMaster: function(state) {
2516
- if(this.enabled() === false) {
2517
- this.stop();
2518
- return;
2519
- }
2520
-
2521
- if(this.state === state)
2522
- return;
2791
+NETDATA.gaugeSet = function (state, value, min, max) {
2792
+ if (typeof value !== 'number') {
2793
+ value = 0;
2794
+ }
2795
+ if (typeof min !== 'number') {
2796
+ min = 0;
2797
+ }
2798
+ if (typeof max !== 'number') {
2799
+ max = 0;
2800
+ }
2801
+ if (value > max) {
2802
+ max = value;
2803
+ }
2804
+ if (value < min) {
2805
+ min = value;
2806
+ }
2807
+ if (min > max) {
2808
+ let t = min;
2809
+ min = max;
2810
+ max = t;
2811
+ }
2812
+ else if (min === max) {
2813
+ max = min + 1;
2814
+ }
2815
2524
- if(this.state !== null)
2525
- this.stop();
2816
+ state.legendFormatValueDecimalsFromMinMax(min, max);
2817
2527
- if(NETDATA.options.debug.globalSelectionSync === true)
2528
- console.log('globalSelectionSync.setMaster(' + state.id + ')');
2818
+ // gauge.js has an issue if the needle
2819
+ // is smaller than min or larger than max
2820
+ // when we set the new values
2821
+ // the needle will go crazy
2822
2530
- state.selected = true;
2531
- this.state = state;
2532
- this.last_t = 0;
2823
+ // to prevent it, we always feed it
2824
+ // with a percentage, so that the needle
2825
+ // is always between min and max
2826
+ let pcent = (value - min) * 100 / (max - min);
2827
2534
- // find all slaves
2535
- var targets = NETDATA.intersectionObserver.targets();
2536
- this.slaves = [];
2537
- var len = targets.length;
2538
- while(len--) {
2539
- var st = targets[len];
2540
- if (this.state !== st && st.globalSelectionSyncIsEligible() === true)
2541
- this.slaves.push(st);
2542
- }
2828
+ // bug fix for gauge.js 1.3.1
2829
+ // if the value is the absolute min or max, the chart is broken
2830
+ if (pcent < 0.001) {
2831
+ pcent = 0.001;
2832
+ }
2833
+ if (pcent > 99.999) {
2834
+ pcent = 99.999;
2835
+ }
2836
2544
- // this.delay(100);
2545
- },
2837
+ state.tmp.gauge_instance.set(pcent);
2838
+ // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
2839
2547
- // stop global selection sync
2548
- stop: function() {
2549
- if(this.state !== null) {
2550
- if(NETDATA.options.debug.globalSelectionSync === true)
2551
- console.log('globalSelectionSync.stop()');
2840
+ state.tmp.___gaugeOld__.value = value;
2841
+ state.tmp.___gaugeOld__.min = min;
2842
+ state.tmp.___gaugeOld__.max = max;
2843
+};
2844
2553
- var len = this.slaves.length;
2554
- while (len--)
2555
- this.slaves[len].clearSelection();
2845
+NETDATA.gaugeSetLabels = function (state, value, min, max) {
2846
+ if (state.tmp.___gaugeOld__.valueLabel !== value) {
2847
+ state.tmp.___gaugeOld__.valueLabel = value;
2848
+ state.tmp.gaugeChartLabel.innerText = state.legendFormatValue(value);
2849
+ }
2850
+ if (state.tmp.___gaugeOld__.minLabel !== min) {
2851
+ state.tmp.___gaugeOld__.minLabel = min;
2852
+ state.tmp.gaugeChartMin.innerText = state.legendFormatValue(min);
2853
+ }
2854
+ if (state.tmp.___gaugeOld__.maxLabel !== max) {
2855
+ state.tmp.___gaugeOld__.maxLabel = max;
2856
+ state.tmp.gaugeChartMax.innerText = state.legendFormatValue(max);
2857
+ }
2858
+};
2859
2557
- this.state.clearSelection();
2860
+NETDATA.gaugeClearSelection = function (state, force) {
2861
+ if (typeof state.tmp.gaugeEvent !== 'undefined' && typeof state.tmp.gaugeEvent.timer !== 'undefined') {
2862
+ NETDATA.timeout.clear(state.tmp.gaugeEvent.timer);
2863
+ state.tmp.gaugeEvent.timer = undefined;
2864
+ }
2865
2559
- this.last_t = 0;
2560
- this.slaves = [];
2561
- this.state = null;
2562
- }
2563
- },
2866
+ if (state.isAutoRefreshable() && state.data !== null && force !== true) {
2867
+ NETDATA.gaugeChartUpdate(state, state.data);
2868
+ } else {
2869
+ NETDATA.gaugeAnimation(state, false);
2870
+ NETDATA.gaugeSetLabels(state, null, null, null);
2871
+ NETDATA.gaugeSet(state, null, null, null);
2872
+ }
2873
2565
- // delay global selection sync for some time
2566
- delay: function(ms) {
2567
- if(NETDATA.options.current.sync_selection === true) {
2568
- if(NETDATA.options.debug.globalSelectionSync === true)
2569
- console.log('globalSelectionSync.delay()');
2874
+ NETDATA.gaugeAnimation(state, true);
2875
+ return true;
2876
+};
2877
2571
- if(typeof ms === 'number')
2572
- this.dont_sync_before = Date.now() + ms;
2573
- else
2574
- this.dont_sync_before = Date.now() + NETDATA.options.current.sync_selection_delay;
2575
- }
2576
- },
2878
+NETDATA.gaugeSetSelection = function (state, t) {
2879
+ if (state.timeIsVisible(t) !== true) {
2880
+ return NETDATA.gaugeClearSelection(state, true);
2881
+ }
2882
2578
- __syncSlaves: function() {
2579
- if(NETDATA.globalSelectionSync.enabled() === true) {
2580
- if(NETDATA.options.debug.globalSelectionSync === true)
2581
- console.log('globalSelectionSync.__syncSlaves()');
2883
+ let slot = state.calculateRowForTime(t);
2884
+ if (slot < 0 || slot >= state.data.result.length) {
2885
+ return NETDATA.gaugeClearSelection(state, true);
2886
+ }
2887
2583
- var t = NETDATA.globalSelectionSync.last_t;
2584
- var len = NETDATA.globalSelectionSync.slaves.length;
2585
- while (len--)
2586
- NETDATA.globalSelectionSync.slaves[len].setSelection(t);
2888
+ if (typeof state.tmp.gaugeEvent === 'undefined') {
2889
+ state.tmp.gaugeEvent = {
2890
+ timer: undefined,
2891
+ value: 0,
2892
+ min: 0,
2893
+ max: 0
2894
+ };
2895
+ }
2896
2588
- this.timeout_id = undefined;
2589
- }
2590
- },
2897
+ let value = state.data.result[state.data.result.length - 1 - slot];
2898
+ let min = (state.tmp.gaugeMin === null) ? NETDATA.commonMin.get(state) : state.tmp.gaugeMin;
2899
+ let max = (state.tmp.gaugeMax === null) ? NETDATA.commonMax.get(state) : state.tmp.gaugeMax;
2900
2592
- // sync all the visible charts to the given time
2593
- // this is to be called from the chart libraries
2594
- sync: function(state, t) {
2595
- if(NETDATA.options.current.sync_selection === true) {
2596
- if(NETDATA.options.debug.globalSelectionSync === true)
2597
- console.log('globalSelectionSync.sync(' + state.id + ', ' + t.toString() + ')');
2901
+ // make sure it is zero based
2902
+ // but only if it has not been set by the user
2903
+ if (state.tmp.gaugeMin === null && min > 0) {
2904
+ min = 0;
2905
+ }
2906
+ if (state.tmp.gaugeMax === null && max < 0) {
2907
+ max = 0;
2908
+ }
2909
2599
- this.setMaster(state);
2910
+ state.tmp.gaugeEvent.value = value;
2911
+ state.tmp.gaugeEvent.min = min;
2912
+ state.tmp.gaugeEvent.max = max;
2913
+ NETDATA.gaugeSetLabels(state, value, min, max);
2914
2601
- if(t === this.last_t)
2602
- return;
2915
+ if (state.tmp.gaugeEvent.timer === undefined) {
2916
+ NETDATA.gaugeAnimation(state, false);
2917
2604
- this.last_t = t;
2918
+ state.tmp.gaugeEvent.timer = NETDATA.timeout.set(function () {
2919
+ state.tmp.gaugeEvent.timer = undefined;
2920
+ NETDATA.gaugeSet(state, state.tmp.gaugeEvent.value, state.tmp.gaugeEvent.min, state.tmp.gaugeEvent.max);
2921
+ }, 0);
2922
+ }
2923
2606
- if (state.foreign_element_selection !== null)
2607
- state.foreign_element_selection.innerText = NETDATA.dateTime.localeDateString(t) + ' ' + NETDATA.dateTime.localeTimeString(t);
2924
+ return true;
2925
+};
2926
2609
- if (this.timeout_id)
2610
- NETDATA.timeout.clear(this.timeout_id);
2927
+NETDATA.gaugeChartUpdate = function (state, data) {
This file is too large to show in full.