1 // You can set the following variables before loading this script:
3 // var netdataNoDygraphs = true; // do not use dygraph
4 // var netdataNoSparklines = true; // do not use sparkline
5 // var netdataNoPeitys = true; // do not use peity
6 // var netdataNoGoogleCharts = true; // do not use google
7 // var netdataNoMorris = true; // do not use morris
8 // var netdataNoEasyPieChart = true; // do not use easy pie chart
9 // var netdataNoGauge = true; // do not use gauge.js
10 // var netdataNoD3 = true; // do not use D3
11 // var netdataNoC3 = true; // do not use C3
12 // var netdataNoBootstrap = true; // do not load bootstrap
13 // var netdataDontStart = true; // do not start the thread to process the charts
14 // var netdataErrorCallback = null; // Callback function that will be invoked upon error
15 // var netdataRegistry = true; // Update the registry (default disabled)
16 // var netdataRegistryCallback = null; // Callback function that will be invoked with one param,
17 // the URLs from the registry
18 // var netdataShowHelp = false; // enable/disable help (default enabled)
19 // var netdataShowAlarms = true; // enable/disable alarms checks and notifications (default disabled)
21 // var netdataRegistryAfterMs = 1500 // the time to consult to registry on startup
23 // var netdataCallback = null; // a function to call when netdata is ready
24 // // netdata will be running while this is called (call NETDATA.pause to stop it)
25 // var netdataPrepCallback = null; // a callback to be called before netdata does anything else
27 // You can also set the default netdata server, using the following.
28 // When this variable is not set, we assume the page is hosted on your
29 // netdata server already.
30 // var netdataServer = "http://yourhost:19999"; // set your NetData server
32 //(function(window, document, undefined) {
34 // ------------------------------------------------------------------------
35 // compatibility fixes
37 // fix IE issue with console
38 if(!window.console) { window.console = { log: function(){} }; }
40 // if string.endsWith is not defined, define it
41 if(typeof String.prototype.endsWith !== 'function') {
42 String.prototype.endsWith = function(s) {
43 if(s.length > this.length) return false;
44 return this.slice(-s.length) === s;
48 // if string.startsWith is not defined, define it
49 if(typeof String.prototype.startsWith !== 'function') {
50 String.prototype.startsWith = function(s) {
51 if(s.length > this.length) return false;
52 return this.slice(s.length) === s;
57 var NETDATA = window.NETDATA || {};
59 NETDATA.name2id = function(s) {
68 // ----------------------------------------------------------------------------------------------------------------
69 // Detect the netdata server
71 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
72 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
73 NETDATA._scriptSource = function() {
76 if(typeof document.currentScript !== 'undefined') {
77 script = document.currentScript;
80 var all_scripts = document.getElementsByTagName('script');
81 script = all_scripts[all_scripts.length - 1];
84 if (typeof script.getAttribute.length !== 'undefined')
87 script = script.getAttribute('src', -1);
92 if(typeof netdataServer !== 'undefined')
93 NETDATA.serverDefault = netdataServer;
95 var s = NETDATA._scriptSource();
96 if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
98 console.log('WARNING: Cannot detect the URL of the netdata server.');
99 NETDATA.serverDefault = null;
103 if(NETDATA.serverDefault === null)
104 NETDATA.serverDefault = '';
105 else if(NETDATA.serverDefault.slice(-1) !== '/')
106 NETDATA.serverDefault += '/';
108 // default URLs for all the external files we need
109 // make them RELATIVE so that the whole thing can also be
110 // installed under a web server
111 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-2.2.4.min.js';
112 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity-3.2.0.min.js';
113 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline-2.1.2.min.js';
114 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart-97b5824.min.js';
115 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge-d5260c3.min.js';
116 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined-dd74404.js';
117 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter-dd74404.js';
118 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-2.2.4-min.js';
119 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3-0.4.11.min.js';
120 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3-0.4.11.min.css';
121 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3-3.5.17.min.js';
122 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris-0.5.1.min.js';
123 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris-0.5.1.css';
124 NETDATA.google_js = 'https://www.google.com/jsapi';
128 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-3.3.7.css',
129 dashboard_css: NETDATA.serverDefault + 'dashboard.css?v20161002-1',
130 background: '#FFFFFF',
131 foreground: '#000000',
134 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
135 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
136 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
137 '#329262', '#3B3EAC' ],
138 easypiechart_track: '#f0f0f0',
139 easypiechart_scale: '#dfe0e0',
140 gauge_pointer: '#C0C0C0',
141 gauge_stroke: '#F0F0F0',
142 gauge_gradient: false
145 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-slate-flat-3.3.7.css?v20161218-2',
146 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css?v20161218-1',
147 background: '#272b30',
148 foreground: '#C8C8C8',
151 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
152 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
153 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
154 '#a6a479', '#a66da8' ],
156 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
157 '#5054e6', '#EE9911', '#BB44CC', '#e45757', '#ef0aef', '#CC7700',
158 '#22AA99', '#109618', '#905bfd', '#f54882', '#4381bf', '#ff3737',
159 '#329262', '#3B3EFF' ],
160 easypiechart_track: '#373b40',
161 easypiechart_scale: '#373b40',
162 gauge_pointer: '#474b50',
163 gauge_stroke: '#373b40',
164 gauge_gradient: false
168 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
169 NETDATA.themes.current = NETDATA.themes[netdataTheme];
171 NETDATA.themes.current = NETDATA.themes.white;
173 NETDATA.colors = NETDATA.themes.current.colors;
175 // these are the colors Google Charts are using
176 // we have them here to attempt emulate their look and feel on the other chart libraries
177 // http://there4.io/2012/05/02/google-chart-color-list/
178 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
179 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
180 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
182 // an alternative set
183 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
184 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
185 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
187 if(typeof netdataShowHelp === 'undefined')
188 netdataShowHelp = true;
190 if(typeof netdataShowAlarms === 'undefined')
191 netdataShowAlarms = false;
193 if(typeof netdataRegistryAfterMs !== 'number' || netdataRegistryAfterMs < 0)
194 netdataRegistryAfterMs = 1500;
196 if(typeof netdataRegistry === 'undefined') {
197 // backward compatibility
198 if(typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry === false)
199 netdataRegistry = true;
201 netdataRegistry = false;
203 if(netdataRegistry === false && typeof netdataRegistryCallback === 'function')
204 netdataRegistry = true;
206 // ----------------------------------------------------------------------------------------------------------------
207 // the defaults for all charts
209 // if the user does not specify any of these, the following will be used
211 NETDATA.chartDefaults = {
212 host: NETDATA.serverDefault, // the server to get data from
213 width: '100%', // the chart width - can be null
214 height: '100%', // the chart height - can be null
215 min_width: null, // the chart minimum width - can be null
216 library: 'dygraph', // the graphing library to use
217 method: 'average', // the grouping method
218 before: 0, // panning
219 after: -600, // panning
220 pixels_per_point: 1, // the detail of the chart
221 fill_luminance: 0.8 // luminance of colors in solit areas
224 // ----------------------------------------------------------------------------------------------------------------
228 pauseCallback: null, // a callback when we are really paused
230 pause: false, // when enabled we don't auto-refresh the charts
232 targets: null, // an array of all the state objects that are
233 // currently active (independently of their
234 // viewport visibility)
236 updated_dom: true, // when true, the DOM has been updated with
237 // new elements we have to check.
239 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
240 // rendering charts continiously.
241 // used with .current.fast_render_timeframe
243 page_is_visible: true, // when true, this page is visible
245 auto_refresher_stop_until: 0, // timestamp in ms - used internaly, to stop the
246 // auto-refresher for some time (when a chart is
247 // performing pan or zoom, we need to stop refreshing
248 // all other charts, to have the maximum speed for
249 // rendering the chart that is panned or zoomed).
250 // Used with .current.global_pan_sync_time
252 last_resized: Date.now(), // the timestamp of the last resize request
254 last_page_scroll: 0, // the timestamp the last time the page was scrolled
256 // the current profile
257 // we may have many...
259 pixels_per_point: 1, // the minimum pixels per point for all charts
260 // increase this to speed javascript up
261 // each chart library has its own limit too
262 // the max of this and the chart library is used
263 // the final is calculated every time, so a change
264 // here will have immediate effect on the next chart
267 idle_between_charts: 100, // ms - how much time to wait between chart updates
269 fast_render_timeframe: 200, // ms - render continously until this time of continious
270 // rendering has been reached
271 // this setting is used to make it render e.g. 10
272 // charts at once, sleep idle_between_charts time
273 // and continue for another 10 charts.
275 idle_between_loops: 500, // ms - if all charts have been updated, wait this
276 // time before starting again.
278 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
280 idle_lost_focus: 500, // ms - when the window does not have focus, check
281 // if focus has been regained, every this time
283 global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
284 // autorefreshing of charts is paused for this amount
287 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
288 // of time before setting up synchronized selections
291 sync_selection: true, // enable or disable selection sync
293 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
295 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
297 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
299 update_only_visible: true, // enable or disable visibility management
301 parallel_refresher: true, // enable parallel refresh of charts
303 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
305 destroy_on_hide: false, // destroy charts when they are not visible
307 show_help: netdataShowHelp, // when enabled the charts will show some help
308 show_help_delay_show_ms: 500,
309 show_help_delay_hide_ms: 0,
311 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
313 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
314 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
316 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
318 smooth_plot: true, // enable smooth plot, where possible
320 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
322 color_fill_opacity_line: 1.0,
323 color_fill_opacity_area: 0.2,
324 color_fill_opacity_stacked: 0.8,
326 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
327 pan_and_zoom_factor_multiplier_control: 2.0,
328 pan_and_zoom_factor_multiplier_shift: 3.0,
329 pan_and_zoom_factor_multiplier_alt: 4.0,
331 abort_ajax_on_scroll: false, // kill pending ajax page scroll
332 async_on_scroll: false, // sync/async onscroll handler
333 onscroll_worker_duration_threshold: 30, // time in ms, to consider slow the onscroll handler
335 setOptionCallback: function() { ; }
343 chart_data_url: false,
344 chart_errors: false, // FIXME
352 NETDATA.statistics = {
355 refreshes_active_max: 0
359 // ----------------------------------------------------------------------------------------------------------------
360 // local storage options
362 NETDATA.localStorage = {
365 callback: {} // only used for resetting back to defaults
368 NETDATA.localStorageGet = function(key, def, callback) {
371 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
372 NETDATA.localStorage.default[key.toString()] = def;
373 NETDATA.localStorage.callback[key.toString()] = callback;
376 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
378 // console.log('localStorage: loading "' + key.toString() + '"');
379 ret = localStorage.getItem(key.toString());
380 // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
381 if(ret === null || ret === 'undefined') {
382 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
383 localStorage.setItem(key.toString(), JSON.stringify(def));
387 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
388 ret = JSON.parse(ret);
389 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
393 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
398 if(typeof ret === 'undefined' || ret === 'undefined') {
399 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
403 NETDATA.localStorage.current[key.toString()] = ret;
407 NETDATA.localStorageSet = function(key, value, callback) {
408 if(typeof value === 'undefined' || value === 'undefined') {
409 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
412 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
413 NETDATA.localStorage.default[key.toString()] = value;
414 NETDATA.localStorage.current[key.toString()] = value;
415 NETDATA.localStorage.callback[key.toString()] = callback;
418 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
419 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
421 localStorage.setItem(key.toString(), JSON.stringify(value));
424 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
428 NETDATA.localStorage.current[key.toString()] = value;
432 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
434 if(typeof obj[i] === 'object') {
435 //console.log('object ' + prefix + '.' + i.toString());
436 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
440 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
444 NETDATA.setOption = function(key, value) {
445 if(key.toString() === 'setOptionCallback') {
446 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
447 NETDATA.options.current[key.toString()] = value;
448 NETDATA.options.current.setOptionCallback();
451 else if(NETDATA.options.current[key.toString()] !== value) {
452 var name = 'options.' + key.toString();
454 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
455 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
457 //console.log(NETDATA.localStorage);
458 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
459 //console.log(NETDATA.options);
460 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
462 if(typeof NETDATA.options.current.setOptionCallback === 'function')
463 NETDATA.options.current.setOptionCallback();
469 NETDATA.getOption = function(key) {
470 return NETDATA.options.current[key.toString()];
473 // read settings from local storage
474 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
476 // always start with this option enabled.
477 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
479 NETDATA.resetOptions = function() {
480 for(var i in NETDATA.localStorage.default) {
481 var a = i.split('.');
483 if(a[0] === 'options') {
484 if(a[1] === 'setOptionCallback') continue;
485 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
486 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
488 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
490 else if(a[0] === 'chart_heights') {
491 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
492 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
498 // ----------------------------------------------------------------------------------------------------------------
500 if(NETDATA.options.debug.main_loop === true)
501 console.log('welcome to NETDATA');
503 NETDATA.onresize = function() {
504 NETDATA.options.last_resized = Date.now();
508 NETDATA.onscroll_updater_count = 0;
509 NETDATA.onscroll_updater_running = false;
510 NETDATA.onscroll_updater_last_run = 0;
511 NETDATA.onscroll_updater_watchdog = null;
512 NETDATA.onscroll_updater_max_duration = 0;
513 NETDATA.onscroll_updater_above_threshold_count = 0;
514 NETDATA.onscroll_updater = function() {
515 NETDATA.onscroll_updater_running = true;
516 NETDATA.onscroll_updater_count++;
517 var start = Date.now();
519 var targets = NETDATA.options.targets;
520 var len = targets.length;
522 // when the user scrolls he sees that we have
523 // hidden all the not-visible charts
524 // using this little function we try to switch
525 // the charts back to visible quickly
528 if(NETDATA.options.abort_ajax_on_scroll === true) {
529 // we have to cancel pending requests too
532 if (targets[len]._updating === true) {
533 if (typeof targets[len].xhr !== 'undefined') {
534 targets[len].xhr.abort();
535 targets[len].running = false;
536 targets[len]._updating = false;
538 targets[len].isVisible();
543 // just find which chart is visible
546 targets[len].isVisible();
549 var end = Date.now();
550 // console.log('scroll No ' + NETDATA.onscroll_updater_count + ' calculation took ' + (end - start).toString() + ' ms');
552 if(NETDATA.options.current.async_on_scroll === false) {
553 var dt = end - start;
554 if(dt > NETDATA.onscroll_updater_max_duration) {
555 // console.log('max onscroll event handler duration increased to ' + dt);
556 NETDATA.onscroll_updater_max_duration = dt;
559 if(dt > NETDATA.options.current.onscroll_worker_duration_threshold) {
560 // console.log('slow: ' + dt);
561 NETDATA.onscroll_updater_above_threshold_count++;
563 if(NETDATA.onscroll_updater_above_threshold_count > 2 && NETDATA.onscroll_updater_above_threshold_count * 100 / NETDATA.onscroll_updater_count > 2) {
564 NETDATA.setOption('async_on_scroll', true);
565 console.log('NETDATA: your browser is slow - enabling asynchronous onscroll event handler.');
570 NETDATA.onscroll_updater_last_run = start;
571 NETDATA.onscroll_updater_running = false;
574 NETDATA.onscroll = function() {
575 // console.log('onscroll');
577 NETDATA.options.last_page_scroll = Date.now();
578 NETDATA.options.auto_refresher_stop_until = 0;
580 if(NETDATA.options.targets === null) return;
582 if(NETDATA.options.current.async_on_scroll === true) {
584 if(NETDATA.onscroll_updater_running === false) {
585 NETDATA.onscroll_updater_running = true;
586 setTimeout(NETDATA.onscroll_updater, 0);
589 if(NETDATA.onscroll_updater_watchdog !== null)
590 clearTimeout(NETDATA.onscroll_updater_watchdog);
592 NETDATA.onscroll_updater_watchdog = setTimeout(function() {
593 if(NETDATA.onscroll_updater_running === false && NETDATA.options.last_page_scroll > NETDATA.onscroll_updater_last_run) {
594 // console.log('watchdog');
595 NETDATA.onscroll_updater();
598 NETDATA.onscroll_updater_watchdog = null;
604 NETDATA.onscroll_updater();
608 window.onresize = NETDATA.onresize;
609 window.onscroll = NETDATA.onscroll;
611 // ----------------------------------------------------------------------------------------------------------------
614 NETDATA.errorCodes = {
615 100: { message: "Cannot load chart library", alert: true },
616 101: { message: "Cannot load jQuery", alert: true },
617 402: { message: "Chart library not found", alert: false },
618 403: { message: "Chart library not enabled/is failed", alert: false },
619 404: { message: "Chart not found", alert: false },
620 405: { message: "Cannot download charts index from server", alert: true },
621 406: { message: "Invalid charts index downloaded from server", alert: true },
622 407: { message: "Cannot HELLO netdata server", alert: false },
623 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
624 409: { message: "Cannot ACCESS netdata registry", alert: false },
625 410: { message: "Netdata registry ACCESS failed", alert: false },
626 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
627 412: { message: "Netdata registry DELETE failed", alert: false },
628 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
629 414: { message: "Netdata registry SWITCH failed", alert: false },
630 415: { message: "Netdata alarms download failed", alert: false },
631 416: { message: "Netdata alarms log download failed", alert: false },
632 417: { message: "Netdata registry server send invalid response to SEARCH ", alert: false },
633 418: { message: "Netdata registry SEARCH failed", alert: false }
635 NETDATA.errorLast = {
641 NETDATA.error = function(code, msg) {
642 NETDATA.errorLast.code = code;
643 NETDATA.errorLast.message = msg;
644 NETDATA.errorLast.datetime = Date.now();
646 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
649 if(typeof netdataErrorCallback === 'function') {
650 ret = netdataErrorCallback('system', code, msg);
653 if(ret && NETDATA.errorCodes[code].alert)
654 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
657 NETDATA.errorReset = function() {
658 NETDATA.errorLast.code = 0;
659 NETDATA.errorLast.message = "You are doing fine!";
660 NETDATA.errorLast.datetime = 0;
663 // ----------------------------------------------------------------------------------------------------------------
664 // commonMin & commonMax
666 NETDATA.commonMin = {
670 get: function(state) {
671 if(typeof state.__commonMin === 'undefined') {
672 // get the commonMin setting
673 var self = $(state.element);
674 state.__commonMin = self.data('common-min') || null;
677 var min = state.data.min;
678 var name = state.__commonMin;
681 // we don't need commonMin
682 //state.log('no need for commonMin');
686 var t = this.keys[name];
687 if(typeof t === 'undefined') {
689 this.keys[name] = {};
693 var uuid = state.uuid;
694 if(typeof t[uuid] !== 'undefined') {
695 if(t[uuid] === min) {
696 //state.log('commonMin ' + state.__commonMin + ' not changed: ' + this.latest[name]);
697 return this.latest[name];
699 else if(min < this.latest[name]) {
700 //state.log('commonMin ' + state.__commonMin + ' increased: ' + min);
702 this.latest[name] = min;
710 // find the common min
713 if(t[i] < m) m = t[i];
715 //state.log('commonMin ' + state.__commonMin + ' updated: ' + m);
716 this.latest[name] = m;
721 NETDATA.commonMax = {
725 get: function(state) {
726 if(typeof state.__commonMax === 'undefined') {
727 // get the commonMax setting
728 var self = $(state.element);
729 state.__commonMax = self.data('common-max') || null;
732 var max = state.data.max;
733 var name = state.__commonMax;
736 // we don't need commonMax
737 //state.log('no need for commonMax');
741 var t = this.keys[name];
742 if(typeof t === 'undefined') {
744 this.keys[name] = {};
748 var uuid = state.uuid;
749 if(typeof t[uuid] !== 'undefined') {
750 if(t[uuid] === max) {
751 //state.log('commonMax ' + state.__commonMax + ' not changed: ' + this.latest[name]);
752 return this.latest[name];
754 else if(max > this.latest[name]) {
755 //state.log('commonMax ' + state.__commonMax + ' increased: ' + max);
757 this.latest[name] = max;
765 // find the common max
768 if(t[i] > m) m = t[i];
770 //state.log('commonMax ' + state.__commonMax + ' updated: ' + m);
771 this.latest[name] = m;
776 // ----------------------------------------------------------------------------------------------------------------
779 // When multiple charts need the same chart, we avoid downloading it
780 // multiple times (and having it in browser memory multiple time)
781 // by using this registry.
783 // Every time we download a chart definition, we save it here with .add()
784 // Then we try to get it back with .get(). If that fails, we download it.
786 NETDATA.chartRegistry = {
789 fixid: function(id) {
790 return id.replace(/:/g, "_").replace(/\//g, "_");
793 add: function(host, id, data) {
794 host = this.fixid(host);
797 if(typeof this.charts[host] === 'undefined')
798 this.charts[host] = {};
800 //console.log('added ' + host + '/' + id);
801 this.charts[host][id] = data;
804 get: function(host, id) {
805 host = this.fixid(host);
808 if(typeof this.charts[host] === 'undefined')
811 if(typeof this.charts[host][id] === 'undefined')
814 //console.log('cached ' + host + '/' + id);
815 return this.charts[host][id];
818 downloadAll: function(host, callback) {
819 while(host.slice(-1) === '/')
820 host = host.substring(0, host.length - 1);
825 url: host + '/api/v1/charts',
828 xhrFields: { withCredentials: true } // required for the cookie
830 .done(function(data) {
832 var h = NETDATA.chartRegistry.fixid(host);
833 self.charts[h] = data.charts;
835 else NETDATA.error(406, host + '/api/v1/charts');
837 if(typeof callback === 'function')
841 NETDATA.error(405, host + '/api/v1/charts');
843 if(typeof callback === 'function')
849 // ----------------------------------------------------------------------------------------------------------------
850 // Global Pan and Zoom on charts
852 // Using this structure are synchronize all the charts, so that
853 // when you pan or zoom one, all others are automatically refreshed
854 // to the same timespan.
856 NETDATA.globalPanAndZoom = {
857 seq: 0, // timestamp ms
858 // every time a chart is panned or zoomed
859 // we set the timestamp here
860 // then we use it as a sequence number
861 // to find if other charts are syncronized
864 master: null, // the master chart (state), to which all others
867 force_before_ms: null, // the timespan to sync all other charts
868 force_after_ms: null,
873 setMaster: function(state, after, before) {
874 if(NETDATA.options.current.sync_pan_and_zoom === false)
877 if(this.master !== null && this.master !== state)
878 this.master.resetChart(true, true);
880 var now = Date.now();
883 this.force_after_ms = after;
884 this.force_before_ms = before;
885 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
887 if(typeof this.callback === 'function')
888 this.callback(true, after, before);
892 clearMaster: function() {
893 if(this.master !== null) {
894 var st = this.master;
901 this.force_after_ms = null;
902 this.force_before_ms = null;
903 NETDATA.options.auto_refresher_stop_until = 0;
905 if(typeof this.callback === 'function')
906 this.callback(false, 0, 0);
909 // is the given state the master of the global
910 // pan and zoom sync?
911 isMaster: function(state) {
912 if(this.master === state) return true;
916 // are we currently have a global pan and zoom sync?
917 isActive: function() {
918 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
922 // check if a chart, other than the master
923 // needs to be refreshed, due to the global pan and zoom
924 shouldBeAutoRefreshed: function(state) {
925 if(this.master === null || this.seq === 0)
928 //if(state.needsRecreation())
931 if(state.tm.pan_and_zoom_seq === this.seq)
938 // ----------------------------------------------------------------------------------------------------------------
939 // dimensions selection
942 // move color assignment to dimensions, here
944 dimensionStatus = function(parent, label, name_div, value_div, color) {
945 this.enabled = false;
946 this.parent = parent;
948 this.name_div = null;
949 this.value_div = null;
950 this.color = NETDATA.themes.current.foreground;
952 if(parent.unselected_count === 0)
953 this.selected = true;
955 this.selected = false;
957 this.setOptions(name_div, value_div, color);
960 dimensionStatus.prototype.invalidate = function() {
961 this.name_div = null;
962 this.value_div = null;
963 this.enabled = false;
966 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
969 if(this.name_div != name_div) {
970 this.name_div = name_div;
971 this.name_div.title = this.label;
972 this.name_div.style.color = this.color;
973 if(this.selected === false)
974 this.name_div.className = 'netdata-legend-name not-selected';
976 this.name_div.className = 'netdata-legend-name selected';
979 if(this.value_div != value_div) {
980 this.value_div = value_div;
981 this.value_div.title = this.label;
982 this.value_div.style.color = this.color;
983 if(this.selected === false)
984 this.value_div.className = 'netdata-legend-value not-selected';
986 this.value_div.className = 'netdata-legend-value selected';
993 dimensionStatus.prototype.setHandler = function() {
994 if(this.enabled === false) return;
998 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
999 this.name_div.onclick = this.value_div.onclick = function(e) {
1001 if(ds.isSelected()) {
1003 if(e.shiftKey === true || e.ctrlKey === true) {
1004 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
1007 if(ds.parent.countSelected() === 0)
1008 ds.parent.selectAll();
1011 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
1012 if(ds.parent.countSelected() === 1) {
1013 ds.parent.selectAll();
1016 ds.parent.selectNone();
1022 // this is not selected
1023 if(e.shiftKey === true || e.ctrlKey === true) {
1024 // control or shift key is pressed -> select this too
1028 // no key is pressed -> select only this
1029 ds.parent.selectNone();
1034 ds.parent.state.redrawChart();
1038 dimensionStatus.prototype.select = function() {
1039 if(this.enabled === false) return;
1041 this.name_div.className = 'netdata-legend-name selected';
1042 this.value_div.className = 'netdata-legend-value selected';
1043 this.selected = true;
1046 dimensionStatus.prototype.unselect = function() {
1047 if(this.enabled === false) return;
1049 this.name_div.className = 'netdata-legend-name not-selected';
1050 this.value_div.className = 'netdata-legend-value hidden';
1051 this.selected = false;
1054 dimensionStatus.prototype.isSelected = function() {
1055 return(this.enabled === true && this.selected === true);
1058 // ----------------------------------------------------------------------------------------------------------------
1060 dimensionsVisibility = function(state) {
1063 this.dimensions = {};
1064 this.selected_count = 0;
1065 this.unselected_count = 0;
1068 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
1069 if(typeof this.dimensions[label] === 'undefined') {
1071 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
1074 this.dimensions[label].setOptions(name_div, value_div, color);
1076 return this.dimensions[label];
1079 dimensionsVisibility.prototype.dimensionGet = function(label) {
1080 return this.dimensions[label];
1083 dimensionsVisibility.prototype.invalidateAll = function() {
1084 for(var d in this.dimensions)
1085 this.dimensions[d].invalidate();
1088 dimensionsVisibility.prototype.selectAll = function() {
1089 for(var d in this.dimensions)
1090 this.dimensions[d].select();
1093 dimensionsVisibility.prototype.countSelected = function() {
1095 for(var d in this.dimensions)
1096 if(this.dimensions[d].isSelected()) i++;
1101 dimensionsVisibility.prototype.selectNone = function() {
1102 for(var d in this.dimensions)
1103 this.dimensions[d].unselect();
1106 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
1107 var ret = new Array();
1108 this.selected_count = 0;
1109 this.unselected_count = 0;
1111 var len = array.length;
1113 var ds = this.dimensions[array[len]];
1114 if(typeof ds === 'undefined') {
1115 // console.log(array[i] + ' is not found');
1118 else if(ds.isSelected()) {
1120 this.selected_count++;
1124 this.unselected_count++;
1128 if(this.selected_count === 0 && this.unselected_count !== 0) {
1130 return this.selected2BooleanArray(array);
1137 // ----------------------------------------------------------------------------------------------------------------
1138 // global selection sync
1140 NETDATA.globalSelectionSync = {
1142 dont_sync_before: 0,
1147 if(this.state !== null)
1148 this.state.globalSelectionSyncStop();
1152 if(this.state !== null) {
1153 this.state.globalSelectionSyncDelay();
1158 // ----------------------------------------------------------------------------------------------------------------
1159 // Our state object, where all per-chart values are stored
1161 chartState = function(element) {
1162 var self = $(element);
1163 this.element = element;
1166 // all private functions should use 'that', instead of 'this'
1169 /* error() - private
1170 * show an error instead of the chart
1172 var error = function(msg) {
1175 if(typeof netdataErrorCallback === 'function') {
1176 ret = netdataErrorCallback('chart', that.id, msg);
1180 that.element.innerHTML = that.id + ': ' + msg;
1181 that.enabled = false;
1182 that.current = that.pan;
1186 // GUID - a unique identifier for the chart
1187 this.uuid = NETDATA.guid();
1189 // string - the name of chart
1190 this.id = self.data('netdata');
1192 // string - the key for localStorage settings
1193 this.settings_id = self.data('id') || null;
1195 // the user given dimensions of the element
1196 this.width = self.data('width') || NETDATA.chartDefaults.width;
1197 this.height = self.data('height') || NETDATA.chartDefaults.height;
1199 if(this.settings_id !== null) {
1200 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
1201 // this is the callback that will be called
1202 // if and when the user resets all localStorage variables
1203 // to their defaults
1205 resizeChartToHeight(height);
1209 // string - the netdata server URL, without any path
1210 this.host = self.data('host') || NETDATA.chartDefaults.host;
1212 // make sure the host does not end with /
1213 // all netdata API requests use absolute paths
1214 while(this.host.slice(-1) === '/')
1215 this.host = this.host.substring(0, this.host.length - 1);
1217 // string - the grouping method requested by the user
1218 this.method = self.data('method') || NETDATA.chartDefaults.method;
1220 // the time-range requested by the user
1221 this.after = self.data('after') || NETDATA.chartDefaults.after;
1222 this.before = self.data('before') || NETDATA.chartDefaults.before;
1224 // the pixels per point requested by the user
1225 this.pixels_per_point = self.data('pixels-per-point') || 1;
1226 this.points = self.data('points') || null;
1228 // the dimensions requested by the user
1229 this.dimensions = self.data('dimensions') || null;
1231 // the chart library requested by the user
1232 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1234 // object - the chart library used
1235 this.library = null;
1239 this.colors_assigned = {};
1240 this.colors_available = null;
1242 // the element already created by the user
1243 this.element_message = null;
1245 // the element with the chart
1246 this.element_chart = null;
1248 // the element with the legend of the chart (if created by us)
1249 this.element_legend = null;
1250 this.element_legend_childs = {
1260 this.chart_url = null; // string - the url to download chart info
1261 this.chart = null; // object - the chart as downloaded from the server
1263 this.title = self.data('title') || null; // the title of the chart
1264 this.units = self.data('units') || null; // the units of the chart dimensions
1265 this.append_options = self.data('append-options') || null; // additional options to pass to netdata
1266 this.override_options = self.data('override-options') || null; // override options to pass to netdata
1268 this.running = false; // boolean - true when the chart is being refreshed now
1269 this.validated = false; // boolean - has the chart been validated?
1270 this.enabled = true; // boolean - is the chart enabled for refresh?
1271 this.paused = false; // boolean - is the chart paused for any reason?
1272 this.selected = false; // boolean - is the chart shown a selection?
1273 this.debug = false; // boolean - console.log() debug info about this chart
1275 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1276 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1277 this.requested_after = null; // milliseconds - the timestamp of the request after param
1278 this.requested_before = null; // milliseconds - the timestamp of the request before param
1279 this.requested_padding = null;
1280 this.view_after = 0;
1281 this.view_before = 0;
1283 this.value_decimal_detail = -1;
1285 var d = self.data('decimal-digits');
1286 if(typeof d === 'number') {
1287 this.value_decimal_detail = 1;
1289 this.value_decimal_detail *= 10;
1296 force_update_at: 0, // the timestamp to force the update at
1297 force_before_ms: null,
1298 force_after_ms: null
1303 force_update_at: 0, // the timestamp to force the update at
1304 force_before_ms: null,
1305 force_after_ms: null
1310 force_update_at: 0, // the timestamp to force the update at
1311 force_before_ms: null,
1312 force_after_ms: null
1315 // this is a pointer to one of the sub-classes below
1317 this.current = this.auto;
1319 // check the requested library is available
1320 // we don't initialize it here - it will be initialized when
1321 // this chart will be first used
1322 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1323 NETDATA.error(402, that.library_name);
1324 error('chart library "' + that.library_name + '" is not found');
1327 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1328 NETDATA.error(403, that.library_name);
1329 error('chart library "' + that.library_name + '" is not enabled');
1333 that.library = NETDATA.chartLibraries[that.library_name];
1335 // milliseconds - the time the last refresh took
1336 this.refresh_dt_ms = 0;
1338 // if we need to report the rendering speed
1339 // find the element that needs to be updated
1340 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1342 if(refresh_dt_element_name !== null)
1343 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1345 this.refresh_dt_element = null;
1347 this.dimensions_visibility = new dimensionsVisibility(this);
1349 this._updating = false;
1351 // ============================================================================================================
1352 // PRIVATE FUNCTIONS
1354 var createDOM = function() {
1355 if(that.enabled === false) return;
1357 if(that.element_message !== null) that.element_message.innerHTML = '';
1358 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1359 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1361 that.element.innerHTML = '';
1363 that.element_message = document.createElement('div');
1364 that.element_message.className = ' netdata-message hidden';
1365 that.element.appendChild(that.element_message);
1367 that.element_chart = document.createElement('div');
1368 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1369 that.element.appendChild(that.element_chart);
1371 if(that.hasLegend() === true) {
1372 that.element.className = "netdata-container-with-legend";
1373 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1375 that.element_legend = document.createElement('div');
1376 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1377 that.element.appendChild(that.element_legend);
1380 that.element.className = "netdata-container";
1381 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1383 that.element_legend = null;
1385 that.element_legend_childs.series = null;
1387 if(typeof(that.width) === 'string')
1388 $(that.element).css('width', that.width);
1389 else if(typeof(that.width) === 'number')
1390 $(that.element).css('width', that.width + 'px');
1392 if(typeof(that.library.aspect_ratio) === 'undefined') {
1393 if(typeof(that.height) === 'string')
1394 $(that.element).css('height', that.height);
1395 else if(typeof(that.height) === 'number')
1396 $(that.element).css('height', that.height + 'px');
1399 var w = that.element.offsetWidth;
1400 if(w === null || w === 0) {
1401 // the div is hidden
1402 // this will resize the chart when next viewed
1403 that.tm.last_resized = 0;
1406 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1409 if(NETDATA.chartDefaults.min_width !== null)
1410 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1412 that.tm.last_dom_created = Date.now();
1418 * initialize state variables
1419 * destroy all (possibly) created state elements
1420 * create the basic DOM for a chart
1422 var init = function() {
1423 if(that.enabled === false) return;
1425 that.paused = false;
1426 that.selected = false;
1428 that.chart_created = false; // boolean - is the library.create() been called?
1429 that.updates_counter = 0; // numeric - the number of refreshes made so far
1430 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1431 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1434 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1435 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1436 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1438 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1439 last_updated: 0, // the timestamp the chart last updated with data
1440 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1442 // Used with NETDATA.globalPanAndZoom.seq
1443 last_visible_check: 0, // the time we last checked if it is visible
1444 last_resized: 0, // the time the chart was resized
1445 last_hidden: 0, // the time the chart was hidden
1446 last_unhidden: 0, // the time the chart was unhidden
1447 last_autorefreshed: 0 // the time the chart was last refreshed
1450 that.data = null; // the last data as downloaded from the netdata server
1451 that.data_url = 'invalid://'; // string - the last url used to update the chart
1452 that.data_points = 0; // number - the number of points returned from netdata
1453 that.data_after = 0; // milliseconds - the first timestamp of the data
1454 that.data_before = 0; // milliseconds - the last timestamp of the data
1455 that.data_update_every = 0; // milliseconds - the frequency to update the data
1457 that.tm.last_initialized = Date.now();
1460 that.setMode('auto');
1463 var maxMessageFontSize = function() {
1464 // normally we want a font size, as tall as the element
1465 var h = that.element_message.clientHeight;
1467 // but give it some air, 20% let's say, or 5 pixels min
1468 var lost = Math.max(h * 0.2, 5);
1471 // center the text, vertically
1472 var paddingTop = (lost - 5) / 2;
1474 // but check the width too
1475 // it should fit 10 characters in it
1476 var w = that.element_message.clientWidth / 10;
1478 paddingTop += (h - w) / 2;
1482 // and don't make it too huge
1483 // 5% of the screen size is good
1484 if(h > screen.height / 20) {
1485 paddingTop += (h - (screen.height / 20)) / 2;
1486 h = screen.height / 20;
1490 that.element_message.style.fontSize = h.toString() + 'px';
1491 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1494 var showMessage = function(msg) {
1495 that.element_message.className = 'netdata-message';
1496 that.element_message.innerHTML = msg;
1497 that.element_message.style.fontSize = 'x-small';
1498 that.element_message.style.paddingTop = '0px';
1499 that.___messageHidden___ = undefined;
1502 var showMessageIcon = function(icon) {
1503 that.element_message.innerHTML = icon;
1504 that.element_message.className = 'netdata-message icon';
1505 maxMessageFontSize();
1506 that.___messageHidden___ = undefined;
1509 var hideMessage = function() {
1510 if(typeof that.___messageHidden___ === 'undefined') {
1511 that.___messageHidden___ = true;
1512 that.element_message.className = 'netdata-message hidden';
1516 var showRendering = function() {
1518 if(that.chart !== null) {
1519 if(that.chart.chart_type === 'line')
1520 icon = '<i class="fa fa-line-chart"></i>';
1522 icon = '<i class="fa fa-area-chart"></i>';
1525 icon = '<i class="fa fa-area-chart"></i>';
1527 showMessageIcon(icon + ' netdata');
1530 var showLoading = function() {
1531 if(that.chart_created === false) {
1532 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1538 var isHidden = function() {
1539 if(typeof that.___chartIsHidden___ !== 'undefined')
1545 // hide the chart, when it is not visible - called from isVisible()
1546 var hideChart = function() {
1547 // hide it, if it is not already hidden
1548 if(isHidden() === true) return;
1550 if(that.chart_created === true) {
1551 if(NETDATA.options.current.destroy_on_hide === true) {
1552 // we should destroy it
1557 that.element_chart.style.display = 'none';
1558 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1559 that.tm.last_hidden = Date.now();
1562 // This works, but I not sure there are no corner cases somewhere
1563 // so it is commented - if the user has memory issues he can
1564 // set Destroy on Hide for all charts
1565 // that.data = null;
1569 that.___chartIsHidden___ = true;
1572 // unhide the chart, when it is visible - called from isVisible()
1573 var unhideChart = function() {
1574 if(isHidden() === false) return;
1576 that.___chartIsHidden___ = undefined;
1577 that.updates_since_last_unhide = 0;
1579 if(that.chart_created === false) {
1580 // we need to re-initialize it, to show our background
1581 // logo in bootstrap tabs, until the chart loads
1585 that.tm.last_unhidden = Date.now();
1586 that.element_chart.style.display = '';
1587 if(that.element_legend !== null) that.element_legend.style.display = '';
1593 var canBeRendered = function() {
1594 if(isHidden() === true || that.isVisible(true) === false)
1600 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1601 var callChartLibraryUpdateSafely = function(data) {
1604 if(canBeRendered() === false)
1607 if(NETDATA.options.debug.chart_errors === true)
1608 status = that.library.update(that, data);
1611 status = that.library.update(that, data);
1618 if(status === false) {
1619 error('chart failed to be updated as ' + that.library_name);
1626 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1627 var callChartLibraryCreateSafely = function(data) {
1630 if(canBeRendered() === false)
1633 if(NETDATA.options.debug.chart_errors === true)
1634 status = that.library.create(that, data);
1637 status = that.library.create(that, data);
1644 if(status === false) {
1645 error('chart failed to be created as ' + that.library_name);
1649 that.chart_created = true;
1650 that.updates_since_last_creation = 0;
1654 // ----------------------------------------------------------------------------------------------------------------
1657 // resizeChart() - private
1658 // to be called just before the chart library to make sure that
1659 // a properly sized dom is available
1660 var resizeChart = function() {
1661 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1662 if(that.chart_created === false) return;
1664 if(that.needsRecreation()) {
1667 else if(typeof that.library.resize === 'function') {
1668 that.library.resize(that);
1670 if(that.element_legend_childs.nano !== null && that.element_legend_childs.nano_options !== null)
1671 $(that.element_legend_childs.nano).nanoScroller();
1673 maxMessageFontSize();
1676 that.tm.last_resized = Date.now();
1680 // this is the actual chart resize algorithm
1682 // - resize the entire container
1683 // - update the internal states
1684 // - resize the chart as the div changes height
1685 // - update the scrollbar of the legend
1686 var resizeChartToHeight = function(h) {
1688 that.element.style.height = h;
1690 if(that.settings_id !== null)
1691 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1693 var now = Date.now();
1694 NETDATA.options.last_page_scroll = now;
1695 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1698 that.tm.last_resized = 0;
1702 this.resizeHandler = function(e) {
1705 if(typeof this.event_resize === 'undefined'
1706 || this.event_resize.chart_original_w === 'undefined'
1707 || this.event_resize.chart_original_h === 'undefined')
1708 this.event_resize = {
1709 chart_original_w: this.element.clientWidth,
1710 chart_original_h: this.element.clientHeight,
1714 if(e.type === 'touchstart') {
1715 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1716 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1719 this.event_resize.mouse_start_x = e.clientX;
1720 this.event_resize.mouse_start_y = e.clientY;
1723 this.event_resize.chart_start_w = this.element.clientWidth;
1724 this.event_resize.chart_start_h = this.element.clientHeight;
1725 this.event_resize.chart_last_w = this.element.clientWidth;
1726 this.event_resize.chart_last_h = this.element.clientHeight;
1728 var now = Date.now();
1729 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
1730 // double click / double tap event
1732 // the optimal height of the chart
1733 // showing the entire legend
1734 var optimal = this.event_resize.chart_last_h
1735 + this.element_legend_childs.content.scrollHeight
1736 - this.element_legend_childs.content.clientHeight;
1738 // if we are not optimal, be optimal
1739 if(this.event_resize.chart_last_h != optimal)
1740 resizeChartToHeight(optimal.toString() + 'px');
1742 // else if we do not have the original height
1743 // reset to the original height
1744 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
1745 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1748 this.event_resize.last = now;
1750 // process movement event
1751 document.onmousemove =
1752 document.ontouchmove =
1753 this.element_legend_childs.resize_handler.onmousemove =
1754 this.element_legend_childs.resize_handler.ontouchmove =
1759 case 'mousemove': y = e.clientY; break;
1760 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1764 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1766 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1767 resizeChartToHeight(newH.toString() + 'px');
1768 that.event_resize.chart_last_h = newH;
1773 // process end event
1774 document.onmouseup =
1775 document.ontouchend =
1776 this.element_legend_childs.resize_handler.onmouseup =
1777 this.element_legend_childs.resize_handler.ontouchend =
1779 // remove all the hooks
1780 document.onmouseup =
1781 document.onmousemove =
1782 document.ontouchmove =
1783 document.ontouchend =
1784 that.element_legend_childs.resize_handler.onmousemove =
1785 that.element_legend_childs.resize_handler.ontouchmove =
1786 that.element_legend_childs.resize_handler.onmouseout =
1787 that.element_legend_childs.resize_handler.onmouseup =
1788 that.element_legend_childs.resize_handler.ontouchend =
1791 // allow auto-refreshes
1792 NETDATA.options.auto_refresher_stop_until = 0;
1798 var noDataToShow = function() {
1799 showMessageIcon('<i class="fa fa-warning"></i> empty');
1800 that.legendUpdateDOM();
1801 that.tm.last_autorefreshed = Date.now();
1802 // that.data_update_every = 30 * 1000;
1803 //that.element_chart.style.display = 'none';
1804 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1805 //that.___chartIsHidden___ = true;
1808 // ============================================================================================================
1811 this.error = function(msg) {
1815 this.setMode = function(m) {
1816 if(this.current !== null && this.current.name === m) return;
1819 this.current = this.auto;
1820 else if(m === 'pan')
1821 this.current = this.pan;
1822 else if(m === 'zoom')
1823 this.current = this.zoom;
1825 this.current = this.auto;
1827 this.current.force_update_at = 0;
1828 this.current.force_before_ms = null;
1829 this.current.force_after_ms = null;
1831 this.tm.last_mode_switch = Date.now();
1834 // ----------------------------------------------------------------------------------------------------------------
1835 // global selection sync
1837 // prevent to global selection sync for some time
1838 this.globalSelectionSyncDelay = function(ms) {
1839 if(NETDATA.options.current.sync_selection === false)
1842 if(typeof ms === 'number')
1843 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + ms;
1845 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + NETDATA.options.current.sync_selection_delay;
1848 // can we globally apply selection sync?
1849 this.globalSelectionSyncAbility = function() {
1850 if(NETDATA.options.current.sync_selection === false)
1853 if(NETDATA.globalSelectionSync.dont_sync_before > Date.now())
1859 this.globalSelectionSyncIsMaster = function() {
1860 if(NETDATA.globalSelectionSync.state === this)
1866 // this chart is the master of the global selection sync
1867 this.globalSelectionSyncBeMaster = function() {
1869 if(this.globalSelectionSyncIsMaster()) {
1870 if(this.debug === true)
1871 this.log('sync: I am the master already.');
1876 if(NETDATA.globalSelectionSync.state) {
1877 if(this.debug === true)
1878 this.log('sync: I am not the sync master. Resetting global sync.');
1880 this.globalSelectionSyncStop();
1883 // become the master
1884 if(this.debug === true)
1885 this.log('sync: becoming sync master.');
1887 this.selected = true;
1888 NETDATA.globalSelectionSync.state = this;
1890 // find the all slaves
1891 var targets = NETDATA.options.targets;
1892 var len = targets.length;
1897 if(this.debug === true)
1898 st.log('sync: not adding me to sync');
1900 else if(st.globalSelectionSyncIsEligible()) {
1901 if(this.debug === true)
1902 st.log('sync: adding to sync as slave');
1904 st.globalSelectionSyncBeSlave();
1908 // this.globalSelectionSyncDelay(100);
1911 // can the chart participate to the global selection sync as a slave?
1912 this.globalSelectionSyncIsEligible = function() {
1913 if(this.enabled === true
1914 && this.library !== null
1915 && typeof this.library.setSelection === 'function'
1916 && this.isVisible() === true
1917 && this.chart_created === true)
1923 // this chart becomes a slave of the global selection sync
1924 this.globalSelectionSyncBeSlave = function() {
1925 if(NETDATA.globalSelectionSync.state !== this)
1926 NETDATA.globalSelectionSync.slaves.push(this);
1929 // sync all the visible charts to the given time
1930 // this is to be called from the chart libraries
1931 this.globalSelectionSync = function(t) {
1932 if(this.globalSelectionSyncAbility() === false) {
1933 if(this.debug === true)
1934 this.log('sync: cannot sync (yet?).');
1939 if(this.globalSelectionSyncIsMaster() === false) {
1940 if(this.debug === true)
1941 this.log('sync: trying to be sync master.');
1943 this.globalSelectionSyncBeMaster();
1945 if(this.globalSelectionSyncAbility() === false) {
1946 if(this.debug === true)
1947 this.log('sync: cannot sync (yet?).');
1953 NETDATA.globalSelectionSync.last_t = t;
1954 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1959 // stop syncing all charts to the given time
1960 this.globalSelectionSyncStop = function() {
1961 if(NETDATA.globalSelectionSync.slaves.length) {
1962 if(this.debug === true)
1963 this.log('sync: cleaning up...');
1965 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1967 if(that.debug === true)
1968 st.log('sync: not adding me to sync stop');
1971 if(that.debug === true)
1972 st.log('sync: removed slave from sync');
1974 st.clearSelection();
1978 NETDATA.globalSelectionSync.last_t = 0;
1979 NETDATA.globalSelectionSync.slaves = [];
1980 NETDATA.globalSelectionSync.state = null;
1983 this.clearSelection();
1986 this.setSelection = function(t) {
1987 if(typeof this.library.setSelection === 'function') {
1988 if(this.library.setSelection(this, t) === true)
1989 this.selected = true;
1991 this.selected = false;
1993 else this.selected = true;
1995 if(this.selected === true && this.debug === true)
1996 this.log('selection set to ' + t.toString());
1998 return this.selected;
2001 this.clearSelection = function() {
2002 if(this.selected === true) {
2003 if(typeof this.library.clearSelection === 'function') {
2004 if(this.library.clearSelection(this) === true)
2005 this.selected = false;
2007 this.selected = true;
2009 else this.selected = false;
2011 if(this.selected === false && this.debug === true)
2012 this.log('selection cleared');
2017 return this.selected;
2020 // find if a timestamp (ms) is shown in the current chart
2021 this.timeIsVisible = function(t) {
2022 if(t >= this.data_after && t <= this.data_before)
2027 this.calculateRowForTime = function(t) {
2028 if(this.timeIsVisible(t) === false) return -1;
2029 return Math.floor((t - this.data_after) / this.data_update_every);
2032 // ----------------------------------------------------------------------------------------------------------------
2035 this.log = function(msg) {
2036 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
2039 this.pauseChart = function() {
2040 if(this.paused === false) {
2041 if(this.debug === true)
2042 this.log('pauseChart()');
2048 this.unpauseChart = function() {
2049 if(this.paused === true) {
2050 if(this.debug === true)
2051 this.log('unpauseChart()');
2053 this.paused = false;
2057 this.resetChart = function(dont_clear_master, dont_update) {
2058 if(this.debug === true)
2059 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
2061 if(typeof dont_clear_master === 'undefined')
2062 dont_clear_master = false;
2064 if(typeof dont_update === 'undefined')
2065 dont_update = false;
2067 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
2068 if(this.debug === true)
2069 this.log('resetChart() diverting to clearMaster().');
2070 // this will call us back with master === true
2071 NETDATA.globalPanAndZoom.clearMaster();
2075 this.clearSelection();
2077 this.tm.pan_and_zoom_seq = 0;
2079 this.setMode('auto');
2080 this.current.force_update_at = 0;
2081 this.current.force_before_ms = null;
2082 this.current.force_after_ms = null;
2083 this.tm.last_autorefreshed = 0;
2084 this.paused = false;
2085 this.selected = false;
2086 this.enabled = true;
2087 // this.debug = false;
2089 // do not update the chart here
2090 // or the chart will flip-flop when it is the master
2091 // of a selection sync and another chart becomes
2094 if(dont_update !== true && this.isVisible() === true) {
2099 this.updateChartPanOrZoom = function(after, before) {
2100 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
2103 if(this.debug === true)
2106 if(before < after) {
2107 if(this.debug === true)
2108 this.log(logme + 'flipped parameters, rejecting it.');
2113 if(typeof this.fixed_min_duration === 'undefined')
2114 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
2116 var min_duration = this.fixed_min_duration;
2117 var current_duration = Math.round(this.view_before - this.view_after);
2119 // round the numbers
2120 after = Math.round(after);
2121 before = Math.round(before);
2123 // align them to update_every
2124 // stretching them further away
2125 after -= after % this.data_update_every;
2126 before += this.data_update_every - (before % this.data_update_every);
2128 // the final wanted duration
2129 var wanted_duration = before - after;
2131 // to allow panning, accept just a point below our minimum
2132 if((current_duration - this.data_update_every) < min_duration)
2133 min_duration = current_duration - this.data_update_every;
2135 // we do it, but we adjust to minimum size and return false
2136 // when the wanted size is below the current and the minimum
2138 if(wanted_duration < current_duration && wanted_duration < min_duration) {
2139 if(this.debug === true)
2140 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
2142 min_duration = this.fixed_min_duration;
2144 var dt = (min_duration - wanted_duration) / 2;
2147 wanted_duration = before - after;
2151 var tolerance = this.data_update_every * 2;
2152 var movement = Math.abs(before - this.view_before);
2154 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
2155 if(this.debug === true)
2156 this.log(logme + 'REJECTING UPDATE: current/min duration: ' + (current_duration / 1000).toString() + '/' + (this.fixed_min_duration / 1000).toString() + ', wanted duration: ' + (wanted_duration / 1000).toString() + ', duration diff: ' + (Math.round(Math.abs(current_duration - wanted_duration) / 1000)).toString() + ', movement: ' + (movement / 1000).toString() + ', tolerance: ' + (tolerance / 1000).toString() + ', returning: ' + false);
2160 if(this.current.name === 'auto') {
2161 this.log(logme + 'caller called me with mode: ' + this.current.name);
2162 this.setMode('pan');
2165 if(this.debug === true)
2166 this.log(logme + 'ACCEPTING UPDATE: current/min duration: ' + (current_duration / 1000).toString() + '/' + (this.fixed_min_duration / 1000).toString() + ', wanted duration: ' + (wanted_duration / 1000).toString() + ', duration diff: ' + (Math.round(Math.abs(current_duration - wanted_duration) / 1000)).toString() + ', movement: ' + (movement / 1000).toString() + ', tolerance: ' + (tolerance / 1000).toString() + ', returning: ' + ret);
2168 this.current.force_update_at = Date.now() + NETDATA.options.current.pan_and_zoom_delay;
2169 this.current.force_after_ms = after;
2170 this.current.force_before_ms = before;
2171 NETDATA.globalPanAndZoom.setMaster(this, after, before);
2175 this.legendFormatValue = function(value) {
2176 if(value === null || value === 'undefined') return '-';
2177 if(typeof value !== 'number') return value;
2179 if(this.value_decimal_detail !== -1)
2180 return (Math.round(value * this.value_decimal_detail) / this.value_decimal_detail).toLocaleString();
2182 var abs = Math.abs(value);
2183 if(abs >= 1000) return (Math.round(value)).toLocaleString();
2184 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
2185 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
2186 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
2187 return (Math.round(value * 10000) / 10000).toLocaleString();
2190 this.legendSetLabelValue = function(label, value) {
2191 var series = this.element_legend_childs.series[label];
2192 if(typeof series === 'undefined') return;
2193 if(series.value === null && series.user === null) return;
2195 // if the value has not changed, skip DOM update
2196 //if(series.last === value) return;
2199 if(typeof value === 'number') {
2200 var v = Math.abs(value);
2201 s = r = this.legendFormatValue(value);
2203 if(typeof series.last === 'number') {
2204 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2205 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2206 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2208 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2213 series.last = value;
2216 if(series.value !== null) series.value.innerHTML = s;
2217 if(series.user !== null) series.user.innerHTML = r;
2220 this.legendSetDate = function(ms) {
2221 if(typeof ms !== 'number') {
2222 this.legendShowUndefined();
2226 var d = new Date(ms);
2228 if(this.element_legend_childs.title_date)
2229 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
2231 if(this.element_legend_childs.title_time)
2232 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
2234 if(this.element_legend_childs.title_units)
2235 this.element_legend_childs.title_units.innerHTML = this.units;
2238 this.legendShowUndefined = function() {
2239 if(this.element_legend_childs.title_date)
2240 this.element_legend_childs.title_date.innerHTML = ' ';
2242 if(this.element_legend_childs.title_time)
2243 this.element_legend_childs.title_time.innerHTML = this.chart.name;
2245 if(this.element_legend_childs.title_units)
2246 this.element_legend_childs.title_units.innerHTML = ' ';
2248 if(this.data && this.element_legend_childs.series !== null) {
2249 var labels = this.data.dimension_names;
2250 var i = labels.length;
2252 var label = labels[i];
2254 if(typeof label === 'undefined') continue;
2255 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2256 this.legendSetLabelValue(label, null);
2261 this.legendShowLatestValues = function() {
2262 if(this.chart === null) return;
2263 if(this.selected) return;
2265 if(this.data === null || this.element_legend_childs.series === null) {
2266 this.legendShowUndefined();
2270 var show_undefined = true;
2271 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2272 show_undefined = false;
2274 if(show_undefined) {
2275 this.legendShowUndefined();
2279 this.legendSetDate(this.view_before);
2281 var labels = this.data.dimension_names;
2282 var i = labels.length;
2284 var label = labels[i];
2286 if(typeof label === 'undefined') continue;
2287 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2290 this.legendSetLabelValue(label, null);
2292 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2296 this.legendReset = function() {
2297 this.legendShowLatestValues();
2300 // this should be called just ONCE per dimension per chart
2301 this._chartDimensionColor = function(label) {
2302 if(this.colors === null) this.chartColors();
2304 if(typeof this.colors_assigned[label] === 'undefined') {
2305 if(this.colors_available.length === 0) {
2306 var len = NETDATA.themes.current.colors.length;
2308 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2311 this.colors_assigned[label] = this.colors_available.shift();
2313 if(this.debug === true)
2314 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2317 if(this.debug === true)
2318 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2321 this.colors.push(this.colors_assigned[label]);
2322 return this.colors_assigned[label];
2325 this.chartColors = function() {
2326 if(this.colors !== null) return this.colors;
2328 this.colors = new Array();
2329 this.colors_available = new Array();
2331 // add the standard colors
2332 var len = NETDATA.themes.current.colors.length;
2334 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2336 // add the user supplied colors
2337 var c = $(this.element).data('colors');
2338 // this.log('read colors: ' + c);
2339 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2340 if(typeof c !== 'string') {
2341 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2351 this.colors_available.unshift(c[len]);
2352 // this.log('adding color: ' + c[len]);
2361 this.legendUpdateDOM = function() {
2364 // check that the legend DOM is up to date for the downloaded dimensions
2365 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2366 // this.log('the legend does not have any series - requesting legend update');
2369 else if(this.data === null) {
2370 // this.log('the chart does not have any data - requesting legend update');
2373 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2377 var labels = this.data.dimension_names.toString();
2378 if(labels !== this.element_legend_childs.series.labels_key) {
2381 if(this.debug === true)
2382 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2386 if(needed === false) {
2387 // make sure colors available
2390 // do we have to update the current values?
2391 // we do this, only when the visible chart is current
2392 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2393 if(this.debug === true)
2394 this.log('chart is in latest position... updating values on legend...');
2396 //var labels = this.data.dimension_names;
2397 //var i = labels.length;
2399 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2403 if(this.colors === null) {
2404 // this is the first time we update the chart
2405 // let's assign colors to all dimensions
2406 if(this.library.track_colors() === true)
2407 for(var dim in this.chart.dimensions)
2408 this._chartDimensionColor(this.chart.dimensions[dim].name);
2410 // we will re-generate the colors for the chart
2411 // based on the selected dimensions
2414 if(this.debug === true)
2415 this.log('updating Legend DOM');
2417 // mark all dimensions as invalid
2418 this.dimensions_visibility.invalidateAll();
2420 var genLabel = function(state, parent, dim, name, count) {
2421 var color = state._chartDimensionColor(name);
2423 var user_element = null;
2424 var user_id = self.data('show-value-of-' + name.toLowerCase() + '-at') || null;
2425 if(user_id === null)
2426 user_id = self.data('show-value-of-' + dim.toLowerCase() + '-at') || null;
2427 if(user_id !== null) {
2428 user_element = document.getElementById(user_id) || null;
2429 if (user_element === null)
2430 state.log('Cannot find element with id: ' + user_id);
2433 state.element_legend_childs.series[name] = {
2434 name: document.createElement('span'),
2435 value: document.createElement('span'),
2440 var label = state.element_legend_childs.series[name];
2442 // create the dimension visibility tracking for this label
2443 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2445 var rgb = NETDATA.colorHex2Rgb(color);
2446 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2447 + state.chart.chart_type
2448 + '" style="background-color: '
2449 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2450 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2452 var text = document.createTextNode(' ' + name);
2453 label.name.appendChild(text);
2456 parent.appendChild(document.createElement('br'));
2458 parent.appendChild(label.name);
2459 parent.appendChild(label.value);
2462 var content = document.createElement('div');
2464 if(this.hasLegend()) {
2465 this.element_legend_childs = {
2467 resize_handler: document.createElement('div'),
2468 toolbox: document.createElement('div'),
2469 toolbox_left: document.createElement('div'),
2470 toolbox_right: document.createElement('div'),
2471 toolbox_reset: document.createElement('div'),
2472 toolbox_zoomin: document.createElement('div'),
2473 toolbox_zoomout: document.createElement('div'),
2474 toolbox_volume: document.createElement('div'),
2475 title_date: document.createElement('span'),
2476 title_time: document.createElement('span'),
2477 title_units: document.createElement('span'),
2478 nano: document.createElement('div'),
2480 paneClass: 'netdata-legend-series-pane',
2481 sliderClass: 'netdata-legend-series-slider',
2482 contentClass: 'netdata-legend-series-content',
2483 enabledClass: '__enabled',
2484 flashedClass: '__flashed',
2485 activeClass: '__active',
2487 alwaysVisible: true,
2493 this.element_legend.innerHTML = '';
2495 if(this.library.toolboxPanAndZoom !== null) {
2497 function get_pan_and_zoom_step(event) {
2499 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2501 else if (event.shiftKey)
2502 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2504 else if (event.altKey)
2505 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2508 return NETDATA.options.current.pan_and_zoom_factor;
2511 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2512 this.element.appendChild(this.element_legend_childs.toolbox);
2514 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2515 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2516 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2517 this.element_legend_childs.toolbox_left.onclick = function(e) {
2520 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2521 var before = that.view_before - step;
2522 var after = that.view_after - step;
2523 if(after >= that.netdata_first)
2524 that.library.toolboxPanAndZoom(that, after, before);
2526 if(NETDATA.options.current.show_help === true)
2527 $(this.element_legend_childs.toolbox_left).popover({
2532 placement: 'bottom',
2533 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2535 content: 'Pan the chart to the left. You can also <b>drag it</b> with your mouse or your finger (on touch devices).<br/><small>Help, can be disabled from the settings.</small>'
2539 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2540 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2541 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2542 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2544 NETDATA.resetAllCharts(that);
2546 if(NETDATA.options.current.show_help === true)
2547 $(this.element_legend_childs.toolbox_reset).popover({
2552 placement: 'bottom',
2553 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2554 title: 'Chart Reset',
2555 content: 'Reset all the charts to their default auto-refreshing state. You can also <b>double click</b> the chart contents with your mouse or your finger (on touch devices).<br/><small>Help, can be disabled from the settings.</small>'
2558 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2559 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2560 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2561 this.element_legend_childs.toolbox_right.onclick = function(e) {
2563 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2564 var before = that.view_before + step;
2565 var after = that.view_after + step;
2566 if(before <= that.netdata_last)
2567 that.library.toolboxPanAndZoom(that, after, before);
2569 if(NETDATA.options.current.show_help === true)
2570 $(this.element_legend_childs.toolbox_right).popover({
2575 placement: 'bottom',
2576 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2578 content: 'Pan the chart to the right. You can also <b>drag it</b> with your mouse or your finger (on touch devices).<br/><small>Help, can be disabled from the settings.</small>'
2582 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2583 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2584 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2585 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2587 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2588 var before = that.view_before - dt;
2589 var after = that.view_after + dt;
2590 that.library.toolboxPanAndZoom(that, after, before);
2592 if(NETDATA.options.current.show_help === true)
2593 $(this.element_legend_childs.toolbox_zoomin).popover({
2598 placement: 'bottom',
2599 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2600 title: 'Chart Zoom In',
2601 content: 'Zoom in the chart. You can also press SHIFT and select an area of the chart to zoom in. On Chrome and Opera, you can press the SHIFT or the ALT keys and then use the mouse wheel to zoom in or out.<br/><small>Help, can be disabled from the settings.</small>'
2604 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2605 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2606 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2607 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2609 var dt = (((that.view_before - that.view_after) / (1.0 - (get_pan_and_zoom_step(e) * 0.8)) - (that.view_before - that.view_after)) / 2);
2610 var before = that.view_before + dt;
2611 var after = that.view_after - dt;
2613 that.library.toolboxPanAndZoom(that, after, before);
2615 if(NETDATA.options.current.show_help === true)
2616 $(this.element_legend_childs.toolbox_zoomout).popover({
2621 placement: 'bottom',
2622 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2623 title: 'Chart Zoom Out',
2624 content: 'Zoom out the chart. On Chrome and Opera, you can also press the SHIFT or the ALT keys and then use the mouse wheel to zoom in or out.<br/><small>Help, can be disabled from the settings.</small>'
2627 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2628 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2629 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2630 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2631 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2632 //e.preventDefault();
2633 //alert('clicked toolbox_volume on ' + that.id);
2637 this.element_legend_childs.toolbox = null;
2638 this.element_legend_childs.toolbox_left = null;
2639 this.element_legend_childs.toolbox_reset = null;
2640 this.element_legend_childs.toolbox_right = null;
2641 this.element_legend_childs.toolbox_zoomin = null;
2642 this.element_legend_childs.toolbox_zoomout = null;
2643 this.element_legend_childs.toolbox_volume = null;
2646 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2647 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2648 this.element.appendChild(this.element_legend_childs.resize_handler);
2649 if(NETDATA.options.current.show_help === true)
2650 $(this.element_legend_childs.resize_handler).popover({
2655 placement: 'bottom',
2656 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2657 title: 'Chart Resize',
2658 content: 'Drag this point with your mouse or your finger (on touch devices), to resize the chart vertically. You can also <b>double click it</b> or <b>double tap it</b> to reset between 2 states: the default and the one that fits all the values.<br/><small>Help, can be disabled from the settings.</small>'
2662 this.element_legend_childs.resize_handler.onmousedown =
2664 that.resizeHandler(e);
2668 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2669 that.resizeHandler(e);
2672 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2673 this.element_legend.appendChild(this.element_legend_childs.title_date);
2675 this.element_legend.appendChild(document.createElement('br'));
2677 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2678 this.element_legend.appendChild(this.element_legend_childs.title_time);
2680 this.element_legend.appendChild(document.createElement('br'));
2682 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2683 this.element_legend.appendChild(this.element_legend_childs.title_units);
2685 this.element_legend.appendChild(document.createElement('br'));
2687 this.element_legend_childs.nano.className = 'netdata-legend-series';
2688 this.element_legend.appendChild(this.element_legend_childs.nano);
2690 content.className = 'netdata-legend-series-content';
2691 this.element_legend_childs.nano.appendChild(content);
2693 if(NETDATA.options.current.show_help === true)
2694 $(content).popover({
2699 placement: 'bottom',
2700 title: 'Chart Legend',
2701 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2702 content: 'You can click or tap on the values or the labels to select dimentions. By pressing SHIFT or CONTROL, you can enable or disable multiple dimensions.<br/><small>Help, can be disabled from the settings.</small>'
2706 this.element_legend_childs = {
2708 resize_handler: null,
2711 toolbox_right: null,
2712 toolbox_reset: null,
2713 toolbox_zoomin: null,
2714 toolbox_zoomout: null,
2715 toolbox_volume: null,
2726 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2727 if(this.debug === true)
2728 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2730 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2731 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2735 var tmp = new Array();
2736 for(var dim in this.chart.dimensions) {
2737 tmp.push(this.chart.dimensions[dim].name);
2738 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2740 this.element_legend_childs.series.labels_key = tmp.toString();
2741 if(this.debug === true)
2742 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2745 // create a hidden div to be used for hidding
2746 // the original legend of the chart library
2747 var el = document.createElement('div');
2748 if(this.element_legend !== null)
2749 this.element_legend.appendChild(el);
2750 el.style.display = 'none';
2752 this.element_legend_childs.hidden = document.createElement('div');
2753 el.appendChild(this.element_legend_childs.hidden);
2755 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2756 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2758 this.legendShowLatestValues();
2761 this.hasLegend = function() {
2762 if(typeof this.___hasLegendCache___ !== 'undefined')
2763 return this.___hasLegendCache___;
2766 if(this.library && this.library.legend(this) === 'right-side') {
2767 var legend = $(this.element).data('legend') || 'yes';
2768 if(legend === 'yes') leg = true;
2771 this.___hasLegendCache___ = leg;
2775 this.legendWidth = function() {
2776 return (this.hasLegend())?140:0;
2779 this.legendHeight = function() {
2780 return $(this.element).height();
2783 this.chartWidth = function() {
2784 return $(this.element).width() - this.legendWidth();
2787 this.chartHeight = function() {
2788 return $(this.element).height();
2791 this.chartPixelsPerPoint = function() {
2792 // force an options provided detail
2793 var px = this.pixels_per_point;
2795 if(this.library && px < this.library.pixels_per_point(this))
2796 px = this.library.pixels_per_point(this);
2798 if(px < NETDATA.options.current.pixels_per_point)
2799 px = NETDATA.options.current.pixels_per_point;
2804 this.needsRecreation = function() {
2806 this.chart_created === true
2808 && this.library.autoresize() === false
2809 && this.tm.last_resized < NETDATA.options.last_resized
2813 this.chartURL = function() {
2814 var after, before, points_multiplier = 1;
2815 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2816 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2818 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2819 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2820 this.view_after = after * 1000;
2821 this.view_before = before * 1000;
2823 this.requested_padding = null;
2824 points_multiplier = 1;
2826 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2827 this.tm.pan_and_zoom_seq = 0;
2829 before = Math.round(this.current.force_before_ms / 1000);
2830 after = Math.round(this.current.force_after_ms / 1000);
2831 this.view_after = after * 1000;
2832 this.view_before = before * 1000;
2834 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2835 this.requested_padding = Math.round((before - after) / 2);
2836 after -= this.requested_padding;
2837 before += this.requested_padding;
2838 this.requested_padding *= 1000;
2839 points_multiplier = 2;
2842 this.current.force_before_ms = null;
2843 this.current.force_after_ms = null;
2846 this.tm.pan_and_zoom_seq = 0;
2848 before = this.before;
2850 this.view_after = after * 1000;
2851 this.view_before = before * 1000;
2853 this.requested_padding = null;
2854 points_multiplier = 1;
2857 this.requested_after = after * 1000;
2858 this.requested_before = before * 1000;
2860 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2862 // build the data URL
2863 this.data_url = this.host + this.chart.data_url;
2864 this.data_url += "&format=" + this.library.format();
2865 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2866 this.data_url += "&group=" + this.method;
2868 if(this.override_options !== null)
2869 this.data_url += "&options=" + this.override_options.toString();
2871 this.data_url += "&options=" + this.library.options(this);
2873 this.data_url += '|jsonwrap';
2875 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2876 this.data_url += '|nonzero';
2878 if(this.append_options !== null)
2879 this.data_url += '|' + this.append_options.toString();
2882 this.data_url += "&after=" + after.toString();
2885 this.data_url += "&before=" + before.toString();
2888 this.data_url += "&dimensions=" + this.dimensions;
2890 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2891 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2894 this.redrawChart = function() {
2895 if(this.data !== null)
2896 this.updateChartWithData(this.data);
2899 this.updateChartWithData = function(data) {
2900 if(this.debug === true)
2901 this.log('updateChartWithData() called.');
2903 // this may force the chart to be re-created
2907 this.updates_counter++;
2908 this.updates_since_last_unhide++;
2909 this.updates_since_last_creation++;
2911 var started = Date.now();
2913 // if the result is JSON, find the latest update-every
2914 this.data_update_every = data.view_update_every * 1000;
2915 this.data_after = data.after * 1000;
2916 this.data_before = data.before * 1000;
2917 this.netdata_first = data.first_entry * 1000;
2918 this.netdata_last = data.last_entry * 1000;
2919 this.data_points = data.points;
2922 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2923 if(this.view_after < this.data_after) {
2924 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2925 this.view_after = this.data_after;
2928 if(this.view_before > this.data_before) {
2929 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2930 this.view_before = this.data_before;
2934 this.view_after = this.data_after;
2935 this.view_before = this.data_before;
2938 if(this.debug === true) {
2939 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2941 if(this.current.force_after_ms)
2942 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2944 this.log('STATUS: forced : unset');
2946 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2947 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2948 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2949 this.log('STATUS: points : ' + (this.data_points).toString());
2952 if(this.data_points === 0) {
2957 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2958 if(this.debug === true)
2959 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2961 this.chart_created = false;
2964 // check and update the legend
2965 this.legendUpdateDOM();
2967 if(this.chart_created === true
2968 && typeof this.library.update === 'function') {
2970 if(this.debug === true)
2971 this.log('updating chart...');
2973 if(callChartLibraryUpdateSafely(data) === false)
2977 if(this.debug === true)
2978 this.log('creating chart...');
2980 if(callChartLibraryCreateSafely(data) === false)
2984 this.legendShowLatestValues();
2985 if(this.selected === true)
2986 NETDATA.globalSelectionSync.stop();
2988 // update the performance counters
2989 var now = Date.now();
2990 this.tm.last_updated = now;
2992 // don't update last_autorefreshed if this chart is
2993 // forced to be updated with global PanAndZoom
2994 if(NETDATA.globalPanAndZoom.isActive())
2995 this.tm.last_autorefreshed = 0;
2997 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
2998 this.tm.last_autorefreshed = now - (now % this.data_update_every);
3000 this.tm.last_autorefreshed = now;
3003 this.refresh_dt_ms = now - started;
3004 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
3006 if(this.refresh_dt_element !== null)
3007 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
3010 this.updateChart = function(callback) {
3011 if(this.debug === true)
3012 this.log('updateChart() called.');
3014 if(this._updating === true) {
3015 if(this.debug === true)
3016 this.log('I am already updating...');
3018 if(typeof callback === 'function') callback();
3022 // due to late initialization of charts and libraries
3023 // we need to check this too
3024 if(this.enabled === false) {
3025 if(this.debug === true)
3026 this.log('I am not enabled');
3028 if(typeof callback === 'function') callback();
3032 if(canBeRendered() === false) {
3033 if(typeof callback === 'function') callback();
3037 if(this.chart === null) {
3038 this.getChart(function() { that.updateChart(callback); });
3042 if(this.library.initialized === false) {
3043 if(this.library.enabled === true) {
3044 this.library.initialize(function() { that.updateChart(callback); });
3048 error('chart library "' + this.library_name + '" is not available.');
3049 if(typeof callback === 'function') callback();
3054 this.clearSelection();
3057 if(this.debug === true)
3058 this.log('updating from ' + this.data_url);
3060 NETDATA.statistics.refreshes_total++;
3061 NETDATA.statistics.refreshes_active++;
3063 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
3064 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
3066 this._updating = true;
3068 this.xhr = $.ajax( {
3073 'Cache-Control': 'no-cache, no-store',
3074 'Pragma': 'no-cache'
3076 xhrFields: { withCredentials: true } // required for the cookie
3078 .done(function(data) {
3079 that.xhr = undefined;
3081 if(that.debug === true)
3082 that.log('data received. updating chart.');
3084 that.updateChartWithData(data);
3086 .fail(function(msg) {
3087 that.xhr = undefined;
3089 if(msg.statusText !== 'abort')
3090 error('data download failed for url: ' + that.data_url);
3092 .always(function() {
3093 that.xhr = undefined;
3095 NETDATA.statistics.refreshes_active--;
3096 that._updating = false;
3097 if(typeof callback === 'function') callback();
3103 this.isVisible = function(nocache) {
3104 if(typeof nocache === 'undefined')
3107 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
3109 // caching - we do not evaluate the charts visibility
3110 // if the page has not been scrolled since the last check
3111 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
3112 return this.___isVisible___;
3114 this.tm.last_visible_check = Date.now();
3116 var wh = window.innerHeight;
3117 var x = this.element.getBoundingClientRect();
3121 if(x.width === 0 || x.height === 0) {
3123 this.___isVisible___ = false;
3124 return this.___isVisible___;
3127 if(x.top < 0 && -x.top > x.height) {
3128 // the chart is entirely above
3129 ret = -x.top - x.height;
3131 else if(x.top > wh) {
3132 // the chart is entirely below
3136 if(ret > tolerance) {
3137 // the chart is too far
3140 this.___isVisible___ = false;
3141 return this.___isVisible___;
3144 // the chart is inside or very close
3147 this.___isVisible___ = true;
3148 return this.___isVisible___;
3152 this.isAutoRefreshable = function() {
3153 return (this.current.autorefresh);
3156 this.canBeAutoRefreshed = function() {
3157 var now = Date.now();
3159 if(this.running === true) {
3160 if(this.debug === true)
3161 this.log('I am already running');
3166 if(this.enabled === false) {
3167 if(this.debug === true)
3168 this.log('I am not enabled');
3173 if(this.library === null || this.library.enabled === false) {
3174 error('charting library "' + this.library_name + '" is not available');
3175 if(this.debug === true)
3176 this.log('My chart library ' + this.library_name + ' is not available');
3181 if(this.isVisible() === false) {
3182 if(NETDATA.options.debug.visibility === true || this.debug === true)
3183 this.log('I am not visible');
3188 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3189 if(this.debug === true)
3190 this.log('timed force update detected - allowing this update');
3192 this.current.force_update_at = 0;
3196 if(this.isAutoRefreshable() === true) {
3197 // allow the first update, even if the page is not visible
3198 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3199 if(NETDATA.options.debug.focus === true || this.debug === true)
3200 this.log('canBeAutoRefreshed(): page does not have focus');
3205 if(this.needsRecreation() === true) {
3206 if(this.debug === true)
3207 this.log('canBeAutoRefreshed(): needs re-creation.');
3212 // options valid only for autoRefresh()
3213 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3214 if(NETDATA.globalPanAndZoom.isActive()) {
3215 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3216 if(this.debug === true)
3217 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3222 if(this.debug === true)
3223 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3229 if(this.selected === true) {
3230 if(this.debug === true)
3231 this.log('canBeAutoRefreshed(): I have a selection in place.');
3236 if(this.paused === true) {
3237 if(this.debug === true)
3238 this.log('canBeAutoRefreshed(): I am paused.');
3243 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3244 if(this.debug === true)
3245 this.log('canBeAutoRefreshed(): It is time to update me.');
3255 this.autoRefresh = function(callback) {
3256 if(this.canBeAutoRefreshed() === true && this.running === false) {
3259 state.running = true;
3260 state.updateChart(function() {
3261 state.running = false;
3263 if(typeof callback !== 'undefined')
3268 if(typeof callback !== 'undefined')
3273 this._defaultsFromDownloadedChart = function(chart) {
3275 this.chart_url = chart.url;
3276 this.data_update_every = chart.update_every * 1000;
3277 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3278 this.tm.last_info_downloaded = Date.now();
3280 if(this.title === null)
3281 this.title = chart.title;
3283 if(this.units === null)
3284 this.units = chart.units;
3287 // fetch the chart description from the netdata server
3288 this.getChart = function(callback) {
3289 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3291 this._defaultsFromDownloadedChart(this.chart);
3292 if(typeof callback === 'function') callback();
3295 this.chart_url = "/api/v1/chart?chart=" + this.id;
3297 if(this.debug === true)
3298 this.log('downloading ' + this.chart_url);
3301 url: this.host + this.chart_url,
3304 xhrFields: { withCredentials: true } // required for the cookie
3306 .done(function(chart) {
3307 chart.url = that.chart_url;
3308 that._defaultsFromDownloadedChart(chart);
3309 NETDATA.chartRegistry.add(that.host, that.id, chart);
3312 NETDATA.error(404, that.chart_url);
3313 error('chart not found on url "' + that.chart_url + '"');
3315 .always(function() {
3316 if(typeof callback === 'function') callback();
3321 // ============================================================================================================
3327 NETDATA.resetAllCharts = function(state) {
3328 // first clear the global selection sync
3329 // to make sure no chart is in selected state
3330 state.globalSelectionSyncStop();
3332 // there are 2 possibilities here
3333 // a. state is the global Pan and Zoom master
3334 // b. state is not the global Pan and Zoom master
3336 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3339 // clear the global Pan and Zoom
3340 // this will also refresh the master
3341 // and unblock any charts currently mirroring the master
3342 NETDATA.globalPanAndZoom.clearMaster();
3344 // if we were not the master, reset our status too
3345 // this is required because most probably the mouse
3346 // is over this chart, blocking it from auto-refreshing
3347 if(master === false && (state.paused === true || state.selected === true))
3351 // get or create a chart state, given a DOM element
3352 NETDATA.chartState = function(element) {
3353 var state = $(element).data('netdata-state-object') || null;
3354 if(state === null) {
3355 state = new chartState(element);
3356 $(element).data('netdata-state-object', state);
3361 // ----------------------------------------------------------------------------------------------------------------
3362 // Library functions
3364 // Load a script without jquery
3365 // This is used to load jquery - after it is loaded, we use jquery
3366 NETDATA._loadjQuery = function(callback) {
3367 if(typeof jQuery === 'undefined') {
3368 if(NETDATA.options.debug.main_loop === true)
3369 console.log('loading ' + NETDATA.jQuery);
3371 var script = document.createElement('script');
3372 script.type = 'text/javascript';
3373 script.async = true;
3374 script.src = NETDATA.jQuery;
3376 // script.onabort = onError;
3377 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3378 if(typeof callback === "function")
3379 script.onload = callback;
3381 var s = document.getElementsByTagName('script')[0];
3382 s.parentNode.insertBefore(script, s);
3384 else if(typeof callback === "function")
3388 NETDATA._loadCSS = function(filename) {
3389 // don't use jQuery here
3390 // styles are loaded before jQuery
3391 // to eliminate showing an unstyled page to the user
3393 var fileref = document.createElement("link");
3394 fileref.setAttribute("rel", "stylesheet");
3395 fileref.setAttribute("type", "text/css");
3396 fileref.setAttribute("href", filename);
3398 if (typeof fileref !== 'undefined')
3399 document.getElementsByTagName("head")[0].appendChild(fileref);
3402 NETDATA.colorHex2Rgb = function(hex) {
3403 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3404 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3405 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3406 return r + r + g + g + b + b;
3409 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3411 r: parseInt(result[1], 16),
3412 g: parseInt(result[2], 16),
3413 b: parseInt(result[3], 16)
3417 NETDATA.colorLuminance = function(hex, lum) {
3418 // validate hex string
3419 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3421 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3425 // convert to decimal and change luminosity
3426 var rgb = "#", c, i;
3427 for (i = 0; i < 3; i++) {
3428 c = parseInt(hex.substr(i*2,2), 16);
3429 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3430 rgb += ("00"+c).substr(c.length);
3436 NETDATA.guid = function() {
3438 return Math.floor((1 + Math.random()) * 0x10000)
3443 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3446 NETDATA.zeropad = function(x) {
3447 if(x > -10 && x < 10) return '0' + x.toString();
3448 else return x.toString();
3451 // user function to signal us the DOM has been
3453 NETDATA.updatedDom = function() {
3454 NETDATA.options.updated_dom = true;
3457 NETDATA.ready = function(callback) {
3458 NETDATA.options.pauseCallback = callback;
3461 NETDATA.pause = function(callback) {
3462 if(NETDATA.options.pause === true)
3465 NETDATA.options.pauseCallback = callback;
3468 NETDATA.unpause = function() {
3469 NETDATA.options.pauseCallback = null;
3470 NETDATA.options.updated_dom = true;
3471 NETDATA.options.pause = false;
3474 // ----------------------------------------------------------------------------------------------------------------
3476 // this is purely sequencial charts refresher
3477 // it is meant to be autonomous
3478 NETDATA.chartRefresherNoParallel = function(index) {
3479 if(NETDATA.options.debug.mail_loop === true)
3480 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3482 if(NETDATA.options.updated_dom === true) {
3483 // the dom has been updated
3484 // get the dom parts again
3485 NETDATA.parseDom(NETDATA.chartRefresher);
3488 if(index >= NETDATA.options.targets.length) {
3489 if(NETDATA.options.debug.main_loop === true)
3490 console.log('waiting to restart main loop...');
3492 NETDATA.options.auto_refresher_fast_weight = 0;
3494 setTimeout(function() {
3495 NETDATA.chartRefresher();
3496 }, NETDATA.options.current.idle_between_loops);
3499 var state = NETDATA.options.targets[index];
3501 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3502 if(NETDATA.options.debug.main_loop === true)
3503 console.log('fast rendering...');
3505 state.autoRefresh(function() {
3506 NETDATA.chartRefresherNoParallel(++index);
3510 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3511 NETDATA.options.auto_refresher_fast_weight = 0;
3513 setTimeout(function() {
3514 state.autoRefresh(function() {
3515 NETDATA.chartRefresherNoParallel(++index);
3517 }, NETDATA.options.current.idle_between_charts);
3522 // this is part of the parallel refresher
3523 // its cause is to refresh sequencially all the charts
3524 // that depend on chart library initialization
3525 // it will call the parallel refresher back
3526 // as soon as it sees a chart that its chart library
3528 NETDATA.chartRefresher_uninitialized = function() {
3529 if(NETDATA.options.updated_dom === true) {
3530 // the dom has been updated
3531 // get the dom parts again
3532 NETDATA.parseDom(NETDATA.chartRefresher);
3536 if(NETDATA.options.sequencial.length === 0)
3537 NETDATA.chartRefresher();
3539 var state = NETDATA.options.sequencial.pop();
3540 if(state.library.initialized === true)
3541 NETDATA.chartRefresher();
3543 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3547 NETDATA.chartRefresherWaitTime = function() {
3548 return NETDATA.options.current.idle_parallel_loops;
3551 // the default refresher
3552 // it will create 2 sets of charts:
3553 // - the ones that can be refreshed in parallel
3554 // - the ones that depend on something else
3555 // the first set will be executed in parallel
3556 // the second will be given to NETDATA.chartRefresher_uninitialized()
3557 NETDATA.chartRefresher = function() {
3558 // console.log('auto-refresher...');
3560 if(NETDATA.options.pause === true) {
3561 // console.log('auto-refresher is paused');
3562 setTimeout(NETDATA.chartRefresher,
3563 NETDATA.chartRefresherWaitTime());
3567 if(typeof NETDATA.options.pauseCallback === 'function') {
3568 // console.log('auto-refresher is calling pauseCallback');
3569 NETDATA.options.pause = true;
3570 NETDATA.options.pauseCallback();
3571 NETDATA.chartRefresher();
3575 if(NETDATA.options.current.parallel_refresher === false) {
3576 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3577 NETDATA.chartRefresherNoParallel(0);
3581 if(NETDATA.options.updated_dom === true) {
3582 // the dom has been updated
3583 // get the dom parts again
3584 // console.log('auto-refresher is calling parseDom()');
3585 NETDATA.parseDom(NETDATA.chartRefresher);
3589 var parallel = new Array();
3590 var targets = NETDATA.options.targets;
3591 var len = targets.length;
3594 state = targets[len];
3595 if(state.isVisible() === false || state.running === true)
3598 if(state.library.initialized === false) {
3599 if(state.library.enabled === true) {
3600 state.library.initialize(NETDATA.chartRefresher);
3604 state.error('chart library "' + state.library_name + '" is not enabled.');
3608 parallel.unshift(state);
3611 if(parallel.length > 0) {
3612 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3613 // this will execute the jobs in parallel
3614 $(parallel).each(function() {
3619 // console.log('auto-refresher nothing to do');
3622 // run the next refresh iteration
3623 setTimeout(NETDATA.chartRefresher,
3624 NETDATA.chartRefresherWaitTime());
3627 NETDATA.parseDom = function(callback) {
3628 NETDATA.options.last_page_scroll = Date.now();
3629 NETDATA.options.updated_dom = false;
3631 var targets = $('div[data-netdata]'); //.filter(':visible');
3633 if(NETDATA.options.debug.main_loop === true)
3634 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3636 NETDATA.options.targets = new Array();
3637 var len = targets.length;
3639 // the initialization will take care of sizing
3640 // and the "loading..." message
3641 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3644 if(typeof callback === 'function') callback();
3647 // this is the main function - where everything starts
3648 NETDATA.start = function() {
3649 // this should be called only once
3651 NETDATA.options.page_is_visible = true;
3653 $(window).blur(function() {
3654 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3655 NETDATA.options.page_is_visible = false;
3656 if(NETDATA.options.debug.focus === true)
3657 console.log('Lost Focus!');
3661 $(window).focus(function() {
3662 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3663 NETDATA.options.page_is_visible = true;
3664 if(NETDATA.options.debug.focus === true)
3665 console.log('Focus restored!');
3669 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3670 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3671 NETDATA.options.page_is_visible = false;
3672 if(NETDATA.options.debug.focus === true)
3673 console.log('Document has no focus!');
3677 // bootstrap tab switching
3678 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3680 // bootstrap modal switching
3681 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3682 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3684 // bootstrap collapse switching
3685 $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3686 $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3688 NETDATA.parseDom(NETDATA.chartRefresher);
3690 // Alarms initialization
3691 setTimeout(NETDATA.alarms.init, 1000);
3693 // Registry initialization
3694 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3696 if(typeof netdataCallback === 'function')
3700 // ----------------------------------------------------------------------------------------------------------------
3703 NETDATA.peityInitialize = function(callback) {
3704 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3706 url: NETDATA.peity_js,
3709 xhrFields: { withCredentials: true } // required for the cookie
3712 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3715 NETDATA.chartLibraries.peity.enabled = false;
3716 NETDATA.error(100, NETDATA.peity_js);
3718 .always(function() {
3719 if(typeof callback === "function")
3724 NETDATA.chartLibraries.peity.enabled = false;
3725 if(typeof callback === "function")
3730 NETDATA.peityChartUpdate = function(state, data) {
3731 state.peity_instance.innerHTML = data.result;
3733 if(state.peity_options.stroke !== state.chartColors()[0]) {
3734 state.peity_options.stroke = state.chartColors()[0];
3735 if(state.chart.chart_type === 'line')
3736 state.peity_options.fill = NETDATA.themes.current.background;
3738 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3741 $(state.peity_instance).peity('line', state.peity_options);
3745 NETDATA.peityChartCreate = function(state, data) {
3746 state.peity_instance = document.createElement('div');
3747 state.element_chart.appendChild(state.peity_instance);
3749 var self = $(state.element);
3750 state.peity_options = {
3751 stroke: NETDATA.themes.current.foreground,
3752 strokeWidth: self.data('peity-strokewidth') || 1,
3753 width: state.chartWidth(),
3754 height: state.chartHeight(),
3755 fill: NETDATA.themes.current.foreground
3758 NETDATA.peityChartUpdate(state, data);
3762 // ----------------------------------------------------------------------------------------------------------------
3765 NETDATA.sparklineInitialize = function(callback) {
3766 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3768 url: NETDATA.sparkline_js,
3771 xhrFields: { withCredentials: true } // required for the cookie
3774 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3777 NETDATA.chartLibraries.sparkline.enabled = false;
3778 NETDATA.error(100, NETDATA.sparkline_js);
3780 .always(function() {
3781 if(typeof callback === "function")
3786 NETDATA.chartLibraries.sparkline.enabled = false;
3787 if(typeof callback === "function")
3792 NETDATA.sparklineChartUpdate = function(state, data) {
3793 state.sparkline_options.width = state.chartWidth();
3794 state.sparkline_options.height = state.chartHeight();
3796 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3800 NETDATA.sparklineChartCreate = function(state, data) {
3801 var self = $(state.element);
3802 var type = self.data('sparkline-type') || 'line';
3803 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3804 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3805 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3806 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3807 var composite = self.data('sparkline-composite') || undefined;
3808 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3809 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3810 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3811 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3812 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3813 var spotColor = self.data('sparkline-spotcolor') || undefined;
3814 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3815 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3816 var spotRadius = self.data('sparkline-spotradius') || undefined;
3817 var valueSpots = self.data('sparkline-valuespots') || undefined;
3818 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3819 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3820 var lineWidth = self.data('sparkline-linewidth') || undefined;
3821 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3822 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3823 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3824 var xvalues = self.data('sparkline-xvalues') || undefined;
3825 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3826 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3827 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3828 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3829 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3830 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3831 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3832 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3833 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3834 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3835 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3836 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3837 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3838 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3839 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3840 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3841 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3842 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3843 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3844 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3845 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3846 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3848 if(spotColor === 'disable') spotColor='';
3849 if(minSpotColor === 'disable') minSpotColor='';
3850 if(maxSpotColor === 'disable') maxSpotColor='';
3852 state.sparkline_options = {
3854 lineColor: lineColor,
3855 fillColor: fillColor,
3856 chartRangeMin: chartRangeMin,
3857 chartRangeMax: chartRangeMax,
3858 composite: composite,
3859 enableTagOptions: enableTagOptions,
3860 tagOptionPrefix: tagOptionPrefix,
3861 tagValuesAttribute: tagValuesAttribute,
3862 disableHiddenCheck: disableHiddenCheck,
3863 defaultPixelsPerValue: defaultPixelsPerValue,
3864 spotColor: spotColor,
3865 minSpotColor: minSpotColor,
3866 maxSpotColor: maxSpotColor,
3867 spotRadius: spotRadius,
3868 valueSpots: valueSpots,
3869 highlightSpotColor: highlightSpotColor,
3870 highlightLineColor: highlightLineColor,
3871 lineWidth: lineWidth,
3872 normalRangeMin: normalRangeMin,
3873 normalRangeMax: normalRangeMax,
3874 drawNormalOnTop: drawNormalOnTop,
3876 chartRangeClip: chartRangeClip,
3877 chartRangeMinX: chartRangeMinX,
3878 chartRangeMaxX: chartRangeMaxX,
3879 disableInteraction: disableInteraction,
3880 disableTooltips: disableTooltips,
3881 disableHighlight: disableHighlight,
3882 highlightLighten: highlightLighten,
3883 highlightColor: highlightColor,
3884 tooltipContainer: tooltipContainer,
3885 tooltipClassname: tooltipClassname,
3886 tooltipChartTitle: state.title,
3887 tooltipFormat: tooltipFormat,
3888 tooltipPrefix: tooltipPrefix,
3889 tooltipSuffix: tooltipSuffix,
3890 tooltipSkipNull: tooltipSkipNull,
3891 tooltipValueLookups: tooltipValueLookups,
3892 tooltipFormatFieldlist: tooltipFormatFieldlist,
3893 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3894 numberFormatter: numberFormatter,
3895 numberDigitGroupSep: numberDigitGroupSep,
3896 numberDecimalMark: numberDecimalMark,
3897 numberDigitGroupCount: numberDigitGroupCount,
3898 animatedZooms: animatedZooms,
3899 width: state.chartWidth(),
3900 height: state.chartHeight()
3903 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3907 // ----------------------------------------------------------------------------------------------------------------
3914 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3915 if(after < state.netdata_first)
3916 after = state.netdata_first;
3918 if(before > state.netdata_last)
3919 before = state.netdata_last;
3921 state.setMode('zoom');
3922 state.globalSelectionSyncStop();
3923 state.globalSelectionSyncDelay();
3924 state.dygraph_user_action = true;
3925 state.dygraph_force_zoom = true;
3926 state.updateChartPanOrZoom(after, before);
3927 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3930 NETDATA.dygraphSetSelection = function(state, t) {
3931 if(typeof state.dygraph_instance !== 'undefined') {
3932 var r = state.calculateRowForTime(t);
3934 state.dygraph_instance.setSelection(r);
3936 state.dygraph_instance.clearSelection();
3937 state.legendShowUndefined();
3944 NETDATA.dygraphClearSelection = function(state, t) {
3945 if(typeof state.dygraph_instance !== 'undefined') {
3946 state.dygraph_instance.clearSelection();
3951 NETDATA.dygraphSmoothInitialize = function(callback) {
3953 url: NETDATA.dygraph_smooth_js,
3956 xhrFields: { withCredentials: true } // required for the cookie
3959 NETDATA.dygraph.smooth = true;
3960 smoothPlotter.smoothing = 0.3;
3963 NETDATA.dygraph.smooth = false;
3965 .always(function() {
3966 if(typeof callback === "function")
3971 NETDATA.dygraphInitialize = function(callback) {
3972 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3974 url: NETDATA.dygraph_js,
3977 xhrFields: { withCredentials: true } // required for the cookie
3980 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3983 NETDATA.chartLibraries.dygraph.enabled = false;
3984 NETDATA.error(100, NETDATA.dygraph_js);
3986 .always(function() {
3987 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3988 NETDATA.dygraphSmoothInitialize(callback);
3989 else if(typeof callback === "function")
3994 NETDATA.chartLibraries.dygraph.enabled = false;
3995 if(typeof callback === "function")
4000 NETDATA.dygraphChartUpdate = function(state, data) {
4001 var dygraph = state.dygraph_instance;
4003 if(typeof dygraph === 'undefined')
4004 return NETDATA.dygraphChartCreate(state, data);
4006 // when the chart is not visible, and hidden
4007 // if there is a window resize, dygraph detects
4008 // its element size as 0x0.
4009 // this will make it re-appear properly
4011 if(state.tm.last_unhidden > state.dygraph_last_rendered)
4015 file: data.result.data,
4016 colors: state.chartColors(),
4017 labels: data.result.labels,
4018 labelsDivWidth: state.chartWidth() - 70,
4019 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4022 if(state.dygraph_force_zoom === true) {
4023 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4024 state.log('dygraphChartUpdate() forced zoom update');
4026 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4027 options.isZoomedIgnoreProgrammaticZoom = true;
4028 state.dygraph_force_zoom = false;
4030 else if(state.current.name !== 'auto') {
4031 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4032 state.log('dygraphChartUpdate() loose update');
4035 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4036 state.log('dygraphChartUpdate() strict update');
4038 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4039 options.isZoomedIgnoreProgrammaticZoom = true;
4042 options.valueRange = state.dygraph_options.valueRange;
4044 var oldMax = null, oldMin = null;
4045 if(state.__commonMin !== null) {
4046 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4047 oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4049 if(state.__commonMax !== null) {
4050 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4051 oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4054 if(state.dygraph_smooth_eligible === true) {
4055 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4056 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4057 NETDATA.dygraphChartCreate(state, data);
4062 dygraph.updateOptions(options);
4065 if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4066 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4067 options.valueRange[0] = NETDATA.commonMin.get(state);
4070 if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4071 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4072 options.valueRange[1] = NETDATA.commonMax.get(state);
4076 if(redraw === true) {
4077 // state.log('forcing redraw to adapt to common- min/max');
4078 dygraph.updateOptions(options);
4081 state.dygraph_last_rendered = Date.now();
4085 NETDATA.dygraphChartCreate = function(state, data) {
4086 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4087 state.log('dygraphChartCreate()');
4089 var self = $(state.element);
4091 var chart_type = state.chart.chart_type;
4092 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4093 chart_type = self.data('dygraph-type') || chart_type;
4095 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
4096 smooth = self.data('dygraph-smooth') || smooth;
4098 if(NETDATA.dygraph.smooth === false)
4101 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
4102 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
4104 state.dygraph_options = {
4105 colors: self.data('dygraph-colors') || state.chartColors(),
4107 // leave a few pixels empty on the right of the chart
4108 rightGap: self.data('dygraph-rightgap') || 5,
4109 showRangeSelector: self.data('dygraph-showrangeselector') || false,
4110 showRoller: self.data('dygraph-showroller') || false,
4112 title: self.data('dygraph-title') || state.title,
4113 titleHeight: self.data('dygraph-titleheight') || 19,
4115 legend: self.data('dygraph-legend') || 'always', // we need this to get selection events
4116 labels: data.result.labels,
4117 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
4118 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
4119 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
4120 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
4121 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
4124 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
4125 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
4127 includeZero: self.data('dygraph-includezero') || ((chart_type === 'stacked')? true : false),
4128 xRangePad: self.data('dygraph-xrangepad') || 0,
4129 yRangePad: self.data('dygraph-yrangepad') || 1,
4131 valueRange: self.data('dygraph-valuerange') || [ null, null ],
4133 ylabel: state.units,
4134 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
4136 // the function to plot the chart
4139 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
4140 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
4141 strokePattern: self.data('dygraph-strokepattern') || undefined,
4143 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
4144 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
4145 drawPoints: self.data('dygraph-drawpoints') || false,
4147 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
4148 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
4150 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
4151 pointSize: self.data('dygraph-pointsize') || 1,
4153 // enabling this makes the chart with little square lines
4154 stepPlot: self.data('dygraph-stepplot') || false,
4156 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
4157 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
4158 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
4160 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
4161 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
4162 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
4163 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
4165 drawAxis: self.data('dygraph-drawaxis') || true,
4166 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
4167 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
4168 axisLineWidth: self.data('dygraph-axislinewidth') || 1.0,
4170 drawGrid: self.data('dygraph-drawgrid') || true,
4171 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
4172 gridLineWidth: self.data('dygraph-gridlinewidth') || 1.0,
4173 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
4175 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
4176 sigFigs: self.data('dygraph-sigfigs') || null,
4177 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
4178 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
4180 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
4181 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
4182 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4184 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
4185 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4189 ticker: Dygraph.dateTicker,
4190 axisLabelFormatter: function (d, gran) {
4191 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4193 valueFormatter: function (ms) {
4194 var d = new Date(ms);
4195 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4196 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4201 valueFormatter: function (x) {
4202 // we format legends with the state object
4203 // no need to do anything here
4204 // return (Math.round(x*100) / 100).toLocaleString();
4205 // return state.legendFormatValue(x);
4210 legendFormatter: function(data) {
4211 var elements = state.element_legend_childs;
4213 // if the hidden div is not there
4214 // we are not managing the legend
4215 if(elements.hidden === null) return;
4217 if (typeof data.x !== 'undefined') {
4218 state.legendSetDate(data.x);
4219 var i = data.series.length;
4221 var series = data.series[i];
4222 if(!series.isVisible) continue;
4223 state.legendSetLabelValue(series.label, series.y);
4229 drawCallback: function(dygraph, is_initial) {
4230 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4231 state.dygraph_user_action = false;
4233 var x_range = dygraph.xAxisRange();
4234 var after = Math.round(x_range[0]);
4235 var before = Math.round(x_range[1]);
4237 if(NETDATA.options.debug.dygraph === true)
4238 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4240 if(before <= state.netdata_last && after >= state.netdata_first)
4241 state.updateChartPanOrZoom(after, before);
4244 zoomCallback: function(minDate, maxDate, yRanges) {
4245 if(NETDATA.options.debug.dygraph === true)
4246 state.log('dygraphZoomCallback()');
4248 state.globalSelectionSyncStop();
4249 state.globalSelectionSyncDelay();
4250 state.setMode('zoom');
4252 // refresh it to the greatest possible zoom level
4253 state.dygraph_user_action = true;
4254 state.dygraph_force_zoom = true;
4255 state.updateChartPanOrZoom(minDate, maxDate);
4257 highlightCallback: function(event, x, points, row, seriesName) {
4258 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4259 state.log('dygraphHighlightCallback()');
4263 // there is a bug in dygraph when the chart is zoomed enough
4264 // the time it thinks is selected is wrong
4265 // here we calculate the time t based on the row number selected
4267 var t = state.data_after + row * state.data_update_every;
4268 // 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);
4270 state.globalSelectionSync(x);
4272 // fix legend zIndex using the internal structures of dygraph legend module
4273 // this works, but it is a hack!
4274 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4276 unhighlightCallback: function(event) {
4277 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4278 state.log('dygraphUnhighlightCallback()');
4280 state.unpauseChart();
4281 state.globalSelectionSyncStop();
4283 interactionModel : {
4284 mousedown: function(event, dygraph, context) {
4285 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4286 state.log('interactionModel.mousedown()');
4288 state.dygraph_user_action = true;
4289 state.globalSelectionSyncStop();
4291 if(NETDATA.options.debug.dygraph === true)
4292 state.log('dygraphMouseDown()');
4294 // Right-click should not initiate a zoom.
4295 if(event.button && event.button === 2) return;
4297 context.initializeMouseDown(event, dygraph, context);
4299 if(event.button && event.button === 1) {
4300 if (event.altKey || event.shiftKey) {
4301 state.setMode('pan');
4302 state.globalSelectionSyncDelay();
4303 Dygraph.startPan(event, dygraph, context);
4306 state.setMode('zoom');
4307 state.globalSelectionSyncDelay();
4308 Dygraph.startZoom(event, dygraph, context);
4312 if (event.altKey || event.shiftKey) {
4313 state.setMode('zoom');
4314 state.globalSelectionSyncDelay();
4315 Dygraph.startZoom(event, dygraph, context);
4318 state.setMode('pan');
4319 state.globalSelectionSyncDelay();
4320 Dygraph.startPan(event, dygraph, context);
4324 mousemove: function(event, dygraph, context) {
4325 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4326 state.log('interactionModel.mousemove()');
4328 if(context.isPanning) {
4329 state.dygraph_user_action = true;
4330 state.globalSelectionSyncStop();
4331 state.globalSelectionSyncDelay();
4332 state.setMode('pan');
4333 context.is2DPan = false;
4334 Dygraph.movePan(event, dygraph, context);
4336 else if(context.isZooming) {
4337 state.dygraph_user_action = true;
4338 state.globalSelectionSyncStop();
4339 state.globalSelectionSyncDelay();
4340 state.setMode('zoom');
4341 Dygraph.moveZoom(event, dygraph, context);
4344 mouseup: function(event, dygraph, context) {
4345 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4346 state.log('interactionModel.mouseup()');
4348 if (context.isPanning) {
4349 state.dygraph_user_action = true;
4350 state.globalSelectionSyncDelay();
4351 Dygraph.endPan(event, dygraph, context);
4353 else if (context.isZooming) {
4354 state.dygraph_user_action = true;
4355 state.globalSelectionSyncDelay();
4356 Dygraph.endZoom(event, dygraph, context);
4359 click: function(event, dygraph, context) {
4360 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4361 state.log('interactionModel.click()');
4363 event.preventDefault();
4365 dblclick: function(event, dygraph, context) {
4366 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4367 state.log('interactionModel.dblclick()');
4368 NETDATA.resetAllCharts(state);
4370 wheel: function(event, dygraph, context) {
4371 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4372 state.log('interactionModel.wheel()');
4374 // Take the offset of a mouse event on the dygraph canvas and
4375 // convert it to a pair of percentages from the bottom left.
4376 // (Not top left, bottom is where the lower value is.)
4377 function offsetToPercentage(g, offsetX, offsetY) {
4378 // This is calculating the pixel offset of the leftmost date.
4379 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4380 var yar0 = g.yAxisRange(0);
4382 // This is calculating the pixel of the higest value. (Top pixel)
4383 var yOffset = g.toDomCoords(null, yar0[1])[1];
4385 // x y w and h are relative to the corner of the drawing area,
4386 // so that the upper corner of the drawing area is (0, 0).
4387 var x = offsetX - xOffset;
4388 var y = offsetY - yOffset;
4390 // This is computing the rightmost pixel, effectively defining the
4392 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4394 // This is computing the lowest pixel, effectively defining the height.
4395 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4397 // Percentage from the left.
4398 var xPct = w === 0 ? 0 : (x / w);
4399 // Percentage from the top.
4400 var yPct = h === 0 ? 0 : (y / h);
4402 // The (1-) part below changes it from "% distance down from the top"
4403 // to "% distance up from the bottom".
4404 return [xPct, (1-yPct)];
4407 // Adjusts [x, y] toward each other by zoomInPercentage%
4408 // Split it so the left/bottom axis gets xBias/yBias of that change and
4409 // tight/top gets (1-xBias)/(1-yBias) of that change.
4411 // If a bias is missing it splits it down the middle.
4412 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4413 xBias = xBias || 0.5;
4414 yBias = yBias || 0.5;
4416 function adjustAxis(axis, zoomInPercentage, bias) {
4417 var delta = axis[1] - axis[0];
4418 var increment = delta * zoomInPercentage;
4419 var foo = [increment * bias, increment * (1-bias)];
4421 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4424 var yAxes = g.yAxisRanges();
4426 for (var i = 0; i < yAxes.length; i++) {
4427 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4430 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4433 if(event.altKey || event.shiftKey) {
4434 state.dygraph_user_action = true;
4436 state.globalSelectionSyncStop();
4437 state.globalSelectionSyncDelay();
4439 // http://dygraphs.com/gallery/interaction-api.js
4441 if(typeof event.wheelDelta === 'number' && event.wheelDelta != NaN)
4443 normal_def = event.wheelDelta / 40;
4446 normal_def = event.deltaY * -1.2;
4448 var normal = (event.detail) ? event.detail * -1 : normal_def;
4449 var percentage = normal / 50;
4451 if (!(event.offsetX && event.offsetY)){
4452 event.offsetX = event.layerX - event.target.offsetLeft;
4453 event.offsetY = event.layerY - event.target.offsetTop;
4456 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4457 var xPct = percentages[0];
4458 var yPct = percentages[1];
4460 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4461 var after = new_x_range[0];
4462 var before = new_x_range[1];
4464 var first = state.netdata_first + state.data_update_every;
4465 var last = state.netdata_last + state.data_update_every;
4468 after -= (before - last);
4475 state.setMode('zoom');
4476 if(state.updateChartPanOrZoom(after, before) === true)
4477 dygraph.updateOptions({ dateWindow: [ after, before ] });
4479 event.preventDefault();
4482 touchstart: function(event, dygraph, context) {
4483 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4484 state.log('interactionModel.touchstart()');
4486 state.dygraph_user_action = true;
4487 state.setMode('zoom');
4490 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4492 // we overwrite the touch directions at the end, to overwrite
4493 // the internal default of dygraphs
4494 context.touchDirections = { x: true, y: false };
4496 state.dygraph_last_touch_start = Date.now();
4497 state.dygraph_last_touch_move = 0;
4499 if(typeof event.touches[0].pageX === 'number')
4500 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4502 state.dygraph_last_touch_page_x = 0;
4504 touchmove: function(event, dygraph, context) {
4505 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4506 state.log('interactionModel.touchmove()');
4508 state.dygraph_user_action = true;
4509 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4511 state.dygraph_last_touch_move = Date.now();
4513 touchend: function(event, dygraph, context) {
4514 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4515 state.log('interactionModel.touchend()');
4517 state.dygraph_user_action = true;
4518 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4520 // if it didn't move, it is a selection
4521 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4522 // internal api of dygraphs
4523 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4524 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4525 if(NETDATA.dygraphSetSelection(state, t) === true)
4526 state.globalSelectionSync(t);
4529 // if it was double tap within double click time, reset the charts
4530 var now = Date.now();
4531 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4532 if(state.dygraph_last_touch_move === 0) {
4533 var dt = now - state.dygraph_last_touch_end;
4534 if(dt <= NETDATA.options.current.double_click_speed)
4535 NETDATA.resetAllCharts(state);
4539 // remember the timestamp of the last touch end
4540 state.dygraph_last_touch_end = now;
4545 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4546 state.dygraph_options.drawGrid = false;
4547 state.dygraph_options.drawAxis = false;
4548 state.dygraph_options.title = undefined;
4549 state.dygraph_options.ylabel = undefined;
4550 state.dygraph_options.yLabelWidth = 0;
4551 state.dygraph_options.labelsDivWidth = 120;
4552 state.dygraph_options.labelsDivStyles.width = '120px';
4553 state.dygraph_options.labelsSeparateLines = true;
4554 state.dygraph_options.rightGap = 0;
4555 state.dygraph_options.yRangePad = 1;
4558 if(smooth === true) {
4559 state.dygraph_smooth_eligible = true;
4561 if(NETDATA.options.current.smooth_plot === true)
4562 state.dygraph_options.plotter = smoothPlotter;
4564 else state.dygraph_smooth_eligible = false;
4566 state.dygraph_instance = new Dygraph(state.element_chart,
4567 data.result.data, state.dygraph_options);
4569 state.dygraph_force_zoom = false;
4570 state.dygraph_user_action = false;
4571 state.dygraph_last_rendered = Date.now();
4573 if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4574 state.__commonMin = self.data('common-min') || null;
4575 state.__commonMax = self.data('common-max') || null;
4578 state.log('incompatible version of dygraphs detected');
4579 state.__commonMin = null;
4580 state.__commonMax = null;
4586 // ----------------------------------------------------------------------------------------------------------------
4589 NETDATA.morrisInitialize = function(callback) {
4590 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4592 // morris requires raphael
4593 if(!NETDATA.chartLibraries.raphael.initialized) {
4594 if(NETDATA.chartLibraries.raphael.enabled) {
4595 NETDATA.raphaelInitialize(function() {
4596 NETDATA.morrisInitialize(callback);
4600 NETDATA.chartLibraries.morris.enabled = false;
4601 if(typeof callback === "function")
4606 NETDATA._loadCSS(NETDATA.morris_css);
4609 url: NETDATA.morris_js,
4612 xhrFields: { withCredentials: true } // required for the cookie
4615 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4618 NETDATA.chartLibraries.morris.enabled = false;
4619 NETDATA.error(100, NETDATA.morris_js);
4621 .always(function() {
4622 if(typeof callback === "function")
4628 NETDATA.chartLibraries.morris.enabled = false;
4629 if(typeof callback === "function")
4634 NETDATA.morrisChartUpdate = function(state, data) {
4635 state.morris_instance.setData(data.result.data);
4639 NETDATA.morrisChartCreate = function(state, data) {
4641 state.morris_options = {
4642 element: state.element_chart.id,
4643 data: data.result.data,
4645 ykeys: data.dimension_names,
4646 labels: data.dimension_names,
4652 continuousLine: false,
4653 behaveLikeLine: false
4656 if(state.chart.chart_type === 'line')
4657 state.morris_instance = new Morris.Line(state.morris_options);
4659 else if(state.chart.chart_type === 'area') {
4660 state.morris_options.behaveLikeLine = true;
4661 state.morris_instance = new Morris.Area(state.morris_options);
4664 state.morris_instance = new Morris.Area(state.morris_options);
4669 // ----------------------------------------------------------------------------------------------------------------
4672 NETDATA.raphaelInitialize = function(callback) {
4673 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4675 url: NETDATA.raphael_js,
4678 xhrFields: { withCredentials: true } // required for the cookie
4681 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4684 NETDATA.chartLibraries.raphael.enabled = false;
4685 NETDATA.error(100, NETDATA.raphael_js);
4687 .always(function() {
4688 if(typeof callback === "function")
4693 NETDATA.chartLibraries.raphael.enabled = false;
4694 if(typeof callback === "function")
4699 NETDATA.raphaelChartUpdate = function(state, data) {
4700 $(state.element_chart).raphael(data.result, {
4701 width: state.chartWidth(),
4702 height: state.chartHeight()
4708 NETDATA.raphaelChartCreate = function(state, data) {
4709 $(state.element_chart).raphael(data.result, {
4710 width: state.chartWidth(),
4711 height: state.chartHeight()
4717 // ----------------------------------------------------------------------------------------------------------------
4720 NETDATA.c3Initialize = function(callback) {
4721 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4724 if(!NETDATA.chartLibraries.d3.initialized) {
4725 if(NETDATA.chartLibraries.d3.enabled) {
4726 NETDATA.d3Initialize(function() {
4727 NETDATA.c3Initialize(callback);
4731 NETDATA.chartLibraries.c3.enabled = false;
4732 if(typeof callback === "function")
4737 NETDATA._loadCSS(NETDATA.c3_css);
4743 xhrFields: { withCredentials: true } // required for the cookie
4746 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4749 NETDATA.chartLibraries.c3.enabled = false;
4750 NETDATA.error(100, NETDATA.c3_js);
4752 .always(function() {
4753 if(typeof callback === "function")
4759 NETDATA.chartLibraries.c3.enabled = false;
4760 if(typeof callback === "function")
4765 NETDATA.c3ChartUpdate = function(state, data) {
4766 state.c3_instance.destroy();
4767 return NETDATA.c3ChartCreate(state, data);
4769 //state.c3_instance.load({
4770 // rows: data.result,
4777 NETDATA.c3ChartCreate = function(state, data) {
4779 state.element_chart.id = 'c3-' + state.uuid;
4780 // console.log('id = ' + state.element_chart.id);
4782 state.c3_instance = c3.generate({
4783 bindto: '#' + state.element_chart.id,
4785 width: state.chartWidth(),
4786 height: state.chartHeight()
4789 pattern: state.chartColors()
4794 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4800 format: function(x) {
4801 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4828 // console.log(state.c3_instance);
4833 // ----------------------------------------------------------------------------------------------------------------
4836 NETDATA.d3Initialize = function(callback) {
4837 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4842 xhrFields: { withCredentials: true } // required for the cookie
4845 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4848 NETDATA.chartLibraries.d3.enabled = false;
4849 NETDATA.error(100, NETDATA.d3_js);
4851 .always(function() {
4852 if(typeof callback === "function")
4857 NETDATA.chartLibraries.d3.enabled = false;
4858 if(typeof callback === "function")
4863 NETDATA.d3ChartUpdate = function(state, data) {
4867 NETDATA.d3ChartCreate = function(state, data) {
4871 // ----------------------------------------------------------------------------------------------------------------
4874 NETDATA.googleInitialize = function(callback) {
4875 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4877 url: NETDATA.google_js,
4880 xhrFields: { withCredentials: true } // required for the cookie
4883 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4884 google.load('visualization', '1.1', {
4885 'packages': ['corechart', 'controls'],
4886 'callback': callback
4890 NETDATA.chartLibraries.google.enabled = false;
4891 NETDATA.error(100, NETDATA.google_js);
4892 if(typeof callback === "function")
4897 NETDATA.chartLibraries.google.enabled = false;
4898 if(typeof callback === "function")
4903 NETDATA.googleChartUpdate = function(state, data) {
4904 var datatable = new google.visualization.DataTable(data.result);
4905 state.google_instance.draw(datatable, state.google_options);
4909 NETDATA.googleChartCreate = function(state, data) {
4910 var datatable = new google.visualization.DataTable(data.result);
4912 state.google_options = {
4913 colors: state.chartColors(),
4915 // do not set width, height - the chart resizes itself
4916 //width: state.chartWidth(),
4917 //height: state.chartHeight(),
4922 // title: "Time of Day",
4923 // format:'HH:mm:ss',
4924 viewWindowMode: 'maximized',
4936 viewWindowMode: 'pretty',
4951 focusTarget: 'category',
4958 titlePosition: 'out',
4969 curveType: 'function',
4974 switch(state.chart.chart_type) {
4976 state.google_options.vAxis.viewWindowMode = 'maximized';
4977 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4978 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4982 state.google_options.isStacked = true;
4983 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4984 state.google_options.vAxis.viewWindowMode = 'maximized';
4985 state.google_options.vAxis.minValue = null;
4986 state.google_options.vAxis.maxValue = null;
4987 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4992 state.google_options.lineWidth = 2;
4993 state.google_instance = new google.visualization.LineChart(state.element_chart);
4997 state.google_instance.draw(datatable, state.google_options);
5001 // ----------------------------------------------------------------------------------------------------------------
5003 NETDATA.percentFromValueMinMax = function(value, min, max) {
5004 if(typeof value !== 'number') value = 0;
5005 if(typeof min !== 'number') min = 0;
5006 if(typeof max !== 'number') max = 0;
5010 if(max < value) max = value;
5012 pcent = Math.round(value * 100 / max);
5013 if(pcent === 0 && value > 0) pcent = 1;
5017 if(min > value) min = value;
5019 pcent = Math.round(-value * 100 / min);
5020 if(pcent === 0 && value < 0) pcent = -1;
5027 // ----------------------------------------------------------------------------------------------------------------
5030 NETDATA.easypiechartInitialize = function(callback) {
5031 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5033 url: NETDATA.easypiechart_js,
5036 xhrFields: { withCredentials: true } // required for the cookie
5039 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5042 NETDATA.chartLibraries.easypiechart.enabled = false;
5043 NETDATA.error(100, NETDATA.easypiechart_js);
5045 .always(function() {
5046 if(typeof callback === "function")
5051 NETDATA.chartLibraries.easypiechart.enabled = false;
5052 if(typeof callback === "function")
5057 NETDATA.easypiechartClearSelection = function(state) {
5058 if(typeof state.easyPieChartEvent !== 'undefined') {
5059 if(state.easyPieChartEvent.timer !== null)
5060 clearTimeout(state.easyPieChartEvent.timer);
5062 state.easyPieChartEvent.timer = null;
5065 if(state.isAutoRefreshable() === true && state.data !== null) {
5066 NETDATA.easypiechartChartUpdate(state, state.data);
5069 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
5070 state.easyPieChart_instance.update(0);
5072 state.easyPieChart_instance.enableAnimation();
5077 NETDATA.easypiechartSetSelection = function(state, t) {
5078 if(state.timeIsVisible(t) !== true)
5079 return NETDATA.easypiechartClearSelection(state);
5081 var slot = state.calculateRowForTime(t);
5082 if(slot < 0 || slot >= state.data.result.length)
5083 return NETDATA.easypiechartClearSelection(state);
5085 if(typeof state.easyPieChartEvent === 'undefined') {
5086 state.easyPieChartEvent = {
5093 var value = state.data.result[state.data.result.length - 1 - slot];
5094 var min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5095 var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5096 var pcent = NETDATA.percentFromValueMinMax(value, min, max);
5098 state.easyPieChartEvent.value = value;
5099 state.easyPieChartEvent.pcent = pcent;
5100 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
5102 if(state.easyPieChartEvent.timer === null) {
5103 state.easyPieChart_instance.disableAnimation();
5105 state.easyPieChartEvent.timer = setTimeout(function() {
5106 state.easyPieChartEvent.timer = null;
5107 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5108 }, NETDATA.options.current.charts_selection_animation_delay);
5114 NETDATA.easypiechartChartUpdate = function(state, data) {
5115 var value, min, max, pcent;
5117 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5122 value = data.result[0];
5123 min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5124 max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5125 pcent = NETDATA.percentFromValueMinMax(value, min, max);
5128 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
5129 state.easyPieChart_instance.update(pcent);
5133 NETDATA.easypiechartChartCreate = function(state, data) {
5134 var self = $(state.element);
5135 var chart = $(state.element_chart);
5137 var value = data.result[0];
5138 var min = self.data('easypiechart-min-value') || null;
5139 var max = self.data('easypiechart-max-value') || null;
5140 var adjust = self.data('easypiechart-adjust') || null;
5143 min = NETDATA.commonMin.get(state);
5144 state.easyPieChartMin = null;
5147 state.easyPieChartMin = min;
5150 max = NETDATA.commonMax.get(state);
5151 state.easyPieChartMax = null;
5154 state.easyPieChartMax = max;
5156 var pcent = NETDATA.percentFromValueMinMax(value, min, max);
5158 chart.data('data-percent', pcent);
5162 case 'width': size = state.chartHeight(); break;
5163 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5164 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5166 default: size = state.chartWidth(); break;
5168 state.element.style.width = size + 'px';
5169 state.element.style.height = size + 'px';
5171 var stroke = Math.floor(size / 22);
5172 if(stroke < 3) stroke = 2;
5174 var valuefontsize = Math.floor((size * 2 / 3) / 5);
5175 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5176 state.easyPieChartLabel = document.createElement('span');
5177 state.easyPieChartLabel.className = 'easyPieChartLabel';
5178 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
5179 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5180 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5181 state.element_chart.appendChild(state.easyPieChartLabel);
5183 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5184 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5185 state.easyPieChartTitle = document.createElement('span');
5186 state.easyPieChartTitle.className = 'easyPieChartTitle';
5187 state.easyPieChartTitle.innerHTML = state.title;
5188 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5189 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5190 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5191 state.element_chart.appendChild(state.easyPieChartTitle);
5193 var unitfontsize = Math.round(titlefontsize * 0.9);
5194 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5195 state.easyPieChartUnits = document.createElement('span');
5196 state.easyPieChartUnits.className = 'easyPieChartUnits';
5197 state.easyPieChartUnits.innerHTML = state.units;
5198 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5199 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5200 state.element_chart.appendChild(state.easyPieChartUnits);
5202 var barColor = self.data('easypiechart-barcolor');
5203 if(typeof barColor === 'undefined' || barColor === null)
5204 barColor = state.chartColors()[0];
5206 // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5207 var tmp = eval(barColor);
5208 if(typeof tmp === 'function')
5212 chart.easyPieChart({
5214 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5215 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5216 scaleLength: self.data('easypiechart-scalelength') || 5,
5217 lineCap: self.data('easypiechart-linecap') || 'round',
5218 lineWidth: self.data('easypiechart-linewidth') || stroke,
5219 trackWidth: self.data('easypiechart-trackwidth') || undefined,
5220 size: self.data('easypiechart-size') || size,
5221 rotate: self.data('easypiechart-rotate') || 0,
5222 animate: self.data('easypiechart-animate') || {duration: 500, enabled: true},
5223 easing: self.data('easypiechart-easing') || undefined
5226 // when we just re-create the chart
5227 // do not animate the first update
5229 if(typeof state.easyPieChart_instance !== 'undefined')
5232 state.easyPieChart_instance = chart.data('easyPieChart');
5233 if(animate === false) state.easyPieChart_instance.disableAnimation();
5234 state.easyPieChart_instance.update(pcent);
5235 if(animate === false) state.easyPieChart_instance.enableAnimation();
5239 // ----------------------------------------------------------------------------------------------------------------
5242 NETDATA.gaugeInitialize = function(callback) {
5243 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5245 url: NETDATA.gauge_js,
5248 xhrFields: { withCredentials: true } // required for the cookie
5251 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5254 NETDATA.chartLibraries.gauge.enabled = false;
5255 NETDATA.error(100, NETDATA.gauge_js);
5257 .always(function() {
5258 if(typeof callback === "function")
5263 NETDATA.chartLibraries.gauge.enabled = false;
5264 if(typeof callback === "function")
5269 NETDATA.gaugeAnimation = function(state, status) {
5272 if(typeof status === 'boolean' && status === false)
5274 else if(typeof status === 'number')
5277 // console.log('gauge speed ' + speed);
5278 state.gauge_instance.animationSpeed = speed;
5279 state.___gaugeOld__.speed = speed;
5282 NETDATA.gaugeSet = function(state, value, min, max) {
5283 if(typeof value !== 'number') value = 0;
5284 if(typeof min !== 'number') min = 0;
5285 if(typeof max !== 'number') max = 0;
5286 if(value > max) max = value;
5287 if(value < min) min = value;
5296 // gauge.js has an issue if the needle
5297 // is smaller than min or larger than max
5298 // when we set the new values
5299 // the needle will go crazy
5301 // to prevent it, we always feed it
5302 // with a percentage, so that the needle
5303 // is always between min and max
5304 var pcent = (value - min) * 100 / (max - min);
5306 // these should never happen
5307 if(pcent < 0) pcent = 0;
5308 if(pcent > 100) pcent = 100;
5310 state.gauge_instance.set(pcent);
5311 // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5313 state.___gaugeOld__.value = value;
5314 state.___gaugeOld__.min = min;
5315 state.___gaugeOld__.max = max;
5318 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5319 if(state.___gaugeOld__.valueLabel !== value) {
5320 state.___gaugeOld__.valueLabel = value;
5321 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
5323 if(state.___gaugeOld__.minLabel !== min) {
5324 state.___gaugeOld__.minLabel = min;
5325 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
5327 if(state.___gaugeOld__.maxLabel !== max) {
5328 state.___gaugeOld__.maxLabel = max;
5329 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
5333 NETDATA.gaugeClearSelection = function(state) {
5334 if(typeof state.gaugeEvent !== 'undefined') {
5335 if(state.gaugeEvent.timer !== null)
5336 clearTimeout(state.gaugeEvent.timer);
5338 state.gaugeEvent.timer = null;
5341 if(state.isAutoRefreshable() === true && state.data !== null) {
5342 NETDATA.gaugeChartUpdate(state, state.data);
5345 NETDATA.gaugeAnimation(state, false);
5346 NETDATA.gaugeSet(state, null, null, null);
5347 NETDATA.gaugeSetLabels(state, null, null, null);
5350 NETDATA.gaugeAnimation(state, true);
5354 NETDATA.gaugeSetSelection = function(state, t) {
5355 if(state.timeIsVisible(t) !== true)
5356 return NETDATA.gaugeClearSelection(state);
5358 var slot = state.calculateRowForTime(t);
5359 if(slot < 0 || slot >= state.data.result.length)
5360 return NETDATA.gaugeClearSelection(state);
5362 if(typeof state.gaugeEvent === 'undefined') {
5363 state.gaugeEvent = {
5371 var value = state.data.result[state.data.result.length - 1 - slot];
5372 var min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5373 var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5375 state.gaugeEvent.value = value;
5376 state.gaugeEvent.min = min;
5377 state.gaugeEvent.max = max;
5378 NETDATA.gaugeSetLabels(state, value, min, max);
5380 if(state.gaugeEvent.timer === null) {
5381 NETDATA.gaugeAnimation(state, false);
5383 state.gaugeEvent.timer = setTimeout(function() {
5384 state.gaugeEvent.timer = null;
5385 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5386 }, NETDATA.options.current.charts_selection_animation_delay);
5392 NETDATA.gaugeChartUpdate = function(state, data) {
5393 var value, min, max;
5395 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5399 NETDATA.gaugeSetLabels(state, null, null, null);
5402 value = data.result[0];
5403 min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5404 max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5405 if(value < min) min = value;
5406 if(value > max) max = value;
5407 NETDATA.gaugeSetLabels(state, value, min, max);
5410 NETDATA.gaugeSet(state, value, min, max);
5414 NETDATA.gaugeChartCreate = function(state, data) {
5415 var self = $(state.element);
5416 // var chart = $(state.element_chart);
5418 var value = data.result[0];
5419 var min = self.data('gauge-min-value') || null;
5420 var max = self.data('gauge-max-value') || null;
5421 var adjust = self.data('gauge-adjust') || null;
5422 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5423 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5424 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5425 var stopColor = self.data('gauge-stop-color') || void 0;
5426 var generateGradient = self.data('gauge-generate-gradient') || false;
5429 min = NETDATA.commonMin.get(state);
5430 state.gaugeMin = null;
5433 state.gaugeMin = min;
5436 max = NETDATA.commonMax.get(state);
5437 state.gaugeMax = null;
5440 state.gaugeMax = max;
5442 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5444 // case 'width': width = height * ratio; break;
5446 // default: height = width / ratio; break;
5448 //state.element.style.width = width.toString() + 'px';
5449 //state.element.style.height = height.toString() + 'px';
5454 lines: 12, // The number of lines to draw
5455 angle: 0.15, // The length of each line
5456 lineWidth: 0.44, // 0.44 The line thickness
5458 length: 0.8, // 0.9 The radius of the inner circle
5459 strokeWidth: 0.035, // The rotation offset
5460 color: pointerColor // Fill color
5462 colorStart: startColor, // Colors
5463 colorStop: stopColor, // just experiment with them
5464 strokeColor: strokeColor, // to see which ones work best for you
5466 generateGradient: (generateGradient === true)?true:false,
5470 if (generateGradient.constructor === Array) {
5472 // data-gauge-generate-gradient="[0, 50, 100]"
5473 // data-gauge-gradient-percent-color-0="#FFFFFF"
5474 // data-gauge-gradient-percent-color-50="#999900"
5475 // data-gauge-gradient-percent-color-100="#000000"
5477 options.percentColors = new Array();
5478 var len = generateGradient.length;
5480 var pcent = generateGradient[len];
5481 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5482 if(color !== false) {
5483 var a = new Array();
5486 options.percentColors.unshift(a);
5489 if(options.percentColors.length === 0)
5490 delete options.percentColors;
5492 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5493 options.percentColors = [
5494 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5495 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5496 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5497 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5498 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5499 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5500 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5501 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5502 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5503 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5504 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5507 state.gauge_canvas = document.createElement('canvas');
5508 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5509 state.gauge_canvas.className = 'gaugeChart';
5510 state.gauge_canvas.width = width;
5511 state.gauge_canvas.height = height;
5512 state.element_chart.appendChild(state.gauge_canvas);
5514 var valuefontsize = Math.floor(height / 6);
5515 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5516 state.gaugeChartLabel = document.createElement('span');
5517 state.gaugeChartLabel.className = 'gaugeChartLabel';
5518 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5519 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5520 state.element_chart.appendChild(state.gaugeChartLabel);
5522 var titlefontsize = Math.round(valuefontsize / 2);
5524 state.gaugeChartTitle = document.createElement('span');
5525 state.gaugeChartTitle.className = 'gaugeChartTitle';
5526 state.gaugeChartTitle.innerHTML = state.title;
5527 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5528 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5529 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5530 state.element_chart.appendChild(state.gaugeChartTitle);
5532 var unitfontsize = Math.round(titlefontsize * 0.9);
5533 state.gaugeChartUnits = document.createElement('span');
5534 state.gaugeChartUnits.className = 'gaugeChartUnits';
5535 state.gaugeChartUnits.innerHTML = state.units;
5536 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5537 state.element_chart.appendChild(state.gaugeChartUnits);
5539 state.gaugeChartMin = document.createElement('span');
5540 state.gaugeChartMin.className = 'gaugeChartMin';
5541 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5542 state.element_chart.appendChild(state.gaugeChartMin);
5544 state.gaugeChartMax = document.createElement('span');
5545 state.gaugeChartMax.className = 'gaugeChartMax';
5546 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5547 state.element_chart.appendChild(state.gaugeChartMax);
5549 // when we just re-create the chart
5550 // do not animate the first update
5552 if(typeof state.gauge_instance !== 'undefined')
5555 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5557 state.___gaugeOld__ = {
5566 // we will always feed a percentage
5567 state.gauge_instance.minValue = 0;
5568 state.gauge_instance.maxValue = 100;
5570 NETDATA.gaugeAnimation(state, animate);
5571 NETDATA.gaugeSet(state, value, min, max);
5572 NETDATA.gaugeSetLabels(state, value, min, max);
5573 NETDATA.gaugeAnimation(state, true);
5577 // ----------------------------------------------------------------------------------------------------------------
5578 // Charts Libraries Registration
5580 NETDATA.chartLibraries = {
5582 initialize: NETDATA.dygraphInitialize,
5583 create: NETDATA.dygraphChartCreate,
5584 update: NETDATA.dygraphChartUpdate,
5585 resize: function(state) {
5586 if(typeof state.dygraph_instance.resize === 'function')
5587 state.dygraph_instance.resize();
5589 setSelection: NETDATA.dygraphSetSelection,
5590 clearSelection: NETDATA.dygraphClearSelection,
5591 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5594 format: function(state) { return 'json'; },
5595 options: function(state) { return 'ms|flip'; },
5596 legend: function(state) {
5597 if(this.isSparkline(state) === false)
5598 return 'right-side';
5602 autoresize: function(state) { return true; },
5603 max_updates_to_recreate: function(state) { return 5000; },
5604 track_colors: function(state) { return true; },
5605 pixels_per_point: function(state) {
5606 if(this.isSparkline(state) === false)
5612 isSparkline: function(state) {
5613 if(typeof state.dygraph_sparkline === 'undefined') {
5614 var t = $(state.element).data('dygraph-theme');
5615 if(t === 'sparkline')
5616 state.dygraph_sparkline = true;
5618 state.dygraph_sparkline = false;
5620 return state.dygraph_sparkline;
5624 initialize: NETDATA.sparklineInitialize,
5625 create: NETDATA.sparklineChartCreate,
5626 update: NETDATA.sparklineChartUpdate,
5628 setSelection: undefined, // function(state, t) { return true; },
5629 clearSelection: undefined, // function(state) { return true; },
5630 toolboxPanAndZoom: null,
5633 format: function(state) { return 'array'; },
5634 options: function(state) { return 'flip|abs'; },
5635 legend: function(state) { return null; },
5636 autoresize: function(state) { return false; },
5637 max_updates_to_recreate: function(state) { return 5000; },
5638 track_colors: function(state) { return false; },
5639 pixels_per_point: function(state) { return 3; }
5642 initialize: NETDATA.peityInitialize,
5643 create: NETDATA.peityChartCreate,
5644 update: NETDATA.peityChartUpdate,
5646 setSelection: undefined, // function(state, t) { return true; },
5647 clearSelection: undefined, // function(state) { return true; },
5648 toolboxPanAndZoom: null,
5651 format: function(state) { return 'ssvcomma'; },
5652 options: function(state) { return 'null2zero|flip|abs'; },
5653 legend: function(state) { return null; },
5654 autoresize: function(state) { return false; },
5655 max_updates_to_recreate: function(state) { return 5000; },
5656 track_colors: function(state) { return false; },
5657 pixels_per_point: function(state) { return 3; }
5660 initialize: NETDATA.morrisInitialize,
5661 create: NETDATA.morrisChartCreate,
5662 update: NETDATA.morrisChartUpdate,
5664 setSelection: undefined, // function(state, t) { return true; },
5665 clearSelection: undefined, // function(state) { return true; },
5666 toolboxPanAndZoom: null,
5669 format: function(state) { return 'json'; },
5670 options: function(state) { return 'objectrows|ms'; },
5671 legend: function(state) { return null; },
5672 autoresize: function(state) { return false; },
5673 max_updates_to_recreate: function(state) { return 50; },
5674 track_colors: function(state) { return false; },
5675 pixels_per_point: function(state) { return 15; }
5678 initialize: NETDATA.googleInitialize,
5679 create: NETDATA.googleChartCreate,
5680 update: NETDATA.googleChartUpdate,
5682 setSelection: undefined, //function(state, t) { return true; },
5683 clearSelection: undefined, //function(state) { return true; },
5684 toolboxPanAndZoom: null,
5687 format: function(state) { return 'datatable'; },
5688 options: function(state) { return ''; },
5689 legend: function(state) { return null; },
5690 autoresize: function(state) { return false; },
5691 max_updates_to_recreate: function(state) { return 300; },
5692 track_colors: function(state) { return false; },
5693 pixels_per_point: function(state) { return 4; }
5696 initialize: NETDATA.raphaelInitialize,
5697 create: NETDATA.raphaelChartCreate,
5698 update: NETDATA.raphaelChartUpdate,
5700 setSelection: undefined, // function(state, t) { return true; },
5701 clearSelection: undefined, // function(state) { return true; },
5702 toolboxPanAndZoom: null,
5705 format: function(state) { return 'json'; },
5706 options: function(state) { return ''; },
5707 legend: function(state) { return null; },
5708 autoresize: function(state) { return false; },
5709 max_updates_to_recreate: function(state) { return 5000; },
5710 track_colors: function(state) { return false; },
5711 pixels_per_point: function(state) { return 3; }
5714 initialize: NETDATA.c3Initialize,
5715 create: NETDATA.c3ChartCreate,
5716 update: NETDATA.c3ChartUpdate,
5718 setSelection: undefined, // function(state, t) { return true; },
5719 clearSelection: undefined, // function(state) { return true; },
5720 toolboxPanAndZoom: null,
5723 format: function(state) { return 'csvjsonarray'; },
5724 options: function(state) { return 'milliseconds'; },
5725 legend: function(state) { return null; },
5726 autoresize: function(state) { return false; },
5727 max_updates_to_recreate: function(state) { return 5000; },
5728 track_colors: function(state) { return false; },
5729 pixels_per_point: function(state) { return 15; }
5732 initialize: NETDATA.d3Initialize,
5733 create: NETDATA.d3ChartCreate,
5734 update: NETDATA.d3ChartUpdate,
5736 setSelection: undefined, // function(state, t) { return true; },
5737 clearSelection: undefined, // function(state) { return true; },
5738 toolboxPanAndZoom: null,
5741 format: function(state) { return 'json'; },
5742 options: function(state) { return ''; },
5743 legend: function(state) { return null; },
5744 autoresize: function(state) { return false; },
5745 max_updates_to_recreate: function(state) { return 5000; },
5746 track_colors: function(state) { return false; },
5747 pixels_per_point: function(state) { return 3; }
5750 initialize: NETDATA.easypiechartInitialize,
5751 create: NETDATA.easypiechartChartCreate,
5752 update: NETDATA.easypiechartChartUpdate,
5754 setSelection: NETDATA.easypiechartSetSelection,
5755 clearSelection: NETDATA.easypiechartClearSelection,
5756 toolboxPanAndZoom: null,
5759 format: function(state) { return 'array'; },
5760 options: function(state) { return 'absolute'; },
5761 legend: function(state) { return null; },
5762 autoresize: function(state) { return false; },
5763 max_updates_to_recreate: function(state) { return 5000; },
5764 track_colors: function(state) { return true; },
5765 pixels_per_point: function(state) { return 3; },
5769 initialize: NETDATA.gaugeInitialize,
5770 create: NETDATA.gaugeChartCreate,
5771 update: NETDATA.gaugeChartUpdate,
5773 setSelection: NETDATA.gaugeSetSelection,
5774 clearSelection: NETDATA.gaugeClearSelection,
5775 toolboxPanAndZoom: null,
5778 format: function(state) { return 'array'; },
5779 options: function(state) { return 'absolute'; },
5780 legend: function(state) { return null; },
5781 autoresize: function(state) { return false; },
5782 max_updates_to_recreate: function(state) { return 5000; },
5783 track_colors: function(state) { return true; },
5784 pixels_per_point: function(state) { return 3; },
5789 NETDATA.registerChartLibrary = function(library, url) {
5790 if(NETDATA.options.debug.libraries === true)
5791 console.log("registering chart library: " + library);
5793 NETDATA.chartLibraries[library].url = url;
5794 NETDATA.chartLibraries[library].initialized = true;
5795 NETDATA.chartLibraries[library].enabled = true;
5798 // ----------------------------------------------------------------------------------------------------------------
5799 // Load required JS libraries and CSS
5801 NETDATA.requiredJs = [
5803 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
5805 isAlreadyLoaded: function() {
5806 // check if bootstrap is loaded
5807 if(typeof $().emulateTransitionEnd == 'function')
5810 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5818 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller-0.8.7.min.js',
5819 isAlreadyLoaded: function() { return false; }
5823 NETDATA.requiredCSS = [
5825 url: NETDATA.themes.current.bootstrap_css,
5826 isAlreadyLoaded: function() {
5827 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5834 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
5835 isAlreadyLoaded: function() { return false; }
5838 url: NETDATA.themes.current.dashboard_css,
5839 isAlreadyLoaded: function() { return false; }
5843 NETDATA.loadedRequiredJs = 0;
5844 NETDATA.loadRequiredJs = function(index, callback) {
5845 if(index >= NETDATA.requiredJs.length) {
5846 if(typeof callback === 'function')
5851 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5852 NETDATA.loadedRequiredJs++;
5853 NETDATA.loadRequiredJs(++index, callback);
5857 if(NETDATA.options.debug.main_loop === true)
5858 console.log('loading ' + NETDATA.requiredJs[index].url);
5861 if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
5865 url: NETDATA.requiredJs[index].url,
5868 xhrFields: { withCredentials: true } // required for the cookie
5871 if(NETDATA.options.debug.main_loop === true)
5872 console.log('loaded ' + NETDATA.requiredJs[index].url);
5875 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5877 .always(function() {
5878 NETDATA.loadedRequiredJs++;
5881 NETDATA.loadRequiredJs(++index, callback);
5885 NETDATA.loadRequiredJs(++index, callback);
5888 NETDATA.loadRequiredCSS = function(index) {
5889 if(index >= NETDATA.requiredCSS.length)
5892 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5893 NETDATA.loadRequiredCSS(++index);
5897 if(NETDATA.options.debug.main_loop === true)
5898 console.log('loading ' + NETDATA.requiredCSS[index].url);
5900 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5901 NETDATA.loadRequiredCSS(++index);
5905 // ----------------------------------------------------------------------------------------------------------------
5906 // Registry of netdata hosts
5909 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
5910 chart_div_offset: 100, // give that space above the chart when scrolling to it
5911 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
5912 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
5914 ms_penalty: 0, // the time penalty of the next alarm
5915 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
5916 // if alarms are shown faster than: one per 500ms
5918 notifications: false, // when true, the browser supports notifications (may not be granted though)
5919 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
5920 first_notification_id: 0, // the id of the first alarm_log entry for this session
5921 // this is used to prevent CLEAR notifications for past events
5922 // notifications_shown: new Array(),
5924 server: null, // the server to connect to for fetching alarms
5925 current: null, // the list of raised alarms - updated in the background
5926 callback: null, // a callback function to call every time the list of raised alarms is refreshed
5928 notify: function(entry) {
5929 // console.log('alarm ' + entry.unique_id);
5931 if(entry.updated === true) {
5932 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
5936 var value = entry.value;
5937 if(NETDATA.alarms.current !== null) {
5938 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
5939 if(typeof t !== 'undefined' && entry.status == t.status)
5943 var name = entry.name.replace(/_/g, ' ');
5944 var status = entry.status.toLowerCase();
5945 var title = name + ' = ' + ((value === null)?'NaN':Math.floor(value)).toString() + ' ' + entry.units;
5946 var tag = entry.alarm_id;
5947 var icon = 'images/seo-performance-128.png';
5948 var interaction = false;
5952 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
5954 switch(entry.status) {
5962 case 'UNINITIALIZED':
5966 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
5967 // console.log('alarm ' + entry.unique_id + ' is not current');
5970 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
5971 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
5974 title = name + ' back to normal';
5975 icon = 'images/check-mark-2-128-green.png'
5976 interaction = false;
5980 if(entry.old_status === 'CRITICAL')
5981 status = 'demoted to ' + entry.status.toLowerCase();
5983 icon = 'images/alert-128-orange.png';
5984 interaction = false;
5988 if(entry.old_status === 'WARNING')
5989 status = 'escalated to ' + entry.status.toLowerCase();
5991 icon = 'images/alert-128-red.png'
5996 console.log('invalid alarm status ' + entry.status);
6001 // cleanup old notifications with the same alarm_id as this one
6002 // FIXME: it does not seem to work on any web browser!
6003 var len = NETDATA.alarms.notifications_shown.length;
6005 var n = NETDATA.alarms.notifications_shown[len];
6006 if(n.data.alarm_id === entry.alarm_id) {
6007 console.log('removing old alarm ' + n.data.unique_id);
6009 // close the notification
6012 // remove it from the array
6013 NETDATA.alarms.notifications_shown.splice(len, 1);
6014 len = NETDATA.alarms.notifications_shown.length;
6021 setTimeout(function() {
6022 // show this notification
6023 // console.log('new notification: ' + title);
6024 var n = new Notification(title, {
6025 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
6027 requireInteraction: interaction,
6028 icon: NETDATA.serverDefault + icon,
6032 n.onclick = function(event) {
6033 event.preventDefault();
6034 NETDATA.alarms.onclick(event.target.data);
6038 // NETDATA.alarms.notifications_shown.push(n);
6039 // console.log(entry);
6040 }, NETDATA.alarms.ms_penalty);
6042 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6046 scrollToChart: function(chart_id) {
6047 if(typeof chart_id === 'string') {
6048 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6049 if(typeof offset !== 'undefined') {
6050 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6057 scrollToAlarm: function(alarm) {
6058 if(typeof alarm === 'object') {
6059 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6061 if(ret === true && NETDATA.options.page_is_visible === false)
6063 // alert('netdata dashboard will now scroll to chart: ' + alarm.chart + '\n\nThis alarm opened to bring the browser window in front of the screen. Click on the dashboard to prevent it from appearing again.');
6068 notifyAll: function() {
6069 // console.log('FETCHING ALARM LOG');
6070 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6071 // console.log('ALARM LOG FETCHED');
6073 if(data === null || typeof data !== 'object') {
6074 console.log('invalid alarms log response');
6078 if(data.length === 0) {
6079 console.log('received empty alarm log');
6083 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6085 data.sort(function(a, b) {
6086 if(a.unique_id > b.unique_id) return -1;
6087 if(a.unique_id < b.unique_id) return 1;
6091 NETDATA.alarms.ms_penalty = 0;
6093 var len = data.length;
6095 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6096 NETDATA.alarms.notify(data[len]);
6099 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6102 NETDATA.alarms.last_notification_id = data[0].unique_id;
6103 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6104 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6108 check_notifications: function() {
6109 // returns true if we should fire 1+ notifications
6111 if(NETDATA.alarms.notifications !== true) {
6112 // console.log('notifications not available');
6116 if(Notification.permission !== 'granted') {
6117 // console.log('notifications not granted');
6121 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6122 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6124 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6125 // console.log('new alarms detected');
6128 //else console.log('no new alarms');
6130 // else console.log('cannot process alarms');
6135 get: function(what, callback) {
6137 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6141 'Cache-Control': 'no-cache, no-store',
6142 'Pragma': 'no-cache'
6144 xhrFields: { withCredentials: true } // required for the cookie
6146 .done(function(data) {
6147 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6148 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6150 if(typeof callback === 'function')
6154 NETDATA.error(415, NETDATA.alarms.server);
6156 if(typeof callback === 'function')
6161 update_forever: function() {
6162 NETDATA.alarms.get('active', function(data) {
6164 NETDATA.alarms.current = data;
6166 if(NETDATA.alarms.check_notifications() === true) {
6167 NETDATA.alarms.notifyAll();
6170 if (typeof NETDATA.alarms.callback === 'function') {
6171 NETDATA.alarms.callback(data);
6174 // Health monitoring is disabled on this netdata
6175 if(data.status === false) return;
6178 setTimeout(NETDATA.alarms.update_forever, 10000);
6182 get_log: function(last_id, callback) {
6183 // console.log('fetching all log after ' + last_id.toString());
6185 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6189 'Cache-Control': 'no-cache, no-store',
6190 'Pragma': 'no-cache'
6192 xhrFields: { withCredentials: true } // required for the cookie
6194 .done(function(data) {
6195 if(typeof callback === 'function')
6199 NETDATA.error(416, NETDATA.alarms.server);
6201 if(typeof callback === 'function')
6207 var host = NETDATA.serverDefault;
6208 while(host.slice(-1) === '/')
6209 host = host.substring(0, host.length - 1);
6210 NETDATA.alarms.server = host;
6212 NETDATA.alarms.last_notification_id = NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6214 if(NETDATA.alarms.onclick === null)
6215 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6217 if(netdataShowAlarms === true) {
6218 NETDATA.alarms.update_forever();
6220 if('Notification' in window) {
6221 // console.log('notifications available');
6222 NETDATA.alarms.notifications = true;
6224 if(Notification.permission === 'default')
6225 Notification.requestPermission();
6231 // ----------------------------------------------------------------------------------------------------------------
6232 // Registry of netdata hosts
6234 NETDATA.registry = {
6235 server: null, // the netdata registry server
6236 person_guid: null, // the unique ID of this browser / user
6237 machine_guid: null, // the unique ID the netdata server that served dashboard.js
6238 hostname: null, // the hostname of the netdata server that served dashboard.js
6239 machines: null, // the user's other URLs
6240 machines_array: null, // the user's other URLs in an array
6243 parsePersonUrls: function(person_urls) {
6244 // console.log(person_urls);
6245 NETDATA.registry.person_urls = person_urls;
6248 NETDATA.registry.machines = {};
6249 NETDATA.registry.machines_array = new Array();
6251 var now = Date.now();
6252 var apu = person_urls;
6255 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6256 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6262 accesses: apu[i][3],
6264 alternate_urls: new Array()
6266 obj.alternate_urls.push(apu[i][1]);
6268 NETDATA.registry.machines[apu[i][0]] = obj;
6269 NETDATA.registry.machines_array.push(obj);
6272 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6274 var pu = NETDATA.registry.machines[apu[i][0]];
6275 if(pu.last_t < apu[i][2]) {
6277 pu.last_t = apu[i][2];
6278 pu.name = apu[i][4];
6280 pu.accesses += apu[i][3];
6281 pu.alternate_urls.push(apu[i][1]);
6286 if(typeof netdataRegistryCallback === 'function')
6287 netdataRegistryCallback(NETDATA.registry.machines_array);
6291 if(netdataRegistry !== true) return;
6293 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6295 NETDATA.registry.server = data.registry;
6296 NETDATA.registry.machine_guid = data.machine_guid;
6297 NETDATA.registry.hostname = data.hostname;
6299 NETDATA.registry.access(2, function (person_urls) {
6300 NETDATA.registry.parsePersonUrls(person_urls);
6307 hello: function(host, callback) {
6308 while(host.slice(-1) === '/')
6309 host = host.substring(0, host.length - 1);
6311 // send HELLO to a netdata server:
6312 // 1. verifies the server is reachable
6313 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6315 url: host + '/api/v1/registry?action=hello',
6319 'Cache-Control': 'no-cache, no-store',
6320 'Pragma': 'no-cache'
6322 xhrFields: { withCredentials: true } // required for the cookie
6324 .done(function(data) {
6325 if(typeof data.status !== 'string' || data.status !== 'ok') {
6326 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6330 if(typeof callback === 'function')
6334 NETDATA.error(407, host);
6336 if(typeof callback === 'function')
6341 access: function(max_redirects, callback) {
6342 // send ACCESS to a netdata registry:
6343 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6344 // 2. it responds with a list of netdata servers we know
6345 // the registry identifies us using a cookie it sets the first time we access it
6346 // the registry may respond with a redirect URL to send us to another registry
6348 url: NETDATA.registry.server + '/api/v1/registry?action=access&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(NETDATA.registry.hostname) + '&url=' + encodeURIComponent(NETDATA.serverDefault), // + '&visible_url=' + encodeURIComponent(document.location),
6352 'Cache-Control': 'no-cache, no-store',
6353 'Pragma': 'no-cache'
6355 xhrFields: { withCredentials: true } // required for the cookie
6357 .done(function(data) {
6358 var redirect = null;
6359 if(typeof data.registry === 'string')
6360 redirect = data.registry;
6362 if(typeof data.status !== 'string' || data.status !== 'ok') {
6363 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6368 if(redirect !== null && max_redirects > 0) {
6369 NETDATA.registry.server = redirect;
6370 NETDATA.registry.access(max_redirects - 1, callback);
6373 if(typeof callback === 'function')
6378 if(typeof data.person_guid === 'string')
6379 NETDATA.registry.person_guid = data.person_guid;
6381 if(typeof callback === 'function')
6382 callback(data.urls);
6386 NETDATA.error(410, NETDATA.registry.server);
6388 if(typeof callback === 'function')
6393 delete: function(delete_url, callback) {
6394 // send DELETE to a netdata registry:
6396 url: NETDATA.registry.server + '/api/v1/registry?action=delete&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(NETDATA.registry.hostname) + '&url=' + encodeURIComponent(NETDATA.serverDefault) + '&delete_url=' + encodeURIComponent(delete_url),
6400 'Cache-Control': 'no-cache, no-store',
6401 'Pragma': 'no-cache'
6403 xhrFields: { withCredentials: true } // required for the cookie
6405 .done(function(data) {
6406 if(typeof data.status !== 'string' || data.status !== 'ok') {
6407 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6411 if(typeof callback === 'function')
6415 NETDATA.error(412, NETDATA.registry.server);
6417 if(typeof callback === 'function')
6422 search: function(machine_guid, callback) {
6423 // SEARCH for the URLs of a machine:
6425 url: NETDATA.registry.server + '/api/v1/registry?action=search&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(NETDATA.registry.hostname) + '&url=' + encodeURIComponent(NETDATA.serverDefault) + '&for=' + machine_guid,
6429 'Cache-Control': 'no-cache, no-store',
6430 'Pragma': 'no-cache'
6432 xhrFields: { withCredentials: true } // required for the cookie
6434 .done(function(data) {
6435 if(typeof data.status !== 'string' || data.status !== 'ok') {
6436 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6440 if(typeof callback === 'function')
6444 NETDATA.error(418, NETDATA.registry.server);
6446 if(typeof callback === 'function')
6451 switch: function(new_person_guid, callback) {
6454 url: NETDATA.registry.server + '/api/v1/registry?action=switch&machine=' + NETDATA.registry.machine_guid + '&name=' + encodeURIComponent(NETDATA.registry.hostname) + '&url=' + encodeURIComponent(NETDATA.serverDefault) + '&to=' + new_person_guid,
6458 'Cache-Control': 'no-cache, no-store',
6459 'Pragma': 'no-cache'
6461 xhrFields: { withCredentials: true } // required for the cookie
6463 .done(function(data) {
6464 if(typeof data.status !== 'string' || data.status !== 'ok') {
6465 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6469 if(typeof callback === 'function')
6473 NETDATA.error(414, NETDATA.registry.server);
6475 if(typeof callback === 'function')
6481 // ----------------------------------------------------------------------------------------------------------------
6484 if(typeof netdataPrepCallback === 'function')
6485 netdataPrepCallback();
6487 NETDATA.errorReset();
6488 NETDATA.loadRequiredCSS(0);
6490 NETDATA._loadjQuery(function() {
6491 NETDATA.loadRequiredJs(0, function() {
6492 if(typeof $().emulateTransitionEnd !== 'function') {
6493 // bootstrap is not available
6494 NETDATA.options.current.show_help = false;
6497 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6498 if(NETDATA.options.debug.main_loop === true)
6499 console.log('starting chart refresh thread');
6506 // window.NETDATA = NETDATA;
6507 // })(window, document);