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.min.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.min.css?v20161002-1',
146 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css?v20161002-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: new Date().getTime(), // 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 = new Date().getTime();
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 = new Date().getTime();
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 = new Date().getTime();
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 = new Date().getTime();
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 = new Date().getTime();
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 = new Date().getTime();
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; // the units of the chart dimensions
1267 this.running = false; // boolean - true when the chart is being refreshed now
1268 this.validated = false; // boolean - has the chart been validated?
1269 this.enabled = true; // boolean - is the chart enabled for refresh?
1270 this.paused = false; // boolean - is the chart paused for any reason?
1271 this.selected = false; // boolean - is the chart shown a selection?
1272 this.debug = false; // boolean - console.log() debug info about this chart
1274 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1275 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1276 this.requested_after = null; // milliseconds - the timestamp of the request after param
1277 this.requested_before = null; // milliseconds - the timestamp of the request before param
1278 this.requested_padding = null;
1279 this.view_after = 0;
1280 this.view_before = 0;
1282 this.value_decimal_detail = -1;
1284 var d = self.data('decimal-digits');
1285 if(typeof d === 'number') {
1286 this.value_decimal_detail = 1;
1288 this.value_decimal_detail *= 10;
1295 force_update_at: 0, // the timestamp to force the update at
1296 force_before_ms: null,
1297 force_after_ms: null
1302 force_update_at: 0, // the timestamp to force the update at
1303 force_before_ms: null,
1304 force_after_ms: null
1309 force_update_at: 0, // the timestamp to force the update at
1310 force_before_ms: null,
1311 force_after_ms: null
1314 // this is a pointer to one of the sub-classes below
1316 this.current = this.auto;
1318 // check the requested library is available
1319 // we don't initialize it here - it will be initialized when
1320 // this chart will be first used
1321 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1322 NETDATA.error(402, that.library_name);
1323 error('chart library "' + that.library_name + '" is not found');
1326 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1327 NETDATA.error(403, that.library_name);
1328 error('chart library "' + that.library_name + '" is not enabled');
1332 that.library = NETDATA.chartLibraries[that.library_name];
1334 // milliseconds - the time the last refresh took
1335 this.refresh_dt_ms = 0;
1337 // if we need to report the rendering speed
1338 // find the element that needs to be updated
1339 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1341 if(refresh_dt_element_name !== null)
1342 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1344 this.refresh_dt_element = null;
1346 this.dimensions_visibility = new dimensionsVisibility(this);
1348 this._updating = false;
1350 // ============================================================================================================
1351 // PRIVATE FUNCTIONS
1353 var createDOM = function() {
1354 if(that.enabled === false) return;
1356 if(that.element_message !== null) that.element_message.innerHTML = '';
1357 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1358 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1360 that.element.innerHTML = '';
1362 that.element_message = document.createElement('div');
1363 that.element_message.className = ' netdata-message hidden';
1364 that.element.appendChild(that.element_message);
1366 that.element_chart = document.createElement('div');
1367 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1368 that.element.appendChild(that.element_chart);
1370 if(that.hasLegend() === true) {
1371 that.element.className = "netdata-container-with-legend";
1372 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1374 that.element_legend = document.createElement('div');
1375 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1376 that.element.appendChild(that.element_legend);
1379 that.element.className = "netdata-container";
1380 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1382 that.element_legend = null;
1384 that.element_legend_childs.series = null;
1386 if(typeof(that.width) === 'string')
1387 $(that.element).css('width', that.width);
1388 else if(typeof(that.width) === 'number')
1389 $(that.element).css('width', that.width + 'px');
1391 if(typeof(that.library.aspect_ratio) === 'undefined') {
1392 if(typeof(that.height) === 'string')
1393 $(that.element).css('height', that.height);
1394 else if(typeof(that.height) === 'number')
1395 $(that.element).css('height', that.height + 'px');
1398 var w = that.element.offsetWidth;
1399 if(w === null || w === 0) {
1400 // the div is hidden
1401 // this will resize the chart when next viewed
1402 that.tm.last_resized = 0;
1405 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1408 if(NETDATA.chartDefaults.min_width !== null)
1409 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1411 that.tm.last_dom_created = new Date().getTime();
1417 * initialize state variables
1418 * destroy all (possibly) created state elements
1419 * create the basic DOM for a chart
1421 var init = function() {
1422 if(that.enabled === false) return;
1424 that.paused = false;
1425 that.selected = false;
1427 that.chart_created = false; // boolean - is the library.create() been called?
1428 that.updates_counter = 0; // numeric - the number of refreshes made so far
1429 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1430 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1433 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1434 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1435 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1437 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1438 last_updated: 0, // the timestamp the chart last updated with data
1439 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1441 // Used with NETDATA.globalPanAndZoom.seq
1442 last_visible_check: 0, // the time we last checked if it is visible
1443 last_resized: 0, // the time the chart was resized
1444 last_hidden: 0, // the time the chart was hidden
1445 last_unhidden: 0, // the time the chart was unhidden
1446 last_autorefreshed: 0 // the time the chart was last refreshed
1449 that.data = null; // the last data as downloaded from the netdata server
1450 that.data_url = 'invalid://'; // string - the last url used to update the chart
1451 that.data_points = 0; // number - the number of points returned from netdata
1452 that.data_after = 0; // milliseconds - the first timestamp of the data
1453 that.data_before = 0; // milliseconds - the last timestamp of the data
1454 that.data_update_every = 0; // milliseconds - the frequency to update the data
1456 that.tm.last_initialized = new Date().getTime();
1459 that.setMode('auto');
1462 var maxMessageFontSize = function() {
1463 // normally we want a font size, as tall as the element
1464 var h = that.element_message.clientHeight;
1466 // but give it some air, 20% let's say, or 5 pixels min
1467 var lost = Math.max(h * 0.2, 5);
1470 // center the text, vertically
1471 var paddingTop = (lost - 5) / 2;
1473 // but check the width too
1474 // it should fit 10 characters in it
1475 var w = that.element_message.clientWidth / 10;
1477 paddingTop += (h - w) / 2;
1481 // and don't make it too huge
1482 // 5% of the screen size is good
1483 if(h > screen.height / 20) {
1484 paddingTop += (h - (screen.height / 20)) / 2;
1485 h = screen.height / 20;
1489 that.element_message.style.fontSize = h.toString() + 'px';
1490 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1493 var showMessage = function(msg) {
1494 that.element_message.className = 'netdata-message';
1495 that.element_message.innerHTML = msg;
1496 that.element_message.style.fontSize = 'x-small';
1497 that.element_message.style.paddingTop = '0px';
1498 that.___messageHidden___ = undefined;
1501 var showMessageIcon = function(icon) {
1502 that.element_message.innerHTML = icon;
1503 that.element_message.className = 'netdata-message icon';
1504 maxMessageFontSize();
1505 that.___messageHidden___ = undefined;
1508 var hideMessage = function() {
1509 if(typeof that.___messageHidden___ === 'undefined') {
1510 that.___messageHidden___ = true;
1511 that.element_message.className = 'netdata-message hidden';
1515 var showRendering = function() {
1517 if(that.chart !== null) {
1518 if(that.chart.chart_type === 'line')
1519 icon = '<i class="fa fa-line-chart"></i>';
1521 icon = '<i class="fa fa-area-chart"></i>';
1524 icon = '<i class="fa fa-area-chart"></i>';
1526 showMessageIcon(icon + ' netdata');
1529 var showLoading = function() {
1530 if(that.chart_created === false) {
1531 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1537 var isHidden = function() {
1538 if(typeof that.___chartIsHidden___ !== 'undefined')
1544 // hide the chart, when it is not visible - called from isVisible()
1545 var hideChart = function() {
1546 // hide it, if it is not already hidden
1547 if(isHidden() === true) return;
1549 if(that.chart_created === true) {
1550 if(NETDATA.options.current.destroy_on_hide === true) {
1551 // we should destroy it
1556 that.element_chart.style.display = 'none';
1557 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1558 that.tm.last_hidden = new Date().getTime();
1561 // This works, but I not sure there are no corner cases somewhere
1562 // so it is commented - if the user has memory issues he can
1563 // set Destroy on Hide for all charts
1564 // that.data = null;
1568 that.___chartIsHidden___ = true;
1571 // unhide the chart, when it is visible - called from isVisible()
1572 var unhideChart = function() {
1573 if(isHidden() === false) return;
1575 that.___chartIsHidden___ = undefined;
1576 that.updates_since_last_unhide = 0;
1578 if(that.chart_created === false) {
1579 // we need to re-initialize it, to show our background
1580 // logo in bootstrap tabs, until the chart loads
1584 that.tm.last_unhidden = new Date().getTime();
1585 that.element_chart.style.display = '';
1586 if(that.element_legend !== null) that.element_legend.style.display = '';
1592 var canBeRendered = function() {
1593 if(isHidden() === true || that.isVisible(true) === false)
1599 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1600 var callChartLibraryUpdateSafely = function(data) {
1603 if(canBeRendered() === false)
1606 if(NETDATA.options.debug.chart_errors === true)
1607 status = that.library.update(that, data);
1610 status = that.library.update(that, data);
1617 if(status === false) {
1618 error('chart failed to be updated as ' + that.library_name);
1625 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1626 var callChartLibraryCreateSafely = function(data) {
1629 if(canBeRendered() === false)
1632 if(NETDATA.options.debug.chart_errors === true)
1633 status = that.library.create(that, data);
1636 status = that.library.create(that, data);
1643 if(status === false) {
1644 error('chart failed to be created as ' + that.library_name);
1648 that.chart_created = true;
1649 that.updates_since_last_creation = 0;
1653 // ----------------------------------------------------------------------------------------------------------------
1656 // resizeChart() - private
1657 // to be called just before the chart library to make sure that
1658 // a properly sized dom is available
1659 var resizeChart = function() {
1660 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1661 if(that.chart_created === false) return;
1663 if(that.needsRecreation()) {
1666 else if(typeof that.library.resize === 'function') {
1667 that.library.resize(that);
1669 if(that.element_legend_childs.nano !== null && that.element_legend_childs.nano_options !== null)
1670 $(that.element_legend_childs.nano).nanoScroller();
1672 maxMessageFontSize();
1675 that.tm.last_resized = new Date().getTime();
1679 // this is the actual chart resize algorithm
1681 // - resize the entire container
1682 // - update the internal states
1683 // - resize the chart as the div changes height
1684 // - update the scrollbar of the legend
1685 var resizeChartToHeight = function(h) {
1687 that.element.style.height = h;
1689 if(that.settings_id !== null)
1690 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1692 var now = new Date().getTime();
1693 NETDATA.options.last_page_scroll = now;
1694 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1697 that.tm.last_resized = 0;
1701 this.resizeHandler = function(e) {
1704 if(typeof this.event_resize === 'undefined'
1705 || this.event_resize.chart_original_w === 'undefined'
1706 || this.event_resize.chart_original_h === 'undefined')
1707 this.event_resize = {
1708 chart_original_w: this.element.clientWidth,
1709 chart_original_h: this.element.clientHeight,
1713 if(e.type === 'touchstart') {
1714 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1715 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1718 this.event_resize.mouse_start_x = e.clientX;
1719 this.event_resize.mouse_start_y = e.clientY;
1722 this.event_resize.chart_start_w = this.element.clientWidth;
1723 this.event_resize.chart_start_h = this.element.clientHeight;
1724 this.event_resize.chart_last_w = this.element.clientWidth;
1725 this.event_resize.chart_last_h = this.element.clientHeight;
1727 var now = new Date().getTime();
1728 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
1729 // double click / double tap event
1731 // the optimal height of the chart
1732 // showing the entire legend
1733 var optimal = this.event_resize.chart_last_h
1734 + this.element_legend_childs.content.scrollHeight
1735 - this.element_legend_childs.content.clientHeight;
1737 // if we are not optimal, be optimal
1738 if(this.event_resize.chart_last_h != optimal)
1739 resizeChartToHeight(optimal.toString() + 'px');
1741 // else if we do not have the original height
1742 // reset to the original height
1743 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
1744 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1747 this.event_resize.last = now;
1749 // process movement event
1750 document.onmousemove =
1751 document.ontouchmove =
1752 this.element_legend_childs.resize_handler.onmousemove =
1753 this.element_legend_childs.resize_handler.ontouchmove =
1758 case 'mousemove': y = e.clientY; break;
1759 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1763 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1765 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1766 resizeChartToHeight(newH.toString() + 'px');
1767 that.event_resize.chart_last_h = newH;
1772 // process end event
1773 document.onmouseup =
1774 document.ontouchend =
1775 this.element_legend_childs.resize_handler.onmouseup =
1776 this.element_legend_childs.resize_handler.ontouchend =
1778 // remove all the hooks
1779 document.onmouseup =
1780 document.onmousemove =
1781 document.ontouchmove =
1782 document.ontouchend =
1783 that.element_legend_childs.resize_handler.onmousemove =
1784 that.element_legend_childs.resize_handler.ontouchmove =
1785 that.element_legend_childs.resize_handler.onmouseout =
1786 that.element_legend_childs.resize_handler.onmouseup =
1787 that.element_legend_childs.resize_handler.ontouchend =
1790 // allow auto-refreshes
1791 NETDATA.options.auto_refresher_stop_until = 0;
1797 var noDataToShow = function() {
1798 showMessageIcon('<i class="fa fa-warning"></i> empty');
1799 that.legendUpdateDOM();
1800 that.tm.last_autorefreshed = new Date().getTime();
1801 // that.data_update_every = 30 * 1000;
1802 //that.element_chart.style.display = 'none';
1803 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1804 //that.___chartIsHidden___ = true;
1807 // ============================================================================================================
1810 this.error = function(msg) {
1814 this.setMode = function(m) {
1815 if(this.current !== null && this.current.name === m) return;
1818 this.current = this.auto;
1819 else if(m === 'pan')
1820 this.current = this.pan;
1821 else if(m === 'zoom')
1822 this.current = this.zoom;
1824 this.current = this.auto;
1826 this.current.force_update_at = 0;
1827 this.current.force_before_ms = null;
1828 this.current.force_after_ms = null;
1830 this.tm.last_mode_switch = new Date().getTime();
1833 // ----------------------------------------------------------------------------------------------------------------
1834 // global selection sync
1836 // prevent to global selection sync for some time
1837 this.globalSelectionSyncDelay = function(ms) {
1838 if(NETDATA.options.current.sync_selection === false)
1841 if(typeof ms === 'number')
1842 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + ms;
1844 NETDATA.globalSelectionSync.dont_sync_before = new Date().getTime() + NETDATA.options.current.sync_selection_delay;
1847 // can we globally apply selection sync?
1848 this.globalSelectionSyncAbility = function() {
1849 if(NETDATA.options.current.sync_selection === false)
1852 if(NETDATA.globalSelectionSync.dont_sync_before > new Date().getTime())
1858 this.globalSelectionSyncIsMaster = function() {
1859 if(NETDATA.globalSelectionSync.state === this)
1865 // this chart is the master of the global selection sync
1866 this.globalSelectionSyncBeMaster = function() {
1868 if(this.globalSelectionSyncIsMaster()) {
1869 if(this.debug === true)
1870 this.log('sync: I am the master already.');
1875 if(NETDATA.globalSelectionSync.state) {
1876 if(this.debug === true)
1877 this.log('sync: I am not the sync master. Resetting global sync.');
1879 this.globalSelectionSyncStop();
1882 // become the master
1883 if(this.debug === true)
1884 this.log('sync: becoming sync master.');
1886 this.selected = true;
1887 NETDATA.globalSelectionSync.state = this;
1889 // find the all slaves
1890 var targets = NETDATA.options.targets;
1891 var len = targets.length;
1896 if(this.debug === true)
1897 st.log('sync: not adding me to sync');
1899 else if(st.globalSelectionSyncIsEligible()) {
1900 if(this.debug === true)
1901 st.log('sync: adding to sync as slave');
1903 st.globalSelectionSyncBeSlave();
1907 // this.globalSelectionSyncDelay(100);
1910 // can the chart participate to the global selection sync as a slave?
1911 this.globalSelectionSyncIsEligible = function() {
1912 if(this.enabled === true
1913 && this.library !== null
1914 && typeof this.library.setSelection === 'function'
1915 && this.isVisible() === true
1916 && this.chart_created === true)
1922 // this chart becomes a slave of the global selection sync
1923 this.globalSelectionSyncBeSlave = function() {
1924 if(NETDATA.globalSelectionSync.state !== this)
1925 NETDATA.globalSelectionSync.slaves.push(this);
1928 // sync all the visible charts to the given time
1929 // this is to be called from the chart libraries
1930 this.globalSelectionSync = function(t) {
1931 if(this.globalSelectionSyncAbility() === false) {
1932 if(this.debug === true)
1933 this.log('sync: cannot sync (yet?).');
1938 if(this.globalSelectionSyncIsMaster() === false) {
1939 if(this.debug === true)
1940 this.log('sync: trying to be sync master.');
1942 this.globalSelectionSyncBeMaster();
1944 if(this.globalSelectionSyncAbility() === false) {
1945 if(this.debug === true)
1946 this.log('sync: cannot sync (yet?).');
1952 NETDATA.globalSelectionSync.last_t = t;
1953 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1958 // stop syncing all charts to the given time
1959 this.globalSelectionSyncStop = function() {
1960 if(NETDATA.globalSelectionSync.slaves.length) {
1961 if(this.debug === true)
1962 this.log('sync: cleaning up...');
1964 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1966 if(that.debug === true)
1967 st.log('sync: not adding me to sync stop');
1970 if(that.debug === true)
1971 st.log('sync: removed slave from sync');
1973 st.clearSelection();
1977 NETDATA.globalSelectionSync.last_t = 0;
1978 NETDATA.globalSelectionSync.slaves = [];
1979 NETDATA.globalSelectionSync.state = null;
1982 this.clearSelection();
1985 this.setSelection = function(t) {
1986 if(typeof this.library.setSelection === 'function') {
1987 if(this.library.setSelection(this, t) === true)
1988 this.selected = true;
1990 this.selected = false;
1992 else this.selected = true;
1994 if(this.selected === true && this.debug === true)
1995 this.log('selection set to ' + t.toString());
1997 return this.selected;
2000 this.clearSelection = function() {
2001 if(this.selected === true) {
2002 if(typeof this.library.clearSelection === 'function') {
2003 if(this.library.clearSelection(this) === true)
2004 this.selected = false;
2006 this.selected = true;
2008 else this.selected = false;
2010 if(this.selected === false && this.debug === true)
2011 this.log('selection cleared');
2016 return this.selected;
2019 // find if a timestamp (ms) is shown in the current chart
2020 this.timeIsVisible = function(t) {
2021 if(t >= this.data_after && t <= this.data_before)
2026 this.calculateRowForTime = function(t) {
2027 if(this.timeIsVisible(t) === false) return -1;
2028 return Math.floor((t - this.data_after) / this.data_update_every);
2031 // ----------------------------------------------------------------------------------------------------------------
2034 this.log = function(msg) {
2035 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
2038 this.pauseChart = function() {
2039 if(this.paused === false) {
2040 if(this.debug === true)
2041 this.log('pauseChart()');
2047 this.unpauseChart = function() {
2048 if(this.paused === true) {
2049 if(this.debug === true)
2050 this.log('unpauseChart()');
2052 this.paused = false;
2056 this.resetChart = function(dont_clear_master, dont_update) {
2057 if(this.debug === true)
2058 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
2060 if(typeof dont_clear_master === 'undefined')
2061 dont_clear_master = false;
2063 if(typeof dont_update === 'undefined')
2064 dont_update = false;
2066 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
2067 if(this.debug === true)
2068 this.log('resetChart() diverting to clearMaster().');
2069 // this will call us back with master === true
2070 NETDATA.globalPanAndZoom.clearMaster();
2074 this.clearSelection();
2076 this.tm.pan_and_zoom_seq = 0;
2078 this.setMode('auto');
2079 this.current.force_update_at = 0;
2080 this.current.force_before_ms = null;
2081 this.current.force_after_ms = null;
2082 this.tm.last_autorefreshed = 0;
2083 this.paused = false;
2084 this.selected = false;
2085 this.enabled = true;
2086 // this.debug = false;
2088 // do not update the chart here
2089 // or the chart will flip-flop when it is the master
2090 // of a selection sync and another chart becomes
2093 if(dont_update !== true && this.isVisible() === true) {
2098 this.updateChartPanOrZoom = function(after, before) {
2099 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
2102 if(this.debug === true)
2105 if(before < after) {
2106 if(this.debug === true)
2107 this.log(logme + 'flipped parameters, rejecting it.');
2112 if(typeof this.fixed_min_duration === 'undefined')
2113 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
2115 var min_duration = this.fixed_min_duration;
2116 var current_duration = Math.round(this.view_before - this.view_after);
2118 // round the numbers
2119 after = Math.round(after);
2120 before = Math.round(before);
2122 // align them to update_every
2123 // stretching them further away
2124 after -= after % this.data_update_every;
2125 before += this.data_update_every - (before % this.data_update_every);
2127 // the final wanted duration
2128 var wanted_duration = before - after;
2130 // to allow panning, accept just a point below our minimum
2131 if((current_duration - this.data_update_every) < min_duration)
2132 min_duration = current_duration - this.data_update_every;
2134 // we do it, but we adjust to minimum size and return false
2135 // when the wanted size is below the current and the minimum
2137 if(wanted_duration < current_duration && wanted_duration < min_duration) {
2138 if(this.debug === true)
2139 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
2141 min_duration = this.fixed_min_duration;
2143 var dt = (min_duration - wanted_duration) / 2;
2146 wanted_duration = before - after;
2150 var tolerance = this.data_update_every * 2;
2151 var movement = Math.abs(before - this.view_before);
2153 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
2154 if(this.debug === true)
2155 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);
2159 if(this.current.name === 'auto') {
2160 this.log(logme + 'caller called me with mode: ' + this.current.name);
2161 this.setMode('pan');
2164 if(this.debug === true)
2165 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);
2167 this.current.force_update_at = new Date().getTime() + NETDATA.options.current.pan_and_zoom_delay;
2168 this.current.force_after_ms = after;
2169 this.current.force_before_ms = before;
2170 NETDATA.globalPanAndZoom.setMaster(this, after, before);
2174 this.legendFormatValue = function(value) {
2175 if(value === null || value === 'undefined') return '-';
2176 if(typeof value !== 'number') return value;
2178 if(this.value_decimal_detail !== -1)
2179 return (Math.round(value * this.value_decimal_detail) / this.value_decimal_detail).toLocaleString();
2181 var abs = Math.abs(value);
2182 if(abs >= 1000) return (Math.round(value)).toLocaleString();
2183 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
2184 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
2185 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
2186 return (Math.round(value * 10000) / 10000).toLocaleString();
2189 this.legendSetLabelValue = function(label, value) {
2190 var series = this.element_legend_childs.series[label];
2191 if(typeof series === 'undefined') return;
2192 if(series.value === null && series.user === null) return;
2194 // if the value has not changed, skip DOM update
2195 //if(series.last === value) return;
2198 if(typeof value === 'number') {
2199 var v = Math.abs(value);
2200 s = r = this.legendFormatValue(value);
2202 if(typeof series.last === 'number') {
2203 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2204 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2205 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2207 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2212 series.last = value;
2215 if(series.value !== null) series.value.innerHTML = s;
2216 if(series.user !== null) series.user.innerHTML = r;
2219 this.legendSetDate = function(ms) {
2220 if(typeof ms !== 'number') {
2221 this.legendShowUndefined();
2225 var d = new Date(ms);
2227 if(this.element_legend_childs.title_date)
2228 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
2230 if(this.element_legend_childs.title_time)
2231 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
2233 if(this.element_legend_childs.title_units)
2234 this.element_legend_childs.title_units.innerHTML = this.units;
2237 this.legendShowUndefined = function() {
2238 if(this.element_legend_childs.title_date)
2239 this.element_legend_childs.title_date.innerHTML = ' ';
2241 if(this.element_legend_childs.title_time)
2242 this.element_legend_childs.title_time.innerHTML = this.chart.name;
2244 if(this.element_legend_childs.title_units)
2245 this.element_legend_childs.title_units.innerHTML = ' ';
2247 if(this.data && this.element_legend_childs.series !== null) {
2248 var labels = this.data.dimension_names;
2249 var i = labels.length;
2251 var label = labels[i];
2253 if(typeof label === 'undefined') continue;
2254 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2255 this.legendSetLabelValue(label, null);
2260 this.legendShowLatestValues = function() {
2261 if(this.chart === null) return;
2262 if(this.selected) return;
2264 if(this.data === null || this.element_legend_childs.series === null) {
2265 this.legendShowUndefined();
2269 var show_undefined = true;
2270 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2271 show_undefined = false;
2273 if(show_undefined) {
2274 this.legendShowUndefined();
2278 this.legendSetDate(this.view_before);
2280 var labels = this.data.dimension_names;
2281 var i = labels.length;
2283 var label = labels[i];
2285 if(typeof label === 'undefined') continue;
2286 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2289 this.legendSetLabelValue(label, null);
2291 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2295 this.legendReset = function() {
2296 this.legendShowLatestValues();
2299 // this should be called just ONCE per dimension per chart
2300 this._chartDimensionColor = function(label) {
2301 if(this.colors === null) this.chartColors();
2303 if(typeof this.colors_assigned[label] === 'undefined') {
2304 if(this.colors_available.length === 0) {
2305 var len = NETDATA.themes.current.colors.length;
2307 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2310 this.colors_assigned[label] = this.colors_available.shift();
2312 if(this.debug === true)
2313 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2316 if(this.debug === true)
2317 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2320 this.colors.push(this.colors_assigned[label]);
2321 return this.colors_assigned[label];
2324 this.chartColors = function() {
2325 if(this.colors !== null) return this.colors;
2327 this.colors = new Array();
2328 this.colors_available = new Array();
2330 // add the standard colors
2331 var len = NETDATA.themes.current.colors.length;
2333 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2335 // add the user supplied colors
2336 var c = $(this.element).data('colors');
2337 // this.log('read colors: ' + c);
2338 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2339 if(typeof c !== 'string') {
2340 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2350 this.colors_available.unshift(c[len]);
2351 // this.log('adding color: ' + c[len]);
2360 this.legendUpdateDOM = function() {
2363 // check that the legend DOM is up to date for the downloaded dimensions
2364 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2365 // this.log('the legend does not have any series - requesting legend update');
2368 else if(this.data === null) {
2369 // this.log('the chart does not have any data - requesting legend update');
2372 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2376 var labels = this.data.dimension_names.toString();
2377 if(labels !== this.element_legend_childs.series.labels_key) {
2380 if(this.debug === true)
2381 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2385 if(needed === false) {
2386 // make sure colors available
2389 // do we have to update the current values?
2390 // we do this, only when the visible chart is current
2391 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2392 if(this.debug === true)
2393 this.log('chart is in latest position... updating values on legend...');
2395 //var labels = this.data.dimension_names;
2396 //var i = labels.length;
2398 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2402 if(this.colors === null) {
2403 // this is the first time we update the chart
2404 // let's assign colors to all dimensions
2405 if(this.library.track_colors() === true)
2406 for(var dim in this.chart.dimensions)
2407 this._chartDimensionColor(this.chart.dimensions[dim].name);
2409 // we will re-generate the colors for the chart
2410 // based on the selected dimensions
2413 if(this.debug === true)
2414 this.log('updating Legend DOM');
2416 // mark all dimensions as invalid
2417 this.dimensions_visibility.invalidateAll();
2419 var genLabel = function(state, parent, dim, name, count) {
2420 var color = state._chartDimensionColor(name);
2422 var user_element = null;
2423 var user_id = self.data('show-value-of-' + dim + '-at') || null;
2424 if(user_id !== null) {
2425 user_element = document.getElementById(user_id) || null;
2426 if(user_element === null)
2427 state.log('Cannot find element with id: ' + user_id);
2430 state.element_legend_childs.series[name] = {
2431 name: document.createElement('span'),
2432 value: document.createElement('span'),
2437 var label = state.element_legend_childs.series[name];
2439 // create the dimension visibility tracking for this label
2440 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2442 var rgb = NETDATA.colorHex2Rgb(color);
2443 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2444 + state.chart.chart_type
2445 + '" style="background-color: '
2446 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2447 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2449 var text = document.createTextNode(' ' + name);
2450 label.name.appendChild(text);
2453 parent.appendChild(document.createElement('br'));
2455 parent.appendChild(label.name);
2456 parent.appendChild(label.value);
2459 var content = document.createElement('div');
2461 if(this.hasLegend()) {
2462 this.element_legend_childs = {
2464 resize_handler: document.createElement('div'),
2465 toolbox: document.createElement('div'),
2466 toolbox_left: document.createElement('div'),
2467 toolbox_right: document.createElement('div'),
2468 toolbox_reset: document.createElement('div'),
2469 toolbox_zoomin: document.createElement('div'),
2470 toolbox_zoomout: document.createElement('div'),
2471 toolbox_volume: document.createElement('div'),
2472 title_date: document.createElement('span'),
2473 title_time: document.createElement('span'),
2474 title_units: document.createElement('span'),
2475 nano: document.createElement('div'),
2477 paneClass: 'netdata-legend-series-pane',
2478 sliderClass: 'netdata-legend-series-slider',
2479 contentClass: 'netdata-legend-series-content',
2480 enabledClass: '__enabled',
2481 flashedClass: '__flashed',
2482 activeClass: '__active',
2484 alwaysVisible: true,
2490 this.element_legend.innerHTML = '';
2492 if(this.library.toolboxPanAndZoom !== null) {
2494 function get_pan_and_zoom_step(event) {
2496 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2498 else if (event.shiftKey)
2499 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2501 else if (event.altKey)
2502 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2505 return NETDATA.options.current.pan_and_zoom_factor;
2508 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2509 this.element.appendChild(this.element_legend_childs.toolbox);
2511 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2512 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2513 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2514 this.element_legend_childs.toolbox_left.onclick = function(e) {
2517 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2518 var before = that.view_before - step;
2519 var after = that.view_after - step;
2520 if(after >= that.netdata_first)
2521 that.library.toolboxPanAndZoom(that, after, before);
2523 if(NETDATA.options.current.show_help === true)
2524 $(this.element_legend_childs.toolbox_left).popover({
2529 placement: 'bottom',
2530 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2532 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>'
2536 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2537 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2538 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2539 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2541 NETDATA.resetAllCharts(that);
2543 if(NETDATA.options.current.show_help === true)
2544 $(this.element_legend_childs.toolbox_reset).popover({
2549 placement: 'bottom',
2550 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2551 title: 'Chart Reset',
2552 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>'
2555 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2556 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2557 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2558 this.element_legend_childs.toolbox_right.onclick = function(e) {
2560 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2561 var before = that.view_before + step;
2562 var after = that.view_after + step;
2563 if(before <= that.netdata_last)
2564 that.library.toolboxPanAndZoom(that, after, before);
2566 if(NETDATA.options.current.show_help === true)
2567 $(this.element_legend_childs.toolbox_right).popover({
2572 placement: 'bottom',
2573 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2575 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>'
2579 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2580 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2581 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2582 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2584 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2585 var before = that.view_before - dt;
2586 var after = that.view_after + dt;
2587 that.library.toolboxPanAndZoom(that, after, before);
2589 if(NETDATA.options.current.show_help === true)
2590 $(this.element_legend_childs.toolbox_zoomin).popover({
2595 placement: 'bottom',
2596 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2597 title: 'Chart Zoom In',
2598 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>'
2601 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2602 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2603 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2604 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2606 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);
2607 var before = that.view_before + dt;
2608 var after = that.view_after - dt;
2610 that.library.toolboxPanAndZoom(that, after, before);
2612 if(NETDATA.options.current.show_help === true)
2613 $(this.element_legend_childs.toolbox_zoomout).popover({
2618 placement: 'bottom',
2619 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2620 title: 'Chart Zoom Out',
2621 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>'
2624 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2625 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2626 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2627 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2628 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2629 //e.preventDefault();
2630 //alert('clicked toolbox_volume on ' + that.id);
2634 this.element_legend_childs.toolbox = null;
2635 this.element_legend_childs.toolbox_left = null;
2636 this.element_legend_childs.toolbox_reset = null;
2637 this.element_legend_childs.toolbox_right = null;
2638 this.element_legend_childs.toolbox_zoomin = null;
2639 this.element_legend_childs.toolbox_zoomout = null;
2640 this.element_legend_childs.toolbox_volume = null;
2643 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2644 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2645 this.element.appendChild(this.element_legend_childs.resize_handler);
2646 if(NETDATA.options.current.show_help === true)
2647 $(this.element_legend_childs.resize_handler).popover({
2652 placement: 'bottom',
2653 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2654 title: 'Chart Resize',
2655 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>'
2659 this.element_legend_childs.resize_handler.onmousedown =
2661 that.resizeHandler(e);
2665 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2666 that.resizeHandler(e);
2669 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2670 this.element_legend.appendChild(this.element_legend_childs.title_date);
2672 this.element_legend.appendChild(document.createElement('br'));
2674 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2675 this.element_legend.appendChild(this.element_legend_childs.title_time);
2677 this.element_legend.appendChild(document.createElement('br'));
2679 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2680 this.element_legend.appendChild(this.element_legend_childs.title_units);
2682 this.element_legend.appendChild(document.createElement('br'));
2684 this.element_legend_childs.nano.className = 'netdata-legend-series';
2685 this.element_legend.appendChild(this.element_legend_childs.nano);
2687 content.className = 'netdata-legend-series-content';
2688 this.element_legend_childs.nano.appendChild(content);
2690 if(NETDATA.options.current.show_help === true)
2691 $(content).popover({
2696 placement: 'bottom',
2697 title: 'Chart Legend',
2698 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2699 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>'
2703 this.element_legend_childs = {
2705 resize_handler: null,
2708 toolbox_right: null,
2709 toolbox_reset: null,
2710 toolbox_zoomin: null,
2711 toolbox_zoomout: null,
2712 toolbox_volume: null,
2723 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2724 if(this.debug === true)
2725 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2727 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2728 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2732 var tmp = new Array();
2733 for(var dim in this.chart.dimensions) {
2734 tmp.push(this.chart.dimensions[dim].name);
2735 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2737 this.element_legend_childs.series.labels_key = tmp.toString();
2738 if(this.debug === true)
2739 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2742 // create a hidden div to be used for hidding
2743 // the original legend of the chart library
2744 var el = document.createElement('div');
2745 if(this.element_legend !== null)
2746 this.element_legend.appendChild(el);
2747 el.style.display = 'none';
2749 this.element_legend_childs.hidden = document.createElement('div');
2750 el.appendChild(this.element_legend_childs.hidden);
2752 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2753 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2755 this.legendShowLatestValues();
2758 this.hasLegend = function() {
2759 if(typeof this.___hasLegendCache___ !== 'undefined')
2760 return this.___hasLegendCache___;
2763 if(this.library && this.library.legend(this) === 'right-side') {
2764 var legend = $(this.element).data('legend') || 'yes';
2765 if(legend === 'yes') leg = true;
2768 this.___hasLegendCache___ = leg;
2772 this.legendWidth = function() {
2773 return (this.hasLegend())?140:0;
2776 this.legendHeight = function() {
2777 return $(this.element).height();
2780 this.chartWidth = function() {
2781 return $(this.element).width() - this.legendWidth();
2784 this.chartHeight = function() {
2785 return $(this.element).height();
2788 this.chartPixelsPerPoint = function() {
2789 // force an options provided detail
2790 var px = this.pixels_per_point;
2792 if(this.library && px < this.library.pixels_per_point(this))
2793 px = this.library.pixels_per_point(this);
2795 if(px < NETDATA.options.current.pixels_per_point)
2796 px = NETDATA.options.current.pixels_per_point;
2801 this.needsRecreation = function() {
2803 this.chart_created === true
2805 && this.library.autoresize() === false
2806 && this.tm.last_resized < NETDATA.options.last_resized
2810 this.chartURL = function() {
2811 var after, before, points_multiplier = 1;
2812 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2813 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2815 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2816 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2817 this.view_after = after * 1000;
2818 this.view_before = before * 1000;
2820 this.requested_padding = null;
2821 points_multiplier = 1;
2823 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2824 this.tm.pan_and_zoom_seq = 0;
2826 before = Math.round(this.current.force_before_ms / 1000);
2827 after = Math.round(this.current.force_after_ms / 1000);
2828 this.view_after = after * 1000;
2829 this.view_before = before * 1000;
2831 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2832 this.requested_padding = Math.round((before - after) / 2);
2833 after -= this.requested_padding;
2834 before += this.requested_padding;
2835 this.requested_padding *= 1000;
2836 points_multiplier = 2;
2839 this.current.force_before_ms = null;
2840 this.current.force_after_ms = null;
2843 this.tm.pan_and_zoom_seq = 0;
2845 before = this.before;
2847 this.view_after = after * 1000;
2848 this.view_before = before * 1000;
2850 this.requested_padding = null;
2851 points_multiplier = 1;
2854 this.requested_after = after * 1000;
2855 this.requested_before = before * 1000;
2857 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2859 // build the data URL
2860 this.data_url = this.host + this.chart.data_url;
2861 this.data_url += "&format=" + this.library.format();
2862 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2863 this.data_url += "&group=" + this.method;
2864 this.data_url += "&options=" + this.library.options(this);
2865 this.data_url += '|jsonwrap';
2867 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2868 this.data_url += '|nonzero';
2870 if(this.append_options !== null)
2871 this.data_url += '|' + this.append_options.toString();
2874 this.data_url += "&after=" + after.toString();
2877 this.data_url += "&before=" + before.toString();
2880 this.data_url += "&dimensions=" + this.dimensions;
2882 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2883 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2886 this.redrawChart = function() {
2887 if(this.data !== null)
2888 this.updateChartWithData(this.data);
2891 this.updateChartWithData = function(data) {
2892 if(this.debug === true)
2893 this.log('updateChartWithData() called.');
2895 // this may force the chart to be re-created
2899 this.updates_counter++;
2900 this.updates_since_last_unhide++;
2901 this.updates_since_last_creation++;
2903 var started = new Date().getTime();
2905 // if the result is JSON, find the latest update-every
2906 this.data_update_every = data.view_update_every * 1000;
2907 this.data_after = data.after * 1000;
2908 this.data_before = data.before * 1000;
2909 this.netdata_first = data.first_entry * 1000;
2910 this.netdata_last = data.last_entry * 1000;
2911 this.data_points = data.points;
2914 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2915 if(this.view_after < this.data_after) {
2916 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2917 this.view_after = this.data_after;
2920 if(this.view_before > this.data_before) {
2921 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2922 this.view_before = this.data_before;
2926 this.view_after = this.data_after;
2927 this.view_before = this.data_before;
2930 if(this.debug === true) {
2931 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2933 if(this.current.force_after_ms)
2934 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2936 this.log('STATUS: forced : unset');
2938 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2939 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2940 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2941 this.log('STATUS: points : ' + (this.data_points).toString());
2944 if(this.data_points === 0) {
2949 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2950 if(this.debug === true)
2951 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2953 this.chart_created = false;
2956 // check and update the legend
2957 this.legendUpdateDOM();
2959 if(this.chart_created === true
2960 && typeof this.library.update === 'function') {
2962 if(this.debug === true)
2963 this.log('updating chart...');
2965 if(callChartLibraryUpdateSafely(data) === false)
2969 if(this.debug === true)
2970 this.log('creating chart...');
2972 if(callChartLibraryCreateSafely(data) === false)
2976 this.legendShowLatestValues();
2977 if(this.selected === true)
2978 NETDATA.globalSelectionSync.stop();
2980 // update the performance counters
2981 var now = new Date().getTime();
2982 this.tm.last_updated = now;
2984 // don't update last_autorefreshed if this chart is
2985 // forced to be updated with global PanAndZoom
2986 if(NETDATA.globalPanAndZoom.isActive())
2987 this.tm.last_autorefreshed = 0;
2989 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
2990 this.tm.last_autorefreshed = now - (now % this.data_update_every);
2992 this.tm.last_autorefreshed = now;
2995 this.refresh_dt_ms = now - started;
2996 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
2998 if(this.refresh_dt_element !== null)
2999 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
3002 this.updateChart = function(callback) {
3003 if(this.debug === true)
3004 this.log('updateChart() called.');
3006 if(this._updating === true) {
3007 if(this.debug === true)
3008 this.log('I am already updating...');
3010 if(typeof callback === 'function') callback();
3014 // due to late initialization of charts and libraries
3015 // we need to check this too
3016 if(this.enabled === false) {
3017 if(this.debug === true)
3018 this.log('I am not enabled');
3020 if(typeof callback === 'function') callback();
3024 if(canBeRendered() === false) {
3025 if(typeof callback === 'function') callback();
3029 if(this.chart === null) {
3030 this.getChart(function() { that.updateChart(callback); });
3034 if(this.library.initialized === false) {
3035 if(this.library.enabled === true) {
3036 this.library.initialize(function() { that.updateChart(callback); });
3040 error('chart library "' + this.library_name + '" is not available.');
3041 if(typeof callback === 'function') callback();
3046 this.clearSelection();
3049 if(this.debug === true)
3050 this.log('updating from ' + this.data_url);
3052 NETDATA.statistics.refreshes_total++;
3053 NETDATA.statistics.refreshes_active++;
3055 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
3056 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
3058 this._updating = true;
3060 this.xhr = $.ajax( {
3065 'Cache-Control': 'no-cache, no-store',
3066 'Pragma': 'no-cache'
3068 xhrFields: { withCredentials: true } // required for the cookie
3070 .done(function(data) {
3071 that.xhr = undefined;
3073 if(that.debug === true)
3074 that.log('data received. updating chart.');
3076 that.updateChartWithData(data);
3078 .fail(function(msg) {
3079 that.xhr = undefined;
3081 if(msg.statusText !== 'abort')
3082 error('data download failed for url: ' + that.data_url);
3084 .always(function() {
3085 that.xhr = undefined;
3087 NETDATA.statistics.refreshes_active--;
3088 that._updating = false;
3089 if(typeof callback === 'function') callback();
3095 this.isVisible = function(nocache) {
3096 if(typeof nocache === 'undefined')
3099 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
3101 // caching - we do not evaluate the charts visibility
3102 // if the page has not been scrolled since the last check
3103 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
3104 return this.___isVisible___;
3106 this.tm.last_visible_check = new Date().getTime();
3108 var wh = window.innerHeight;
3109 var x = this.element.getBoundingClientRect();
3113 if(x.width === 0 || x.height === 0) {
3115 this.___isVisible___ = false;
3116 return this.___isVisible___;
3119 if(x.top < 0 && -x.top > x.height) {
3120 // the chart is entirely above
3121 ret = -x.top - x.height;
3123 else if(x.top > wh) {
3124 // the chart is entirely below
3128 if(ret > tolerance) {
3129 // the chart is too far
3132 this.___isVisible___ = false;
3133 return this.___isVisible___;
3136 // the chart is inside or very close
3139 this.___isVisible___ = true;
3140 return this.___isVisible___;
3144 this.isAutoRefreshable = function() {
3145 return (this.current.autorefresh);
3148 this.canBeAutoRefreshed = function() {
3149 var now = new Date().getTime();
3151 if(this.running === true) {
3152 if(this.debug === true)
3153 this.log('I am already running');
3158 if(this.enabled === false) {
3159 if(this.debug === true)
3160 this.log('I am not enabled');
3165 if(this.library === null || this.library.enabled === false) {
3166 error('charting library "' + this.library_name + '" is not available');
3167 if(this.debug === true)
3168 this.log('My chart library ' + this.library_name + ' is not available');
3173 if(this.isVisible() === false) {
3174 if(NETDATA.options.debug.visibility === true || this.debug === true)
3175 this.log('I am not visible');
3180 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3181 if(this.debug === true)
3182 this.log('timed force update detected - allowing this update');
3184 this.current.force_update_at = 0;
3188 if(this.isAutoRefreshable() === true) {
3189 // allow the first update, even if the page is not visible
3190 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3191 if(NETDATA.options.debug.focus === true || this.debug === true)
3192 this.log('canBeAutoRefreshed(): page does not have focus');
3197 if(this.needsRecreation() === true) {
3198 if(this.debug === true)
3199 this.log('canBeAutoRefreshed(): needs re-creation.');
3204 // options valid only for autoRefresh()
3205 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3206 if(NETDATA.globalPanAndZoom.isActive()) {
3207 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3208 if(this.debug === true)
3209 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3214 if(this.debug === true)
3215 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3221 if(this.selected === true) {
3222 if(this.debug === true)
3223 this.log('canBeAutoRefreshed(): I have a selection in place.');
3228 if(this.paused === true) {
3229 if(this.debug === true)
3230 this.log('canBeAutoRefreshed(): I am paused.');
3235 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3236 if(this.debug === true)
3237 this.log('canBeAutoRefreshed(): It is time to update me.');
3247 this.autoRefresh = function(callback) {
3248 if(this.canBeAutoRefreshed() === true && this.running === false) {
3251 state.running = true;
3252 state.updateChart(function() {
3253 state.running = false;
3255 if(typeof callback !== 'undefined')
3260 if(typeof callback !== 'undefined')
3265 this._defaultsFromDownloadedChart = function(chart) {
3267 this.chart_url = chart.url;
3268 this.data_update_every = chart.update_every * 1000;
3269 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3270 this.tm.last_info_downloaded = new Date().getTime();
3272 if(this.title === null)
3273 this.title = chart.title;
3275 if(this.units === null)
3276 this.units = chart.units;
3279 // fetch the chart description from the netdata server
3280 this.getChart = function(callback) {
3281 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3283 this._defaultsFromDownloadedChart(this.chart);
3284 if(typeof callback === 'function') callback();
3287 this.chart_url = "/api/v1/chart?chart=" + this.id;
3289 if(this.debug === true)
3290 this.log('downloading ' + this.chart_url);
3293 url: this.host + this.chart_url,
3296 xhrFields: { withCredentials: true } // required for the cookie
3298 .done(function(chart) {
3299 chart.url = that.chart_url;
3300 that._defaultsFromDownloadedChart(chart);
3301 NETDATA.chartRegistry.add(that.host, that.id, chart);
3304 NETDATA.error(404, that.chart_url);
3305 error('chart not found on url "' + that.chart_url + '"');
3307 .always(function() {
3308 if(typeof callback === 'function') callback();
3313 // ============================================================================================================
3319 NETDATA.resetAllCharts = function(state) {
3320 // first clear the global selection sync
3321 // to make sure no chart is in selected state
3322 state.globalSelectionSyncStop();
3324 // there are 2 possibilities here
3325 // a. state is the global Pan and Zoom master
3326 // b. state is not the global Pan and Zoom master
3328 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3331 // clear the global Pan and Zoom
3332 // this will also refresh the master
3333 // and unblock any charts currently mirroring the master
3334 NETDATA.globalPanAndZoom.clearMaster();
3336 // if we were not the master, reset our status too
3337 // this is required because most probably the mouse
3338 // is over this chart, blocking it from auto-refreshing
3339 if(master === false && (state.paused === true || state.selected === true))
3343 // get or create a chart state, given a DOM element
3344 NETDATA.chartState = function(element) {
3345 var state = $(element).data('netdata-state-object') || null;
3346 if(state === null) {
3347 state = new chartState(element);
3348 $(element).data('netdata-state-object', state);
3353 // ----------------------------------------------------------------------------------------------------------------
3354 // Library functions
3356 // Load a script without jquery
3357 // This is used to load jquery - after it is loaded, we use jquery
3358 NETDATA._loadjQuery = function(callback) {
3359 if(typeof jQuery === 'undefined') {
3360 if(NETDATA.options.debug.main_loop === true)
3361 console.log('loading ' + NETDATA.jQuery);
3363 var script = document.createElement('script');
3364 script.type = 'text/javascript';
3365 script.async = true;
3366 script.src = NETDATA.jQuery;
3368 // script.onabort = onError;
3369 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3370 if(typeof callback === "function")
3371 script.onload = callback;
3373 var s = document.getElementsByTagName('script')[0];
3374 s.parentNode.insertBefore(script, s);
3376 else if(typeof callback === "function")
3380 NETDATA._loadCSS = function(filename) {
3381 // don't use jQuery here
3382 // styles are loaded before jQuery
3383 // to eliminate showing an unstyled page to the user
3385 var fileref = document.createElement("link");
3386 fileref.setAttribute("rel", "stylesheet");
3387 fileref.setAttribute("type", "text/css");
3388 fileref.setAttribute("href", filename);
3390 if (typeof fileref !== 'undefined')
3391 document.getElementsByTagName("head")[0].appendChild(fileref);
3394 NETDATA.colorHex2Rgb = function(hex) {
3395 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3396 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3397 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3398 return r + r + g + g + b + b;
3401 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3403 r: parseInt(result[1], 16),
3404 g: parseInt(result[2], 16),
3405 b: parseInt(result[3], 16)
3409 NETDATA.colorLuminance = function(hex, lum) {
3410 // validate hex string
3411 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3413 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3417 // convert to decimal and change luminosity
3418 var rgb = "#", c, i;
3419 for (i = 0; i < 3; i++) {
3420 c = parseInt(hex.substr(i*2,2), 16);
3421 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3422 rgb += ("00"+c).substr(c.length);
3428 NETDATA.guid = function() {
3430 return Math.floor((1 + Math.random()) * 0x10000)
3435 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3438 NETDATA.zeropad = function(x) {
3439 if(x > -10 && x < 10) return '0' + x.toString();
3440 else return x.toString();
3443 // user function to signal us the DOM has been
3445 NETDATA.updatedDom = function() {
3446 NETDATA.options.updated_dom = true;
3449 NETDATA.ready = function(callback) {
3450 NETDATA.options.pauseCallback = callback;
3453 NETDATA.pause = function(callback) {
3454 if(NETDATA.options.pause === true)
3457 NETDATA.options.pauseCallback = callback;
3460 NETDATA.unpause = function() {
3461 NETDATA.options.pauseCallback = null;
3462 NETDATA.options.updated_dom = true;
3463 NETDATA.options.pause = false;
3466 // ----------------------------------------------------------------------------------------------------------------
3468 // this is purely sequencial charts refresher
3469 // it is meant to be autonomous
3470 NETDATA.chartRefresherNoParallel = function(index) {
3471 if(NETDATA.options.debug.mail_loop === true)
3472 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3474 if(NETDATA.options.updated_dom === true) {
3475 // the dom has been updated
3476 // get the dom parts again
3477 NETDATA.parseDom(NETDATA.chartRefresher);
3480 if(index >= NETDATA.options.targets.length) {
3481 if(NETDATA.options.debug.main_loop === true)
3482 console.log('waiting to restart main loop...');
3484 NETDATA.options.auto_refresher_fast_weight = 0;
3486 setTimeout(function() {
3487 NETDATA.chartRefresher();
3488 }, NETDATA.options.current.idle_between_loops);
3491 var state = NETDATA.options.targets[index];
3493 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3494 if(NETDATA.options.debug.main_loop === true)
3495 console.log('fast rendering...');
3497 state.autoRefresh(function() {
3498 NETDATA.chartRefresherNoParallel(++index);
3502 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3503 NETDATA.options.auto_refresher_fast_weight = 0;
3505 setTimeout(function() {
3506 state.autoRefresh(function() {
3507 NETDATA.chartRefresherNoParallel(++index);
3509 }, NETDATA.options.current.idle_between_charts);
3514 // this is part of the parallel refresher
3515 // its cause is to refresh sequencially all the charts
3516 // that depend on chart library initialization
3517 // it will call the parallel refresher back
3518 // as soon as it sees a chart that its chart library
3520 NETDATA.chartRefresher_uninitialized = function() {
3521 if(NETDATA.options.updated_dom === true) {
3522 // the dom has been updated
3523 // get the dom parts again
3524 NETDATA.parseDom(NETDATA.chartRefresher);
3528 if(NETDATA.options.sequencial.length === 0)
3529 NETDATA.chartRefresher();
3531 var state = NETDATA.options.sequencial.pop();
3532 if(state.library.initialized === true)
3533 NETDATA.chartRefresher();
3535 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3539 NETDATA.chartRefresherWaitTime = function() {
3540 return NETDATA.options.current.idle_parallel_loops;
3543 // the default refresher
3544 // it will create 2 sets of charts:
3545 // - the ones that can be refreshed in parallel
3546 // - the ones that depend on something else
3547 // the first set will be executed in parallel
3548 // the second will be given to NETDATA.chartRefresher_uninitialized()
3549 NETDATA.chartRefresher = function() {
3550 // console.log('auto-refresher...');
3552 if(NETDATA.options.pause === true) {
3553 // console.log('auto-refresher is paused');
3554 setTimeout(NETDATA.chartRefresher,
3555 NETDATA.chartRefresherWaitTime());
3559 if(typeof NETDATA.options.pauseCallback === 'function') {
3560 // console.log('auto-refresher is calling pauseCallback');
3561 NETDATA.options.pause = true;
3562 NETDATA.options.pauseCallback();
3563 NETDATA.chartRefresher();
3567 if(NETDATA.options.current.parallel_refresher === false) {
3568 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3569 NETDATA.chartRefresherNoParallel(0);
3573 if(NETDATA.options.updated_dom === true) {
3574 // the dom has been updated
3575 // get the dom parts again
3576 // console.log('auto-refresher is calling parseDom()');
3577 NETDATA.parseDom(NETDATA.chartRefresher);
3581 var parallel = new Array();
3582 var targets = NETDATA.options.targets;
3583 var len = targets.length;
3586 state = targets[len];
3587 if(state.isVisible() === false || state.running === true)
3590 if(state.library.initialized === false) {
3591 if(state.library.enabled === true) {
3592 state.library.initialize(NETDATA.chartRefresher);
3596 state.error('chart library "' + state.library_name + '" is not enabled.');
3600 parallel.unshift(state);
3603 if(parallel.length > 0) {
3604 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3605 // this will execute the jobs in parallel
3606 $(parallel).each(function() {
3611 // console.log('auto-refresher nothing to do');
3614 // run the next refresh iteration
3615 setTimeout(NETDATA.chartRefresher,
3616 NETDATA.chartRefresherWaitTime());
3619 NETDATA.parseDom = function(callback) {
3620 NETDATA.options.last_page_scroll = new Date().getTime();
3621 NETDATA.options.updated_dom = false;
3623 var targets = $('div[data-netdata]'); //.filter(':visible');
3625 if(NETDATA.options.debug.main_loop === true)
3626 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3628 NETDATA.options.targets = new Array();
3629 var len = targets.length;
3631 // the initialization will take care of sizing
3632 // and the "loading..." message
3633 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3636 if(typeof callback === 'function') callback();
3639 // this is the main function - where everything starts
3640 NETDATA.start = function() {
3641 // this should be called only once
3643 NETDATA.options.page_is_visible = true;
3645 $(window).blur(function() {
3646 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3647 NETDATA.options.page_is_visible = false;
3648 if(NETDATA.options.debug.focus === true)
3649 console.log('Lost Focus!');
3653 $(window).focus(function() {
3654 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3655 NETDATA.options.page_is_visible = true;
3656 if(NETDATA.options.debug.focus === true)
3657 console.log('Focus restored!');
3661 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3662 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3663 NETDATA.options.page_is_visible = false;
3664 if(NETDATA.options.debug.focus === true)
3665 console.log('Document has no focus!');
3669 // bootstrap tab switching
3670 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3672 // bootstrap modal switching
3673 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3674 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3676 // bootstrap collapse switching
3677 $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3678 $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3680 NETDATA.parseDom(NETDATA.chartRefresher);
3682 // Alarms initialization
3683 setTimeout(NETDATA.alarms.init, 1000);
3685 // Registry initialization
3686 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3688 if(typeof netdataCallback === 'function')
3692 // ----------------------------------------------------------------------------------------------------------------
3695 NETDATA.peityInitialize = function(callback) {
3696 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3698 url: NETDATA.peity_js,
3701 xhrFields: { withCredentials: true } // required for the cookie
3704 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3707 NETDATA.chartLibraries.peity.enabled = false;
3708 NETDATA.error(100, NETDATA.peity_js);
3710 .always(function() {
3711 if(typeof callback === "function")
3716 NETDATA.chartLibraries.peity.enabled = false;
3717 if(typeof callback === "function")
3722 NETDATA.peityChartUpdate = function(state, data) {
3723 state.peity_instance.innerHTML = data.result;
3725 if(state.peity_options.stroke !== state.chartColors()[0]) {
3726 state.peity_options.stroke = state.chartColors()[0];
3727 if(state.chart.chart_type === 'line')
3728 state.peity_options.fill = NETDATA.themes.current.background;
3730 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3733 $(state.peity_instance).peity('line', state.peity_options);
3737 NETDATA.peityChartCreate = function(state, data) {
3738 state.peity_instance = document.createElement('div');
3739 state.element_chart.appendChild(state.peity_instance);
3741 var self = $(state.element);
3742 state.peity_options = {
3743 stroke: NETDATA.themes.current.foreground,
3744 strokeWidth: self.data('peity-strokewidth') || 1,
3745 width: state.chartWidth(),
3746 height: state.chartHeight(),
3747 fill: NETDATA.themes.current.foreground
3750 NETDATA.peityChartUpdate(state, data);
3754 // ----------------------------------------------------------------------------------------------------------------
3757 NETDATA.sparklineInitialize = function(callback) {
3758 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3760 url: NETDATA.sparkline_js,
3763 xhrFields: { withCredentials: true } // required for the cookie
3766 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3769 NETDATA.chartLibraries.sparkline.enabled = false;
3770 NETDATA.error(100, NETDATA.sparkline_js);
3772 .always(function() {
3773 if(typeof callback === "function")
3778 NETDATA.chartLibraries.sparkline.enabled = false;
3779 if(typeof callback === "function")
3784 NETDATA.sparklineChartUpdate = function(state, data) {
3785 state.sparkline_options.width = state.chartWidth();
3786 state.sparkline_options.height = state.chartHeight();
3788 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3792 NETDATA.sparklineChartCreate = function(state, data) {
3793 var self = $(state.element);
3794 var type = self.data('sparkline-type') || 'line';
3795 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3796 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3797 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3798 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3799 var composite = self.data('sparkline-composite') || undefined;
3800 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3801 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3802 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3803 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3804 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3805 var spotColor = self.data('sparkline-spotcolor') || undefined;
3806 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3807 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3808 var spotRadius = self.data('sparkline-spotradius') || undefined;
3809 var valueSpots = self.data('sparkline-valuespots') || undefined;
3810 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3811 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3812 var lineWidth = self.data('sparkline-linewidth') || undefined;
3813 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3814 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3815 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3816 var xvalues = self.data('sparkline-xvalues') || undefined;
3817 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3818 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3819 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3820 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3821 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3822 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3823 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3824 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3825 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3826 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3827 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3828 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3829 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3830 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3831 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3832 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3833 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3834 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3835 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3836 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3837 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3838 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3840 if(spotColor === 'disable') spotColor='';
3841 if(minSpotColor === 'disable') minSpotColor='';
3842 if(maxSpotColor === 'disable') maxSpotColor='';
3844 state.sparkline_options = {
3846 lineColor: lineColor,
3847 fillColor: fillColor,
3848 chartRangeMin: chartRangeMin,
3849 chartRangeMax: chartRangeMax,
3850 composite: composite,
3851 enableTagOptions: enableTagOptions,
3852 tagOptionPrefix: tagOptionPrefix,
3853 tagValuesAttribute: tagValuesAttribute,
3854 disableHiddenCheck: disableHiddenCheck,
3855 defaultPixelsPerValue: defaultPixelsPerValue,
3856 spotColor: spotColor,
3857 minSpotColor: minSpotColor,
3858 maxSpotColor: maxSpotColor,
3859 spotRadius: spotRadius,
3860 valueSpots: valueSpots,
3861 highlightSpotColor: highlightSpotColor,
3862 highlightLineColor: highlightLineColor,
3863 lineWidth: lineWidth,
3864 normalRangeMin: normalRangeMin,
3865 normalRangeMax: normalRangeMax,
3866 drawNormalOnTop: drawNormalOnTop,
3868 chartRangeClip: chartRangeClip,
3869 chartRangeMinX: chartRangeMinX,
3870 chartRangeMaxX: chartRangeMaxX,
3871 disableInteraction: disableInteraction,
3872 disableTooltips: disableTooltips,
3873 disableHighlight: disableHighlight,
3874 highlightLighten: highlightLighten,
3875 highlightColor: highlightColor,
3876 tooltipContainer: tooltipContainer,
3877 tooltipClassname: tooltipClassname,
3878 tooltipChartTitle: state.title,
3879 tooltipFormat: tooltipFormat,
3880 tooltipPrefix: tooltipPrefix,
3881 tooltipSuffix: tooltipSuffix,
3882 tooltipSkipNull: tooltipSkipNull,
3883 tooltipValueLookups: tooltipValueLookups,
3884 tooltipFormatFieldlist: tooltipFormatFieldlist,
3885 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3886 numberFormatter: numberFormatter,
3887 numberDigitGroupSep: numberDigitGroupSep,
3888 numberDecimalMark: numberDecimalMark,
3889 numberDigitGroupCount: numberDigitGroupCount,
3890 animatedZooms: animatedZooms,
3891 width: state.chartWidth(),
3892 height: state.chartHeight()
3895 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3899 // ----------------------------------------------------------------------------------------------------------------
3906 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3907 if(after < state.netdata_first)
3908 after = state.netdata_first;
3910 if(before > state.netdata_last)
3911 before = state.netdata_last;
3913 state.setMode('zoom');
3914 state.globalSelectionSyncStop();
3915 state.globalSelectionSyncDelay();
3916 state.dygraph_user_action = true;
3917 state.dygraph_force_zoom = true;
3918 state.updateChartPanOrZoom(after, before);
3919 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3922 NETDATA.dygraphSetSelection = function(state, t) {
3923 if(typeof state.dygraph_instance !== 'undefined') {
3924 var r = state.calculateRowForTime(t);
3926 state.dygraph_instance.setSelection(r);
3928 state.dygraph_instance.clearSelection();
3929 state.legendShowUndefined();
3936 NETDATA.dygraphClearSelection = function(state, t) {
3937 if(typeof state.dygraph_instance !== 'undefined') {
3938 state.dygraph_instance.clearSelection();
3943 NETDATA.dygraphSmoothInitialize = function(callback) {
3945 url: NETDATA.dygraph_smooth_js,
3948 xhrFields: { withCredentials: true } // required for the cookie
3951 NETDATA.dygraph.smooth = true;
3952 smoothPlotter.smoothing = 0.3;
3955 NETDATA.dygraph.smooth = false;
3957 .always(function() {
3958 if(typeof callback === "function")
3963 NETDATA.dygraphInitialize = function(callback) {
3964 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3966 url: NETDATA.dygraph_js,
3969 xhrFields: { withCredentials: true } // required for the cookie
3972 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3975 NETDATA.chartLibraries.dygraph.enabled = false;
3976 NETDATA.error(100, NETDATA.dygraph_js);
3978 .always(function() {
3979 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3980 NETDATA.dygraphSmoothInitialize(callback);
3981 else if(typeof callback === "function")
3986 NETDATA.chartLibraries.dygraph.enabled = false;
3987 if(typeof callback === "function")
3992 NETDATA.dygraphChartUpdate = function(state, data) {
3993 var dygraph = state.dygraph_instance;
3995 if(typeof dygraph === 'undefined')
3996 return NETDATA.dygraphChartCreate(state, data);
3998 // when the chart is not visible, and hidden
3999 // if there is a window resize, dygraph detects
4000 // its element size as 0x0.
4001 // this will make it re-appear properly
4003 if(state.tm.last_unhidden > state.dygraph_last_rendered)
4007 file: data.result.data,
4008 colors: state.chartColors(),
4009 labels: data.result.labels,
4010 labelsDivWidth: state.chartWidth() - 70,
4011 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4014 if(state.dygraph_force_zoom === true) {
4015 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4016 state.log('dygraphChartUpdate() forced zoom update');
4018 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4019 options.isZoomedIgnoreProgrammaticZoom = true;
4020 state.dygraph_force_zoom = false;
4022 else if(state.current.name !== 'auto') {
4023 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4024 state.log('dygraphChartUpdate() loose update');
4027 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4028 state.log('dygraphChartUpdate() strict update');
4030 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4031 options.isZoomedIgnoreProgrammaticZoom = true;
4034 options.valueRange = state.dygraph_options.valueRange;
4036 var oldMax = null, oldMin = null;
4037 if(state.__commonMin !== null) {
4038 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4039 oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4041 if(state.__commonMax !== null) {
4042 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4043 oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4046 if(state.dygraph_smooth_eligible === true) {
4047 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4048 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4049 NETDATA.dygraphChartCreate(state, data);
4054 dygraph.updateOptions(options);
4057 if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4058 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4059 options.valueRange[0] = NETDATA.commonMin.get(state);
4062 if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4063 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4064 options.valueRange[1] = NETDATA.commonMax.get(state);
4068 if(redraw === true) {
4069 // state.log('forcing redraw to adapt to common- min/max');
4070 dygraph.updateOptions(options);
4073 state.dygraph_last_rendered = new Date().getTime();
4077 NETDATA.dygraphChartCreate = function(state, data) {
4078 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4079 state.log('dygraphChartCreate()');
4081 var self = $(state.element);
4083 var chart_type = state.chart.chart_type;
4084 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4085 chart_type = self.data('dygraph-type') || chart_type;
4087 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
4088 smooth = self.data('dygraph-smooth') || smooth;
4090 if(NETDATA.dygraph.smooth === false)
4093 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
4094 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
4096 state.dygraph_options = {
4097 colors: self.data('dygraph-colors') || state.chartColors(),
4099 // leave a few pixels empty on the right of the chart
4100 rightGap: self.data('dygraph-rightgap') || 5,
4101 showRangeSelector: self.data('dygraph-showrangeselector') || false,
4102 showRoller: self.data('dygraph-showroller') || false,
4104 title: self.data('dygraph-title') || state.title,
4105 titleHeight: self.data('dygraph-titleheight') || 19,
4107 legend: self.data('dygraph-legend') || 'always', // we need this to get selection events
4108 labels: data.result.labels,
4109 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
4110 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
4111 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
4112 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
4113 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
4116 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
4117 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
4119 includeZero: self.data('dygraph-includezero') || ((chart_type === 'stacked')? true : false),
4120 xRangePad: self.data('dygraph-xrangepad') || 0,
4121 yRangePad: self.data('dygraph-yrangepad') || 1,
4123 valueRange: self.data('dygraph-valuerange') || [ null, null ],
4125 ylabel: state.units,
4126 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
4128 // the function to plot the chart
4131 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
4132 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
4133 strokePattern: self.data('dygraph-strokepattern') || undefined,
4135 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
4136 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
4137 drawPoints: self.data('dygraph-drawpoints') || false,
4139 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
4140 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
4142 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
4143 pointSize: self.data('dygraph-pointsize') || 1,
4145 // enabling this makes the chart with little square lines
4146 stepPlot: self.data('dygraph-stepplot') || false,
4148 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
4149 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
4150 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
4152 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
4153 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
4154 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
4155 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
4157 drawAxis: self.data('dygraph-drawaxis') || true,
4158 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
4159 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
4160 axisLineWidth: self.data('dygraph-axislinewidth') || 1.0,
4162 drawGrid: self.data('dygraph-drawgrid') || true,
4163 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
4164 gridLineWidth: self.data('dygraph-gridlinewidth') || 1.0,
4165 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
4167 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
4168 sigFigs: self.data('dygraph-sigfigs') || null,
4169 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
4170 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
4172 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
4173 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
4174 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4176 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
4177 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4181 ticker: Dygraph.dateTicker,
4182 axisLabelFormatter: function (d, gran) {
4183 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4185 valueFormatter: function (ms) {
4186 var d = new Date(ms);
4187 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4188 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4193 valueFormatter: function (x) {
4194 // we format legends with the state object
4195 // no need to do anything here
4196 // return (Math.round(x*100) / 100).toLocaleString();
4197 // return state.legendFormatValue(x);
4202 legendFormatter: function(data) {
4203 var elements = state.element_legend_childs;
4205 // if the hidden div is not there
4206 // we are not managing the legend
4207 if(elements.hidden === null) return;
4209 if (typeof data.x !== 'undefined') {
4210 state.legendSetDate(data.x);
4211 var i = data.series.length;
4213 var series = data.series[i];
4214 if(!series.isVisible) continue;
4215 state.legendSetLabelValue(series.label, series.y);
4221 drawCallback: function(dygraph, is_initial) {
4222 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4223 state.dygraph_user_action = false;
4225 var x_range = dygraph.xAxisRange();
4226 var after = Math.round(x_range[0]);
4227 var before = Math.round(x_range[1]);
4229 if(NETDATA.options.debug.dygraph === true)
4230 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4232 if(before <= state.netdata_last && after >= state.netdata_first)
4233 state.updateChartPanOrZoom(after, before);
4236 zoomCallback: function(minDate, maxDate, yRanges) {
4237 if(NETDATA.options.debug.dygraph === true)
4238 state.log('dygraphZoomCallback()');
4240 state.globalSelectionSyncStop();
4241 state.globalSelectionSyncDelay();
4242 state.setMode('zoom');
4244 // refresh it to the greatest possible zoom level
4245 state.dygraph_user_action = true;
4246 state.dygraph_force_zoom = true;
4247 state.updateChartPanOrZoom(minDate, maxDate);
4249 highlightCallback: function(event, x, points, row, seriesName) {
4250 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4251 state.log('dygraphHighlightCallback()');
4255 // there is a bug in dygraph when the chart is zoomed enough
4256 // the time it thinks is selected is wrong
4257 // here we calculate the time t based on the row number selected
4259 var t = state.data_after + row * state.data_update_every;
4260 // 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);
4262 state.globalSelectionSync(x);
4264 // fix legend zIndex using the internal structures of dygraph legend module
4265 // this works, but it is a hack!
4266 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4268 unhighlightCallback: function(event) {
4269 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4270 state.log('dygraphUnhighlightCallback()');
4272 state.unpauseChart();
4273 state.globalSelectionSyncStop();
4275 interactionModel : {
4276 mousedown: function(event, dygraph, context) {
4277 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4278 state.log('interactionModel.mousedown()');
4280 state.dygraph_user_action = true;
4281 state.globalSelectionSyncStop();
4283 if(NETDATA.options.debug.dygraph === true)
4284 state.log('dygraphMouseDown()');
4286 // Right-click should not initiate a zoom.
4287 if(event.button && event.button === 2) return;
4289 context.initializeMouseDown(event, dygraph, context);
4291 if(event.button && event.button === 1) {
4292 if (event.altKey || event.shiftKey) {
4293 state.setMode('pan');
4294 state.globalSelectionSyncDelay();
4295 Dygraph.startPan(event, dygraph, context);
4298 state.setMode('zoom');
4299 state.globalSelectionSyncDelay();
4300 Dygraph.startZoom(event, dygraph, context);
4304 if (event.altKey || event.shiftKey) {
4305 state.setMode('zoom');
4306 state.globalSelectionSyncDelay();
4307 Dygraph.startZoom(event, dygraph, context);
4310 state.setMode('pan');
4311 state.globalSelectionSyncDelay();
4312 Dygraph.startPan(event, dygraph, context);
4316 mousemove: function(event, dygraph, context) {
4317 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4318 state.log('interactionModel.mousemove()');
4320 if(context.isPanning) {
4321 state.dygraph_user_action = true;
4322 state.globalSelectionSyncStop();
4323 state.globalSelectionSyncDelay();
4324 state.setMode('pan');
4325 context.is2DPan = false;
4326 Dygraph.movePan(event, dygraph, context);
4328 else if(context.isZooming) {
4329 state.dygraph_user_action = true;
4330 state.globalSelectionSyncStop();
4331 state.globalSelectionSyncDelay();
4332 state.setMode('zoom');
4333 Dygraph.moveZoom(event, dygraph, context);
4336 mouseup: function(event, dygraph, context) {
4337 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4338 state.log('interactionModel.mouseup()');
4340 if (context.isPanning) {
4341 state.dygraph_user_action = true;
4342 state.globalSelectionSyncDelay();
4343 Dygraph.endPan(event, dygraph, context);
4345 else if (context.isZooming) {
4346 state.dygraph_user_action = true;
4347 state.globalSelectionSyncDelay();
4348 Dygraph.endZoom(event, dygraph, context);
4351 click: function(event, dygraph, context) {
4352 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4353 state.log('interactionModel.click()');
4355 event.preventDefault();
4357 dblclick: function(event, dygraph, context) {
4358 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4359 state.log('interactionModel.dblclick()');
4360 NETDATA.resetAllCharts(state);
4362 wheel: function(event, dygraph, context) {
4363 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4364 state.log('interactionModel.wheel()');
4366 // Take the offset of a mouse event on the dygraph canvas and
4367 // convert it to a pair of percentages from the bottom left.
4368 // (Not top left, bottom is where the lower value is.)
4369 function offsetToPercentage(g, offsetX, offsetY) {
4370 // This is calculating the pixel offset of the leftmost date.
4371 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4372 var yar0 = g.yAxisRange(0);
4374 // This is calculating the pixel of the higest value. (Top pixel)
4375 var yOffset = g.toDomCoords(null, yar0[1])[1];
4377 // x y w and h are relative to the corner of the drawing area,
4378 // so that the upper corner of the drawing area is (0, 0).
4379 var x = offsetX - xOffset;
4380 var y = offsetY - yOffset;
4382 // This is computing the rightmost pixel, effectively defining the
4384 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4386 // This is computing the lowest pixel, effectively defining the height.
4387 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4389 // Percentage from the left.
4390 var xPct = w === 0 ? 0 : (x / w);
4391 // Percentage from the top.
4392 var yPct = h === 0 ? 0 : (y / h);
4394 // The (1-) part below changes it from "% distance down from the top"
4395 // to "% distance up from the bottom".
4396 return [xPct, (1-yPct)];
4399 // Adjusts [x, y] toward each other by zoomInPercentage%
4400 // Split it so the left/bottom axis gets xBias/yBias of that change and
4401 // tight/top gets (1-xBias)/(1-yBias) of that change.
4403 // If a bias is missing it splits it down the middle.
4404 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4405 xBias = xBias || 0.5;
4406 yBias = yBias || 0.5;
4408 function adjustAxis(axis, zoomInPercentage, bias) {
4409 var delta = axis[1] - axis[0];
4410 var increment = delta * zoomInPercentage;
4411 var foo = [increment * bias, increment * (1-bias)];
4413 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4416 var yAxes = g.yAxisRanges();
4418 for (var i = 0; i < yAxes.length; i++) {
4419 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4422 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4425 if(event.altKey || event.shiftKey) {
4426 state.dygraph_user_action = true;
4428 state.globalSelectionSyncStop();
4429 state.globalSelectionSyncDelay();
4431 // http://dygraphs.com/gallery/interaction-api.js
4433 if(typeof event.wheelDelta === 'number' && event.wheelDelta != NaN)
4435 normal_def = event.wheelDelta / 40;
4438 normal_def = event.deltaY * -1.2;
4440 var normal = (event.detail) ? event.detail * -1 : normal_def;
4441 var percentage = normal / 50;
4443 if (!(event.offsetX && event.offsetY)){
4444 event.offsetX = event.layerX - event.target.offsetLeft;
4445 event.offsetY = event.layerY - event.target.offsetTop;
4448 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4449 var xPct = percentages[0];
4450 var yPct = percentages[1];
4452 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4453 var after = new_x_range[0];
4454 var before = new_x_range[1];
4456 var first = state.netdata_first + state.data_update_every;
4457 var last = state.netdata_last + state.data_update_every;
4460 after -= (before - last);
4467 state.setMode('zoom');
4468 if(state.updateChartPanOrZoom(after, before) === true)
4469 dygraph.updateOptions({ dateWindow: [ after, before ] });
4471 event.preventDefault();
4474 touchstart: function(event, dygraph, context) {
4475 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4476 state.log('interactionModel.touchstart()');
4478 state.dygraph_user_action = true;
4479 state.setMode('zoom');
4482 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4484 // we overwrite the touch directions at the end, to overwrite
4485 // the internal default of dygraphs
4486 context.touchDirections = { x: true, y: false };
4488 state.dygraph_last_touch_start = new Date().getTime();
4489 state.dygraph_last_touch_move = 0;
4491 if(typeof event.touches[0].pageX === 'number')
4492 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4494 state.dygraph_last_touch_page_x = 0;
4496 touchmove: function(event, dygraph, context) {
4497 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4498 state.log('interactionModel.touchmove()');
4500 state.dygraph_user_action = true;
4501 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4503 state.dygraph_last_touch_move = new Date().getTime();
4505 touchend: function(event, dygraph, context) {
4506 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4507 state.log('interactionModel.touchend()');
4509 state.dygraph_user_action = true;
4510 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4512 // if it didn't move, it is a selection
4513 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4514 // internal api of dygraphs
4515 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4516 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4517 if(NETDATA.dygraphSetSelection(state, t) === true)
4518 state.globalSelectionSync(t);
4521 // if it was double tap within double click time, reset the charts
4522 var now = new Date().getTime();
4523 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4524 if(state.dygraph_last_touch_move === 0) {
4525 var dt = now - state.dygraph_last_touch_end;
4526 if(dt <= NETDATA.options.current.double_click_speed)
4527 NETDATA.resetAllCharts(state);
4531 // remember the timestamp of the last touch end
4532 state.dygraph_last_touch_end = now;
4537 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4538 state.dygraph_options.drawGrid = false;
4539 state.dygraph_options.drawAxis = false;
4540 state.dygraph_options.title = undefined;
4541 state.dygraph_options.ylabel = undefined;
4542 state.dygraph_options.yLabelWidth = 0;
4543 state.dygraph_options.labelsDivWidth = 120;
4544 state.dygraph_options.labelsDivStyles.width = '120px';
4545 state.dygraph_options.labelsSeparateLines = true;
4546 state.dygraph_options.rightGap = 0;
4547 state.dygraph_options.yRangePad = 1;
4550 if(smooth === true) {
4551 state.dygraph_smooth_eligible = true;
4553 if(NETDATA.options.current.smooth_plot === true)
4554 state.dygraph_options.plotter = smoothPlotter;
4556 else state.dygraph_smooth_eligible = false;
4558 state.dygraph_instance = new Dygraph(state.element_chart,
4559 data.result.data, state.dygraph_options);
4561 state.dygraph_force_zoom = false;
4562 state.dygraph_user_action = false;
4563 state.dygraph_last_rendered = new Date().getTime();
4565 if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4566 state.__commonMin = self.data('common-min') || null;
4567 state.__commonMax = self.data('common-max') || null;
4570 state.log('incompatible version of dygraphs detected');
4571 state.__commonMin = null;
4572 state.__commonMax = null;
4578 // ----------------------------------------------------------------------------------------------------------------
4581 NETDATA.morrisInitialize = function(callback) {
4582 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4584 // morris requires raphael
4585 if(!NETDATA.chartLibraries.raphael.initialized) {
4586 if(NETDATA.chartLibraries.raphael.enabled) {
4587 NETDATA.raphaelInitialize(function() {
4588 NETDATA.morrisInitialize(callback);
4592 NETDATA.chartLibraries.morris.enabled = false;
4593 if(typeof callback === "function")
4598 NETDATA._loadCSS(NETDATA.morris_css);
4601 url: NETDATA.morris_js,
4604 xhrFields: { withCredentials: true } // required for the cookie
4607 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4610 NETDATA.chartLibraries.morris.enabled = false;
4611 NETDATA.error(100, NETDATA.morris_js);
4613 .always(function() {
4614 if(typeof callback === "function")
4620 NETDATA.chartLibraries.morris.enabled = false;
4621 if(typeof callback === "function")
4626 NETDATA.morrisChartUpdate = function(state, data) {
4627 state.morris_instance.setData(data.result.data);
4631 NETDATA.morrisChartCreate = function(state, data) {
4633 state.morris_options = {
4634 element: state.element_chart.id,
4635 data: data.result.data,
4637 ykeys: data.dimension_names,
4638 labels: data.dimension_names,
4644 continuousLine: false,
4645 behaveLikeLine: false
4648 if(state.chart.chart_type === 'line')
4649 state.morris_instance = new Morris.Line(state.morris_options);
4651 else if(state.chart.chart_type === 'area') {
4652 state.morris_options.behaveLikeLine = true;
4653 state.morris_instance = new Morris.Area(state.morris_options);
4656 state.morris_instance = new Morris.Area(state.morris_options);
4661 // ----------------------------------------------------------------------------------------------------------------
4664 NETDATA.raphaelInitialize = function(callback) {
4665 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4667 url: NETDATA.raphael_js,
4670 xhrFields: { withCredentials: true } // required for the cookie
4673 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4676 NETDATA.chartLibraries.raphael.enabled = false;
4677 NETDATA.error(100, NETDATA.raphael_js);
4679 .always(function() {
4680 if(typeof callback === "function")
4685 NETDATA.chartLibraries.raphael.enabled = false;
4686 if(typeof callback === "function")
4691 NETDATA.raphaelChartUpdate = function(state, data) {
4692 $(state.element_chart).raphael(data.result, {
4693 width: state.chartWidth(),
4694 height: state.chartHeight()
4700 NETDATA.raphaelChartCreate = function(state, data) {
4701 $(state.element_chart).raphael(data.result, {
4702 width: state.chartWidth(),
4703 height: state.chartHeight()
4709 // ----------------------------------------------------------------------------------------------------------------
4712 NETDATA.c3Initialize = function(callback) {
4713 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4716 if(!NETDATA.chartLibraries.d3.initialized) {
4717 if(NETDATA.chartLibraries.d3.enabled) {
4718 NETDATA.d3Initialize(function() {
4719 NETDATA.c3Initialize(callback);
4723 NETDATA.chartLibraries.c3.enabled = false;
4724 if(typeof callback === "function")
4729 NETDATA._loadCSS(NETDATA.c3_css);
4735 xhrFields: { withCredentials: true } // required for the cookie
4738 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4741 NETDATA.chartLibraries.c3.enabled = false;
4742 NETDATA.error(100, NETDATA.c3_js);
4744 .always(function() {
4745 if(typeof callback === "function")
4751 NETDATA.chartLibraries.c3.enabled = false;
4752 if(typeof callback === "function")
4757 NETDATA.c3ChartUpdate = function(state, data) {
4758 state.c3_instance.destroy();
4759 return NETDATA.c3ChartCreate(state, data);
4761 //state.c3_instance.load({
4762 // rows: data.result,
4769 NETDATA.c3ChartCreate = function(state, data) {
4771 state.element_chart.id = 'c3-' + state.uuid;
4772 // console.log('id = ' + state.element_chart.id);
4774 state.c3_instance = c3.generate({
4775 bindto: '#' + state.element_chart.id,
4777 width: state.chartWidth(),
4778 height: state.chartHeight()
4781 pattern: state.chartColors()
4786 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4792 format: function(x) {
4793 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4820 // console.log(state.c3_instance);
4825 // ----------------------------------------------------------------------------------------------------------------
4828 NETDATA.d3Initialize = function(callback) {
4829 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4834 xhrFields: { withCredentials: true } // required for the cookie
4837 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4840 NETDATA.chartLibraries.d3.enabled = false;
4841 NETDATA.error(100, NETDATA.d3_js);
4843 .always(function() {
4844 if(typeof callback === "function")
4849 NETDATA.chartLibraries.d3.enabled = false;
4850 if(typeof callback === "function")
4855 NETDATA.d3ChartUpdate = function(state, data) {
4859 NETDATA.d3ChartCreate = function(state, data) {
4863 // ----------------------------------------------------------------------------------------------------------------
4866 NETDATA.googleInitialize = function(callback) {
4867 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4869 url: NETDATA.google_js,
4872 xhrFields: { withCredentials: true } // required for the cookie
4875 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4876 google.load('visualization', '1.1', {
4877 'packages': ['corechart', 'controls'],
4878 'callback': callback
4882 NETDATA.chartLibraries.google.enabled = false;
4883 NETDATA.error(100, NETDATA.google_js);
4884 if(typeof callback === "function")
4889 NETDATA.chartLibraries.google.enabled = false;
4890 if(typeof callback === "function")
4895 NETDATA.googleChartUpdate = function(state, data) {
4896 var datatable = new google.visualization.DataTable(data.result);
4897 state.google_instance.draw(datatable, state.google_options);
4901 NETDATA.googleChartCreate = function(state, data) {
4902 var datatable = new google.visualization.DataTable(data.result);
4904 state.google_options = {
4905 colors: state.chartColors(),
4907 // do not set width, height - the chart resizes itself
4908 //width: state.chartWidth(),
4909 //height: state.chartHeight(),
4914 // title: "Time of Day",
4915 // format:'HH:mm:ss',
4916 viewWindowMode: 'maximized',
4928 viewWindowMode: 'pretty',
4943 focusTarget: 'category',
4950 titlePosition: 'out',
4961 curveType: 'function',
4966 switch(state.chart.chart_type) {
4968 state.google_options.vAxis.viewWindowMode = 'maximized';
4969 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4970 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4974 state.google_options.isStacked = true;
4975 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4976 state.google_options.vAxis.viewWindowMode = 'maximized';
4977 state.google_options.vAxis.minValue = null;
4978 state.google_options.vAxis.maxValue = null;
4979 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4984 state.google_options.lineWidth = 2;
4985 state.google_instance = new google.visualization.LineChart(state.element_chart);
4989 state.google_instance.draw(datatable, state.google_options);
4993 // ----------------------------------------------------------------------------------------------------------------
4995 NETDATA.percentFromValueMax = function(value, max) {
4996 if(value === null) value = 0;
4997 if(max < value) max = value;
5001 pcent = Math.round(value * 100 / max);
5002 if(pcent === 0 && value > 0) pcent = 1;
5008 // ----------------------------------------------------------------------------------------------------------------
5011 NETDATA.easypiechartInitialize = function(callback) {
5012 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5014 url: NETDATA.easypiechart_js,
5017 xhrFields: { withCredentials: true } // required for the cookie
5020 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5023 NETDATA.chartLibraries.easypiechart.enabled = false;
5024 NETDATA.error(100, NETDATA.easypiechart_js);
5026 .always(function() {
5027 if(typeof callback === "function")
5032 NETDATA.chartLibraries.easypiechart.enabled = false;
5033 if(typeof callback === "function")
5038 NETDATA.easypiechartClearSelection = function(state) {
5039 if(typeof state.easyPieChartEvent !== 'undefined') {
5040 if(state.easyPieChartEvent.timer !== null)
5041 clearTimeout(state.easyPieChartEvent.timer);
5043 state.easyPieChartEvent.timer = null;
5046 if(state.isAutoRefreshable() === true && state.data !== null) {
5047 NETDATA.easypiechartChartUpdate(state, state.data);
5050 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
5051 state.easyPieChart_instance.update(0);
5053 state.easyPieChart_instance.enableAnimation();
5058 NETDATA.easypiechartSetSelection = function(state, t) {
5059 if(state.timeIsVisible(t) !== true)
5060 return NETDATA.easypiechartClearSelection(state);
5062 var slot = state.calculateRowForTime(t);
5063 if(slot < 0 || slot >= state.data.result.length)
5064 return NETDATA.easypiechartClearSelection(state);
5066 if(typeof state.easyPieChartEvent === 'undefined') {
5067 state.easyPieChartEvent = {
5074 var value = state.data.result[state.data.result.length - 1 - slot];
5075 var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5076 var pcent = NETDATA.percentFromValueMax(value, max);
5078 state.easyPieChartEvent.value = value;
5079 state.easyPieChartEvent.pcent = pcent;
5080 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
5082 if(state.easyPieChartEvent.timer === null) {
5083 state.easyPieChart_instance.disableAnimation();
5085 state.easyPieChartEvent.timer = setTimeout(function() {
5086 state.easyPieChartEvent.timer = null;
5087 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5088 }, NETDATA.options.current.charts_selection_animation_delay);
5094 NETDATA.easypiechartChartUpdate = function(state, data) {
5095 var value, max, pcent;
5097 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5103 value = data.result[0];
5104 max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5105 pcent = NETDATA.percentFromValueMax(value, max);
5108 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
5109 state.easyPieChart_instance.update(pcent);
5113 NETDATA.easypiechartChartCreate = function(state, data) {
5114 var self = $(state.element);
5115 var chart = $(state.element_chart);
5117 var value = data.result[0];
5118 var max = self.data('easypiechart-max-value') || null;
5119 var adjust = self.data('easypiechart-adjust') || null;
5122 max = NETDATA.commonMax.get(state);
5123 state.easyPieChartMax = null;
5126 state.easyPieChartMax = max;
5128 var pcent = NETDATA.percentFromValueMax(value, max);
5130 chart.data('data-percent', pcent);
5134 case 'width': size = state.chartHeight(); break;
5135 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5136 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5138 default: size = state.chartWidth(); break;
5140 state.element.style.width = size + 'px';
5141 state.element.style.height = size + 'px';
5143 var stroke = Math.floor(size / 22);
5144 if(stroke < 3) stroke = 2;
5146 var valuefontsize = Math.floor((size * 2 / 3) / 5);
5147 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5148 state.easyPieChartLabel = document.createElement('span');
5149 state.easyPieChartLabel.className = 'easyPieChartLabel';
5150 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
5151 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5152 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5153 state.element_chart.appendChild(state.easyPieChartLabel);
5155 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5156 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5157 state.easyPieChartTitle = document.createElement('span');
5158 state.easyPieChartTitle.className = 'easyPieChartTitle';
5159 state.easyPieChartTitle.innerHTML = state.title;
5160 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5161 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5162 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5163 state.element_chart.appendChild(state.easyPieChartTitle);
5165 var unitfontsize = Math.round(titlefontsize * 0.9);
5166 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5167 state.easyPieChartUnits = document.createElement('span');
5168 state.easyPieChartUnits.className = 'easyPieChartUnits';
5169 state.easyPieChartUnits.innerHTML = state.units;
5170 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5171 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5172 state.element_chart.appendChild(state.easyPieChartUnits);
5174 var barColor = self.data('easypiechart-barcolor');
5175 if(typeof barColor === 'undefined' || barColor === null)
5176 barColor = state.chartColors()[0];
5178 // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5179 var tmp = eval(barColor);
5180 if(typeof tmp === 'function')
5184 chart.easyPieChart({
5186 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5187 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5188 scaleLength: self.data('easypiechart-scalelength') || 5,
5189 lineCap: self.data('easypiechart-linecap') || 'round',
5190 lineWidth: self.data('easypiechart-linewidth') || stroke,
5191 trackWidth: self.data('easypiechart-trackwidth') || undefined,
5192 size: self.data('easypiechart-size') || size,
5193 rotate: self.data('easypiechart-rotate') || 0,
5194 animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
5195 easing: self.data('easypiechart-easing') || undefined
5198 // when we just re-create the chart
5199 // do not animate the first update
5201 if(typeof state.easyPieChart_instance !== 'undefined')
5204 state.easyPieChart_instance = chart.data('easyPieChart');
5205 if(animate === false) state.easyPieChart_instance.disableAnimation();
5206 state.easyPieChart_instance.update(pcent);
5207 if(animate === false) state.easyPieChart_instance.enableAnimation();
5211 // ----------------------------------------------------------------------------------------------------------------
5214 NETDATA.gaugeInitialize = function(callback) {
5215 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5217 url: NETDATA.gauge_js,
5220 xhrFields: { withCredentials: true } // required for the cookie
5223 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5226 NETDATA.chartLibraries.gauge.enabled = false;
5227 NETDATA.error(100, NETDATA.gauge_js);
5229 .always(function() {
5230 if(typeof callback === "function")
5235 NETDATA.chartLibraries.gauge.enabled = false;
5236 if(typeof callback === "function")
5241 NETDATA.gaugeAnimation = function(state, status) {
5244 if(typeof status === 'boolean' && status === false)
5246 else if(typeof status === 'number')
5249 // console.log('gauge speed ' + speed);
5250 state.gauge_instance.animationSpeed = speed;
5251 state.___gaugeOld__.speed = speed;
5254 NETDATA.gaugeSet = function(state, value, min, max) {
5255 if(typeof value !== 'number') value = 0;
5256 if(typeof min !== 'number') min = 0;
5257 if(typeof max !== 'number') max = 0;
5258 if(value > max) max = value;
5259 if(value < min) min = value;
5268 // gauge.js has an issue if the needle
5269 // is smaller than min or larger than max
5270 // when we set the new values
5271 // the needle will go crazy
5273 // to prevent it, we always feed it
5274 // with a percentage, so that the needle
5275 // is always between min and max
5276 var pcent = (value - min) * 100 / (max - min);
5278 // these should never happen
5279 if(pcent < 0) pcent = 0;
5280 if(pcent > 100) pcent = 100;
5282 state.gauge_instance.set(pcent);
5283 // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5285 state.___gaugeOld__.value = value;
5286 state.___gaugeOld__.min = min;
5287 state.___gaugeOld__.max = max;
5290 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5291 if(state.___gaugeOld__.valueLabel !== value) {
5292 state.___gaugeOld__.valueLabel = value;
5293 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
5295 if(state.___gaugeOld__.minLabel !== min) {
5296 state.___gaugeOld__.minLabel = min;
5297 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
5299 if(state.___gaugeOld__.maxLabel !== max) {
5300 state.___gaugeOld__.maxLabel = max;
5301 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
5305 NETDATA.gaugeClearSelection = function(state) {
5306 if(typeof state.gaugeEvent !== 'undefined') {
5307 if(state.gaugeEvent.timer !== null)
5308 clearTimeout(state.gaugeEvent.timer);
5310 state.gaugeEvent.timer = null;
5313 if(state.isAutoRefreshable() === true && state.data !== null) {
5314 NETDATA.gaugeChartUpdate(state, state.data);
5317 NETDATA.gaugeAnimation(state, false);
5318 NETDATA.gaugeSet(state, null, null, null);
5319 NETDATA.gaugeSetLabels(state, null, null, null);
5322 NETDATA.gaugeAnimation(state, true);
5326 NETDATA.gaugeSetSelection = function(state, t) {
5327 if(state.timeIsVisible(t) !== true)
5328 return NETDATA.gaugeClearSelection(state);
5330 var slot = state.calculateRowForTime(t);
5331 if(slot < 0 || slot >= state.data.result.length)
5332 return NETDATA.gaugeClearSelection(state);
5334 if(typeof state.gaugeEvent === 'undefined') {
5335 state.gaugeEvent = {
5343 var value = state.data.result[state.data.result.length - 1 - slot];
5344 var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5347 state.gaugeEvent.value = value;
5348 state.gaugeEvent.max = max;
5349 state.gaugeEvent.min = min;
5350 NETDATA.gaugeSetLabels(state, value, min, max);
5352 if(state.gaugeEvent.timer === null) {
5353 NETDATA.gaugeAnimation(state, false);
5355 state.gaugeEvent.timer = setTimeout(function() {
5356 state.gaugeEvent.timer = null;
5357 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5358 }, NETDATA.options.current.charts_selection_animation_delay);
5364 NETDATA.gaugeChartUpdate = function(state, data) {
5365 var value, min, max;
5367 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5371 NETDATA.gaugeSetLabels(state, null, null, null);
5374 value = data.result[0];
5376 max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5377 if(value > max) max = value;
5378 NETDATA.gaugeSetLabels(state, value, min, max);
5381 NETDATA.gaugeSet(state, value, min, max);
5385 NETDATA.gaugeChartCreate = function(state, data) {
5386 var self = $(state.element);
5387 // var chart = $(state.element_chart);
5389 var value = data.result[0];
5390 var max = self.data('gauge-max-value') || null;
5391 var adjust = self.data('gauge-adjust') || null;
5392 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5393 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5394 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5395 var stopColor = self.data('gauge-stop-color') || void 0;
5396 var generateGradient = self.data('gauge-generate-gradient') || false;
5399 max = NETDATA.commonMax.get(state);
5400 state.gaugeMax = null;
5403 state.gaugeMax = max;
5405 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5407 // case 'width': width = height * ratio; break;
5409 // default: height = width / ratio; break;
5411 //state.element.style.width = width.toString() + 'px';
5412 //state.element.style.height = height.toString() + 'px';
5417 lines: 12, // The number of lines to draw
5418 angle: 0.15, // The length of each line
5419 lineWidth: 0.44, // 0.44 The line thickness
5421 length: 0.8, // 0.9 The radius of the inner circle
5422 strokeWidth: 0.035, // The rotation offset
5423 color: pointerColor // Fill color
5425 colorStart: startColor, // Colors
5426 colorStop: stopColor, // just experiment with them
5427 strokeColor: strokeColor, // to see which ones work best for you
5429 generateGradient: (generateGradient === true)?true:false,
5433 if (generateGradient.constructor === Array) {
5435 // data-gauge-generate-gradient="[0, 50, 100]"
5436 // data-gauge-gradient-percent-color-0="#FFFFFF"
5437 // data-gauge-gradient-percent-color-50="#999900"
5438 // data-gauge-gradient-percent-color-100="#000000"
5440 options.percentColors = new Array();
5441 var len = generateGradient.length;
5443 var pcent = generateGradient[len];
5444 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5445 if(color !== false) {
5446 var a = new Array();
5449 options.percentColors.unshift(a);
5452 if(options.percentColors.length === 0)
5453 delete options.percentColors;
5455 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5456 options.percentColors = [
5457 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5458 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5459 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5460 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5461 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5462 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5463 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5464 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5465 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5466 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5467 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5470 state.gauge_canvas = document.createElement('canvas');
5471 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5472 state.gauge_canvas.className = 'gaugeChart';
5473 state.gauge_canvas.width = width;
5474 state.gauge_canvas.height = height;
5475 state.element_chart.appendChild(state.gauge_canvas);
5477 var valuefontsize = Math.floor(height / 6);
5478 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5479 state.gaugeChartLabel = document.createElement('span');
5480 state.gaugeChartLabel.className = 'gaugeChartLabel';
5481 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5482 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5483 state.element_chart.appendChild(state.gaugeChartLabel);
5485 var titlefontsize = Math.round(valuefontsize / 2);
5487 state.gaugeChartTitle = document.createElement('span');
5488 state.gaugeChartTitle.className = 'gaugeChartTitle';
5489 state.gaugeChartTitle.innerHTML = state.title;
5490 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5491 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5492 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5493 state.element_chart.appendChild(state.gaugeChartTitle);
5495 var unitfontsize = Math.round(titlefontsize * 0.9);
5496 state.gaugeChartUnits = document.createElement('span');
5497 state.gaugeChartUnits.className = 'gaugeChartUnits';
5498 state.gaugeChartUnits.innerHTML = state.units;
5499 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5500 state.element_chart.appendChild(state.gaugeChartUnits);
5502 state.gaugeChartMin = document.createElement('span');
5503 state.gaugeChartMin.className = 'gaugeChartMin';
5504 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5505 state.element_chart.appendChild(state.gaugeChartMin);
5507 state.gaugeChartMax = document.createElement('span');
5508 state.gaugeChartMax.className = 'gaugeChartMax';
5509 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5510 state.element_chart.appendChild(state.gaugeChartMax);
5512 // when we just re-create the chart
5513 // do not animate the first update
5515 if(typeof state.gauge_instance !== 'undefined')
5518 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5520 state.___gaugeOld__ = {
5529 // we will always feed a percentage
5530 state.gauge_instance.minValue = 0;
5531 state.gauge_instance.maxValue = 100;
5533 NETDATA.gaugeAnimation(state, animate);
5534 NETDATA.gaugeSet(state, value, 0, max);
5535 NETDATA.gaugeSetLabels(state, value, 0, max);
5536 NETDATA.gaugeAnimation(state, true);
5540 // ----------------------------------------------------------------------------------------------------------------
5541 // Charts Libraries Registration
5543 NETDATA.chartLibraries = {
5545 initialize: NETDATA.dygraphInitialize,
5546 create: NETDATA.dygraphChartCreate,
5547 update: NETDATA.dygraphChartUpdate,
5548 resize: function(state) {
5549 if(typeof state.dygraph_instance.resize === 'function')
5550 state.dygraph_instance.resize();
5552 setSelection: NETDATA.dygraphSetSelection,
5553 clearSelection: NETDATA.dygraphClearSelection,
5554 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5557 format: function(state) { return 'json'; },
5558 options: function(state) { return 'ms|flip'; },
5559 legend: function(state) {
5560 if(this.isSparkline(state) === false)
5561 return 'right-side';
5565 autoresize: function(state) { return true; },
5566 max_updates_to_recreate: function(state) { return 5000; },
5567 track_colors: function(state) { return true; },
5568 pixels_per_point: function(state) {
5569 if(this.isSparkline(state) === false)
5575 isSparkline: function(state) {
5576 if(typeof state.dygraph_sparkline === 'undefined') {
5577 var t = $(state.element).data('dygraph-theme');
5578 if(t === 'sparkline')
5579 state.dygraph_sparkline = true;
5581 state.dygraph_sparkline = false;
5583 return state.dygraph_sparkline;
5587 initialize: NETDATA.sparklineInitialize,
5588 create: NETDATA.sparklineChartCreate,
5589 update: NETDATA.sparklineChartUpdate,
5591 setSelection: undefined, // function(state, t) { return true; },
5592 clearSelection: undefined, // function(state) { return true; },
5593 toolboxPanAndZoom: null,
5596 format: function(state) { return 'array'; },
5597 options: function(state) { return 'flip|abs'; },
5598 legend: function(state) { return null; },
5599 autoresize: function(state) { return false; },
5600 max_updates_to_recreate: function(state) { return 5000; },
5601 track_colors: function(state) { return false; },
5602 pixels_per_point: function(state) { return 3; }
5605 initialize: NETDATA.peityInitialize,
5606 create: NETDATA.peityChartCreate,
5607 update: NETDATA.peityChartUpdate,
5609 setSelection: undefined, // function(state, t) { return true; },
5610 clearSelection: undefined, // function(state) { return true; },
5611 toolboxPanAndZoom: null,
5614 format: function(state) { return 'ssvcomma'; },
5615 options: function(state) { return 'null2zero|flip|abs'; },
5616 legend: function(state) { return null; },
5617 autoresize: function(state) { return false; },
5618 max_updates_to_recreate: function(state) { return 5000; },
5619 track_colors: function(state) { return false; },
5620 pixels_per_point: function(state) { return 3; }
5623 initialize: NETDATA.morrisInitialize,
5624 create: NETDATA.morrisChartCreate,
5625 update: NETDATA.morrisChartUpdate,
5627 setSelection: undefined, // function(state, t) { return true; },
5628 clearSelection: undefined, // function(state) { return true; },
5629 toolboxPanAndZoom: null,
5632 format: function(state) { return 'json'; },
5633 options: function(state) { return 'objectrows|ms'; },
5634 legend: function(state) { return null; },
5635 autoresize: function(state) { return false; },
5636 max_updates_to_recreate: function(state) { return 50; },
5637 track_colors: function(state) { return false; },
5638 pixels_per_point: function(state) { return 15; }
5641 initialize: NETDATA.googleInitialize,
5642 create: NETDATA.googleChartCreate,
5643 update: NETDATA.googleChartUpdate,
5645 setSelection: undefined, //function(state, t) { return true; },
5646 clearSelection: undefined, //function(state) { return true; },
5647 toolboxPanAndZoom: null,
5650 format: function(state) { return 'datatable'; },
5651 options: function(state) { return ''; },
5652 legend: function(state) { return null; },
5653 autoresize: function(state) { return false; },
5654 max_updates_to_recreate: function(state) { return 300; },
5655 track_colors: function(state) { return false; },
5656 pixels_per_point: function(state) { return 4; }
5659 initialize: NETDATA.raphaelInitialize,
5660 create: NETDATA.raphaelChartCreate,
5661 update: NETDATA.raphaelChartUpdate,
5663 setSelection: undefined, // function(state, t) { return true; },
5664 clearSelection: undefined, // function(state) { return true; },
5665 toolboxPanAndZoom: null,
5668 format: function(state) { return 'json'; },
5669 options: function(state) { return ''; },
5670 legend: function(state) { return null; },
5671 autoresize: function(state) { return false; },
5672 max_updates_to_recreate: function(state) { return 5000; },
5673 track_colors: function(state) { return false; },
5674 pixels_per_point: function(state) { return 3; }
5677 initialize: NETDATA.c3Initialize,
5678 create: NETDATA.c3ChartCreate,
5679 update: NETDATA.c3ChartUpdate,
5681 setSelection: undefined, // function(state, t) { return true; },
5682 clearSelection: undefined, // function(state) { return true; },
5683 toolboxPanAndZoom: null,
5686 format: function(state) { return 'csvjsonarray'; },
5687 options: function(state) { return 'milliseconds'; },
5688 legend: function(state) { return null; },
5689 autoresize: function(state) { return false; },
5690 max_updates_to_recreate: function(state) { return 5000; },
5691 track_colors: function(state) { return false; },
5692 pixels_per_point: function(state) { return 15; }
5695 initialize: NETDATA.d3Initialize,
5696 create: NETDATA.d3ChartCreate,
5697 update: NETDATA.d3ChartUpdate,
5699 setSelection: undefined, // function(state, t) { return true; },
5700 clearSelection: undefined, // function(state) { return true; },
5701 toolboxPanAndZoom: null,
5704 format: function(state) { return 'json'; },
5705 options: function(state) { return ''; },
5706 legend: function(state) { return null; },
5707 autoresize: function(state) { return false; },
5708 max_updates_to_recreate: function(state) { return 5000; },
5709 track_colors: function(state) { return false; },
5710 pixels_per_point: function(state) { return 3; }
5713 initialize: NETDATA.easypiechartInitialize,
5714 create: NETDATA.easypiechartChartCreate,
5715 update: NETDATA.easypiechartChartUpdate,
5717 setSelection: NETDATA.easypiechartSetSelection,
5718 clearSelection: NETDATA.easypiechartClearSelection,
5719 toolboxPanAndZoom: null,
5722 format: function(state) { return 'array'; },
5723 options: function(state) { return 'absolute'; },
5724 legend: function(state) { return null; },
5725 autoresize: function(state) { return false; },
5726 max_updates_to_recreate: function(state) { return 5000; },
5727 track_colors: function(state) { return true; },
5728 pixels_per_point: function(state) { return 3; },
5732 initialize: NETDATA.gaugeInitialize,
5733 create: NETDATA.gaugeChartCreate,
5734 update: NETDATA.gaugeChartUpdate,
5736 setSelection: NETDATA.gaugeSetSelection,
5737 clearSelection: NETDATA.gaugeClearSelection,
5738 toolboxPanAndZoom: null,
5741 format: function(state) { return 'array'; },
5742 options: function(state) { return 'absolute'; },
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 true; },
5747 pixels_per_point: function(state) { return 3; },
5752 NETDATA.registerChartLibrary = function(library, url) {
5753 if(NETDATA.options.debug.libraries === true)
5754 console.log("registering chart library: " + library);
5756 NETDATA.chartLibraries[library].url = url;
5757 NETDATA.chartLibraries[library].initialized = true;
5758 NETDATA.chartLibraries[library].enabled = true;
5761 // ----------------------------------------------------------------------------------------------------------------
5762 // Load required JS libraries and CSS
5764 NETDATA.requiredJs = [
5766 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
5768 isAlreadyLoaded: function() {
5769 // check if bootstrap is loaded
5770 if(typeof $().emulateTransitionEnd == 'function')
5773 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5781 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller-0.8.7.min.js',
5782 isAlreadyLoaded: function() { return false; }
5786 NETDATA.requiredCSS = [
5788 url: NETDATA.themes.current.bootstrap_css,
5789 isAlreadyLoaded: function() {
5790 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5797 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
5798 isAlreadyLoaded: function() { return false; }
5801 url: NETDATA.themes.current.dashboard_css,
5802 isAlreadyLoaded: function() { return false; }
5806 NETDATA.loadedRequiredJs = 0;
5807 NETDATA.loadRequiredJs = function(index, callback) {
5808 if(index >= NETDATA.requiredJs.length) {
5809 if(typeof callback === 'function')
5814 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5815 NETDATA.loadedRequiredJs++;
5816 NETDATA.loadRequiredJs(++index, callback);
5820 if(NETDATA.options.debug.main_loop === true)
5821 console.log('loading ' + NETDATA.requiredJs[index].url);
5824 if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
5828 url: NETDATA.requiredJs[index].url,
5831 xhrFields: { withCredentials: true } // required for the cookie
5834 if(NETDATA.options.debug.main_loop === true)
5835 console.log('loaded ' + NETDATA.requiredJs[index].url);
5838 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5840 .always(function() {
5841 NETDATA.loadedRequiredJs++;
5844 NETDATA.loadRequiredJs(++index, callback);
5848 NETDATA.loadRequiredJs(++index, callback);
5851 NETDATA.loadRequiredCSS = function(index) {
5852 if(index >= NETDATA.requiredCSS.length)
5855 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5856 NETDATA.loadRequiredCSS(++index);
5860 if(NETDATA.options.debug.main_loop === true)
5861 console.log('loading ' + NETDATA.requiredCSS[index].url);
5863 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5864 NETDATA.loadRequiredCSS(++index);
5868 // ----------------------------------------------------------------------------------------------------------------
5869 // Registry of netdata hosts
5872 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
5873 chart_div_offset: 100, // give that space above the chart when scrolling to it
5874 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
5875 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
5877 ms_penalty: 0, // the time penalty of the next alarm
5878 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
5879 // if alarms are shown faster than: one per 500ms
5881 notifications: false, // when true, the browser supports notifications (may not be granted though)
5882 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
5883 first_notification_id: 0, // the id of the first alarm_log entry for this session
5884 // this is used to prevent CLEAR notifications for past events
5885 // notifications_shown: new Array(),
5887 server: null, // the server to connect to for fetching alarms
5888 current: null, // the list of raised alarms - updated in the background
5889 callback: null, // a callback function to call every time the list of raised alarms is refreshed
5891 notify: function(entry) {
5892 // console.log('alarm ' + entry.unique_id);
5894 if(entry.updated === true) {
5895 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
5899 var value = entry.value;
5900 if(NETDATA.alarms.current !== null) {
5901 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
5902 if(typeof t !== 'undefined' && entry.status == t.status)
5906 var name = entry.name.replace(/_/g, ' ');
5907 var status = entry.status.toLowerCase();
5908 var title = name + ' = ' + ((value === null)?'NaN':Math.floor(value)).toString() + ' ' + entry.units;
5909 var tag = entry.alarm_id;
5910 var icon = 'images/seo-performance-128.png';
5911 var interaction = false;
5915 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
5917 switch(entry.status) {
5925 case 'UNINITIALIZED':
5929 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
5930 // console.log('alarm ' + entry.unique_id + ' is not current');
5933 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
5934 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
5937 title = name + ' back to normal';
5938 icon = 'images/check-mark-2-128-green.png'
5939 interaction = false;
5943 if(entry.old_status === 'CRITICAL')
5944 status = 'demoted to ' + entry.status.toLowerCase();
5946 icon = 'images/alert-128-orange.png';
5947 interaction = false;
5951 if(entry.old_status === 'WARNING')
5952 status = 'escalated to ' + entry.status.toLowerCase();
5954 icon = 'images/alert-128-red.png'
5959 console.log('invalid alarm status ' + entry.status);
5964 // cleanup old notifications with the same alarm_id as this one
5965 // FIXME: it does not seem to work on any web browser!
5966 var len = NETDATA.alarms.notifications_shown.length;
5968 var n = NETDATA.alarms.notifications_shown[len];
5969 if(n.data.alarm_id === entry.alarm_id) {
5970 console.log('removing old alarm ' + n.data.unique_id);
5972 // close the notification
5975 // remove it from the array
5976 NETDATA.alarms.notifications_shown.splice(len, 1);
5977 len = NETDATA.alarms.notifications_shown.length;
5984 setTimeout(function() {
5985 // show this notification
5986 // console.log('new notification: ' + title);
5987 var n = new Notification(title, {
5988 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
5990 requireInteraction: interaction,
5991 icon: NETDATA.serverDefault + icon,
5995 n.onclick = function(event) {
5996 event.preventDefault();
5997 NETDATA.alarms.onclick(event.target.data);
6001 // NETDATA.alarms.notifications_shown.push(n);
6002 // console.log(entry);
6003 }, NETDATA.alarms.ms_penalty);
6005 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6009 scrollToChart: function(chart_id) {
6010 if(typeof chart_id === 'string') {
6011 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6012 if(typeof offset !== 'undefined') {
6013 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6020 scrollToAlarm: function(alarm) {
6021 if(typeof alarm === 'object') {
6022 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6024 if(ret === true && NETDATA.options.page_is_visible === false)
6026 // 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.');
6031 notifyAll: function() {
6032 // console.log('FETCHING ALARM LOG');
6033 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6034 // console.log('ALARM LOG FETCHED');
6036 if(data === null || typeof data !== 'object') {
6037 console.log('invalid alarms log response');
6041 if(data.length === 0) {
6042 console.log('received empty alarm log');
6046 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6048 data.sort(function(a, b) {
6049 if(a.unique_id > b.unique_id) return -1;
6050 if(a.unique_id < b.unique_id) return 1;
6054 NETDATA.alarms.ms_penalty = 0;
6056 var len = data.length;
6058 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6059 NETDATA.alarms.notify(data[len]);
6062 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6065 NETDATA.alarms.last_notification_id = data[0].unique_id;
6066 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6067 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6071 check_notifications: function() {
6072 // returns true if we should fire 1+ notifications
6074 if(NETDATA.alarms.notifications !== true) {
6075 // console.log('notifications not available');
6079 if(Notification.permission !== 'granted') {
6080 // console.log('notifications not granted');
6084 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6085 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6087 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6088 // console.log('new alarms detected');
6091 //else console.log('no new alarms');
6093 // else console.log('cannot process alarms');
6098 get: function(what, callback) {
6100 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6104 'Cache-Control': 'no-cache, no-store',
6105 'Pragma': 'no-cache'
6107 xhrFields: { withCredentials: true } // required for the cookie
6109 .done(function(data) {
6110 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6111 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6113 if(typeof callback === 'function')
6117 NETDATA.error(415, NETDATA.alarms.server);
6119 if(typeof callback === 'function')
6124 update_forever: function() {
6125 NETDATA.alarms.get('active', function(data) {
6127 NETDATA.alarms.current = data;
6129 if(NETDATA.alarms.check_notifications() === true) {
6130 NETDATA.alarms.notifyAll();
6133 if (typeof NETDATA.alarms.callback === 'function') {
6134 NETDATA.alarms.callback(data);
6137 // Health monitoring is disabled on this netdata
6138 if(data.status === false) return;
6141 setTimeout(NETDATA.alarms.update_forever, 10000);
6145 get_log: function(last_id, callback) {
6146 // console.log('fetching all log after ' + last_id.toString());
6148 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6152 'Cache-Control': 'no-cache, no-store',
6153 'Pragma': 'no-cache'
6155 xhrFields: { withCredentials: true } // required for the cookie
6157 .done(function(data) {
6158 if(typeof callback === 'function')
6162 NETDATA.error(416, NETDATA.alarms.server);
6164 if(typeof callback === 'function')
6170 var host = NETDATA.serverDefault;
6171 while(host.slice(-1) === '/')
6172 host = host.substring(0, host.length - 1);
6173 NETDATA.alarms.server = host;
6175 NETDATA.alarms.last_notification_id = NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6177 if(NETDATA.alarms.onclick === null)
6178 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6180 if(netdataShowAlarms === true) {
6181 NETDATA.alarms.update_forever();
6183 if('Notification' in window) {
6184 // console.log('notifications available');
6185 NETDATA.alarms.notifications = true;
6187 if(Notification.permission === 'default')
6188 Notification.requestPermission();
6194 // ----------------------------------------------------------------------------------------------------------------
6195 // Registry of netdata hosts
6197 NETDATA.registry = {
6198 server: null, // the netdata registry server
6199 person_guid: null, // the unique ID of this browser / user
6200 machine_guid: null, // the unique ID the netdata server that served dashboard.js
6201 hostname: null, // the hostname of the netdata server that served dashboard.js
6202 machines: null, // the user's other URLs
6203 machines_array: null, // the user's other URLs in an array
6206 parsePersonUrls: function(person_urls) {
6207 // console.log(person_urls);
6208 NETDATA.registry.person_urls = person_urls;
6211 NETDATA.registry.machines = {};
6212 NETDATA.registry.machines_array = new Array();
6214 var now = new Date().getTime();
6215 var apu = person_urls;
6218 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6219 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6225 accesses: apu[i][3],
6227 alternate_urls: new Array()
6229 obj.alternate_urls.push(apu[i][1]);
6231 NETDATA.registry.machines[apu[i][0]] = obj;
6232 NETDATA.registry.machines_array.push(obj);
6235 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6237 var pu = NETDATA.registry.machines[apu[i][0]];
6238 if(pu.last_t < apu[i][2]) {
6240 pu.last_t = apu[i][2];
6241 pu.name = apu[i][4];
6243 pu.accesses += apu[i][3];
6244 pu.alternate_urls.push(apu[i][1]);
6249 if(typeof netdataRegistryCallback === 'function')
6250 netdataRegistryCallback(NETDATA.registry.machines_array);
6254 if(netdataRegistry !== true) return;
6256 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6258 NETDATA.registry.server = data.registry;
6259 NETDATA.registry.machine_guid = data.machine_guid;
6260 NETDATA.registry.hostname = data.hostname;
6262 NETDATA.registry.access(2, function (person_urls) {
6263 NETDATA.registry.parsePersonUrls(person_urls);
6270 hello: function(host, callback) {
6271 while(host.slice(-1) === '/')
6272 host = host.substring(0, host.length - 1);
6274 // send HELLO to a netdata server:
6275 // 1. verifies the server is reachable
6276 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6278 url: host + '/api/v1/registry?action=hello',
6282 'Cache-Control': 'no-cache, no-store',
6283 'Pragma': 'no-cache'
6285 xhrFields: { withCredentials: true } // required for the cookie
6287 .done(function(data) {
6288 if(typeof data.status !== 'string' || data.status !== 'ok') {
6289 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6293 if(typeof callback === 'function')
6297 NETDATA.error(407, host);
6299 if(typeof callback === 'function')
6304 access: function(max_redirects, callback) {
6305 // send ACCESS to a netdata registry:
6306 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6307 // 2. it responds with a list of netdata servers we know
6308 // the registry identifies us using a cookie it sets the first time we access it
6309 // the registry may respond with a redirect URL to send us to another registry
6311 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),
6315 'Cache-Control': 'no-cache, no-store',
6316 'Pragma': 'no-cache'
6318 xhrFields: { withCredentials: true } // required for the cookie
6320 .done(function(data) {
6321 var redirect = null;
6322 if(typeof data.registry === 'string')
6323 redirect = data.registry;
6325 if(typeof data.status !== 'string' || data.status !== 'ok') {
6326 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6331 if(redirect !== null && max_redirects > 0) {
6332 NETDATA.registry.server = redirect;
6333 NETDATA.registry.access(max_redirects - 1, callback);
6336 if(typeof callback === 'function')
6341 if(typeof data.person_guid === 'string')
6342 NETDATA.registry.person_guid = data.person_guid;
6344 if(typeof callback === 'function')
6345 callback(data.urls);
6349 NETDATA.error(410, NETDATA.registry.server);
6351 if(typeof callback === 'function')
6356 delete: function(delete_url, callback) {
6357 // send DELETE to a netdata registry:
6359 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),
6363 'Cache-Control': 'no-cache, no-store',
6364 'Pragma': 'no-cache'
6366 xhrFields: { withCredentials: true } // required for the cookie
6368 .done(function(data) {
6369 if(typeof data.status !== 'string' || data.status !== 'ok') {
6370 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6374 if(typeof callback === 'function')
6378 NETDATA.error(412, NETDATA.registry.server);
6380 if(typeof callback === 'function')
6385 search: function(machine_guid, callback) {
6386 // SEARCH for the URLs of a machine:
6388 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,
6392 'Cache-Control': 'no-cache, no-store',
6393 'Pragma': 'no-cache'
6395 xhrFields: { withCredentials: true } // required for the cookie
6397 .done(function(data) {
6398 if(typeof data.status !== 'string' || data.status !== 'ok') {
6399 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6403 if(typeof callback === 'function')
6407 NETDATA.error(418, NETDATA.registry.server);
6409 if(typeof callback === 'function')
6414 switch: function(new_person_guid, callback) {
6417 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,
6421 'Cache-Control': 'no-cache, no-store',
6422 'Pragma': 'no-cache'
6424 xhrFields: { withCredentials: true } // required for the cookie
6426 .done(function(data) {
6427 if(typeof data.status !== 'string' || data.status !== 'ok') {
6428 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6432 if(typeof callback === 'function')
6436 NETDATA.error(414, NETDATA.registry.server);
6438 if(typeof callback === 'function')
6444 // ----------------------------------------------------------------------------------------------------------------
6447 if(typeof netdataPrepCallback === 'function')
6448 netdataPrepCallback();
6450 NETDATA.errorReset();
6451 NETDATA.loadRequiredCSS(0);
6453 NETDATA._loadjQuery(function() {
6454 NETDATA.loadRequiredJs(0, function() {
6455 if(typeof $().emulateTransitionEnd !== 'function') {
6456 // bootstrap is not available
6457 NETDATA.options.current.show_help = false;
6460 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6461 if(NETDATA.options.debug.main_loop === true)
6462 console.log('starting chart refresh thread');
6469 // window.NETDATA = NETDATA;
6470 // })(window, document);