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 Dygraph.movePan(event, dygraph, context);
4327 else if(context.isZooming) {
4328 state.dygraph_user_action = true;
4329 state.globalSelectionSyncStop();
4330 state.globalSelectionSyncDelay();
4331 state.setMode('zoom');
4332 Dygraph.moveZoom(event, dygraph, context);
4335 mouseup: function(event, dygraph, context) {
4336 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4337 state.log('interactionModel.mouseup()');
4339 if (context.isPanning) {
4340 state.dygraph_user_action = true;
4341 state.globalSelectionSyncDelay();
4342 Dygraph.endPan(event, dygraph, context);
4344 else if (context.isZooming) {
4345 state.dygraph_user_action = true;
4346 state.globalSelectionSyncDelay();
4347 Dygraph.endZoom(event, dygraph, context);
4350 click: function(event, dygraph, context) {
4351 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4352 state.log('interactionModel.click()');
4354 event.preventDefault();
4356 dblclick: function(event, dygraph, context) {
4357 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4358 state.log('interactionModel.dblclick()');
4359 NETDATA.resetAllCharts(state);
4361 wheel: function(event, dygraph, context) {
4362 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4363 state.log('interactionModel.wheel()');
4365 // Take the offset of a mouse event on the dygraph canvas and
4366 // convert it to a pair of percentages from the bottom left.
4367 // (Not top left, bottom is where the lower value is.)
4368 function offsetToPercentage(g, offsetX, offsetY) {
4369 // This is calculating the pixel offset of the leftmost date.
4370 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4371 var yar0 = g.yAxisRange(0);
4373 // This is calculating the pixel of the higest value. (Top pixel)
4374 var yOffset = g.toDomCoords(null, yar0[1])[1];
4376 // x y w and h are relative to the corner of the drawing area,
4377 // so that the upper corner of the drawing area is (0, 0).
4378 var x = offsetX - xOffset;
4379 var y = offsetY - yOffset;
4381 // This is computing the rightmost pixel, effectively defining the
4383 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4385 // This is computing the lowest pixel, effectively defining the height.
4386 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4388 // Percentage from the left.
4389 var xPct = w === 0 ? 0 : (x / w);
4390 // Percentage from the top.
4391 var yPct = h === 0 ? 0 : (y / h);
4393 // The (1-) part below changes it from "% distance down from the top"
4394 // to "% distance up from the bottom".
4395 return [xPct, (1-yPct)];
4398 // Adjusts [x, y] toward each other by zoomInPercentage%
4399 // Split it so the left/bottom axis gets xBias/yBias of that change and
4400 // tight/top gets (1-xBias)/(1-yBias) of that change.
4402 // If a bias is missing it splits it down the middle.
4403 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4404 xBias = xBias || 0.5;
4405 yBias = yBias || 0.5;
4407 function adjustAxis(axis, zoomInPercentage, bias) {
4408 var delta = axis[1] - axis[0];
4409 var increment = delta * zoomInPercentage;
4410 var foo = [increment * bias, increment * (1-bias)];
4412 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4415 var yAxes = g.yAxisRanges();
4417 for (var i = 0; i < yAxes.length; i++) {
4418 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4421 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4424 if(event.altKey || event.shiftKey) {
4425 state.dygraph_user_action = true;
4427 state.globalSelectionSyncStop();
4428 state.globalSelectionSyncDelay();
4430 // http://dygraphs.com/gallery/interaction-api.js
4432 if(typeof event.wheelDelta === 'number' && event.wheelDelta != NaN)
4434 normal_def = event.wheelDelta / 40;
4437 normal_def = event.deltaY * -2;
4439 var normal = (event.detail) ? event.detail * -1 : normal_def;
4440 var percentage = normal / 50;
4442 if (!(event.offsetX && event.offsetY)){
4443 event.offsetX = event.layerX - event.target.offsetLeft;
4444 event.offsetY = event.layerY - event.target.offsetTop;
4447 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4448 var xPct = percentages[0];
4449 var yPct = percentages[1];
4451 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4452 var after = new_x_range[0];
4453 var before = new_x_range[1];
4455 var first = state.netdata_first + state.data_update_every;
4456 var last = state.netdata_last + state.data_update_every;
4459 after -= (before - last);
4466 state.setMode('zoom');
4467 if(state.updateChartPanOrZoom(after, before) === true)
4468 dygraph.updateOptions({ dateWindow: [ after, before ] });
4470 event.preventDefault();
4473 touchstart: function(event, dygraph, context) {
4474 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4475 state.log('interactionModel.touchstart()');
4477 state.dygraph_user_action = true;
4478 state.setMode('zoom');
4481 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4483 // we overwrite the touch directions at the end, to overwrite
4484 // the internal default of dygraphs
4485 context.touchDirections = { x: true, y: false };
4487 state.dygraph_last_touch_start = new Date().getTime();
4488 state.dygraph_last_touch_move = 0;
4490 if(typeof event.touches[0].pageX === 'number')
4491 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4493 state.dygraph_last_touch_page_x = 0;
4495 touchmove: function(event, dygraph, context) {
4496 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4497 state.log('interactionModel.touchmove()');
4499 state.dygraph_user_action = true;
4500 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4502 state.dygraph_last_touch_move = new Date().getTime();
4504 touchend: function(event, dygraph, context) {
4505 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4506 state.log('interactionModel.touchend()');
4508 state.dygraph_user_action = true;
4509 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4511 // if it didn't move, it is a selection
4512 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4513 // internal api of dygraphs
4514 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4515 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4516 if(NETDATA.dygraphSetSelection(state, t) === true)
4517 state.globalSelectionSync(t);
4520 // if it was double tap within double click time, reset the charts
4521 var now = new Date().getTime();
4522 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4523 if(state.dygraph_last_touch_move === 0) {
4524 var dt = now - state.dygraph_last_touch_end;
4525 if(dt <= NETDATA.options.current.double_click_speed)
4526 NETDATA.resetAllCharts(state);
4530 // remember the timestamp of the last touch end
4531 state.dygraph_last_touch_end = now;
4536 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4537 state.dygraph_options.drawGrid = false;
4538 state.dygraph_options.drawAxis = false;
4539 state.dygraph_options.title = undefined;
4540 state.dygraph_options.ylabel = undefined;
4541 state.dygraph_options.yLabelWidth = 0;
4542 state.dygraph_options.labelsDivWidth = 120;
4543 state.dygraph_options.labelsDivStyles.width = '120px';
4544 state.dygraph_options.labelsSeparateLines = true;
4545 state.dygraph_options.rightGap = 0;
4546 state.dygraph_options.yRangePad = 1;
4549 if(smooth === true) {
4550 state.dygraph_smooth_eligible = true;
4552 if(NETDATA.options.current.smooth_plot === true)
4553 state.dygraph_options.plotter = smoothPlotter;
4555 else state.dygraph_smooth_eligible = false;
4557 state.dygraph_instance = new Dygraph(state.element_chart,
4558 data.result.data, state.dygraph_options);
4560 state.dygraph_force_zoom = false;
4561 state.dygraph_user_action = false;
4562 state.dygraph_last_rendered = new Date().getTime();
4564 if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4565 state.__commonMin = self.data('common-min') || null;
4566 state.__commonMax = self.data('common-max') || null;
4569 state.log('incompatible version of dygraphs detected');
4570 state.__commonMin = null;
4571 state.__commonMax = null;
4577 // ----------------------------------------------------------------------------------------------------------------
4580 NETDATA.morrisInitialize = function(callback) {
4581 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4583 // morris requires raphael
4584 if(!NETDATA.chartLibraries.raphael.initialized) {
4585 if(NETDATA.chartLibraries.raphael.enabled) {
4586 NETDATA.raphaelInitialize(function() {
4587 NETDATA.morrisInitialize(callback);
4591 NETDATA.chartLibraries.morris.enabled = false;
4592 if(typeof callback === "function")
4597 NETDATA._loadCSS(NETDATA.morris_css);
4600 url: NETDATA.morris_js,
4603 xhrFields: { withCredentials: true } // required for the cookie
4606 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4609 NETDATA.chartLibraries.morris.enabled = false;
4610 NETDATA.error(100, NETDATA.morris_js);
4612 .always(function() {
4613 if(typeof callback === "function")
4619 NETDATA.chartLibraries.morris.enabled = false;
4620 if(typeof callback === "function")
4625 NETDATA.morrisChartUpdate = function(state, data) {
4626 state.morris_instance.setData(data.result.data);
4630 NETDATA.morrisChartCreate = function(state, data) {
4632 state.morris_options = {
4633 element: state.element_chart.id,
4634 data: data.result.data,
4636 ykeys: data.dimension_names,
4637 labels: data.dimension_names,
4643 continuousLine: false,
4644 behaveLikeLine: false
4647 if(state.chart.chart_type === 'line')
4648 state.morris_instance = new Morris.Line(state.morris_options);
4650 else if(state.chart.chart_type === 'area') {
4651 state.morris_options.behaveLikeLine = true;
4652 state.morris_instance = new Morris.Area(state.morris_options);
4655 state.morris_instance = new Morris.Area(state.morris_options);
4660 // ----------------------------------------------------------------------------------------------------------------
4663 NETDATA.raphaelInitialize = function(callback) {
4664 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4666 url: NETDATA.raphael_js,
4669 xhrFields: { withCredentials: true } // required for the cookie
4672 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4675 NETDATA.chartLibraries.raphael.enabled = false;
4676 NETDATA.error(100, NETDATA.raphael_js);
4678 .always(function() {
4679 if(typeof callback === "function")
4684 NETDATA.chartLibraries.raphael.enabled = false;
4685 if(typeof callback === "function")
4690 NETDATA.raphaelChartUpdate = function(state, data) {
4691 $(state.element_chart).raphael(data.result, {
4692 width: state.chartWidth(),
4693 height: state.chartHeight()
4699 NETDATA.raphaelChartCreate = function(state, data) {
4700 $(state.element_chart).raphael(data.result, {
4701 width: state.chartWidth(),
4702 height: state.chartHeight()
4708 // ----------------------------------------------------------------------------------------------------------------
4711 NETDATA.c3Initialize = function(callback) {
4712 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4715 if(!NETDATA.chartLibraries.d3.initialized) {
4716 if(NETDATA.chartLibraries.d3.enabled) {
4717 NETDATA.d3Initialize(function() {
4718 NETDATA.c3Initialize(callback);
4722 NETDATA.chartLibraries.c3.enabled = false;
4723 if(typeof callback === "function")
4728 NETDATA._loadCSS(NETDATA.c3_css);
4734 xhrFields: { withCredentials: true } // required for the cookie
4737 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4740 NETDATA.chartLibraries.c3.enabled = false;
4741 NETDATA.error(100, NETDATA.c3_js);
4743 .always(function() {
4744 if(typeof callback === "function")
4750 NETDATA.chartLibraries.c3.enabled = false;
4751 if(typeof callback === "function")
4756 NETDATA.c3ChartUpdate = function(state, data) {
4757 state.c3_instance.destroy();
4758 return NETDATA.c3ChartCreate(state, data);
4760 //state.c3_instance.load({
4761 // rows: data.result,
4768 NETDATA.c3ChartCreate = function(state, data) {
4770 state.element_chart.id = 'c3-' + state.uuid;
4771 // console.log('id = ' + state.element_chart.id);
4773 state.c3_instance = c3.generate({
4774 bindto: '#' + state.element_chart.id,
4776 width: state.chartWidth(),
4777 height: state.chartHeight()
4780 pattern: state.chartColors()
4785 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4791 format: function(x) {
4792 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4819 // console.log(state.c3_instance);
4824 // ----------------------------------------------------------------------------------------------------------------
4827 NETDATA.d3Initialize = function(callback) {
4828 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4833 xhrFields: { withCredentials: true } // required for the cookie
4836 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4839 NETDATA.chartLibraries.d3.enabled = false;
4840 NETDATA.error(100, NETDATA.d3_js);
4842 .always(function() {
4843 if(typeof callback === "function")
4848 NETDATA.chartLibraries.d3.enabled = false;
4849 if(typeof callback === "function")
4854 NETDATA.d3ChartUpdate = function(state, data) {
4858 NETDATA.d3ChartCreate = function(state, data) {
4862 // ----------------------------------------------------------------------------------------------------------------
4865 NETDATA.googleInitialize = function(callback) {
4866 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4868 url: NETDATA.google_js,
4871 xhrFields: { withCredentials: true } // required for the cookie
4874 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4875 google.load('visualization', '1.1', {
4876 'packages': ['corechart', 'controls'],
4877 'callback': callback
4881 NETDATA.chartLibraries.google.enabled = false;
4882 NETDATA.error(100, NETDATA.google_js);
4883 if(typeof callback === "function")
4888 NETDATA.chartLibraries.google.enabled = false;
4889 if(typeof callback === "function")
4894 NETDATA.googleChartUpdate = function(state, data) {
4895 var datatable = new google.visualization.DataTable(data.result);
4896 state.google_instance.draw(datatable, state.google_options);
4900 NETDATA.googleChartCreate = function(state, data) {
4901 var datatable = new google.visualization.DataTable(data.result);
4903 state.google_options = {
4904 colors: state.chartColors(),
4906 // do not set width, height - the chart resizes itself
4907 //width: state.chartWidth(),
4908 //height: state.chartHeight(),
4913 // title: "Time of Day",
4914 // format:'HH:mm:ss',
4915 viewWindowMode: 'maximized',
4927 viewWindowMode: 'pretty',
4942 focusTarget: 'category',
4949 titlePosition: 'out',
4960 curveType: 'function',
4965 switch(state.chart.chart_type) {
4967 state.google_options.vAxis.viewWindowMode = 'maximized';
4968 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4969 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4973 state.google_options.isStacked = true;
4974 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4975 state.google_options.vAxis.viewWindowMode = 'maximized';
4976 state.google_options.vAxis.minValue = null;
4977 state.google_options.vAxis.maxValue = null;
4978 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4983 state.google_options.lineWidth = 2;
4984 state.google_instance = new google.visualization.LineChart(state.element_chart);
4988 state.google_instance.draw(datatable, state.google_options);
4992 // ----------------------------------------------------------------------------------------------------------------
4994 NETDATA.percentFromValueMax = function(value, max) {
4995 if(value === null) value = 0;
4996 if(max < value) max = value;
5000 pcent = Math.round(value * 100 / max);
5001 if(pcent === 0 && value > 0) pcent = 1;
5007 // ----------------------------------------------------------------------------------------------------------------
5010 NETDATA.easypiechartInitialize = function(callback) {
5011 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5013 url: NETDATA.easypiechart_js,
5016 xhrFields: { withCredentials: true } // required for the cookie
5019 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5022 NETDATA.chartLibraries.easypiechart.enabled = false;
5023 NETDATA.error(100, NETDATA.easypiechart_js);
5025 .always(function() {
5026 if(typeof callback === "function")
5031 NETDATA.chartLibraries.easypiechart.enabled = false;
5032 if(typeof callback === "function")
5037 NETDATA.easypiechartClearSelection = function(state) {
5038 if(typeof state.easyPieChartEvent !== 'undefined') {
5039 if(state.easyPieChartEvent.timer !== null)
5040 clearTimeout(state.easyPieChartEvent.timer);
5042 state.easyPieChartEvent.timer = null;
5045 if(state.isAutoRefreshable() === true && state.data !== null) {
5046 NETDATA.easypiechartChartUpdate(state, state.data);
5049 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
5050 state.easyPieChart_instance.update(0);
5052 state.easyPieChart_instance.enableAnimation();
5057 NETDATA.easypiechartSetSelection = function(state, t) {
5058 if(state.timeIsVisible(t) !== true)
5059 return NETDATA.easypiechartClearSelection(state);
5061 var slot = state.calculateRowForTime(t);
5062 if(slot < 0 || slot >= state.data.result.length)
5063 return NETDATA.easypiechartClearSelection(state);
5065 if(typeof state.easyPieChartEvent === 'undefined') {
5066 state.easyPieChartEvent = {
5073 var value = state.data.result[state.data.result.length - 1 - slot];
5074 var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5075 var pcent = NETDATA.percentFromValueMax(value, max);
5077 state.easyPieChartEvent.value = value;
5078 state.easyPieChartEvent.pcent = pcent;
5079 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
5081 if(state.easyPieChartEvent.timer === null) {
5082 state.easyPieChart_instance.disableAnimation();
5084 state.easyPieChartEvent.timer = setTimeout(function() {
5085 state.easyPieChartEvent.timer = null;
5086 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5087 }, NETDATA.options.current.charts_selection_animation_delay);
5093 NETDATA.easypiechartChartUpdate = function(state, data) {
5094 var value, max, pcent;
5096 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5102 value = data.result[0];
5103 max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5104 pcent = NETDATA.percentFromValueMax(value, max);
5107 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
5108 state.easyPieChart_instance.update(pcent);
5112 NETDATA.easypiechartChartCreate = function(state, data) {
5113 var self = $(state.element);
5114 var chart = $(state.element_chart);
5116 var value = data.result[0];
5117 var max = self.data('easypiechart-max-value') || null;
5118 var adjust = self.data('easypiechart-adjust') || null;
5121 max = NETDATA.commonMax.get(state);
5122 state.easyPieChartMax = null;
5125 state.easyPieChartMax = max;
5127 var pcent = NETDATA.percentFromValueMax(value, max);
5129 chart.data('data-percent', pcent);
5133 case 'width': size = state.chartHeight(); break;
5134 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5135 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5137 default: size = state.chartWidth(); break;
5139 state.element.style.width = size + 'px';
5140 state.element.style.height = size + 'px';
5142 var stroke = Math.floor(size / 22);
5143 if(stroke < 3) stroke = 2;
5145 var valuefontsize = Math.floor((size * 2 / 3) / 5);
5146 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5147 state.easyPieChartLabel = document.createElement('span');
5148 state.easyPieChartLabel.className = 'easyPieChartLabel';
5149 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
5150 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5151 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5152 state.element_chart.appendChild(state.easyPieChartLabel);
5154 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5155 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5156 state.easyPieChartTitle = document.createElement('span');
5157 state.easyPieChartTitle.className = 'easyPieChartTitle';
5158 state.easyPieChartTitle.innerHTML = state.title;
5159 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5160 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5161 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5162 state.element_chart.appendChild(state.easyPieChartTitle);
5164 var unitfontsize = Math.round(titlefontsize * 0.9);
5165 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5166 state.easyPieChartUnits = document.createElement('span');
5167 state.easyPieChartUnits.className = 'easyPieChartUnits';
5168 state.easyPieChartUnits.innerHTML = state.units;
5169 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5170 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5171 state.element_chart.appendChild(state.easyPieChartUnits);
5173 var barColor = self.data('easypiechart-barcolor');
5174 if(typeof barColor === 'undefined' || barColor === null)
5175 barColor = state.chartColors()[0];
5177 // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5178 var tmp = eval(barColor);
5179 if(typeof tmp === 'function')
5183 chart.easyPieChart({
5185 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5186 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5187 scaleLength: self.data('easypiechart-scalelength') || 5,
5188 lineCap: self.data('easypiechart-linecap') || 'round',
5189 lineWidth: self.data('easypiechart-linewidth') || stroke,
5190 trackWidth: self.data('easypiechart-trackwidth') || undefined,
5191 size: self.data('easypiechart-size') || size,
5192 rotate: self.data('easypiechart-rotate') || 0,
5193 animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
5194 easing: self.data('easypiechart-easing') || undefined
5197 // when we just re-create the chart
5198 // do not animate the first update
5200 if(typeof state.easyPieChart_instance !== 'undefined')
5203 state.easyPieChart_instance = chart.data('easyPieChart');
5204 if(animate === false) state.easyPieChart_instance.disableAnimation();
5205 state.easyPieChart_instance.update(pcent);
5206 if(animate === false) state.easyPieChart_instance.enableAnimation();
5210 // ----------------------------------------------------------------------------------------------------------------
5213 NETDATA.gaugeInitialize = function(callback) {
5214 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5216 url: NETDATA.gauge_js,
5219 xhrFields: { withCredentials: true } // required for the cookie
5222 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5225 NETDATA.chartLibraries.gauge.enabled = false;
5226 NETDATA.error(100, NETDATA.gauge_js);
5228 .always(function() {
5229 if(typeof callback === "function")
5234 NETDATA.chartLibraries.gauge.enabled = false;
5235 if(typeof callback === "function")
5240 NETDATA.gaugeAnimation = function(state, status) {
5243 if(typeof status === 'boolean' && status === false)
5245 else if(typeof status === 'number')
5248 // console.log('gauge speed ' + speed);
5249 state.gauge_instance.animationSpeed = speed;
5250 state.___gaugeOld__.speed = speed;
5253 NETDATA.gaugeSet = function(state, value, min, max) {
5254 if(typeof value !== 'number') value = 0;
5255 if(typeof min !== 'number') min = 0;
5256 if(typeof max !== 'number') max = 0;
5257 if(value > max) max = value;
5258 if(value < min) min = value;
5267 // gauge.js has an issue if the needle
5268 // is smaller than min or larger than max
5269 // when we set the new values
5270 // the needle will go crazy
5272 // to prevent it, we always feed it
5273 // with a percentage, so that the needle
5274 // is always between min and max
5275 var pcent = (value - min) * 100 / (max - min);
5277 // these should never happen
5278 if(pcent < 0) pcent = 0;
5279 if(pcent > 100) pcent = 100;
5281 state.gauge_instance.set(pcent);
5282 // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5284 state.___gaugeOld__.value = value;
5285 state.___gaugeOld__.min = min;
5286 state.___gaugeOld__.max = max;
5289 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5290 if(state.___gaugeOld__.valueLabel !== value) {
5291 state.___gaugeOld__.valueLabel = value;
5292 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
5294 if(state.___gaugeOld__.minLabel !== min) {
5295 state.___gaugeOld__.minLabel = min;
5296 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
5298 if(state.___gaugeOld__.maxLabel !== max) {
5299 state.___gaugeOld__.maxLabel = max;
5300 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
5304 NETDATA.gaugeClearSelection = function(state) {
5305 if(typeof state.gaugeEvent !== 'undefined') {
5306 if(state.gaugeEvent.timer !== null)
5307 clearTimeout(state.gaugeEvent.timer);
5309 state.gaugeEvent.timer = null;
5312 if(state.isAutoRefreshable() === true && state.data !== null) {
5313 NETDATA.gaugeChartUpdate(state, state.data);
5316 NETDATA.gaugeAnimation(state, false);
5317 NETDATA.gaugeSet(state, null, null, null);
5318 NETDATA.gaugeSetLabels(state, null, null, null);
5321 NETDATA.gaugeAnimation(state, true);
5325 NETDATA.gaugeSetSelection = function(state, t) {
5326 if(state.timeIsVisible(t) !== true)
5327 return NETDATA.gaugeClearSelection(state);
5329 var slot = state.calculateRowForTime(t);
5330 if(slot < 0 || slot >= state.data.result.length)
5331 return NETDATA.gaugeClearSelection(state);
5333 if(typeof state.gaugeEvent === 'undefined') {
5334 state.gaugeEvent = {
5342 var value = state.data.result[state.data.result.length - 1 - slot];
5343 var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5346 state.gaugeEvent.value = value;
5347 state.gaugeEvent.max = max;
5348 state.gaugeEvent.min = min;
5349 NETDATA.gaugeSetLabels(state, value, min, max);
5351 if(state.gaugeEvent.timer === null) {
5352 NETDATA.gaugeAnimation(state, false);
5354 state.gaugeEvent.timer = setTimeout(function() {
5355 state.gaugeEvent.timer = null;
5356 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5357 }, NETDATA.options.current.charts_selection_animation_delay);
5363 NETDATA.gaugeChartUpdate = function(state, data) {
5364 var value, min, max;
5366 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5370 NETDATA.gaugeSetLabels(state, null, null, null);
5373 value = data.result[0];
5375 max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5376 if(value > max) max = value;
5377 NETDATA.gaugeSetLabels(state, value, min, max);
5380 NETDATA.gaugeSet(state, value, min, max);
5384 NETDATA.gaugeChartCreate = function(state, data) {
5385 var self = $(state.element);
5386 // var chart = $(state.element_chart);
5388 var value = data.result[0];
5389 var max = self.data('gauge-max-value') || null;
5390 var adjust = self.data('gauge-adjust') || null;
5391 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5392 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5393 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5394 var stopColor = self.data('gauge-stop-color') || void 0;
5395 var generateGradient = self.data('gauge-generate-gradient') || false;
5398 max = NETDATA.commonMax.get(state);
5399 state.gaugeMax = null;
5402 state.gaugeMax = max;
5404 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5406 // case 'width': width = height * ratio; break;
5408 // default: height = width / ratio; break;
5410 //state.element.style.width = width.toString() + 'px';
5411 //state.element.style.height = height.toString() + 'px';
5416 lines: 12, // The number of lines to draw
5417 angle: 0.15, // The length of each line
5418 lineWidth: 0.44, // 0.44 The line thickness
5420 length: 0.8, // 0.9 The radius of the inner circle
5421 strokeWidth: 0.035, // The rotation offset
5422 color: pointerColor // Fill color
5424 colorStart: startColor, // Colors
5425 colorStop: stopColor, // just experiment with them
5426 strokeColor: strokeColor, // to see which ones work best for you
5428 generateGradient: (generateGradient === true)?true:false,
5432 if (generateGradient.constructor === Array) {
5434 // data-gauge-generate-gradient="[0, 50, 100]"
5435 // data-gauge-gradient-percent-color-0="#FFFFFF"
5436 // data-gauge-gradient-percent-color-50="#999900"
5437 // data-gauge-gradient-percent-color-100="#000000"
5439 options.percentColors = new Array();
5440 var len = generateGradient.length;
5442 var pcent = generateGradient[len];
5443 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5444 if(color !== false) {
5445 var a = new Array();
5448 options.percentColors.unshift(a);
5451 if(options.percentColors.length === 0)
5452 delete options.percentColors;
5454 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5455 options.percentColors = [
5456 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5457 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5458 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5459 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5460 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5461 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5462 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5463 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5464 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5465 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5466 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5469 state.gauge_canvas = document.createElement('canvas');
5470 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5471 state.gauge_canvas.className = 'gaugeChart';
5472 state.gauge_canvas.width = width;
5473 state.gauge_canvas.height = height;
5474 state.element_chart.appendChild(state.gauge_canvas);
5476 var valuefontsize = Math.floor(height / 6);
5477 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5478 state.gaugeChartLabel = document.createElement('span');
5479 state.gaugeChartLabel.className = 'gaugeChartLabel';
5480 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5481 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5482 state.element_chart.appendChild(state.gaugeChartLabel);
5484 var titlefontsize = Math.round(valuefontsize / 2);
5486 state.gaugeChartTitle = document.createElement('span');
5487 state.gaugeChartTitle.className = 'gaugeChartTitle';
5488 state.gaugeChartTitle.innerHTML = state.title;
5489 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5490 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5491 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5492 state.element_chart.appendChild(state.gaugeChartTitle);
5494 var unitfontsize = Math.round(titlefontsize * 0.9);
5495 state.gaugeChartUnits = document.createElement('span');
5496 state.gaugeChartUnits.className = 'gaugeChartUnits';
5497 state.gaugeChartUnits.innerHTML = state.units;
5498 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5499 state.element_chart.appendChild(state.gaugeChartUnits);
5501 state.gaugeChartMin = document.createElement('span');
5502 state.gaugeChartMin.className = 'gaugeChartMin';
5503 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5504 state.element_chart.appendChild(state.gaugeChartMin);
5506 state.gaugeChartMax = document.createElement('span');
5507 state.gaugeChartMax.className = 'gaugeChartMax';
5508 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5509 state.element_chart.appendChild(state.gaugeChartMax);
5511 // when we just re-create the chart
5512 // do not animate the first update
5514 if(typeof state.gauge_instance !== 'undefined')
5517 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5519 state.___gaugeOld__ = {
5528 // we will always feed a percentage
5529 state.gauge_instance.minValue = 0;
5530 state.gauge_instance.maxValue = 100;
5532 NETDATA.gaugeAnimation(state, animate);
5533 NETDATA.gaugeSet(state, value, 0, max);
5534 NETDATA.gaugeSetLabels(state, value, 0, max);
5535 NETDATA.gaugeAnimation(state, true);
5539 // ----------------------------------------------------------------------------------------------------------------
5540 // Charts Libraries Registration
5542 NETDATA.chartLibraries = {
5544 initialize: NETDATA.dygraphInitialize,
5545 create: NETDATA.dygraphChartCreate,
5546 update: NETDATA.dygraphChartUpdate,
5547 resize: function(state) {
5548 if(typeof state.dygraph_instance.resize === 'function')
5549 state.dygraph_instance.resize();
5551 setSelection: NETDATA.dygraphSetSelection,
5552 clearSelection: NETDATA.dygraphClearSelection,
5553 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5556 format: function(state) { return 'json'; },
5557 options: function(state) { return 'ms|flip'; },
5558 legend: function(state) {
5559 if(this.isSparkline(state) === false)
5560 return 'right-side';
5564 autoresize: function(state) { return true; },
5565 max_updates_to_recreate: function(state) { return 5000; },
5566 track_colors: function(state) { return true; },
5567 pixels_per_point: function(state) {
5568 if(this.isSparkline(state) === false)
5574 isSparkline: function(state) {
5575 if(typeof state.dygraph_sparkline === 'undefined') {
5576 var t = $(state.element).data('dygraph-theme');
5577 if(t === 'sparkline')
5578 state.dygraph_sparkline = true;
5580 state.dygraph_sparkline = false;
5582 return state.dygraph_sparkline;
5586 initialize: NETDATA.sparklineInitialize,
5587 create: NETDATA.sparklineChartCreate,
5588 update: NETDATA.sparklineChartUpdate,
5590 setSelection: undefined, // function(state, t) { return true; },
5591 clearSelection: undefined, // function(state) { return true; },
5592 toolboxPanAndZoom: null,
5595 format: function(state) { return 'array'; },
5596 options: function(state) { return 'flip|abs'; },
5597 legend: function(state) { return null; },
5598 autoresize: function(state) { return false; },
5599 max_updates_to_recreate: function(state) { return 5000; },
5600 track_colors: function(state) { return false; },
5601 pixels_per_point: function(state) { return 3; }
5604 initialize: NETDATA.peityInitialize,
5605 create: NETDATA.peityChartCreate,
5606 update: NETDATA.peityChartUpdate,
5608 setSelection: undefined, // function(state, t) { return true; },
5609 clearSelection: undefined, // function(state) { return true; },
5610 toolboxPanAndZoom: null,
5613 format: function(state) { return 'ssvcomma'; },
5614 options: function(state) { return 'null2zero|flip|abs'; },
5615 legend: function(state) { return null; },
5616 autoresize: function(state) { return false; },
5617 max_updates_to_recreate: function(state) { return 5000; },
5618 track_colors: function(state) { return false; },
5619 pixels_per_point: function(state) { return 3; }
5622 initialize: NETDATA.morrisInitialize,
5623 create: NETDATA.morrisChartCreate,
5624 update: NETDATA.morrisChartUpdate,
5626 setSelection: undefined, // function(state, t) { return true; },
5627 clearSelection: undefined, // function(state) { return true; },
5628 toolboxPanAndZoom: null,
5631 format: function(state) { return 'json'; },
5632 options: function(state) { return 'objectrows|ms'; },
5633 legend: function(state) { return null; },
5634 autoresize: function(state) { return false; },
5635 max_updates_to_recreate: function(state) { return 50; },
5636 track_colors: function(state) { return false; },
5637 pixels_per_point: function(state) { return 15; }
5640 initialize: NETDATA.googleInitialize,
5641 create: NETDATA.googleChartCreate,
5642 update: NETDATA.googleChartUpdate,
5644 setSelection: undefined, //function(state, t) { return true; },
5645 clearSelection: undefined, //function(state) { return true; },
5646 toolboxPanAndZoom: null,
5649 format: function(state) { return 'datatable'; },
5650 options: function(state) { return ''; },
5651 legend: function(state) { return null; },
5652 autoresize: function(state) { return false; },
5653 max_updates_to_recreate: function(state) { return 300; },
5654 track_colors: function(state) { return false; },
5655 pixels_per_point: function(state) { return 4; }
5658 initialize: NETDATA.raphaelInitialize,
5659 create: NETDATA.raphaelChartCreate,
5660 update: NETDATA.raphaelChartUpdate,
5662 setSelection: undefined, // function(state, t) { return true; },
5663 clearSelection: undefined, // function(state) { return true; },
5664 toolboxPanAndZoom: null,
5667 format: function(state) { return 'json'; },
5668 options: function(state) { return ''; },
5669 legend: function(state) { return null; },
5670 autoresize: function(state) { return false; },
5671 max_updates_to_recreate: function(state) { return 5000; },
5672 track_colors: function(state) { return false; },
5673 pixels_per_point: function(state) { return 3; }
5676 initialize: NETDATA.c3Initialize,
5677 create: NETDATA.c3ChartCreate,
5678 update: NETDATA.c3ChartUpdate,
5680 setSelection: undefined, // function(state, t) { return true; },
5681 clearSelection: undefined, // function(state) { return true; },
5682 toolboxPanAndZoom: null,
5685 format: function(state) { return 'csvjsonarray'; },
5686 options: function(state) { return 'milliseconds'; },
5687 legend: function(state) { return null; },
5688 autoresize: function(state) { return false; },
5689 max_updates_to_recreate: function(state) { return 5000; },
5690 track_colors: function(state) { return false; },
5691 pixels_per_point: function(state) { return 15; }
5694 initialize: NETDATA.d3Initialize,
5695 create: NETDATA.d3ChartCreate,
5696 update: NETDATA.d3ChartUpdate,
5698 setSelection: undefined, // function(state, t) { return true; },
5699 clearSelection: undefined, // function(state) { return true; },
5700 toolboxPanAndZoom: null,
5703 format: function(state) { return 'json'; },
5704 options: function(state) { return ''; },
5705 legend: function(state) { return null; },
5706 autoresize: function(state) { return false; },
5707 max_updates_to_recreate: function(state) { return 5000; },
5708 track_colors: function(state) { return false; },
5709 pixels_per_point: function(state) { return 3; }
5712 initialize: NETDATA.easypiechartInitialize,
5713 create: NETDATA.easypiechartChartCreate,
5714 update: NETDATA.easypiechartChartUpdate,
5716 setSelection: NETDATA.easypiechartSetSelection,
5717 clearSelection: NETDATA.easypiechartClearSelection,
5718 toolboxPanAndZoom: null,
5721 format: function(state) { return 'array'; },
5722 options: function(state) { return 'absolute'; },
5723 legend: function(state) { return null; },
5724 autoresize: function(state) { return false; },
5725 max_updates_to_recreate: function(state) { return 5000; },
5726 track_colors: function(state) { return true; },
5727 pixels_per_point: function(state) { return 3; },
5731 initialize: NETDATA.gaugeInitialize,
5732 create: NETDATA.gaugeChartCreate,
5733 update: NETDATA.gaugeChartUpdate,
5735 setSelection: NETDATA.gaugeSetSelection,
5736 clearSelection: NETDATA.gaugeClearSelection,
5737 toolboxPanAndZoom: null,
5740 format: function(state) { return 'array'; },
5741 options: function(state) { return 'absolute'; },
5742 legend: function(state) { return null; },
5743 autoresize: function(state) { return false; },
5744 max_updates_to_recreate: function(state) { return 5000; },
5745 track_colors: function(state) { return true; },
5746 pixels_per_point: function(state) { return 3; },
5751 NETDATA.registerChartLibrary = function(library, url) {
5752 if(NETDATA.options.debug.libraries === true)
5753 console.log("registering chart library: " + library);
5755 NETDATA.chartLibraries[library].url = url;
5756 NETDATA.chartLibraries[library].initialized = true;
5757 NETDATA.chartLibraries[library].enabled = true;
5760 // ----------------------------------------------------------------------------------------------------------------
5761 // Load required JS libraries and CSS
5763 NETDATA.requiredJs = [
5765 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
5767 isAlreadyLoaded: function() {
5768 // check if bootstrap is loaded
5769 if(typeof $().emulateTransitionEnd == 'function')
5772 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5780 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller-0.8.7.min.js',
5781 isAlreadyLoaded: function() { return false; }
5785 NETDATA.requiredCSS = [
5787 url: NETDATA.themes.current.bootstrap_css,
5788 isAlreadyLoaded: function() {
5789 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5796 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.6.3',
5797 isAlreadyLoaded: function() { return false; }
5800 url: NETDATA.themes.current.dashboard_css,
5801 isAlreadyLoaded: function() { return false; }
5805 NETDATA.loadedRequiredJs = 0;
5806 NETDATA.loadRequiredJs = function(index, callback) {
5807 if(index >= NETDATA.requiredJs.length) {
5808 if(typeof callback === 'function')
5813 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5814 NETDATA.loadedRequiredJs++;
5815 NETDATA.loadRequiredJs(++index, callback);
5819 if(NETDATA.options.debug.main_loop === true)
5820 console.log('loading ' + NETDATA.requiredJs[index].url);
5823 if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
5827 url: NETDATA.requiredJs[index].url,
5830 xhrFields: { withCredentials: true } // required for the cookie
5833 if(NETDATA.options.debug.main_loop === true)
5834 console.log('loaded ' + NETDATA.requiredJs[index].url);
5837 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5839 .always(function() {
5840 NETDATA.loadedRequiredJs++;
5843 NETDATA.loadRequiredJs(++index, callback);
5847 NETDATA.loadRequiredJs(++index, callback);
5850 NETDATA.loadRequiredCSS = function(index) {
5851 if(index >= NETDATA.requiredCSS.length)
5854 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5855 NETDATA.loadRequiredCSS(++index);
5859 if(NETDATA.options.debug.main_loop === true)
5860 console.log('loading ' + NETDATA.requiredCSS[index].url);
5862 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5863 NETDATA.loadRequiredCSS(++index);
5867 // ----------------------------------------------------------------------------------------------------------------
5868 // Registry of netdata hosts
5871 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
5872 chart_div_offset: 100, // give that space above the chart when scrolling to it
5873 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
5874 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
5876 ms_penalty: 0, // the time penalty of the next alarm
5877 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
5878 // if alarms are shown faster than: one per 500ms
5880 notifications: false, // when true, the browser supports notifications (may not be granted though)
5881 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
5882 first_notification_id: 0, // the id of the first alarm_log entry for this session
5883 // this is used to prevent CLEAR notifications for past events
5884 // notifications_shown: new Array(),
5886 server: null, // the server to connect to for fetching alarms
5887 current: null, // the list of raised alarms - updated in the background
5888 callback: null, // a callback function to call every time the list of raised alarms is refreshed
5890 notify: function(entry) {
5891 // console.log('alarm ' + entry.unique_id);
5893 if(entry.updated === true) {
5894 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
5898 var value = entry.value;
5899 if(NETDATA.alarms.current !== null) {
5900 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
5901 if(typeof t !== 'undefined' && entry.status == t.status)
5905 var name = entry.name.replace(/_/g, ' ');
5906 var status = entry.status.toLowerCase();
5907 var title = name + ' = ' + ((value === null)?'NaN':Math.floor(value)).toString() + ' ' + entry.units;
5908 var tag = entry.alarm_id;
5909 var icon = 'images/seo-performance-128.png';
5910 var interaction = false;
5914 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
5916 switch(entry.status) {
5924 case 'UNINITIALIZED':
5928 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
5929 // console.log('alarm ' + entry.unique_id + ' is not current');
5932 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
5933 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
5936 title = name + ' back to normal';
5937 icon = 'images/check-mark-2-128-green.png'
5938 interaction = false;
5942 if(entry.old_status === 'CRITICAL')
5943 status = 'demoted to ' + entry.status.toLowerCase();
5945 icon = 'images/alert-128-orange.png';
5946 interaction = false;
5950 if(entry.old_status === 'WARNING')
5951 status = 'escalated to ' + entry.status.toLowerCase();
5953 icon = 'images/alert-128-red.png'
5958 console.log('invalid alarm status ' + entry.status);
5963 // cleanup old notifications with the same alarm_id as this one
5964 // FIXME: it does not seem to work on any web browser!
5965 var len = NETDATA.alarms.notifications_shown.length;
5967 var n = NETDATA.alarms.notifications_shown[len];
5968 if(n.data.alarm_id === entry.alarm_id) {
5969 console.log('removing old alarm ' + n.data.unique_id);
5971 // close the notification
5974 // remove it from the array
5975 NETDATA.alarms.notifications_shown.splice(len, 1);
5976 len = NETDATA.alarms.notifications_shown.length;
5983 setTimeout(function() {
5984 // show this notification
5985 // console.log('new notification: ' + title);
5986 var n = new Notification(title, {
5987 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
5989 requireInteraction: interaction,
5990 icon: NETDATA.serverDefault + icon,
5994 n.onclick = function(event) {
5995 event.preventDefault();
5996 NETDATA.alarms.onclick(event.target.data);
6000 // NETDATA.alarms.notifications_shown.push(n);
6001 // console.log(entry);
6002 }, NETDATA.alarms.ms_penalty);
6004 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6008 scrollToChart: function(chart_id) {
6009 if(typeof chart_id === 'string') {
6010 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6011 if(typeof offset !== 'undefined') {
6012 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6019 scrollToAlarm: function(alarm) {
6020 if(typeof alarm === 'object') {
6021 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6023 if(ret === true && NETDATA.options.page_is_visible === false)
6025 // 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.');
6030 notifyAll: function() {
6031 // console.log('FETCHING ALARM LOG');
6032 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6033 // console.log('ALARM LOG FETCHED');
6035 if(data === null || typeof data !== 'object') {
6036 console.log('invalid alarms log response');
6040 if(data.length === 0) {
6041 console.log('received empty alarm log');
6045 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6047 data.sort(function(a, b) {
6048 if(a.unique_id > b.unique_id) return -1;
6049 if(a.unique_id < b.unique_id) return 1;
6053 NETDATA.alarms.ms_penalty = 0;
6055 var len = data.length;
6057 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6058 NETDATA.alarms.notify(data[len]);
6061 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6064 NETDATA.alarms.last_notification_id = data[0].unique_id;
6065 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6066 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6070 check_notifications: function() {
6071 // returns true if we should fire 1+ notifications
6073 if(NETDATA.alarms.notifications !== true) {
6074 // console.log('notifications not available');
6078 if(Notification.permission !== 'granted') {
6079 // console.log('notifications not granted');
6083 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6084 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6086 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6087 // console.log('new alarms detected');
6090 //else console.log('no new alarms');
6092 // else console.log('cannot process alarms');
6097 get: function(what, callback) {
6099 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6103 'Cache-Control': 'no-cache, no-store',
6104 'Pragma': 'no-cache'
6106 xhrFields: { withCredentials: true } // required for the cookie
6108 .done(function(data) {
6109 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6110 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6112 if(typeof callback === 'function')
6116 NETDATA.error(415, NETDATA.alarms.server);
6118 if(typeof callback === 'function')
6123 update_forever: function() {
6124 NETDATA.alarms.get('active', function(data) {
6126 NETDATA.alarms.current = data;
6128 if(NETDATA.alarms.check_notifications() === true) {
6129 NETDATA.alarms.notifyAll();
6132 if (typeof NETDATA.alarms.callback === 'function') {
6133 NETDATA.alarms.callback(data);
6136 // Health monitoring is disabled on this netdata
6137 if(data.status === false) return;
6140 setTimeout(NETDATA.alarms.update_forever, 10000);
6144 get_log: function(last_id, callback) {
6145 // console.log('fetching all log after ' + last_id.toString());
6147 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6151 'Cache-Control': 'no-cache, no-store',
6152 'Pragma': 'no-cache'
6154 xhrFields: { withCredentials: true } // required for the cookie
6156 .done(function(data) {
6157 if(typeof callback === 'function')
6161 NETDATA.error(416, NETDATA.alarms.server);
6163 if(typeof callback === 'function')
6169 var host = NETDATA.serverDefault;
6170 while(host.slice(-1) === '/')
6171 host = host.substring(0, host.length - 1);
6172 NETDATA.alarms.server = host;
6174 NETDATA.alarms.last_notification_id = NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6176 if(NETDATA.alarms.onclick === null)
6177 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6179 if(netdataShowAlarms === true) {
6180 NETDATA.alarms.update_forever();
6182 if('Notification' in window) {
6183 // console.log('notifications available');
6184 NETDATA.alarms.notifications = true;
6186 if(Notification.permission === 'default')
6187 Notification.requestPermission();
6193 // ----------------------------------------------------------------------------------------------------------------
6194 // Registry of netdata hosts
6196 NETDATA.registry = {
6197 server: null, // the netdata registry server
6198 person_guid: null, // the unique ID of this browser / user
6199 machine_guid: null, // the unique ID the netdata server that served dashboard.js
6200 hostname: null, // the hostname of the netdata server that served dashboard.js
6201 machines: null, // the user's other URLs
6202 machines_array: null, // the user's other URLs in an array
6205 parsePersonUrls: function(person_urls) {
6206 // console.log(person_urls);
6207 NETDATA.registry.person_urls = person_urls;
6210 NETDATA.registry.machines = {};
6211 NETDATA.registry.machines_array = new Array();
6213 var now = new Date().getTime();
6214 var apu = person_urls;
6217 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6218 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6224 accesses: apu[i][3],
6226 alternate_urls: new Array()
6228 obj.alternate_urls.push(apu[i][1]);
6230 NETDATA.registry.machines[apu[i][0]] = obj;
6231 NETDATA.registry.machines_array.push(obj);
6234 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6236 var pu = NETDATA.registry.machines[apu[i][0]];
6237 if(pu.last_t < apu[i][2]) {
6239 pu.last_t = apu[i][2];
6240 pu.name = apu[i][4];
6242 pu.accesses += apu[i][3];
6243 pu.alternate_urls.push(apu[i][1]);
6248 if(typeof netdataRegistryCallback === 'function')
6249 netdataRegistryCallback(NETDATA.registry.machines_array);
6253 if(netdataRegistry !== true) return;
6255 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6257 NETDATA.registry.server = data.registry;
6258 NETDATA.registry.machine_guid = data.machine_guid;
6259 NETDATA.registry.hostname = data.hostname;
6261 NETDATA.registry.access(2, function (person_urls) {
6262 NETDATA.registry.parsePersonUrls(person_urls);
6269 hello: function(host, callback) {
6270 while(host.slice(-1) === '/')
6271 host = host.substring(0, host.length - 1);
6273 // send HELLO to a netdata server:
6274 // 1. verifies the server is reachable
6275 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6277 url: host + '/api/v1/registry?action=hello',
6281 'Cache-Control': 'no-cache, no-store',
6282 'Pragma': 'no-cache'
6284 xhrFields: { withCredentials: true } // required for the cookie
6286 .done(function(data) {
6287 if(typeof data.status !== 'string' || data.status !== 'ok') {
6288 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6292 if(typeof callback === 'function')
6296 NETDATA.error(407, host);
6298 if(typeof callback === 'function')
6303 access: function(max_redirects, callback) {
6304 // send ACCESS to a netdata registry:
6305 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6306 // 2. it responds with a list of netdata servers we know
6307 // the registry identifies us using a cookie it sets the first time we access it
6308 // the registry may respond with a redirect URL to send us to another registry
6310 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),
6314 'Cache-Control': 'no-cache, no-store',
6315 'Pragma': 'no-cache'
6317 xhrFields: { withCredentials: true } // required for the cookie
6319 .done(function(data) {
6320 var redirect = null;
6321 if(typeof data.registry === 'string')
6322 redirect = data.registry;
6324 if(typeof data.status !== 'string' || data.status !== 'ok') {
6325 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6330 if(redirect !== null && max_redirects > 0) {
6331 NETDATA.registry.server = redirect;
6332 NETDATA.registry.access(max_redirects - 1, callback);
6335 if(typeof callback === 'function')
6340 if(typeof data.person_guid === 'string')
6341 NETDATA.registry.person_guid = data.person_guid;
6343 if(typeof callback === 'function')
6344 callback(data.urls);
6348 NETDATA.error(410, NETDATA.registry.server);
6350 if(typeof callback === 'function')
6355 delete: function(delete_url, callback) {
6356 // send DELETE to a netdata registry:
6358 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),
6362 'Cache-Control': 'no-cache, no-store',
6363 'Pragma': 'no-cache'
6365 xhrFields: { withCredentials: true } // required for the cookie
6367 .done(function(data) {
6368 if(typeof data.status !== 'string' || data.status !== 'ok') {
6369 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6373 if(typeof callback === 'function')
6377 NETDATA.error(412, NETDATA.registry.server);
6379 if(typeof callback === 'function')
6384 search: function(machine_guid, callback) {
6385 // SEARCH for the URLs of a machine:
6387 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,
6391 'Cache-Control': 'no-cache, no-store',
6392 'Pragma': 'no-cache'
6394 xhrFields: { withCredentials: true } // required for the cookie
6396 .done(function(data) {
6397 if(typeof data.status !== 'string' || data.status !== 'ok') {
6398 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6402 if(typeof callback === 'function')
6406 NETDATA.error(418, NETDATA.registry.server);
6408 if(typeof callback === 'function')
6413 switch: function(new_person_guid, callback) {
6416 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,
6420 'Cache-Control': 'no-cache, no-store',
6421 'Pragma': 'no-cache'
6423 xhrFields: { withCredentials: true } // required for the cookie
6425 .done(function(data) {
6426 if(typeof data.status !== 'string' || data.status !== 'ok') {
6427 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6431 if(typeof callback === 'function')
6435 NETDATA.error(414, NETDATA.registry.server);
6437 if(typeof callback === 'function')
6443 // ----------------------------------------------------------------------------------------------------------------
6446 if(typeof netdataPrepCallback === 'function')
6447 netdataPrepCallback();
6449 NETDATA.errorReset();
6450 NETDATA.loadRequiredCSS(0);
6452 NETDATA._loadjQuery(function() {
6453 NETDATA.loadRequiredJs(0, function() {
6454 if(typeof $().emulateTransitionEnd !== 'function') {
6455 // bootstrap is not available
6456 NETDATA.options.current.show_help = false;
6459 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6460 if(NETDATA.options.debug.main_loop === true)
6461 console.log('starting chart refresh thread');
6468 // window.NETDATA = NETDATA;
6469 // })(window, document);